1// eslint-disable-next-line no-undef 2importScripts( 3 "https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js" 4); 5importScripts( 6 "https://www.gstatic.com/firebasejs/9.6.1/firebase-messaging-compat.js" 7); 8 9const firebaseConfig = { 10 apiKey: "AIzaSyCECdFDKt0LkgfV4NV3-Z1UyXO1FmDm-Uo", 11 authDomain: "ecomsaas-a2791.firebaseapp.com", 12 projectId: "ecomsaas-a2791", 13 storageBucket: "ecomsaas-a2791.firebasestorage.app", 14 messagingSenderId: "798828669804", 15 appId: "1:798828669804:web:49a63acbfe917a238d6149", 16 measurementId: "G-YJ6WCMXLHV", 17}; 18 19// eslint-disable-next-line no-undef 20firebase.initializeApp(firebaseConfig); 21// eslint-disable-next-line no-undef 22const messaging = firebase.messaging(); 23 24messaging.onBackgroundMessage((payload) => { 25 console.log( 26 "[firebase-messaging-sw.js] Received background message ", 27 payload 28 ); 29 30 const notification = payload.notification || {}; 31 const data = payload.data || {}; 32 const notificationTitle = notification.title || data.title; 33 34 const baseOptions = { 35 body: notification.body || data.body, 36 vibrate: [200, 100, 200], 37 requireInteraction: true, 38 data: { 39 click_action: notification.click_action || data.click_action, 40 }, 41 }; 42 43 const optionalFields = { 44 icon: data.icon || notification.icon, 45 image: data.image || notification.image, 46 badge: data.badge || notification.badge, 47 tag: data.tag || notification.tag, 48 actions: [], // Use the actions array directly 49 }; 50 51 // Conditionally add action1 52 if (data.action1 && data.title1) { 53 optionalFields.actions.push({ 54 action: data.action1, 55 title: data.title1, 56 icon: data.icon1, 57 }); 58 } 59 60 // Conditionally add action2 61 if (data.action2 && data.title2) { 62 optionalFields.actions.push({ 63 action: data.action2, 64 title: data.title2, 65 icon: data.icon2, 66 }); 67 } 68 69 // Remove 'actions' if empty 70 if (optionalFields.actions.length === 0) { 71 delete optionalFields.actions; 72 } 73 74 // Merge base + optional, excluding undefined/null 75 const notificationOptions = Object.fromEntries( 76 Object.entries({ ...baseOptions, ...optionalFields }).filter( 77 ([_, v]) => v !== undefined && v !== null 78 ) 79 ); 80 81 // console.log("notificationTitle", notificationTitle); 82 // console.log("notificationOptions", notificationOptions); 83 84 self.registration.showNotification(notificationTitle, notificationOptions); 85}); 86 87// Only ever open a URL on this same origin â the FCM payload (click_action / action) 88// is server-supplied data, so without this check a compromised or misconfigured sender 89// on the push backend could make every subscriber's browser open an arbitrary external 90// URL (phishing) on notification click. 91function resolveSameOriginUrl(candidate) { 92 if (!candidate) return null; 93 try { 94 const resolved = new URL(candidate, self.location.origin); 95 return resolved.origin === self.location.origin ? resolved.href : null; 96 } catch { 97 return null; 98 } 99} 100 101// ð Handle click on notification (main or actions) 102self.addEventListener("notificationclick", function (event) { 103 event.notification.close(); 104 console.log("targetUrl", event.notification); 105 106 // Handle button click if action is present 107 let targetUrl = event.notification?.data?.click_action; 108 109 if (event.action) { 110 // If action is a URL, use it directly 111 targetUrl = event.action; 112 } 113 114 const safeUrl = resolveSameOriginUrl(targetUrl) || self.location.origin; 115 event.waitUntil(clients.openWindow(safeUrl)); 116});
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.