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.