const EMPTY32BYTES = String('').padStart(64, '0'); self.addEventListener('install', (event) => { event.waitUntil(self.skipWaiting()); // Activate worker immediately }); self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); // Become available to all pages }); // ============================================ // MESSAGE HANDLER // ============================================ self.addEventListener('message', async (event) => { const data = event.data; console.log('[Service Worker] Message received:', data.type); if (data.type === 'SCAN') { try { const myProcessesId = data.payload; if (myProcessesId && myProcessesId.length != 0) { const scanResult = await scanMissingData(myProcessesId, event.source); if (scanResult.toDownload.length != 0) { console.log('[Service Worker] Sending TO_DOWNLOAD message'); event.source.postMessage({ type: 'TO_DOWNLOAD', data: scanResult.toDownload }); } if (scanResult.diffsToCreate.length > 0) { console.log('[Service Worker] Sending DIFFS_TO_CREATE message'); event.source.postMessage({ type: 'DIFFS_TO_CREATE', data: scanResult.diffsToCreate }); } } else { event.source.postMessage({ status: 'error', message: 'Empty lists' }); } } catch (error) { console.error('[Service Worker] Scan error:', error); event.source.postMessage({ status: 'error', message: error.message }); } } }); // ============================================ // DATABASE COMMUNICATION // ============================================ async function requestFromMainThread(client, action, payload) { return new Promise((resolve, reject) => { const messageId = `sw_${Date.now()}_${Math.random()}`; const messageHandler = (event) => { if (event.data.id === messageId) { self.removeEventListener('message', messageHandler); if (event.data.type === 'DB_RESPONSE') { resolve(event.data.result); } else if (event.data.type === 'DB_ERROR') { reject(new Error(event.data.error)); } } }; self.addEventListener('message', messageHandler); client.postMessage({ type: 'DB_REQUEST', id: messageId, action, payload }); setTimeout(() => { self.removeEventListener('message', messageHandler); reject(new Error('Database request timeout')); }, 10000); }); } // ============================================ // SCAN LOGIC // ============================================ async function scanMissingData(processesToScan, client) { console.log('[Service Worker] Scanning for missing data...'); const myProcesses = await requestFromMainThread(client, 'GET_MULTIPLE_OBJECTS', { storeName: 'processes', keys: processesToScan }); let toDownload = new Set(); let diffsToCreate = []; if (myProcesses && myProcesses.length != 0) { for (const process of myProcesses) { const firstState = process.states[0]; const processId = firstState.commited_in; for (const state of process.states) { if (state.state_id === EMPTY32BYTES) continue; for (const [field, hash] of Object.entries(state.pcd_commitment)) { if (state.public_data[field] !== undefined || field === 'roles') continue; const existingData = await requestFromMainThread(client, 'GET_OBJECT', { storeName: 'data', key: hash }); if (!existingData) { toDownload.add(hash); const existingDiff = await requestFromMainThread(client, 'GET_OBJECT', { storeName: 'diffs', key: hash }); if (!existingDiff) { diffsToCreate.push({ process_id: processId, state_id: state.state_id, value_commitment: hash, roles: state.roles, field: field, description: null, previous_value: null, new_value: null, notify_user: false, need_validation: false, validation_status: 'None' }); } } else { if (toDownload.delete(hash)) { console.log(`[Service Worker] Removing ${hash} from the set`); } } } } } } console.log('[Service Worker] Scan complete:', { toDownload: toDownload.size, diffsToCreate: diffsToCreate.length }); return { toDownload: Array.from(toDownload), diffsToCreate: diffsToCreate }; }