You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.0 KiB
JavaScript
132 lines
4.0 KiB
JavaScript
/**
|
|
* Service Worker — Italiano Vivo
|
|
* Estratégia:
|
|
* - Shell (HTML/CSS/JS do app): cache-first
|
|
* - /content/* e /audio/*: cache-first com network fallback + runtime caching
|
|
* - /api/*: network-only (nunca cachear)
|
|
*
|
|
* Objetivo: modo resgate offline — frases + áudio já baixados funcionam no avião.
|
|
*/
|
|
|
|
const CACHE_NAME = "italiano-vivo-v2";
|
|
const SHELL_ASSETS = ["/", "/index.html"];
|
|
|
|
// ─── Install: pré-cache do shell ───
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)),
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
|
|
// ─── Activate: limpar caches antigos ───
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((keys) =>
|
|
Promise.all(
|
|
keys
|
|
.filter((k) => k !== CACHE_NAME)
|
|
.map((k) => caches.delete(k)),
|
|
),
|
|
),
|
|
);
|
|
self.clients.claim();
|
|
});
|
|
|
|
// ─── Fetch: roteamento por padrão de URL ───
|
|
self.addEventListener("fetch", (event) => {
|
|
const url = new URL(event.request.url);
|
|
const pathname = url.pathname;
|
|
|
|
// API: sempre network-only
|
|
if (pathname.startsWith("/api/")) {
|
|
return; // deixa o browser fazer o fetch normal
|
|
}
|
|
|
|
// Content e Audio: cache-first com network fallback + runtime caching
|
|
if (pathname.startsWith("/content/") || pathname.startsWith("/audio/")) {
|
|
event.respondWith(cacheFirstWithRuntimeCache(event.request));
|
|
return;
|
|
}
|
|
|
|
// Shell e demais assets: cache-first
|
|
event.respondWith(cacheFirst(event.request));
|
|
});
|
|
|
|
/**
|
|
* Network-first para o shell (HTML): sempre busca a versao mais nova e
|
|
* atualiza o cache em background. Evita o app ficar preso numa build antiga
|
|
* apos deploy. Assets versionados (/assets/*-hash) podem ser cache-first
|
|
* porque o nome muda a cada build.
|
|
*/
|
|
async function cacheFirst(request) {
|
|
const url = new URL(request.url);
|
|
const isShellHtml = url.pathname === "/" || url.pathname === "/index.html" || url.pathname === "/sw.js";
|
|
if (isShellHtml) {
|
|
try {
|
|
const fresh = await fetch(request);
|
|
if (fresh.ok) {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
cache.put(request, fresh.clone());
|
|
}
|
|
return fresh;
|
|
} catch (_err) {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
throw _err;
|
|
}
|
|
}
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
return fetch(request);
|
|
}
|
|
|
|
/**
|
|
* Cache-first com runtime caching: tenta cache, se não achar vai na rede
|
|
* e salva a resposta no cache para uso offline futuro.
|
|
* Usado para /content/* e /audio/* que são baixados sob demanda.
|
|
*/
|
|
async function cacheFirstWithRuntimeCache(request) {
|
|
// Network-first para /content/* (JSON de missoes pode ser corrigido a qualquer
|
|
// momento; cache antigo nao deve esconder o novo). Audio e imutavel -> cache-first.
|
|
const url = new URL(request.url);
|
|
if (url.pathname.startsWith("/content/")) {
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (_err) {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
return new Response("Offline — Italiano Vivo", {
|
|
status: 503,
|
|
statusText: "Service Unavailable",
|
|
headers: { "Content-Type": "text/plain" },
|
|
});
|
|
}
|
|
}
|
|
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
|
|
try {
|
|
const response = await fetch(request);
|
|
// Só cachear respostas OK (não cachear 404/500)
|
|
if (response.ok) {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch (_err) {
|
|
// Offline e sem cache: retorna erro genérico
|
|
return new Response("Offline — Italiano Vivo", {
|
|
status: 503,
|
|
statusText: "Service Unavailable",
|
|
headers: { "Content-Type": "text/plain" },
|
|
});
|
|
}
|
|
}
|