Files
scrabble-game/ui/src/sw.ts
T
Ilia Denisov 3adc4e32c9
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m6s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m44s
fix(offline): hotseat review fixes + PWA stale-shell (SW route order)
Owner review of the pass-and-play PR:
- Roster delete (bug): a correct host PIN now ARMS a delete cross next to
  the row (mirroring the lobby delete) instead of removing it silently;
  tapping the kebab again clears the host authorisation.
- Variant picker (UX): replace the dropdown with the Quick-Match variant
  plaques + the multiple-words toggle, one-to-one.
- Keyboard layout (UX): the roster sits in a scroll region with the Start
  button pinned (friends-form pattern), so a name input's soft keyboard
  shrinks the region instead of leaving a full-height blank spacer painted
  behind the keyboard.
- e2e: variant via plaque + a row-delete (arm then cross) regression step.

PWA stale-version (pre-existing, same PR):
- sw.ts: register the network-first NavigationRoute BEFORE precacheAndRoute.
  Workbox matches in registration order and the precache route (directoryIndex
  index.html) shadowed the shell navigation, serving it cache-first — the old
  build's __APP_VERSION__ until a second load. Fixes both the maintenance
  self-reload and a cold open after a deploy. Doc updated (ARCHITECTURE).
2026-07-07 12:35:33 +02:00

61 lines
3.7 KiB
TypeScript

/// <reference lib="webworker" />
// Offline app-shell service worker (vite-plugin-pwa, injectManifest strategy). It precaches the app
// shell and its hashed assets so the installed PWA cold-launches with no network, and falls every
// in-scope navigation back to the precached shell (the hash router resolves the route client-side,
// so any deep link boots offline). It supersedes the former install-only public/sw.js. The
// Connect-RPC stream and the runtime API calls are POSTs — never precached, never matched by the
// navigation route — so the live app, its immutable hashed assets and the event stream are never
// served stale. Registration stays manual and web-only (pwa.svelte.ts): skipped in the mock build
// and inside a Telegram/VK Mini App.
import { clientsClaim } from 'workbox-core';
import { cleanupOutdatedCaches, matchPrecache, precacheAndRoute } from 'workbox-precaching';
import { NavigationRoute, registerRoute } from 'workbox-routing';
// The injectManifest build replaces self.__WB_MANIFEST with the precache list; the DOM lib knows
// neither it nor the service-worker scope, so narrow self here.
declare const self: ServiceWorkerGlobalScope & {
__WB_MANIFEST: (string | { url: string; revision: string | null })[];
};
// Activate a new revision immediately and take control of open clients, matching the former SW's
// skipWaiting; the no-cache HTML shell + immutable hashed assets keep a mid-session update safe.
self.skipWaiting();
clientsClaim();
// Drop precaches left by a previous SW revision (including the old install-only shell cache).
cleanupOutdatedCaches();
// Navigations are network-first so a new deploy loads immediately when online: fetch the fresh
// (no-cache) shell from the server — it references the new hashed assets, which are then fetched
// fresh — and only fall back to the precached shell when the network is unreachable or too slow (a
// short timeout). This keeps the app up to date online while still cold-launching offline. Only the
// tiny HTML is re-fetched; the immutable hashed assets stay cache-first (precacheAndRoute below) and
// re-download only when their hash — i.e. the version — changes. Deny-list the RPC path and the
// admin console so those are never resolved to the app shell.
//
// This route MUST be registered BEFORE precacheAndRoute: Workbox matches routes in registration
// order, and the precache route matches shell navigations too (its directoryIndex is index.html), so
// registering it first would shadow this handler and serve the shell CACHE-FIRST — i.e. the old
// build's version until the next load. NavigationRoute matches only request.mode === 'navigate', so
// hashed-asset requests still fall through to the cache-first precache route below.
const NAV_TIMEOUT_MS = 3000;
async function freshShell({ request }: { request: Request }): Promise<Response> {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), NAV_TIMEOUT_MS);
try {
const res = await fetch(request, { signal: ctrl.signal });
if (res.ok) return res;
} catch {
/* offline or too slow — serve the precached shell below */
} finally {
clearTimeout(timer);
}
return (await matchPrecache('index.html')) ?? Response.error();
}
registerRoute(new NavigationRoute(freshShell, { denylist: [/^\/scrabble\.edge\.v1\.Gateway/, /^\/_gm\//] }));
// Precache the shell + hashed assets injected at build time — exactly what a cold offline launch
// needs (the precached index.html is also the offline fallback matchPrecache serves above). The
// immutable hashed assets stay cache-first here; the landing page and the old-engine polyfill bundle
// are excluded via the build globs.
precacheAndRoute(self.__WB_MANIFEST);