PageSourceSearch

https://www.ofertero.es/service-worker-logged.js

js ofertero.es collected 2026-09-26 12:03:05 UTC 13,057 bytes, 396 lines download raw bytes

1 //push
2'use strict';
3
4// load indexedDb helper library
5importScripts('/js/split/idbStorage.js');
6
7// Push messaging logic
8self.Pusher = {
9
10    _recentlyOpenedNotifications: {},
11
12    init() {
13        console.debug('[Service Worker]: Pusher init ');
14        // register SW event listeners
15        this._initServiceWorkerEvents();
16    },
17
18    _initServiceWorkerEvents() {
19        // Messages from controlled pages
20
21        // SW lifecycle
22        self.addEventListener('install', event => {
23            self.skipWaiting();
24        });
25
26        self.addEventListener('activate', event => {
27            event.waitUntil(self.clients.claim());
28        });
29
30        // Push event from FCM
31        self.addEventListener('push', event => {
32            event.waitUntil(this._handlePush(event));
33        });
34
35        self.addEventListener('notificationclick', event => {
36            event.waitUntil(this._handleNotificationClick(event));
37        });
38
39        self.addEventListener('notificationclose', event => {
40            event.waitUntil(this._handleNotificationClose(event));
41        });
42    },
43
44    async sendTrackerEvent(status, extraJson = null) {
45        if (!status) {
46            console.error('[Service Worker]: sendTrackerEvent called without status');
47            return;
48        }
49
50        if (!(['opened', 'closed', 'received', 'shown'].includes(status))) {
51            console.error('[Service Worker]: sendTrackerEvent called with invalid status: ' + status);
52            return;
53        }
54
55        const storedUserData = await self.idbStorage.getPusherUserData() || {};
56
57        const fcmToken = storedUserData.fcm_token;
58        if (!fcmToken) {
59            console.error('[Service Worker]: fcmToken not set in idbStorage, cannot send tracker event');
60            return;
61        }
62
63        const trackerUrl = storedUserData.trackerEventUrl;
64        if (!trackerUrl) {
65            console.error('[Service Worker]: trackerEventUrl not set in idbStorage, cannot send tracker event');
66            return;
67        }
68
69        const userUid = storedUserData.uid;
70        if (!userUid) {
71            console.error('[Service Worker]: uid not set in idbStorage, cannot send tracker event');
72            return;
73        }
74
75        // Normalize extra_json
76        let extraJsonPayload = '{}';
77        let brochureId = null;
78        let brochureSef = null;
79        let shopId = null;
80        let shopSef = null;
81        let createType = null;
82        let utmCampaign = null;
83        if (extraJson && typeof extraJson === 'object') {
84            try {
85                extraJsonPayload = JSON.stringify({
86                    taskId: extraJson.task_id ?? null
87                });
88                
89                brochureId = this.toIntOrNull(extraJson.brochure_id ?? null);
90                brochureSef = extraJson.brochure_sef ?? null;
91                shopId = this.toIntOrNull(extraJson.shop_id ?? null);
92                shopSef = extraJson.shop_sef ?? null;
93                createType = extraJson.create_type ?? null;
94                utmCampaign = extraJson.utm_campaign ?? null;
95                
96            } catch (e) {
97                console.warn('[Service Worker]: Failed to stringify extra_json', e);
98            }
99        }
100
101        const payload = {
102            uid: storedUserData.uid,
103            fcm_token: storedUserData.fcm_token,
104            fcm_token_changed: !!storedUserData.fcm_token_changed,
105            status: status,
106            ip: '',
107            ua: storedUserData.ua || '',
108            local_time: this.getLocalDateTimeString(),
109            brochure_id: brochureId,
110            brochure_sef: brochureSef,
111            shop_id: shopId,
112            shop_sef: shopSef,
113            create_type: createType,
114            utm_campaign: utmCampaign,
115            extra_json: extraJsonPayload
116        };
117
118        console.debug('[Service Worker]: Sending tracker event', payload);
119
120        try {
121            const res = await fetch(trackerUrl, {
122                method: 'POST',
123                headers: {
124                    'Content-Type': 'application/json'
125                },
126                body: JSON.stringify(payload)
127            });
128
129            if (!res.ok) {
130                console.error(
131                    '[Service Worker]: Tracker event failed',
132                    res.status,
133                    res.statusText
134                );
135            }
136        } catch (err) {
137            console.error('[Service Worker]: Tracker event fetch error', err);
138        }
139    },
140
141    getLocalDateTimeString() {
142        const d = new Date();
143        const pad = n => String(n).padStart(2, '0');
144        return (
145            d.getFullYear() + '-' +
146            pad(d.getMonth() + 1) + '-' +
147            pad(d.getDate()) + ' ' +
148            pad(d.getHours()) + ':' +
149            pad(d.getMinutes()) + ':' +
150            pad(d.getSeconds())
151        );
152    },
153    
154    toIntOrNull(value) {
155        if (value === null || value === undefined || value === '') {
156            return null;
157        }
158        const n = Number(value);
159        return Number.isInteger(n) && n > 0 ? n : null;
160    },
161
162    /**
163     * Expected FCM data payload:
164     * {
165     *     "data": {
166     *         "project_id": "",
167     *         "device_id": "394",
168     *         "store": "dm drogerie",
169     *         "fcm_token": "dqhoFZz_IDw679Pmd5Haoo:APA91bGjbwcspK-SjfBJqGivr7XgzapJ9V3IlgfCTNXm0bOR06M_d8r_acRmNwOOn7sHNY_8qXAZ7eUr1eT-Mk0_gUQLhSJP3tt2d4c7UI7SwR9c691FhQ0",
170     *         "user_id": "1",
171     *         "send_mode": "single",
172     *         "task_id": "fb91a28dfca07807",
173     *         "dedupe_key": "bcec8a886ec7a229b6b5a57a40603dbe7c64471847366f812987022ea95dcbc5",
174     *         "channel": "push",
175     *         "source": "worker_auto",
176     *         "message_key": "msg_69b2bb05_b7544d9b_5fc385062c587686",
177     *         "click_action": "https://www.letakomat.sk/dm/",
178     *         "style": "promo"
179     *     },
180     *     "from": "778037625297",
181     *     "priority": "normal",
182     *     "notification": {
183     *         "title": "🎉 Nedeľná rodinná akcia v dm! 🛒",
184     *         "body": "Navštívte nás v Žiline od 1. do 7. júla a užite si špeciálne zľavy! Nezmeškajte to!",
185     *         "icon": "https://review-smartpush.vpn.prod.ams3.k8s.hyperia.sk/assets/lidl-logo.png",
186     *         "click_action": "https://www.letakomat.sk/dm/"
187     *     },
188     *     "fcmMessageId": "00c72ab9-62f4-446c-b42b-529adfd38323"
189     * }
190     */
191    async _handlePush(event) {
192        if (!event.data) {
193            console.error('[Service Worker]: No push event data provided');
194            return;
195        }
196
197        let responseData = {};
198
199        try {
200            responseData = event.data.json();
201            console.debug('[Service Worker]: push event', responseData);
202        } catch (e) {
203            console.error('[Service Worker]: push payload not JSON', e);
204            return;
205        }
206
207        const notification = responseData.notification || {};
208        const data = responseData.data || {};
209
210        const title = notification.title || 'Notification';
211        const body = notification.body || '';
212        const icon = notification.icon || '/img/maskot/pwa/icon180_maskable.png';
213        const image = notification.image || undefined;
214        const url = data.click_action || notification.click_action || self.location.origin;
215
216        const options = {
217            body,
218            icon,
219            image,
220            data: {
221                ...data,
222                url
223            },
224            requireInteraction: true
225        };
226
227        await this.sendTrackerEvent('received', data);
228
229        await self.registration.showNotification(title, options);
230
231        await this.sendTrackerEvent('shown', data);
232    },
233
234    async _handleNotificationClick(event) {
235        console.debug('[Service Worker]: notificationclick', event.notification, event.action);
236        event.notification.close();
237
238        const data = event.notification?.data || {};
239        const finalUrl = data.url || self.location.origin;
240
241        // we need to remember opening notification, so we block close event to be sent to tracker
242        const messageKey = data.message_key || null;
243        if (messageKey) {
244            this._recentlyOpenedNotifications[messageKey] = Date.now();
245        }
246
247        await this.sendTrackerEvent('opened', data);
248
249        const allClients = await self.clients.matchAll({
250            type: 'window',
251            includeUncontrolled: true
252        });
253
254        const targetOrigin = new URL(finalUrl).origin;
255        let client = allClients.find(c => {
256            try {
257                return new URL(c.url).origin === targetOrigin;
258            } catch (e) {
259                return false;
260            }
261        });
262
263        if (client) {
264            await client.focus();
265            if (client.url !== finalUrl && 'navigate' in client) {
266                await client.navigate(finalUrl);
267            }
268            return;
269        }
270
271        await self.clients.openWindow(finalUrl);
272    },
273
274    async _handleNotificationClose(event) {
275        console.debug('[Service Worker]: notificationclose', event.notification);
276
277        const data = event.notification?.data || {};
278        const messageKey = data.message_key || null;
279
280        // if notification was opened, we do not send closed event to tracker
281        if (messageKey && this._recentlyOpenedNotifications[messageKey]) {
282            console.debug('[Service Worker]: skipping closed tracker because notification was opened');
283            delete this._recentlyOpenedNotifications[messageKey];
284            return;
285        }
286
287        await this.sendTrackerEvent('closed', data);
288    }
289};
290
291self.Pusher.init();
292
293// PWA logic
294importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.1/workbox-sw.js');
295if (typeof workbox !== 'undefined') {
296
297    // under optimization
298    workbox.precaching.addPlugins([
299        new workbox.cacheableResponse.Plugin({
300            statuses: [0, 200]
301        })
302    ]);
303    workbox.precaching.precacheAndRoute([{"url":"./?utm_source=ofertero_es_pwa&utm_medium=pwa","revision":"b975d50455bda765b68f3142496b6988"}]);
304
305    /* static files cache */
306    workbox.routing.registerRoute(
307        new RegExp('manifest\.json|favicon\.ico'),
308        workbox.strategies.networkFirst({
309            cacheName: 'static-files-cache',
310        })
311    );
312
313    /* external files cache */
314    workbox.routing.registerRoute(
315        new RegExp('.*\/analytics\.js|.*\/gtm\.js|.*\/bootstrap\.min\.js'),
316        workbox.strategies.networkFirst({
317            cacheName: 'external-files-cache',
318        })
319    );
320
321    /* Cache CSS files */
322    workbox.routing.registerRoute(
323        new RegExp('.*\.css'),
324        // Use cache but update in the background ASAP
325        workbox.strategies.staleWhileRevalidate({
326            cacheName: 'css-cache',
327            plugins: [
328                new workbox.cacheableResponse.Plugin({
329                    statuses: [0, 200]
330                })
331            ],
332        })
333    );
334
335    /* Cache CSS files */
336    workbox.routing.registerRoute(
337        new RegExp('.*\.leafletscdn\.com.*\.js'),
338        // Use cache but update in the background ASAP
339        workbox.strategies.staleWhileRevalidate({
340            cacheName: 'js-cache',
341            plugins: [
342                new workbox.cacheableResponse.Plugin({
343                    statuses: [0, 200]
344                })
345            ],
346        })
347    );
348     /* Cache leaflet pages - with timestamp if it is there*/
349    workbox.routing.registerRoute(
350        new RegExp('.*\/data\/[0-9]*\/[0-9]*\/.*\.(?:png|jpg|jpeg|svg|gif)'),
351        workbox.strategies.cacheFirst({
352            cacheName: 'brochure-image-cache',
353            plugins: [
354                new workbox.expiration.Plugin({
355                   maxEntries: 800,
356                   maxAgeSeconds: 31 * 24 * 60 * 60,
357                }),
358                new workbox.cacheableResponse.Plugin({
359                    statuses: [0, 200]
360                })
361            ],
362        })
363    );
364
365    /* Cache shops logo */
366    workbox.routing.registerRoute(
367        new RegExp('.*\/data\/[0-9]*\/logo_.{1,2}\.(?:png|jpg|jpeg|svg|gif)'),
368        workbox.strategies.cacheFirst({
369            cacheName: 'shop-image-cache',
370            plugins: [
371                new workbox.expiration.Plugin({
372                   maxAgeSeconds: 31 * 24 * 60 * 60,
373                }),
374                new workbox.cacheableResponse.Plugin({
375                    statuses: [0, 200]
376                })
377            ],
378        })
379    );
380    /* Cache static web img */
381    workbox.routing.registerRoute(
382        new RegExp('.*\/img\/.*\.(?:png|jpg|jpeg|svg|gif)'),
383        workbox.strategies.cacheFirst({
384            cacheName: 'static-image-cache',
385            plugins: [
386                new workbox.expiration.Plugin({
387                    maxAgeSeconds: 31 * 24 * 60 * 60,
388                }),
389                new workbox.cacheableResponse.Plugin({
390                    statuses: [0, 200]
391                })
392            ],
393        })
394    );
395
396 }

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.