PWA & Offline-First
O tempest-react-sdk traz tudo que um app Tempest precisa pra virar um PWA offline-first — sem depender de Workbox nem de vite-plugin-pwa. Esta página é o mapa: registro e atualização do service worker, cache, sincronização em background, durabilidade do armazenamento e a UI de status. O motor de sincronização (createOfflineSync) tem sua própria receita em Offline Sync (FastAPI) — aqui cobrimos a camada PWA ao redor dele.
As peças
| Camada | O que faz | Onde roda |
|---|---|---|
tempest-react-sdk/vite |
gera manifest, ícones, SW de dev | build (Vite) |
tempest-react-sdk/sw |
precache, cache runtime, background sync | dentro do sw.ts |
| hooks + componentes | update, status, quota, indicadores | main thread (React) |
1. Registrar o service worker e atualizar com consentimento
registerServiceWorker cuida do register() + detecção de update. Para o fluxo guiado pelo usuário ("Nova versão — atualizar"), use o hook useServiceWorkerUpdate com o componente <UpdatePrompt>:
import { useServiceWorkerUpdate, UpdatePrompt } from "tempest-react-sdk";
export function ServiceWorkerGate() {
const { updateAvailable, applyUpdate } = useServiceWorkerUpdate({ url: "/sw.js" });
return <UpdatePrompt open={updateAvailable} onUpdate={applyUpdate} />;
}
Quando um worker novo termina de instalar, updateAvailable vira true; applyUpdate() ativa o worker em espera (skipWaiting) e recarrega a página assim que ele assume o controle.
Deixe autoUpdate desligado aqui
useServiceWorkerUpdate existe para dar a decisão de recarregar ao usuário. Se você quer recarga silenciosa, use registerServiceWorker({ url, autoUpdate: true }) direto, sem o hook.
2. Precache + cache runtime + navigation preload
Dentro do seu sw.ts, registre as rotas específicas antes do precache (para vencerem o catch-all):
/// <reference lib="webworker" />
import { installRuntimeCache, installPrecache } from "tempest-react-sdk/sw";
installRuntimeCache([
{ match: /\/api\//, strategy: "network-first", cacheName: "api", maxAgeSeconds: 300 },
{ match: /\.(png|jpg|webp)$/, strategy: "cache-first", cacheName: "img", maxEntries: 60 },
]);
installPrecache(); // app shell + navegação offline
installPrecache liga a Navigation Preload API no activate por padrão (navigationPreload: true): o navegador começa a buscar a navegação em paralelo com o boot do worker e o handler serve event.preloadResponse, cortando a latência da primeira navegação depois que o worker sobe.
De onde vem o manifest
installPrecache lê precache-manifest.json, emitido pelo plugin tempestPwaManifest() do subpath tempest-react-sdk/vite. Veja Vite config.
Deploy em subpath (GitHub Pages e afins)
O tempestPwaManifest() prefixa as URLs com o base do Vite, mas o installPrecache roda no worker e não enxerga essa config. Se o seu base não é /, aponte as duas opções de caminho para dentro dele:
installPrecache({
manifestUrl: "/meu-app/precache-manifest.json",
navigateFallback: "/meu-app/index.html",
});
Com os defaults (/precache-manifest.json e /index.html), o worker instala, busca um manifest que responde 404 e a navegação offline nunca resolve.
3. Background sync + periodic sync
installBackgroundSync enfileira mutações (POST/PUT/PATCH/DELETE) que falham offline e as reenvia quando a rede volta — via Background Sync API, com fallback oportunista para navegadores sem ela (Safari).
import { installBackgroundSync } from "tempest-react-sdk/sw";
installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
Para reenviar mesmo sem uma navegação nova, registre um periodic sync na main thread. O SW já escuta o evento periodicsync:
import { registerPeriodicSync } from "tempest-react-sdk/sw";
const registration = await navigator.serviceWorker.ready;
await registerPeriodicSync({ registration, minIntervalMinutes: 360 });
registerPeriodicSync checa a permissão periodic-background-sync primeiro e retorna false quando indisponível (Chrome-only) — chamar sempre é seguro.
4. Durabilidade: peça armazenamento persistente
Sem navigator.storage.persist(), o navegador pode despejar o IndexedDB e o Cache Storage sob pressão de disco — fatal pra dados offline. useStorageEstimate mostra uso/quota e expõe a ação de tornar o armazenamento permanente:
import { useStorageEstimate } from "tempest-react-sdk";
function StorageMeter() {
const { usage, quota, ratio, persisted, requestPersist } = useStorageEstimate();
return (
<div>
<progress value={ratio ?? 0} />
<span>
{((usage ?? 0) / 1e6).toFixed(1)} de {((quota ?? 0) / 1e6).toFixed(0)} MB
</span>
{!persisted && <button onClick={requestPersist}>Tornar permanente</button>}
</div>
);
}
Fora do React, use as funções puras estimateStorage() e requestPersistentStorage().
5. Observabilidade do cache
Para um readout "X MB cacheado" ou um "limpar cache" no logout, use os helpers de main thread:
import { inspectCaches, clearCaches } from "tempest-react-sdk/sw";
const reports = await inspectCaches({ filter: "tempest-" });
const totalMb = reports.reduce((n, r) => n + (r.bytes ?? 0), 0) / 1e6;
await clearCaches("tempest-"); // no logout
inspectCaches mede bytes por padrão (lendo cada resposta); passe measureBytes: false para um relatório rápido só de contagem.
6. UI de status offline
Dois componentes prontos, guiados por useOnline e pelo motor de sync:
import { OfflineIndicator, SyncStatusBadge, useSyncStatus } from "tempest-react-sdk";
import { notesSync } from "@/sync/engine";
function StatusBar() {
const { tone, pending } = useSyncStatus(notesSync);
return (
<>
<OfflineIndicator position="top" />
<SyncStatusBadge tone={tone} pending={pending} />
</>
);
}
<OfflineIndicator>— barra fixa que aparece offline e pisca uma confirmação ao reconectar. Não renderiza nada online, então é seguro montar na raiz do app.<SyncStatusBadge>— pílula com ícone + label + contagem de pendências, alimentada poruseSyncStatus(sync).tone.
7. Hooks reativos do motor de sync
useOfflineSync inscreve um componente no OfflineSync e opcionalmente dispara flush no mount, no evento online e num intervalo:
import { useOfflineSync } from "tempest-react-sdk";
import { notesSync } from "@/sync/engine";
function useNotesSync() {
return useOfflineSync(notesSync, { flushOnMount: true, intervalMs: 30_000 });
// → { phase, pending, syncing, lastSummary, lastError, lastSyncedAt, enqueue, flush }
}
Para mutações otimistas que gravam no outbox e atualizam o cache do TanStack Query (com rollback em falha), use useOfflineMutation — veja Query.
8. Resolução de conflitos
O pull sobrescreve o registro local pela versão do servidor. Quando precisar de uma política explícita dentro do applyRemote, os helpers cobrem os dois casos comuns (empate → remoto vence):
import { lastWriteWins, higherVersionWins } from "tempest-react-sdk";
applyRemote: async (dto) => {
const local = await store.get(dto.id);
await store.save(lastWriteWins(local, dto, (r) => r.updatedAt));
};
Recap
useServiceWorkerUpdate+<UpdatePrompt>dão o fluxo de atualização com consentimento. ✅installPrecacheliga navigation preload;installRuntimeCachecobre as estratégias Workbox-like.installBackgroundSync+registerPeriodicSyncreenviam mutações offline, com ou sem navegação nova.useStorageEstimate+requestPersistentStorageevitam o despejo do IndexedDB.inspectCaches/clearCachesdão visibilidade e limpeza do cache.<OfflineIndicator>/<SyncStatusBadge>/useOfflineSync/useSyncStatusmontam a UI de status reativa. 🚀