PageSourceSearch

https://messiax.ai/sw.js

js messiax.ai collected 2026-10-05 14:22:25 UTC 12,654 bytes, 414 lines download raw bytes

1// Service Worker para Messiax PWA
2// Versão: 1.0.8
3// Funcionalidades: Cache offline robusto, widgets, note-taking, múltiplos domínios
4// Correções: Corrigido erro de Response com status 204, melhorado tratamento de arquivos JS/CSS
5
6const CACHE_NAME = 'messiax-v1.0.8';
7const STATIC_CACHE = 'messiax-static-v1.0.8';
8const DYNAMIC_CACHE = 'messiax-dynamic-v1.0.8';
9const WIDGET_CACHE = 'messiax-widget-v1.0.8';
10const OFFLINE_CACHE = 'messiax-offline-v1.0.8';
11
12// Recursos estáticos para cache
13const STATIC_ASSETS = [
14  '/',
15  '/index.html',
16  '/manifest.json',
17  '/favicon.ico',
18  '/icons/icon-192x192.png',
19  '/icons/icon-512x512.png',
20  '/icons/icon-192x192.svg',
21  '/icons/icon-512x512.svg',
22  '/assets/branding/logo.png',
23  '/assets/branding/logo2.png',
24  '/screenshots/desktop-1.svg',
25  '/screenshots/mobile-1.svg',
26  '/screenshots/mobile-2.svg',
27  '/screenshots/widget-chat.svg',
28  '/privacy-policy.html',
29  '/terms-of-service.html',
30  // Páginas PWA
31  '/share',
32  '/open-file',
33  '/protocol',
34  // Adicionar outros assets estáticos conforme necessário
35];
36
37// Recursos dinâmicos para cache
38const DYNAMIC_ASSETS = [
39  '/chat',
40  '/community',
41  '/sacred-books',
42  '/subscription',
43  '/profile',
44  '/bible',
45  '/start',
46  '/share',
47  '/open-file',
48  '/protocol'
49];
50
51// Recursos offline essenciais
52const OFFLINE_ASSETS = [
53  '/',
54  '/index.html',
55  '/chat',
56  '/manifest.json',
57  '/icons/icon-192x192.png',
58  '/icons/icon-512x512.png'
59];
60
61// Domínios bloqueados (não devem ser cacheados)
62const BLOCKED_DOMAINS = [
63  'gc.kis.v2.scr.kaspersky-labs.com',
64  'kaspersky-labs.com'
65];
66
67// Função para verificar se o recurso deve ser bloqueado
68function shouldBlockRequest(url) {
69  try {
70    const urlObj = new URL(url);
71    return BLOCKED_DOMAINS.some(domain => urlObj.hostname.includes(domain));
72  } catch (error) {
73    return false;
74  }
75}
76
77// Instalação do Service Worker
78self.addEventListener('install', (event) => {
79  console.log('[SW] Instalando Service Worker v1.0.7...');
80  
81  event.waitUntil(
82    Promise.all([
83      // Cache estático
84      caches.open(STATIC_CACHE).then((cache) => {
85        console.log('[SW] Cacheando recursos estáticos...');
86        return cache.addAll(STATIC_ASSETS);
87      }),
88      // Cache dinâmico
89      caches.open(DYNAMIC_CACHE).then((cache) => {
90        console.log('[SW] Preparando cache dinâmico...');
91        return cache.addAll(DYNAMIC_ASSETS);
92      }),
93      // Cache de widgets
94      caches.open(WIDGET_CACHE).then((cache) => {
95        console.log('[SW] Preparando cache de widgets...');
96        return cache.addAll(['/chat', '/screenshots/widget-chat.svg']);
97      }),
98      // Cache offline essencial
99      caches.open(OFFLINE_CACHE).then((cache) => {
100        console.log('[SW] Preparando cache offline essencial...');
101        return cache.addAll(OFFLINE_ASSETS);
102      })
103    ]).then(() => {
104      console.log('[SW] Todos os caches preparados com sucesso!');
105      return self.skipWaiting();
106    }).catch((error) => {
107      console.error('[SW] Erro ao preparar caches:', error);
108    })
109  );
110});
111
112// Ativação do Service Worker
113self.addEventListener('activate', (event) => {
114  console.log('[SW] Ativando Service Worker...');
115  
116  event.waitUntil(
117    caches.keys()
118      .then((cacheNames) => {
119        const currentCaches = [STATIC_CACHE, DYNAMIC_CACHE, WIDGET_CACHE, OFFLINE_CACHE];
120        return Promise.all(
121          cacheNames.map((cacheName) => {
122            if (!currentCaches.includes(cacheName)) {
123              console.log('[SW] Removendo cache antigo:', cacheName);
124              return caches.delete(cacheName);
125            }
126          })
127        );
128      })
129      .then(() => {
130        console.log('[SW] Service Worker ativado!');
131        return self.clients.claim();
132      })
133  );
134});
135
136// Interceptação de requisições
137self.addEventListener('fetch', (event) => {
138  const { request } = event;
139  const url = new URL(request.url);
140
141  // Bloquear domínios problemáticos - simplesmente não responder
142  if (shouldBlockRequest(request.url)) {
143    console.log('[SW] Bloqueando requisição para:', request.url);
144    // Não responder a requisições bloqueadas - deixar que falhem naturalmente
145    return;
146  }
147
148  // NÃO interceptar arquivos JS, CSS e outros assets estáticos - deixar o servidor servir diretamente
149  // Isso evita problemas de MIME type
150  if (url.pathname.match(/\.(js|mjs|css|woff|woff2|ttf|eot|png|jpg|jpeg|gif|svg|ico|webp|mp4|webm|mp3|wav|ogg)$/i)) {
151    // Deixar o servidor servir diretamente sem cache do SW
152    event.respondWith(fetch(request));
153    return;
154  }
155
156  // Estratégia de cache para diferentes tipos de recursos
157  if (request.method === 'GET') {
158    // Widgets - Cache First para melhor performance
159    if (url.pathname.includes('widget') || 
160        url.pathname === '/chat' ||
161        url.pathname.includes('note-taking')) {
162      event.respondWith(widgetCacheFirst(request));
163    }
164    // Recursos estáticos - Cache First (mas não JS/CSS que já foram tratados acima)
165    else if (STATIC_ASSETS.includes(url.pathname) || 
166        url.pathname.startsWith('/static/')) {
167      event.respondWith(cacheFirst(request));
168    }
169    // Páginas da aplicação - Network First com fallback
170    else if (url.origin === location.origin) {
171      event.respondWith(networkFirst(request));
172    }
173    // APIs externas - Network First
174    else if (url.hostname.includes('supabase.co') || 
175             url.hostname.includes('stripe.com') ||
176             url.hostname.includes('bible-api.com')) {
177      event.respondWith(networkFirst(request));
178    }
179    // Stripe.js - Sempre permitir (não cachear)
180    else if (url.hostname.includes('js.stripe.com') || 
181             url.hostname.includes('checkout.stripe.com')) {
182      event.respondWith(fetch(request));
183    }
184    // Imagens externas - Network First (não cachear)
185    else if (url.hostname.includes('freepik.com') || 
186             url.hostname.includes('img.freepik.com') ||
187             url.hostname.includes('unsplash.com') ||
188             url.hostname.includes('pixabay.com')) {
189      event.respondWith(fetch(request));
190    }
191    // Outros recursos - Network First
192    else {
193      event.respondWith(networkFirst(request));
194    }
195  }
196});
197
198// Estratégia Cache First
199async function cacheFirst(request) {
200  try {
201    const cachedResponse = await caches.match(request);
202    if (cachedResponse) {
203      return cachedResponse;
204    }
205    
206    const networkResponse = await fetch(request);
207    if (networkResponse.ok) {
208      const cache = await caches.open(STATIC_CACHE);
209      cache.put(request, networkResponse.clone());
210    }
211    return networkResponse;
212  } catch (error) {
213    console.log('[SW] Erro no cache first para:', request.url, error.message);
214    // Retornar erro 503 em vez de resposta vazia
215    return new Response('Service Unavailable', { 
216      status: 503,
217      statusText: 'Service Unavailable',
218      headers: { 'Content-Type': 'text/plain' }
219    });
220  }
221}
222
223// Estratégia Network First
224async function networkFirst(request) {
225  try {
226    const networkResponse = await fetch(request);
227    if (networkResponse.ok) {
228      const cache = await caches.open(DYNAMIC_CACHE);
229      cache.put(request, networkResponse.clone());
230    }
231    return networkResponse;
232  } catch (error) {
233    console.log('[SW] Rede indisponível, tentando cache...');
234    const cachedResponse = await caches.match(request);
235    if (cachedResponse) {
236      return cachedResponse;
237    }
238    
239    // Fallback offline robusto
240    if (request.destination === 'document') {
241      // Tentar cache offline essencial primeiro
242      const offlineResponse = await caches.match(request.url, { cacheName: OFFLINE_CACHE });
243      if (offlineResponse) {
244        console.log('[SW] Servindo página offline:', request.url);
245        return offlineResponse;
246      }
247      
248      // Fallback para index.html
249      const fallbackResponse = await caches.match('/index.html');
250      if (fallbackResponse) {
251        console.log('[SW] Fallback para index.html');
252        return fallbackResponse;
253      }
254    }
255
256    // Fallback para recursos estáticos
257    const staticFallback = await caches.match(request.url, { cacheName: STATIC_CACHE });
258    if (staticFallback) {
259      console.log('[SW] Servindo recurso estático offline:', request.url);
260      return staticFallback;
261    }
262    
263    // Retornar erro 503 se não houver cache disponível
264    return new Response('Service Unavailable', { 
265      status: 503,
266      statusText: 'Service Unavailable',
267      headers: { 'Content-Type': 'text/plain' }
268    });
269  }
270}
271
272// Notificações Push
273self.addEventListener('push', (event) => {
274  console.log('[SW] Notificação push recebida:', event);
275  
276  if (event.data) {
277    const data = event.data.json();
278    const options = {
279      body: data.body || 'Nova mensagem espiritual disponível',
280      icon: '/icons/icon-192x192.png',
281      badge: '/icons/badge-72x72.png',
282      vibrate: [200, 100, 200],
283      data: data.data || {},
284      actions: [
285        {
286          action: 'open',
287          title: 'Abrir',
288          icon: '/icons/open-icon.png'
289        },
290        {
291          action: 'close',
292          title: 'Fechar',
293          icon: '/icons/close-icon.png'
294        }
295      ],
296      requireInteraction: true,
297      silent: false
298    };
299    
300    event.waitUntil(
301      self.registration.showNotification(data.title || 'Messiax', options)
302    );
303  }
304});
305
306// Clique em notificação
307self.addEventListener('notificationclick', (event) => {
308  console.log('[SW] Clique em notificação:', event);
309  
310  event.notification.close();
311  
312  if (event.action === 'open' || !event.action) {
313    event.waitUntil(
314      clients.openWindow('/')
315    );
316  }
317});
318
319// Sincronização em background
320self.addEventListener('sync', (event) => {
321  console.log('[SW] Sincronização em background:', event.tag);
322  
323  if (event.tag === 'background-sync') {
324    event.waitUntil(doBackgroundSync());
325  }
326});
327
328// Função de sincronização em background
329async function doBackgroundSync() {
330  try {
331    // Sincronizar dados offline quando a conexão voltar
332    console.log('[SW] Executando sincronização em background...');
333    
334    // Aqui você pode implementar a lógica de sincronização
335    // Por exemplo, enviar mensagens offline, sincronizar dados do chat, etc.
336    
337    console.log('[SW] Sincronização concluída!');
338  } catch (error) {
339    console.error('[SW] Erro na sincronização:', error);
340  }
341}
342
343// Mensagens do cliente
344self.addEventListener('message', (event) => {
345  console.log('[SW] Mensagem recebida:', event.data);
346  
347  if (event.data && event.data.type === 'SKIP_WAITING') {
348    self.skipWaiting();
349  }
350  
351  if (event.data && event.data.type === 'GET_VERSION') {
352    event.ports[0].postMessage({ version: CACHE_NAME });
353  }
354});
355
356// Limpeza periódica do cache
357self.addEventListener('periodicsync', (event) => {
358  if (event.tag === 'cache-cleanup') {
359    event.waitUntil(cleanupCache());
360  }
361});
362
363// Estratégia Widget Cache First - Otimizada para widgets
364async function widgetCacheFirst(request) {
365  try {
366    const cachedResponse = await caches.match(request);
367    if (cachedResponse) {
368      console.log('[SW] Widget servido do cache:', request.url);
369      return cachedResponse;
370    }
371
372    const networkResponse = await fetch(request);
373    if (networkResponse.ok) {
374      const cache = await caches.open(WIDGET_CACHE);
375      cache.put(request, networkResponse.clone());
376      console.log('[SW] Widget cacheado:', request.url);
377    }
378    return networkResponse;
379  } catch (error) {
380    console.log('[SW] Erro no widget cache first para:', request.url, error.message);
381    
382    // Fallback para página de chat offline
383    if (request.url.includes('/chat')) {
384      const fallbackResponse = await caches.match('/chat');
385      if (fallbackResponse) {
386        return fallbackResponse;
387      }
388    }
389    
390    // Retornar erro 503 se não houver fallback disponível
391    return new Response('Service Unavailable', { 
392      status: 503,
393      statusText: 'Service Unavailable',
394      headers: { 'Content-Type': 'text/plain' }
395    });
396  }
397}
398
399async function cleanupCache() {
400  try {
401    const cacheNames = await caches.keys();
402    const currentCaches = [STATIC_CACHE, DYNAMIC_CACHE, WIDGET_CACHE, OFFLINE_CACHE];
403    const oldCaches = cacheNames.filter(name => 
404      name.startsWith('messiax-') && !currentCaches.includes(name)
405    );
406    
407    await Promise.all(oldCaches.map(name => caches.delete(name)));
408    console.log('[SW] Cache limpo com sucesso! Removidos:', oldCaches);
409  } catch (error) {
410    console.error('[SW] Erro na limpeza do cache:', error);
411  }
412}
413
414console.log('[SW] Service Worker v1.0.8 carregado com cache offline robusto!');

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.