304 lines
9.4 KiB
JavaScript
304 lines
9.4 KiB
JavaScript
/**
|
|
* MobileApp Service Worker
|
|
* Provides caching for PWA shell, assets, and offline workorder support.
|
|
*/
|
|
|
|
const CACHE_NAME = 'xinon-mobile-v2';
|
|
const ASSETS_TO_CACHE = [
|
|
'/MobileApp',
|
|
'/mobile/app.js',
|
|
'/mobile/shared/auth.js',
|
|
'/mobile/shared/api.js',
|
|
'/mobile/shared/base.css',
|
|
'/mobile/shared/db.js',
|
|
'/mobile/shared/offlineSettings.js',
|
|
'/mobile/shared/syncQueue.js',
|
|
'/mobile/shared/workorderOfflineService.js',
|
|
'/mobile/shared/photoQueue.js',
|
|
'/mobile/shared/syncManager.js',
|
|
'/mobile/components/LoginScreen.js',
|
|
'/mobile/components/MainMenu.js',
|
|
'/mobile/components/OfflineIndicator.js',
|
|
'/mobile/components/SyncStatus.js',
|
|
'/mobile/modules/lager/LagerModule.js',
|
|
'/mobile/modules/lager/inventur/StocktakeList.js',
|
|
'/mobile/modules/lager/inventur/Scanner.js',
|
|
'/mobile/modules/workorder/WorkorderModule.js',
|
|
'/assets/images/xinon-full-transparent.png',
|
|
'/assets/images/xinon-full-transparent-white.png',
|
|
'/assets/images/xinon-sm.png'
|
|
];
|
|
|
|
// Workorder API endpoints that can be served from cache
|
|
const WORKORDER_CACHEABLE_ENDPOINTS = [
|
|
'/MobileApp/Workorder/Workorder/get',
|
|
'/MobileApp/Workorder/Workorder/getWorkorder',
|
|
'/MobileApp/Workorder/Workorder/getWorkorderDetail',
|
|
'/MobileApp/Workorder/Workorder/getDocumentation',
|
|
'/MobileApp/Workorder/Workorder/getChecklist',
|
|
'/MobileApp/Workorder/Workorder/getTenantConfig'
|
|
];
|
|
|
|
// Install: cache assets
|
|
self.addEventListener('install', (event) => {
|
|
console.log('[SW] Installing...');
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME)
|
|
.then(cache => cache.addAll(ASSETS_TO_CACHE))
|
|
.then(() => {
|
|
console.log('[SW] Assets cached');
|
|
return self.skipWaiting();
|
|
})
|
|
.catch(err => {
|
|
console.error('[SW] Cache failed:', err);
|
|
})
|
|
);
|
|
});
|
|
|
|
// Activate: clean old caches
|
|
self.addEventListener('activate', (event) => {
|
|
console.log('[SW] Activating...');
|
|
event.waitUntil(
|
|
caches.keys().then(cacheNames => {
|
|
return Promise.all(
|
|
cacheNames
|
|
.filter(name => name.startsWith('xinon-mobile-') && name !== CACHE_NAME)
|
|
.map(name => {
|
|
console.log('[SW] Deleting old cache:', name);
|
|
return caches.delete(name);
|
|
})
|
|
);
|
|
}).then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
// Fetch: handle requests with offline support
|
|
self.addEventListener('fetch', (event) => {
|
|
const url = new URL(event.request.url);
|
|
|
|
// Only handle same-origin requests
|
|
if (url.origin !== location.origin) return;
|
|
|
|
// Handle workorder API requests specially
|
|
if (url.pathname.startsWith('/MobileApp/Workorder/')) {
|
|
event.respondWith(handleWorkorderRequest(event.request, url));
|
|
return;
|
|
}
|
|
|
|
// Other API calls: network only (no caching)
|
|
if (url.pathname.startsWith('/MobileApp/') &&
|
|
url.pathname !== '/MobileApp' &&
|
|
url.pathname !== '/MobileApp/') {
|
|
return;
|
|
}
|
|
|
|
// Static assets: cache-first with background update
|
|
if (event.request.method === 'GET') {
|
|
event.respondWith(handleAssetRequest(event.request, url));
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Handle workorder API requests with offline fallback
|
|
*/
|
|
async function handleWorkorderRequest(request, url) {
|
|
const isGet = request.method === 'GET';
|
|
const isPost = request.method === 'POST';
|
|
const endpoint = url.pathname;
|
|
|
|
// Check if offline mode is enabled via message or localStorage check
|
|
const offlineEnabled = await isOfflineModeEnabled();
|
|
|
|
// For GET requests to cacheable endpoints
|
|
if (isGet && WORKORDER_CACHEABLE_ENDPOINTS.some(ep => endpoint.startsWith(ep))) {
|
|
try {
|
|
// Try network first
|
|
const response = await fetch(request);
|
|
if (response.ok && offlineEnabled) {
|
|
// Cache successful response for offline use
|
|
const cache = await caches.open(CACHE_NAME + '-api');
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
// Network failed, try cache
|
|
if (offlineEnabled) {
|
|
const cached = await caches.match(request);
|
|
if (cached) {
|
|
console.log('[SW] Serving from cache:', endpoint);
|
|
return cached;
|
|
}
|
|
}
|
|
// Return offline error response
|
|
return new Response(JSON.stringify({
|
|
success: false,
|
|
error: 'Offline - keine gecachten Daten',
|
|
offline: true
|
|
}), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// For POST requests (mutations) - let them through, main thread handles queueing
|
|
if (isPost) {
|
|
try {
|
|
return await fetch(request);
|
|
} catch (error) {
|
|
// Network error - return error response
|
|
// The main thread WorkorderOfflineService handles queueing
|
|
return new Response(JSON.stringify({
|
|
success: false,
|
|
error: 'Netzwerkfehler',
|
|
networkError: true
|
|
}), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Default: try network
|
|
return fetch(request);
|
|
}
|
|
|
|
/**
|
|
* Handle static asset requests with cache-first strategy
|
|
*/
|
|
async function handleAssetRequest(request, url) {
|
|
const cached = await caches.match(request);
|
|
|
|
if (cached) {
|
|
// Return cached, update in background
|
|
fetch(request).then(response => {
|
|
if (response.ok) {
|
|
caches.open(CACHE_NAME).then(cache => {
|
|
cache.put(request, response);
|
|
});
|
|
}
|
|
}).catch(() => {});
|
|
return cached;
|
|
}
|
|
|
|
// Not cached, fetch from network
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok && url.origin === location.origin) {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
// Return offline page or error
|
|
return new Response('Offline', { status: 503 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if offline mode is enabled
|
|
* This is a simplified check - the main thread handles the full logic
|
|
*/
|
|
async function isOfflineModeEnabled() {
|
|
// Try to get from clients
|
|
const clients = await self.clients.matchAll();
|
|
for (const client of clients) {
|
|
// Ask client for offline status
|
|
// For now, assume enabled if we have cached API data
|
|
const apiCache = await caches.open(CACHE_NAME + '-api');
|
|
const keys = await apiCache.keys();
|
|
return keys.length > 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Background Sync event (Chrome/Edge only)
|
|
self.addEventListener('sync', (event) => {
|
|
console.log('[SW] Background sync triggered:', event.tag);
|
|
|
|
if (event.tag === 'workorder-sync') {
|
|
event.waitUntil(handleBackgroundSync());
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Handle background sync
|
|
* Notify main thread to process sync queue
|
|
*/
|
|
async function handleBackgroundSync() {
|
|
const clients = await self.clients.matchAll({ type: 'window' });
|
|
|
|
for (const client of clients) {
|
|
client.postMessage({
|
|
type: 'BACKGROUND_SYNC',
|
|
tag: 'workorder-sync'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Message handler for communication with main thread
|
|
self.addEventListener('message', (event) => {
|
|
const { type, data } = event.data || {};
|
|
|
|
switch (type) {
|
|
case 'SKIP_WAITING':
|
|
self.skipWaiting();
|
|
break;
|
|
|
|
case 'CLEAR_API_CACHE':
|
|
caches.delete(CACHE_NAME + '-api').then(() => {
|
|
console.log('[SW] API cache cleared');
|
|
event.ports[0]?.postMessage({ success: true });
|
|
});
|
|
break;
|
|
|
|
case 'CACHE_WORKORDER_DATA':
|
|
// Cache workorder data from main thread
|
|
if (data && data.url && data.response) {
|
|
caches.open(CACHE_NAME + '-api').then(cache => {
|
|
const response = new Response(JSON.stringify(data.response), {
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
cache.put(data.url, response);
|
|
console.log('[SW] Cached workorder data:', data.url);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'GET_CACHE_STATUS':
|
|
getCacheStatus().then(status => {
|
|
event.ports[0]?.postMessage(status);
|
|
});
|
|
break;
|
|
|
|
default:
|
|
console.log('[SW] Unknown message type:', type);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get cache status for debugging
|
|
*/
|
|
async function getCacheStatus() {
|
|
const assetCache = await caches.open(CACHE_NAME);
|
|
const apiCache = await caches.open(CACHE_NAME + '-api');
|
|
|
|
const assetKeys = await assetCache.keys();
|
|
const apiKeys = await apiCache.keys();
|
|
|
|
return {
|
|
cacheName: CACHE_NAME,
|
|
assetsCached: assetKeys.length,
|
|
apiCached: apiKeys.length,
|
|
apiEndpoints: apiKeys.map(k => new URL(k.url).pathname)
|
|
};
|
|
}
|
|
|
|
// Periodic sync (if supported - experimental)
|
|
self.addEventListener('periodicsync', (event) => {
|
|
if (event.tag === 'workorder-periodic-sync') {
|
|
console.log('[SW] Periodic sync triggered');
|
|
event.waitUntil(handleBackgroundSync());
|
|
}
|
|
});
|