pediatric-ai-scribe-v3/public/sw.js
ifedan-ed ee60d269a5 v9: APK hardening, service worker caching, admin model validation, Docker v9
- APK: Add WAKE_LOCK, BOOT_COMPLETED, ACCESS_NETWORK_STATE permissions
- APK: Disable allowBackup for medical data security
- APK: AudioRecordingService now acquires wake lock, has stop action in notification
- Serve /.well-known/assetlinks.json for TWA domain verification
- Service worker: cache app shell, stale-while-revalidate for assets, network-first for API
- Admin model management: validate model ID format, prevent built-in conflicts, audit toggle actions, prevent disabling all models
- Bump version to v9.0.0, Docker tag to v9
2026-03-28 23:53:39 +00:00

94 lines
2.8 KiB
JavaScript

// ============================================================
// SERVICE WORKER — Cache shell, network-first for API
// Provides offline fallback for app shell while keeping
// API calls always fresh (critical for medical data accuracy)
// ============================================================
var CACHE_NAME = 'pedscribe-v11';
var SHELL_ASSETS = [
'/',
'/index.html',
'/css/styles.css',
'/js/app.js',
'/js/auth.js',
'/manifest.json'
];
// Install: precache app shell
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(SHELL_ASSETS);
}).then(function() {
return self.skipWaiting();
})
);
});
// Activate: clear old caches
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(names) {
return Promise.all(
names.filter(function(name) { return name !== CACHE_NAME; })
.map(function(name) { return caches.delete(name); })
);
}).then(function() {
return self.clients.claim();
})
);
});
// Fetch: network-first for API, cache-first for static assets
self.addEventListener('fetch', function(event) {
var url = new URL(event.request.url);
// Only handle same-origin requests
if (url.origin !== self.location.origin) return;
// API calls — always network, never cache (medical data must be fresh)
if (url.pathname.startsWith('/api/')) {
event.respondWith(fetch(event.request));
return;
}
// Component HTML — network-first with cache fallback
if (url.pathname.startsWith('/components/')) {
event.respondWith(
fetch(event.request).then(function(response) {
var clone = response.clone();
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
return response;
}).catch(function() {
return caches.match(event.request);
})
);
return;
}
// Static assets (JS, CSS, icons) — stale-while-revalidate
if (url.pathname.match(/\.(js|css|png|ico|woff2?)$/)) {
event.respondWith(
caches.match(event.request).then(function(cached) {
var fetchPromise = fetch(event.request).then(function(response) {
var clone = response.clone();
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
return response;
}).catch(function() { return cached; });
return cached || fetchPromise;
})
);
return;
}
// HTML pages — network-first
event.respondWith(
fetch(event.request).then(function(response) {
var clone = response.clone();
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
return response;
}).catch(function() {
return caches.match(event.request) || caches.match('/index.html');
})
);
});