| // Service Worker to intercept and serve cached share assets on-demand |
| const pendingRequests = new Map(); |
| let requestIdCounter = 0; |
| |
| self.addEventListener('install', (event) => { |
| self.skipWaiting(); |
| }); |
| |
| self.addEventListener('activate', (event) => { |
| event.waitUntil(self.clients.claim()); |
| }); |
| |
| self.addEventListener('fetch', (event) => { |
| const url = new URL(event.request.url); |
| // Intercept requests matching /share/<32-char-hex-hash>/... |
| const match = url.pathname.match(/\/share\/([a-f0-9]{32})\/(.*)/); |
| if (match) { |
| const hash = match[1]; |
| const filePath = match[2] || 'index.html'; |
| |
| event.respondWith( |
| caches.open(`share-${hash}`).then((cache) => { |
| return cache.match(event.request).then((cachedResponse) => { |
| if (cachedResponse && filePath !== 'index.html' && filePath !== '') { |
| return cachedResponse; |
| } |
| |
| // Request the file from the client window on-demand |
| return new Promise((resolve, reject) => { |
| const requestId = ++requestIdCounter; |
| pendingRequests.set(requestId, { resolve, reject, cache, request: event.request }); |
| |
| // Send message to all matching clients |
| self.clients.matchAll().then((clients) => { |
| if (clients.length === 0) { |
| reject(new Error('No active client window found to fetch file')); |
| pendingRequests.delete(requestId); |
| return; |
| } |
| clients.forEach((client) => { |
| client.postMessage({ |
| type: 'FETCH_FILE', |
| requestId: requestId, |
| hash: hash, |
| path: filePath |
| }); |
| }); |
| }); |
| }); |
| }); |
| }) |
| ); |
| } |
| }); |
| |
| self.addEventListener('message', (event) => { |
| if (event.data.type === 'FETCH_FILE_RESPONSE') { |
| const pending = pendingRequests.get(event.data.requestId); |
| if (pending) { |
| pendingRequests.delete(event.data.requestId); |
| if (event.data.error) { |
| pending.reject(new Error(event.data.error)); |
| } else { |
| const mimeType = event.data.mimeType || 'application/octet-stream'; |
| const headers = new Headers({ |
| 'content-type': mimeType, |
| 'access-control-allow-origin': '*' |
| }); |
| const response = new Response(event.data.bytes, { headers }); |
| // Store in cache for next time |
| pending.cache.put(pending.request, response.clone()); |
| pending.resolve(response); |
| } |
| } |
| } |
| }); |