- Service Worker v1.39.0 com estratégias offline-first - Cache-First: imagens, fontes (resposta imediata) - Network-First: chamadas API (dados frescos) - Stale-While-Revalidate: HTML/CSS/JS - Verificação de atualizações a cada 1 hora - Prompt de atualização quando nova versão disponível - manifest.json otimizado: scope, categories, prefer_related_applications - Meta tags iOS: apple-mobile-web-app-capable, black-translucent - Registro automático do Service Worker em main.jsx - Documentação completa: docs/INSTALACAO_iOS.md - Compatível com upgrade futuro para Capacitor (30 min, zero mudanças) Deploy: frontend/dist deployed to 213.165.93.60
167 lines
4.3 KiB
JavaScript
167 lines
4.3 KiB
JavaScript
// WebMoney Service Worker - PWA Support
|
|
const CACHE_VERSION = 'webmoney-v1.39.0';
|
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
|
const CACHE_DYNAMIC = `${CACHE_VERSION}-dynamic`;
|
|
const CACHE_IMMUTABLE = `${CACHE_VERSION}-immutable`;
|
|
|
|
// Recursos para cache imediato (install)
|
|
const STATIC_ASSETS = [
|
|
'/',
|
|
'/index.html',
|
|
'/manifest.json',
|
|
'/logo-192.png',
|
|
'/logo-512.png',
|
|
'/apple-touch-icon.png',
|
|
];
|
|
|
|
// Estratégia: Cache First com fallback para Network
|
|
const CACHE_FIRST_PATTERNS = [
|
|
/\.(png|jpg|jpeg|svg|gif|webp|ico)$/,
|
|
/\.(woff|woff2|ttf|eot)$/,
|
|
/bootstrap-icons/,
|
|
];
|
|
|
|
// Estratégia: Network First com fallback para Cache
|
|
const NETWORK_FIRST_PATTERNS = [
|
|
/\/api\//,
|
|
];
|
|
|
|
// Install - cacheia recursos estáticos
|
|
self.addEventListener('install', (event) => {
|
|
console.log('[SW] Installing Service Worker...', CACHE_VERSION);
|
|
|
|
event.waitUntil(
|
|
caches.open(CACHE_STATIC).then((cache) => {
|
|
console.log('[SW] Caching static assets');
|
|
return cache.addAll(STATIC_ASSETS);
|
|
})
|
|
.then(() => self.skipWaiting()) // Ativa imediatamente
|
|
);
|
|
});
|
|
|
|
// Activate - limpa caches antigos
|
|
self.addEventListener('activate', (event) => {
|
|
console.log('[SW] Activating Service Worker...', CACHE_VERSION);
|
|
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames
|
|
.filter((name) => name.startsWith('webmoney-') && name !== CACHE_STATIC && name !== CACHE_DYNAMIC && name !== CACHE_IMMUTABLE)
|
|
.map((name) => {
|
|
console.log('[SW] Deleting old cache:', name);
|
|
return caches.delete(name);
|
|
})
|
|
);
|
|
})
|
|
.then(() => self.clients.claim()) // Assume controle imediatamente
|
|
);
|
|
});
|
|
|
|
// Fetch - estratégias de cache
|
|
self.addEventListener('fetch', (event) => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Ignora requisições não-GET
|
|
if (request.method !== 'GET') {
|
|
return;
|
|
}
|
|
|
|
// Ignora chrome-extension e outras URLs especiais
|
|
if (!url.protocol.startsWith('http')) {
|
|
return;
|
|
}
|
|
|
|
// Cache First para assets estáticos
|
|
if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) {
|
|
event.respondWith(cacheFirst(request));
|
|
return;
|
|
}
|
|
|
|
// Network First para API calls
|
|
if (NETWORK_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) {
|
|
event.respondWith(networkFirst(request));
|
|
return;
|
|
}
|
|
|
|
// Stale-While-Revalidate para HTML/CSS/JS
|
|
event.respondWith(staleWhileRevalidate(request));
|
|
});
|
|
|
|
// Estratégia: Cache First (imagens, fontes)
|
|
async function cacheFirst(request) {
|
|
const cache = await caches.open(CACHE_IMMUTABLE);
|
|
const cached = await cache.match(request);
|
|
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
console.log('[SW] Fetch failed for:', request.url);
|
|
// Retorna cache mesmo se expirado
|
|
return cached || new Response('Network error', { status: 408 });
|
|
}
|
|
}
|
|
|
|
// Estratégia: Network First (API)
|
|
async function networkFirst(request) {
|
|
const cache = await caches.open(CACHE_DYNAMIC);
|
|
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
console.log('[SW] Network failed, trying cache:', request.url);
|
|
const cached = await cache.match(request);
|
|
return cached || new Response(JSON.stringify({ error: 'Offline' }), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Estratégia: Stale-While-Revalidate (HTML, CSS, JS)
|
|
async function staleWhileRevalidate(request) {
|
|
const cache = await caches.open(CACHE_DYNAMIC);
|
|
const cached = await cache.match(request);
|
|
|
|
const fetchPromise = fetch(request).then((response) => {
|
|
if (response.ok) {
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
});
|
|
|
|
return cached || fetchPromise;
|
|
}
|
|
|
|
// Mensagens do cliente
|
|
self.addEventListener('message', (event) => {
|
|
if (event.data === 'SKIP_WAITING') {
|
|
self.skipWaiting();
|
|
}
|
|
|
|
if (event.data === 'CLEAR_CACHE') {
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames.map((name) => caches.delete(name))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
console.log('[SW] Service Worker loaded:', CACHE_VERSION);
|