1/* eslint-disable no-undef */ 2// Service Worker globals: importScripts, self, clients, firebase are provided by the SW runtime 3importScripts( 4 'https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js' 5); 6importScripts( 7 'https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js' 8); 9// Initialize Firebase in the Service Worker 10firebase.initializeApp({ 11 apiKey: 'AIzaSyBg5V0PW1vXKj8f4OW9YF-B1ApZzXCeXJA', 12 authDomain: 'time-mobile-prod.firebaseapp.com', 13 projectId: 'time-mobile-prod', 14 storageBucket: 'time-mobile-prod.firebasestorage.app', 15 messagingSenderId: '295945882373', 16 appId: '1:295945882373:web:f08628500b0086069a056c', 17 measurementId: 'G-1XXDT38ZQ3', 18}); 19 20// ============================================ 21// Notification Webhook Configuration 22// ============================================ 23let webhookConfig = { 24 baseUrl: '', 25 clientId: '', 26 deviceToken: '', 27 GUID: '', 28 jwt: '', 29}; 30 31// Listen for config messages from the main app 32self.addEventListener('message', (event) => { 33 if (event.data && event.data.type === 'SET_WEBHOOK_CONFIG') { 34 webhookConfig = { 35 baseUrl: event.data.baseUrl || webhookConfig.baseUrl, 36 clientId: event.data.clientId || webhookConfig.clientId, 37 deviceToken: event.data.deviceToken || webhookConfig.deviceToken, 38 GUID: event.data.GUID || webhookConfig.GUID, 39 jwt: event.data.jwt || webhookConfig.jwt, 40 }; 41 console.log('[SW] Webhook config updated'); 42 } 43}); 44 45/** 46 * Reads a cookie value from within the service worker using the Cookie Store 47 * API (Chromium). Returns '' if unavailable or not found. 48 */ 49async function readCookieValue(name) { 50 try { 51 if (typeof cookieStore !== 'undefined' && cookieStore.get) { 52 const cookie = await cookieStore.get(name); 53 if (cookie && cookie.value) return cookie.value; 54 } 55 } catch (e) { 56 // ignore and fall back to config 57 } 58 return ''; 59} 60 61/** 62 * Sends notification event to backend webhook. 63 * @param {'DELIVERED' | 'CLICKED'} eventType 64 * @param {Object} data - { GUID, messageId } 65 */ 66async function sendNotificationWebhook(eventType, data) { 67 if (!webhookConfig.baseUrl || !webhookConfig.deviceToken) { 68 console.warn( 69 '[SW] Webhook config not available, skipping event:', 70 eventType 71 ); 72 return; 73 } 74 75 // Prefer live cookie values, then config, then anything on the notification. 76 const cookieGuid = await readCookieValue('notificationGUID'); 77 const guid = cookieGuid || webhookConfig.GUID || data.GUID || ''; 78 79 const cookieJwt = await readCookieValue('notificationJwt'); 80 const jwt = cookieJwt || webhookConfig.jwt || ''; 81 82 const payload = { 83 deviceToken: webhookConfig.deviceToken, 84 eventType: eventType, 85 GUID: guid, 86 messageId: data.messageId || '', 87 }; 88 89 const headers = { 90 'Content-Type': 'application/json', 91 clientid: webhookConfig.clientId, 92 }; 93 if (jwt) { 94 headers.Authorization = jwt; 95 } 96 97 return fetch(`${webhookConfig.baseUrl}/comm/webhook/notification/events`, { 98 method: 'POST', 99 headers: headers, 100 body: JSON.stringify(payload), 101 }) 102 .then((res) => { 103 if (res.ok) { 104 console.log( 105 `[SW] ${eventType} webhook sent for messageId: ${data.messageId}` 106 ); 107 } else { 108 console.error( 109 `[SW] ${eventType} webhook failed with status: ${res.status}` 110 ); 111 } 112 }) 113 .catch((err) => { 114 console.error(`[SW] ${eventType} webhook error:`, err); 115 }); 116} 117 118// Extracts GUID from the push payload data, tolerating different key casings. 119function extractGuid(data) { 120 if (!data) return ''; 121 return data.GUID || data.guid || data.Guid || data.guid_id || ''; 122} 123 124// Extracts messageId from the push payload, tolerating different key casings. 125function extractMessageId(payload) { 126 const data = payload?.data || {}; 127 return ( 128 data.messageId || 129 data.messageID || 130 data.message_id || 131 payload?.messageId || 132 payload?.fcmMessageId || 133 '' 134 ); 135} 136 137// Initialize Firebase Messaging 138const isSupported = firebase.messaging.isSupported(); 139if (isSupported) { 140 const messaging = firebase.messaging();
141 messaging.onBackgroundMessage((payload) => { 142 // GUID is sourced from webhookConfig (updateFCMToken response). messageId 143 // still comes from the push payload since it is per-message. 144 const guid = webhookConfig.GUID || extractGuid(payload.data); 145 const messageId = extractMessageId(payload); 146 147 const notificationTitle = 148 payload.data?.notification_title || 'New Notification'; 149 const notificationOptions = { 150 body: payload.data?.notification_body || 'You have a new message.', 151 icon: payload.data?.icon || '/default-icon.png', 152 data: { 153 click_action: payload.data?.click_action, 154 GUID: guid, 155 messageId: messageId, 156 }, 157 }; 158 159 self.registration.showNotification(notificationTitle, notificationOptions); 160 161 // Send DELIVERED webhook (GUID resolved live from cookie inside the helper) 162 sendNotificationWebhook('DELIVERED', { 163 GUID: guid, 164 messageId: messageId, 165 }); 166 }); 167} 168 169self.addEventListener('push', (event) => { 170 console.log('Push event received:', event); 171 if (event.data) { 172 console.log('Push data:', event.data.json()); 173 } 174}); 175 176self.addEventListener('notificationclick', (event) => { 177 event.notification.close(); 178 179 const notificationData = event.notification.data || {}; 180 181 // Keep the SW alive until the CLICKED webhook completes, then open the link. 182 event.waitUntil( 183 sendNotificationWebhook('CLICKED', { 184 GUID: notificationData.GUID || '', 185 messageId: notificationData.messageId || '', 186 }).then(() => { 187 if (notificationData.click_action) { 188 return clients.openWindow(notificationData.click_action); 189 } 190 }) 191 ); 192}); 193 194self.addEventListener('install', (event) => { 195 self.skipWaiting(); 196}); 197 198self.addEventListener('activate', (event) => { 199 event.waitUntil(self.clients.claim()); 200});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.