PageSourceSearch

https://www.multicine.com.bo/firebase-messaging-sw.js

js multicine.com.bo collected 2026-10-08 04:24:33 UTC 8,537 bytes, 222 lines download raw bytes

1// Firebase Cloud Messaging Service Worker
2//
3// GENERATED FILE — do not edit. Source: scripts/generate-firebase-sw.js
4// Regenerated on every build (prebuild/build/dev_build/stg_build) and by `npm run dev`.
5importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js');
6importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js');
7
8// Activate immediately and take control of pages that are already open.
9// This worker is registered lazily at runtime (when the FCM token is created),
10// so without clients.claim() the tabs that are already open stay *uncontrolled*
11// — and WindowClient.navigate() rejects with a TypeError on an uncontrolled
12// client. That is why a notification click did nothing while the site was open
13// but worked fine when it had to open a brand new window.
14self.addEventListener('install', () => {
15    self.skipWaiting();
16});
17
18self.addEventListener('activate', (event) => {
19    event.waitUntil(self.clients.claim());
20});
21
22// Notifications rendered by the FCM SDK itself — which happens for any push
23// whose payload carries a `notification` block — nest the real message under an
24// FCM_MSG envelope rather than exposing our data bag directly. Unwrap both
25// shapes so either kind of notification routes the same way.
26function unwrapNotificationData(data) {
27    const envelope = data && data['FCM_MSG'];
28    if (!envelope) {
29        return data || {};
30    }
31
32    return {
33        ...(envelope.data || {}),
34        click_action:
35            envelope.data?.click_action ||
36            envelope.fcmOptions?.link ||
37            envelope.notification?.click_action ||
38            '/',
39    };
40}
41
42// Map a notification data bag to the in-app URL it should open.
43// Keep in sync with resolveNotificationPath() in src/lib/notificationRoute.ts,
44// which the in-page toast uses for the same payloads.
45function resolveNotificationUrl(rawData) {
46    const data = unwrapNotificationData(rawData);
47    const type = data?.type || 'home';
48    const urlKey = data?.url_key || '';
49    const clickAction = data?.click_action || '/';
50
51    let target;
52    switch (type) {
53        case 'movies':
54        case 'movie_details':
55            target = urlKey ? `/movies/${urlKey}` : clickAction;
56            break;
57        case 'events':
58        case 'event_details':
59            target = urlKey ? `/events/${urlKey}` : clickAction;
60            break;
61        case 'promotions':
62            target = urlKey ? `/promotions/${urlKey}` : clickAction;
63            break;
64        case 'promotion_details':
65            target = urlKey ? `/promotion/${urlKey}` : clickAction;
66            break;
67        case 'home':
68        default:
69            target = clickAction || '/';
70            break;
71    }
72
73    // click_action can arrive as an absolute URL. Normalise everything to a
74    // same-origin URL object and fall back home for anything off-origin or
75    // malformed, so a bad payload can never navigate the user off the site.
76    try {
77        const resolved = new URL(target, self.location.origin);
78        if (resolved.origin !== self.location.origin) {
79            return new URL('/', self.location.origin);
80        }
81        return resolved;
82    } catch (err) {
83        return new URL('/', self.location.origin);
84    }
85}
86
87// Ask an open page to route client-side. Resolves true only when the page
88// acknowledges, so the caller can fall back to a hard navigation otherwise
89// (e.g. a stale page without the message listener).
90function requestClientRoute(client, path) {
91    return new Promise((resolve) => {
92        let settled = false;
93        const finish = (handled) => {
94            if (settled) return;
95            settled = true;
96            resolve(handled);
97        };
98
99        try {
100            const channel = new MessageChannel();
101            channel.port1.onmessage = (event) => finish(event.data?.handled === true);
102            setTimeout(() => finish(false), 400);
103            client.postMessage({ type: 'NOTIFICATION_CLICK', path: path }, [channel.port2]);
104        } catch (err) {
105            finish(false);
106        }
107    });
108}
109
110// Handle notification clicks.
111//
112// IMPORTANT: this must be registered *before* firebase.messaging() below. The
113// SDK registers its own notificationclick listener at that call, and for
114// notifications it rendered itself it calls stopImmediatePropagation() — so a
115// listener added afterwards never runs. Its handler then navigates only if the
116// payload set fcmOptions.link / notification.click_action, and otherwise does
117// nothing at all. Registering first lets us route every notification with the
118// same type/url_key rules, and we stop propagation ourselves so the SDK's
119// handler can't also act on it.
120self.addEventListener('notificationclick', (event) => {
121
122    event.stopImmediatePropagation();
123    event.notification.close();
124
125    const targetUrl = resolveNotificationUrl(event.notification.data);
126    const targetPath = targetUrl.pathname + targetUrl.search + targetUrl.hash;
127
128    event.waitUntil((async () => {
129        // matchAll with includeUncontrolled matches on origin, not scope, so
130        // this finds the page even from a worker that controls nothing.
131        const clientList = await self.clients.matchAll({
132            type: 'window',
133            includeUncontrolled: true,
134        });
135
136        const client =
137            clientList.find((c) => c.focused) ||
138            clientList.find((c) => c.visibilityState === 'visible') ||
139            clientList[0];
140
141        if (client) {
142            // focus() is always permitted from within a notificationclick handler.
143            try {
144                await client.focus();
145            } catch (err) {
146                // Non-fatal: keep going and still route the window.
147            }
148
149            // Preferred: let the loaded app route itself (no reload, keeps state).
150            if (await requestClientRoute(client, targetPath)) {
151                return;
152            }
153
154            // Fallback: hard navigation. Only legal for a client this worker
155            // controls, so a rejection drops through to opening a new window.
156            try {
157                await client.navigate(targetUrl.href);
158                return;
159            } catch (err) {
160                // Uncontrolled client — fall through.
161            }
162        }
163
164        if (self.clients.openWindow) {
165            await self.clients.openWindow(targetUrl.href);
166        }
167    })());
168});
169
170// Initialize Firebase in the service worker
171// Note: These values are injected during build time
172firebase.initializeApp({
173    apiKey: "",
174    authDomain: "",
175    projectId: "",
176    storageBucket: "",
177    messagingSenderId: "",
178    appId: ""
179});
180
181const messaging = firebase.messaging();
182
183// Handle background messages
184messaging.onBackgroundMessage((payload) => {
185
186    // When the push payload carries a `notification` block, the SDK's own push
187    // handler has already called showNotification() by the time this runs — its
188    // comment reads "display if possible *and* pass to onBackgroundMessage
189    // hook" — and then still invokes this one. Rendering again here is what
190    // made every notification appear twice on web. externalizePayload() only
191    // sets payload.notification when that block was present, so this is an
192    // exact test; bail out and let the SDK-rendered one stand, which the click
193    // handler above routes by unwrapping its FCM_MSG envelope.
194    //
195    // Note this is web-only behaviour: the native SDKs do not call their
196    // message handler for notification payloads received in the background,
197    // which is why the mobile apps never showed a duplicate for the same push.
198    //
199    // Data-only payloads skip this branch and get the full treatment below.
200    if (payload.notification) {
201        return;
202    }
203
204    const notificationTitle = payload.data?.title || 'New Notification';
205    const notificationOptions = {
206        body: payload.data?.body || '',
207        icon: payload.data?.icon || '/assets/icons/icon-192x192.png',
208        image: payload.data?.image || '',
209        badge: '/assets/icons/icon-72x72.png',
210        tag: payload.data?.tag || 'default',
211        data: {
212            click_action: payload.data?.click_action || '/',
213            type: payload.data?.type || 'home',
214            id: payload.data?.id || '',
215            url_key: payload.data?.url_key || '',
216        },
217        requireInteraction: false,
218        vibrate: [200, 100, 200],
219    };
220
221    return self.registration.showNotification(notificationTitle, notificationOptions);
222});

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.