PageSourceSearch

https://jornalailha.com.br/encontra-push-sw.js

js jornalailha.com.br collected 2026-10-04 18:52:16 UTC 7,552 bytes, 261 lines download raw bytes

1/* =========================================================================
2   Encontra Push — Service Worker para sites estaticos.
3
4   Modelo: src/pages/encontra-push-sw.js.ts troca os marcadores __EP_*__ no
5   build e publica o resultado em /encontra-push-sw.js (scope "/").
6
7   Mesmo comportamento do Service Worker do plugin WordPress, com uma
8   diferenca: os eventos vao direto para a API publica do painel, porque o
9   site estatico nao tem servidor para reencaminhar. Nenhuma credencial vive
10   aqui — so a chave publica do site, que ja esta no HTML.
11   ========================================================================= */
12
13const EP_VERSION = '1.0.3';
14const EP_API = 'https://push.encontra.com.br/api/v1/public/pk_04e0475db4c6061b4818c2fc587a5279'; // https://push.../api/v1/public/pk_...
15
16self.addEventListener('install', () => {
17    self.skipWaiting();
18});
19
20self.addEventListener('activate', (event) => {
21    event.waitUntil(self.clients.claim());
22});
23
24/* ----------------------------------------------------------------- push -- */
25
26self.addEventListener('push', (event) => {
27    event.waitUntil(handlePush(event));
28});
29
30async function handlePush(event) {
31    const payload = readPayload(event);
32
33    // Toda mensagem precisa virar notificacao visivel: navegadores revogam a
34    // permissao de origens que recebem push "silencioso".
35    if (!payload) {
36        await self.registration.showNotification('Novidades', {
37            body: 'Toque para ver as novidades do site.',
38            tag: 'encontra-push-fallback',
39        });
40
41        return;
42    }
43
44    const receivedBeacon = sendEvent('received', payload);
45
46    const options = {
47        body: payload.body || '',
48        icon: payload.icon || undefined,
49        badge: payload.badge || undefined,
50        image: payload.image || undefined,
51        tag: payload.tag || undefined,
52        renotify: Boolean(payload.renotify && payload.tag),
53        requireInteraction: Boolean(payload.requireInteraction),
54        timestamp: Date.now(),
55        data: {
56            url: payload.url,
57            message_id: payload.message_id,
58            campaign_id: payload.campaign_id,
59            event_token: payload.data && payload.data.event_token,
60        },
61    };
62
63    if (Array.isArray(payload.actions) && payload.actions.length) {
64        options.actions = payload.actions.slice(0, 2);
65    }
66
67    await self.registration.showNotification(payload.title || 'Novidades', options);
68
69    await Promise.all([receivedBeacon, sendEvent('shown', payload)]);
70}
71
72function readPayload(event) {
73    if (!event.data) {
74        return null;
75    }
76
77    try {
78        return event.data.json();
79    } catch (e) {
80        try {
81            const text = event.data.text();
82
83            return text ? { title: 'Novidades', body: text } : null;
84        } catch (err) {
85            return null;
86        }
87    }
88}
89
90/* ------------------------------------------------------ notificationclick */
91
92self.addEventListener('notificationclick', (event) => {
93    event.notification.close();
94
95    const data = event.notification.data || {};
96
97    event.waitUntil(
98        Promise.all([
99            sendEvent('clicked', { message_id: data.message_id, data: { event_token: data.event_token } }),
100            openTarget(data.url),
101        ])
102    );
103});
104
105async function openTarget(url) {
106    if (!url) {
107        return;
108    }
109
110    let target;
111
112    try {
113        target = new URL(url);
114    } catch (e) {
115        return;
116    }
117
118    // So paginas web: nunca javascript:, data: ou file:.
119    if (target.protocol !== 'https:' && target.protocol !== 'http:') {
120        return;
121    }
122
123    const clientList = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
124
125    for (const client of clientList) {
126        let current;
127
128        try {
129            current = new URL(client.url);
130        } catch (e) {
131            continue;
132        }
133
134        if (current.origin === target.origin && 'focus' in client) {
135            await client.focus();
136
137            if ('navigate' in client && current.href !== target.href) {
138                return client.navigate(target.href);
139            }
140
141            return;
142        }
143    }
144
145    if (self.clients.openWindow) {
146        return self.clients.openWindow(target.href);
147    }
148}
149
150/* ------------------------------------------------------ notificationclose */
151
152self.addEventListener('notificationclose', (event) => {
153    const data = event.notification.data || {};
154
155    event.waitUntil(
156        sendEvent('closed', { message_id: data.message_id, data: { event_token: data.event_token } })
157    );
158});
159
160/* ------------------------------------------------- pushsubscriptionchange */
161
162self.addEventListener('pushsubscriptionchange', (event) => {
163    event.waitUntil(renewSubscription(event));
164});
165
166async function renewSubscription(event) {
167    try {
168        const oldSubscription = event.oldSubscription || (await self.registration.pushManager.getSubscription());
169
170        const applicationServerKey = event.oldSubscription && event.oldSubscription.options
171            ? event.oldSubscription.options.applicationServerKey
172            : null;
173
174        if (!applicationServerKey) {
175            return;
176        }
177
178        const fresh = await self.registration.pushManager.subscribe({
179            userVisibleOnly: true,
180            applicationServerKey: applicationServerKey,
181        });
182
183        const json = fresh.toJSON();
184
185        await post('/subscribe', {
186            endpoint: json.endpoint,
187            p256dh: json.keys && json.keys.p256dh,
188            auth: json.keys && json.keys.auth,
189            source_url: self.registration.scope,
190        });
191
192        if (oldSubscription && oldSubscription.endpoint !== json.endpoint) {
193            await post('/unsubscribe', { endpoint: oldSubscription.endpoint });
194        }
195    } catch (e) {
196        // O proximo envio falhara com erro permanente e o painel invalidara a
197        // subscription antiga sozinho.
198    }
199}
200
201/* ---------------------------------------------------------------- beacon -- */
202
203/*
204 * JSON enviado como text/plain: e uma requisicao "simples" de CORS, sem
205 * preflight. O painel decodifica o corpo do mesmo jeito.
206 */
207function post(path, body) {
208    return fetch(EP_API + path, {
209        method: 'POST',
210        mode: 'cors',
211        credentials: 'omit',
212        headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
213        body: JSON.stringify(body),
214        keepalive: true,
215    });
216}
217
218function sendEvent(type, payload) {
219    const token = payload && payload.data && payload.data.event_token;
220
221    if (!token) {
222        return Promise.resolve();
223    }
224
225    return post('/events', {
226        events: [
227            {
228                type: type,
229                token: token,
230                event_id: generateEventId(),
231                event_at: new Date().toISOString(),
232            },
233        ],
234    }).catch(() => {
235        // Beacon e melhor esforco: perder uma metrica nunca impede a notificacao.
236    });
237}
238
239/** ULID simplificado: ordenavel por tempo e sem colisao pratica. */
240function generateEventId() {
241    const alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
242    let timestamp = Date.now();
243    let id = '';
244
245    for (let i = 0; i < 10; i++) {
246        id = alphabet[timestamp % 32] + id;
247        timestamp = Math.floor(timestamp / 32);
248    }
249
250    const random = new Uint8Array(16);
251
252    if (self.crypto && self.crypto.getRandomValues) {
253        self.crypto.getRandomValues(random);
254    }
255
256    for (let i = 0; i < 16; i++) {
257        id += alphabet[random[i] % 32];
258    }
259
260    return id;
261}

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.