@prabhask5/stellar-engine 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -25
- package/dist/actions/remoteChange.d.ts +143 -18
- package/dist/actions/remoteChange.d.ts.map +1 -1
- package/dist/actions/remoteChange.js +182 -58
- package/dist/actions/remoteChange.js.map +1 -1
- package/dist/actions/truncateTooltip.d.ts +56 -0
- package/dist/actions/truncateTooltip.d.ts.map +1 -0
- package/dist/actions/truncateTooltip.js +312 -0
- package/dist/actions/truncateTooltip.js.map +1 -0
- package/dist/auth/admin.d.ts +40 -3
- package/dist/auth/admin.d.ts.map +1 -1
- package/dist/auth/admin.js +45 -5
- package/dist/auth/admin.js.map +1 -1
- package/dist/auth/crypto.d.ts +55 -5
- package/dist/auth/crypto.d.ts.map +1 -1
- package/dist/auth/crypto.js +58 -5
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/deviceVerification.d.ts +236 -20
- package/dist/auth/deviceVerification.d.ts.map +1 -1
- package/dist/auth/deviceVerification.js +293 -40
- package/dist/auth/deviceVerification.js.map +1 -1
- package/dist/auth/displayUtils.d.ts +98 -0
- package/dist/auth/displayUtils.d.ts.map +1 -0
- package/dist/auth/displayUtils.js +133 -0
- package/dist/auth/displayUtils.js.map +1 -0
- package/dist/auth/loginGuard.d.ts +108 -14
- package/dist/auth/loginGuard.d.ts.map +1 -1
- package/dist/auth/loginGuard.js +153 -31
- package/dist/auth/loginGuard.js.map +1 -1
- package/dist/auth/offlineCredentials.d.ts +132 -15
- package/dist/auth/offlineCredentials.d.ts.map +1 -1
- package/dist/auth/offlineCredentials.js +167 -23
- package/dist/auth/offlineCredentials.js.map +1 -1
- package/dist/auth/offlineLogin.d.ts +96 -10
- package/dist/auth/offlineLogin.d.ts.map +1 -1
- package/dist/auth/offlineLogin.js +82 -15
- package/dist/auth/offlineLogin.js.map +1 -1
- package/dist/auth/offlineSession.d.ts +83 -9
- package/dist/auth/offlineSession.d.ts.map +1 -1
- package/dist/auth/offlineSession.js +104 -13
- package/dist/auth/offlineSession.js.map +1 -1
- package/dist/auth/resolveAuthState.d.ts +70 -8
- package/dist/auth/resolveAuthState.d.ts.map +1 -1
- package/dist/auth/resolveAuthState.js +142 -46
- package/dist/auth/resolveAuthState.js.map +1 -1
- package/dist/auth/singleUser.d.ts +390 -37
- package/dist/auth/singleUser.d.ts.map +1 -1
- package/dist/auth/singleUser.js +505 -133
- package/dist/auth/singleUser.js.map +1 -1
- package/dist/bin/install-pwa.d.ts +25 -0
- package/dist/bin/install-pwa.d.ts.map +1 -0
- package/dist/bin/install-pwa.js +2197 -0
- package/dist/bin/install-pwa.js.map +1 -0
- package/dist/config.d.ts +132 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -9
- package/dist/config.js.map +1 -1
- package/dist/conflicts.d.ts +246 -23
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/conflicts.js +495 -46
- package/dist/conflicts.js.map +1 -1
- package/dist/data.d.ts +338 -18
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +385 -34
- package/dist/data.js.map +1 -1
- package/dist/database.d.ts +72 -14
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +120 -29
- package/dist/database.js.map +1 -1
- package/dist/debug.d.ts +77 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +88 -1
- package/dist/debug.js.map +1 -1
- package/dist/deviceId.d.ts +38 -7
- package/dist/deviceId.d.ts.map +1 -1
- package/dist/deviceId.js +68 -10
- package/dist/deviceId.js.map +1 -1
- package/dist/engine.d.ts +175 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +831 -110
- package/dist/engine.js.map +1 -1
- package/dist/entries/actions.d.ts +14 -0
- package/dist/entries/actions.d.ts.map +1 -1
- package/dist/entries/actions.js +27 -1
- package/dist/entries/actions.js.map +1 -1
- package/dist/entries/auth.d.ts +16 -0
- package/dist/entries/auth.d.ts.map +1 -1
- package/dist/entries/auth.js +73 -1
- package/dist/entries/auth.js.map +1 -1
- package/dist/entries/config.d.ts +12 -0
- package/dist/entries/config.d.ts.map +1 -1
- package/dist/entries/config.js +18 -1
- package/dist/entries/config.js.map +1 -1
- package/dist/entries/kit.d.ts +21 -9
- package/dist/entries/kit.d.ts.map +1 -1
- package/dist/entries/kit.js +57 -8
- package/dist/entries/kit.js.map +1 -1
- package/dist/entries/stores.d.ts +11 -0
- package/dist/entries/stores.d.ts.map +1 -1
- package/dist/entries/stores.js +43 -2
- package/dist/entries/stores.js.map +1 -1
- package/dist/entries/types.d.ts +11 -1
- package/dist/entries/types.d.ts.map +1 -1
- package/dist/entries/types.js +10 -0
- package/dist/entries/types.js.map +1 -1
- package/dist/entries/utils.d.ts +7 -1
- package/dist/entries/utils.d.ts.map +1 -1
- package/dist/entries/utils.js +23 -2
- package/dist/entries/utils.js.map +1 -1
- package/dist/entries/vite.d.ts +20 -0
- package/dist/entries/vite.d.ts.map +1 -0
- package/dist/entries/vite.js +26 -0
- package/dist/entries/vite.js.map +1 -0
- package/dist/index.d.ts +33 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +176 -21
- package/dist/index.js.map +1 -1
- package/dist/kit/auth.d.ts +80 -0
- package/dist/kit/auth.d.ts.map +1 -0
- package/dist/kit/auth.js +72 -0
- package/dist/kit/auth.js.map +1 -0
- package/dist/kit/confirm.d.ts +111 -0
- package/dist/kit/confirm.d.ts.map +1 -0
- package/dist/kit/confirm.js +169 -0
- package/dist/kit/confirm.js.map +1 -0
- package/dist/kit/loads.d.ts +189 -0
- package/dist/kit/loads.d.ts.map +1 -0
- package/dist/kit/loads.js +205 -0
- package/dist/kit/loads.js.map +1 -0
- package/dist/kit/server.d.ts +175 -0
- package/dist/kit/server.d.ts.map +1 -0
- package/dist/kit/server.js +297 -0
- package/dist/kit/server.js.map +1 -0
- package/dist/kit/sw.d.ts +176 -0
- package/dist/kit/sw.d.ts.map +1 -0
- package/dist/kit/sw.js +320 -0
- package/dist/kit/sw.js.map +1 -0
- package/dist/queue.d.ts +274 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +556 -38
- package/dist/queue.js.map +1 -1
- package/dist/realtime.d.ts +241 -27
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +633 -109
- package/dist/realtime.js.map +1 -1
- package/dist/runtime/runtimeConfig.d.ts +91 -16
- package/dist/runtime/runtimeConfig.d.ts.map +1 -1
- package/dist/runtime/runtimeConfig.js +146 -19
- package/dist/runtime/runtimeConfig.js.map +1 -1
- package/dist/stores/authState.d.ts +150 -11
- package/dist/stores/authState.d.ts.map +1 -1
- package/dist/stores/authState.js +169 -17
- package/dist/stores/authState.js.map +1 -1
- package/dist/stores/network.d.ts +39 -0
- package/dist/stores/network.d.ts.map +1 -1
- package/dist/stores/network.js +169 -16
- package/dist/stores/network.js.map +1 -1
- package/dist/stores/remoteChanges.d.ts +327 -52
- package/dist/stores/remoteChanges.d.ts.map +1 -1
- package/dist/stores/remoteChanges.js +337 -75
- package/dist/stores/remoteChanges.js.map +1 -1
- package/dist/stores/sync.d.ts +130 -0
- package/dist/stores/sync.d.ts.map +1 -1
- package/dist/stores/sync.js +167 -7
- package/dist/stores/sync.js.map +1 -1
- package/dist/supabase/auth.d.ts +326 -19
- package/dist/supabase/auth.d.ts.map +1 -1
- package/dist/supabase/auth.js +374 -26
- package/dist/supabase/auth.js.map +1 -1
- package/dist/supabase/client.d.ts +79 -6
- package/dist/supabase/client.d.ts.map +1 -1
- package/dist/supabase/client.js +158 -15
- package/dist/supabase/client.js.map +1 -1
- package/dist/supabase/validate.d.ts +101 -7
- package/dist/supabase/validate.d.ts.map +1 -1
- package/dist/supabase/validate.js +117 -8
- package/dist/supabase/validate.js.map +1 -1
- package/dist/sw/build/vite-plugin.d.ts +74 -0
- package/dist/sw/build/vite-plugin.d.ts.map +1 -0
- package/dist/sw/build/vite-plugin.js +183 -0
- package/dist/sw/build/vite-plugin.js.map +1 -0
- package/dist/sw/sw.js +669 -0
- package/dist/types.d.ts +150 -45
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -10
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +55 -13
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +83 -22
- package/dist/utils.js.map +1 -1
- package/package.json +20 -22
- package/src/components/DeferredChangesBanner.svelte +477 -0
- package/src/components/SyncStatus.svelte +1732 -0
- package/dist/crdt/awareness.d.ts +0 -54
- package/dist/crdt/awareness.d.ts.map +0 -1
- package/dist/crdt/awareness.js +0 -219
- package/dist/crdt/awareness.js.map +0 -1
- package/dist/crdt/doc.d.ts +0 -56
- package/dist/crdt/doc.d.ts.map +0 -1
- package/dist/crdt/doc.js +0 -130
- package/dist/crdt/doc.js.map +0 -1
- package/dist/crdt/index.d.ts +0 -15
- package/dist/crdt/index.d.ts.map +0 -1
- package/dist/crdt/index.js +0 -20
- package/dist/crdt/index.js.map +0 -1
- package/dist/crdt/offline.d.ts +0 -91
- package/dist/crdt/offline.d.ts.map +0 -1
- package/dist/crdt/offline.js +0 -353
- package/dist/crdt/offline.js.map +0 -1
- package/dist/crdt/sync.d.ts +0 -58
- package/dist/crdt/sync.d.ts.map +0 -1
- package/dist/crdt/sync.js +0 -399
- package/dist/crdt/sync.js.map +0 -1
- package/dist/crdt/types.d.ts +0 -62
- package/dist/crdt/types.d.ts.map +0 -1
- package/dist/crdt/types.js +0 -7
- package/dist/crdt/types.js.map +0 -1
- package/dist/email/sendEmail.d.ts +0 -31
- package/dist/email/sendEmail.d.ts.map +0 -1
- package/dist/email/sendEmail.js +0 -39
- package/dist/email/sendEmail.js.map +0 -1
- package/dist/email/validateSmtp.d.ts +0 -18
- package/dist/email/validateSmtp.d.ts.map +0 -1
- package/dist/email/validateSmtp.js +0 -33
- package/dist/email/validateSmtp.js.map +0 -1
- package/dist/entries/crdt.d.ts +0 -3
- package/dist/entries/crdt.d.ts.map +0 -1
- package/dist/entries/crdt.js +0 -13
- package/dist/entries/crdt.js.map +0 -1
- package/dist/entries/email.d.ts +0 -4
- package/dist/entries/email.d.ts.map +0 -1
- package/dist/entries/email.js +0 -4
- package/dist/entries/email.js.map +0 -1
- package/dist/kit/authPresets.d.ts +0 -28
- package/dist/kit/authPresets.d.ts.map +0 -1
- package/dist/kit/authPresets.js +0 -23
- package/dist/kit/authPresets.js.map +0 -1
- package/dist/kit/configEndpoint.d.ts +0 -18
- package/dist/kit/configEndpoint.d.ts.map +0 -1
- package/dist/kit/configEndpoint.js +0 -27
- package/dist/kit/configEndpoint.js.map +0 -1
- package/dist/kit/deployEndpoint.d.ts +0 -22
- package/dist/kit/deployEndpoint.d.ts.map +0 -1
- package/dist/kit/deployEndpoint.js +0 -79
- package/dist/kit/deployEndpoint.js.map +0 -1
- package/dist/kit/layoutLoad.d.ts +0 -23
- package/dist/kit/layoutLoad.d.ts.map +0 -1
- package/dist/kit/layoutLoad.js +0 -41
- package/dist/kit/layoutLoad.js.map +0 -1
- package/dist/kit/protectedLoad.d.ts +0 -16
- package/dist/kit/protectedLoad.d.ts.map +0 -1
- package/dist/kit/protectedLoad.js +0 -28
- package/dist/kit/protectedLoad.js.map +0 -1
- package/dist/kit/setupLoad.d.ts +0 -11
- package/dist/kit/setupLoad.d.ts.map +0 -1
- package/dist/kit/setupLoad.js +0 -28
- package/dist/kit/setupLoad.js.map +0 -1
- package/dist/kit/validateEndpoint.d.ts +0 -9
- package/dist/kit/validateEndpoint.d.ts.map +0 -1
- package/dist/kit/validateEndpoint.js +0 -25
- package/dist/kit/validateEndpoint.js.map +0 -1
- package/dist/kit/vercelApi.d.ts +0 -6
- package/dist/kit/vercelApi.d.ts.map +0 -1
- package/dist/kit/vercelApi.js +0 -48
- package/dist/kit/vercelApi.js.map +0 -1
package/dist/sw/sw.js
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Service Worker for the PWA.
|
|
4
|
+
*
|
|
5
|
+
* Implements a **smart caching strategy** designed for SvelteKit's output:
|
|
6
|
+
*
|
|
7
|
+
* - **Immutable assets** (`/_app/immutable/*`) — cache-first, never
|
|
8
|
+
* revalidate. These files have content-hashes in their filenames, so a
|
|
9
|
+
* new hash === a new file. Stored in a persistent `ASSET_CACHE` that
|
|
10
|
+
* survives across deploys.
|
|
11
|
+
*
|
|
12
|
+
* - **Shell / static assets** — cache-first, versioned per deploy.
|
|
13
|
+
* Stored in `SHELL_CACHE` which is keyed by `APP_VERSION` and
|
|
14
|
+
* automatically cleaned up when a new SW activates.
|
|
15
|
+
*
|
|
16
|
+
* - **Navigation requests** (HTML) — network-first with a 3-second
|
|
17
|
+
* timeout, falling back to the cached root `/` document. Ensures the
|
|
18
|
+
* app loads offline while staying fresh when online.
|
|
19
|
+
*
|
|
20
|
+
* - **Background precaching** — after install, the SW can be told to
|
|
21
|
+
* fetch all assets listed in `asset-manifest.json`, downloading only
|
|
22
|
+
* those not already in cache. This makes the entire app available
|
|
23
|
+
* offline without blocking the install event.
|
|
24
|
+
*
|
|
25
|
+
* The `APP_VERSION` constant is patched automatically by the stellarPWA
|
|
26
|
+
* Vite plugin on every production build.
|
|
27
|
+
*
|
|
28
|
+
* @see {@link handleNavigationRequest} for HTML page caching (network-first)
|
|
29
|
+
* @see {@link handleImmutableAsset} for content-hashed asset caching (cache-first)
|
|
30
|
+
* @see {@link handleStaticAsset} for general static asset caching (cache-first)
|
|
31
|
+
* @see {@link backgroundPrecache} for offline-readiness precaching
|
|
32
|
+
* @see {@link cleanupOldAssets} for stale immutable asset removal
|
|
33
|
+
*/
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// VERSIONING
|
|
36
|
+
// =============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* Build-stamped version string — updated automatically by the stellarPWA
|
|
39
|
+
* Vite plugin on each build. Used to key the shell cache and reported
|
|
40
|
+
* back to clients via the `GET_VERSION` message handler.
|
|
41
|
+
*/
|
|
42
|
+
const APP_VERSION = '__SW_VERSION__';
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// CACHE NAMING
|
|
45
|
+
// =============================================================================
|
|
46
|
+
/**
|
|
47
|
+
* Persistent cache for immutable assets (`/_app/immutable/*`).
|
|
48
|
+
* These files contain content hashes in their filenames, making them safe
|
|
49
|
+
* to cache indefinitely. NOT cleared on deploy — assets accumulate across
|
|
50
|
+
* builds and are pruned by {@link cleanupOldAssets} when triggered.
|
|
51
|
+
*/
|
|
52
|
+
const ASSET_CACHE = '__SW_PREFIX__-assets-v1';
|
|
53
|
+
/**
|
|
54
|
+
* Versioned cache for the app shell (HTML, manifest, icons) and other
|
|
55
|
+
* static assets. Re-created on each deploy; old versions are deleted
|
|
56
|
+
* during the `activate` event so only one shell cache exists at a time.
|
|
57
|
+
*/
|
|
58
|
+
const SHELL_CACHE = '__SW_PREFIX__-shell-' + APP_VERSION;
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// PRECACHE MANIFEST
|
|
61
|
+
// =============================================================================
|
|
62
|
+
/**
|
|
63
|
+
* Core app shell resources to precache during the `install` event.
|
|
64
|
+
* These are the minimum files needed for the app to render offline.
|
|
65
|
+
*
|
|
66
|
+
* Note: The root HTML (`/`) is cached separately in the install handler
|
|
67
|
+
* because its failure should abort the installation entirely.
|
|
68
|
+
*/
|
|
69
|
+
const PRECACHE_ASSETS = [
|
|
70
|
+
'/manifest.json',
|
|
71
|
+
'/favicon.png',
|
|
72
|
+
'/icon-192.png',
|
|
73
|
+
'/icon-512.png',
|
|
74
|
+
'/offline.html'
|
|
75
|
+
];
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// INSTALL EVENT
|
|
78
|
+
// =============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Install handler — precaches the minimal app shell.
|
|
81
|
+
*
|
|
82
|
+
* **Strategy:**
|
|
83
|
+
* 1. The root HTML (`/`) is **required** — if it fails, install fails.
|
|
84
|
+
* Better to stay on the working old SW than activate with no cached HTML.
|
|
85
|
+
* 2. Other shell assets (icons, manifest) are **optional** — failures
|
|
86
|
+
* are logged but don't block installation.
|
|
87
|
+
* 3. Notifies all open windows via `postMessage` so the UI can show an
|
|
88
|
+
* "update available" prompt.
|
|
89
|
+
* 4. Auto-promotes via `skipWaiting()` after 5 minutes as a fallback for
|
|
90
|
+
* iOS PWA where the update prompt may never be interacted with.
|
|
91
|
+
*
|
|
92
|
+
* @param event - The `install` {@link ExtendableEvent}.
|
|
93
|
+
*/
|
|
94
|
+
self.addEventListener('install', (event) => {
|
|
95
|
+
console.log(`[SW] Installing version: ${APP_VERSION}`);
|
|
96
|
+
event.waitUntil(caches.open(SHELL_CACHE).then(async (cache) => {
|
|
97
|
+
/* Root HTML is REQUIRED — if it fails, install fails */
|
|
98
|
+
await cache.add('/');
|
|
99
|
+
console.log('[SW] Root HTML precached');
|
|
100
|
+
/* Other shell assets are optional — use allSettled so failures don't block */
|
|
101
|
+
await Promise.allSettled(PRECACHE_ASSETS.map((url) => cache.add(url).catch((err) => console.warn(`[SW] Failed to precache ${url}:`, err))));
|
|
102
|
+
console.log('[SW] Minimal precache complete');
|
|
103
|
+
/* Notify all open clients that a new version has been installed */
|
|
104
|
+
self.clients.matchAll({ type: 'window' }).then((clients) => {
|
|
105
|
+
clients.forEach((client) => {
|
|
106
|
+
client.postMessage({ type: 'SW_INSTALLED', version: APP_VERSION });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}));
|
|
110
|
+
/*
|
|
111
|
+
* Let the UpdatePrompt component control the transition if the user is
|
|
112
|
+
* active, but auto-promote after 5 minutes to handle iOS PWA where the
|
|
113
|
+
* prompt may never show (the app might be backgrounded indefinitely).
|
|
114
|
+
*/
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
self.skipWaiting();
|
|
117
|
+
}, 5 * 60 * 1000);
|
|
118
|
+
});
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// ACTIVATE EVENT
|
|
121
|
+
// =============================================================================
|
|
122
|
+
/**
|
|
123
|
+
* Activate handler — cleans up stale caches and claims all clients.
|
|
124
|
+
*
|
|
125
|
+
* **Deletes:**
|
|
126
|
+
* - Old versioned shell caches (e.g., `__SW_PREFIX__-shell-<old-version>`)
|
|
127
|
+
* - The legacy `__SW_PREFIX__-cache-v1` cache (one-time migration from the
|
|
128
|
+
* original single-cache strategy)
|
|
129
|
+
*
|
|
130
|
+
* **Keeps:**
|
|
131
|
+
* - `ASSET_CACHE` — immutable assets persist across versions
|
|
132
|
+
* - `SHELL_CACHE` — the current deploy's shell cache
|
|
133
|
+
*
|
|
134
|
+
* @param event - The `activate` {@link ExtendableEvent}.
|
|
135
|
+
*/
|
|
136
|
+
self.addEventListener('activate', (event) => {
|
|
137
|
+
console.log(`[SW] Activating version: ${APP_VERSION}`);
|
|
138
|
+
event.waitUntil((async () => {
|
|
139
|
+
const cacheNames = await caches.keys();
|
|
140
|
+
await Promise.all(cacheNames
|
|
141
|
+
.filter((name) => {
|
|
142
|
+
/* Delete old versioned shell caches (not the current one) */
|
|
143
|
+
if (name.startsWith('__SW_PREFIX__-shell-') && name !== SHELL_CACHE)
|
|
144
|
+
return true;
|
|
145
|
+
/* Delete legacy shared cache (one-time migration) */
|
|
146
|
+
if (name === '__SW_PREFIX__-cache-v1')
|
|
147
|
+
return true;
|
|
148
|
+
return false;
|
|
149
|
+
})
|
|
150
|
+
.map((name) => {
|
|
151
|
+
console.log(`[SW] Deleting old cache: ${name}`);
|
|
152
|
+
return caches.delete(name);
|
|
153
|
+
}));
|
|
154
|
+
/* Keep ASSET_CACHE — immutable assets persist across versions */
|
|
155
|
+
/* Take control of all open tabs immediately */
|
|
156
|
+
await self.clients.claim();
|
|
157
|
+
})());
|
|
158
|
+
});
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// FETCH EVENT
|
|
161
|
+
// =============================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Fetch handler — routes requests to the appropriate caching strategy.
|
|
164
|
+
*
|
|
165
|
+
* **Routing logic** (in priority order):
|
|
166
|
+
* 1. Skip non-GET requests (mutations should always hit the network)
|
|
167
|
+
* 2. Skip external origins (only cache same-origin resources)
|
|
168
|
+
* 3. Skip `/api/*` routes (backend data — never cache)
|
|
169
|
+
* 4. Navigation requests --> {@link handleNavigationRequest} (network-first)
|
|
170
|
+
* 5. Immutable assets --> {@link handleImmutableAsset} (cache-first, permanent)
|
|
171
|
+
* 6. Static assets --> {@link handleStaticAsset} (cache-first)
|
|
172
|
+
* 7. Everything else --> {@link handleOtherRequest} (network-first)
|
|
173
|
+
*
|
|
174
|
+
* @param event - The {@link FetchEvent} to handle.
|
|
175
|
+
*/
|
|
176
|
+
self.addEventListener('fetch', (event) => {
|
|
177
|
+
/* Only intercept GET requests — let POST/PUT/DELETE go straight to network */
|
|
178
|
+
if (event.request.method !== 'GET')
|
|
179
|
+
return;
|
|
180
|
+
const url = new URL(event.request.url);
|
|
181
|
+
/* Skip external requests — we only cache same-origin resources */
|
|
182
|
+
if (url.origin !== self.location.origin)
|
|
183
|
+
return;
|
|
184
|
+
/* Skip API routes — backend data must always be fresh */
|
|
185
|
+
if (url.pathname.startsWith('/api/'))
|
|
186
|
+
return;
|
|
187
|
+
/* ── Navigation Requests (HTML pages) ───────────────────────────── */
|
|
188
|
+
if (event.request.mode === 'navigate') {
|
|
189
|
+
event.respondWith(handleNavigationRequest(event.request));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
/* ── Immutable Assets (`/_app/immutable/*`) ─────────────────────── */
|
|
193
|
+
/* Content-hashed filenames → cache forever, never revalidate */
|
|
194
|
+
if (url.pathname.includes('/_app/immutable/')) {
|
|
195
|
+
event.respondWith(handleImmutableAsset(event.request));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
/* ── Other Static Assets (JS, CSS, images, fonts, JSON) ─────────── */
|
|
199
|
+
if (isStaticAsset(url.pathname)) {
|
|
200
|
+
event.respondWith(handleStaticAsset(event.request));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
/* ── Fallback — network-first for everything else ───────────────── */
|
|
204
|
+
event.respondWith(handleOtherRequest(event.request));
|
|
205
|
+
});
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// HELPER: STATIC ASSET CHECK
|
|
208
|
+
// =============================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Determines whether a given pathname looks like a static asset
|
|
211
|
+
* (scripts, styles, images, fonts, data files).
|
|
212
|
+
*
|
|
213
|
+
* Matches by file extension or by the `/_app/` prefix (SvelteKit's
|
|
214
|
+
* client-side output directory).
|
|
215
|
+
*
|
|
216
|
+
* @param pathname - The URL pathname to test (e.g., `/_app/version.json`).
|
|
217
|
+
* @returns `true` if the path matches a known static-asset extension.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* isStaticAsset('/icon-192.png'); // true
|
|
222
|
+
* isStaticAsset('/api/config'); // false
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
function isStaticAsset(pathname) {
|
|
226
|
+
return (pathname.startsWith('/_app/') ||
|
|
227
|
+
pathname.endsWith('.js') ||
|
|
228
|
+
pathname.endsWith('.css') ||
|
|
229
|
+
pathname.endsWith('.png') ||
|
|
230
|
+
pathname.endsWith('.jpg') ||
|
|
231
|
+
pathname.endsWith('.jpeg') ||
|
|
232
|
+
pathname.endsWith('.gif') ||
|
|
233
|
+
pathname.endsWith('.svg') ||
|
|
234
|
+
pathname.endsWith('.ico') ||
|
|
235
|
+
pathname.endsWith('.woff') ||
|
|
236
|
+
pathname.endsWith('.woff2') ||
|
|
237
|
+
pathname.endsWith('.json') ||
|
|
238
|
+
pathname.endsWith('.webp'));
|
|
239
|
+
}
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// STRATEGY: NAVIGATION (NETWORK-FIRST)
|
|
242
|
+
// =============================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Handles HTML navigation requests with a **network-first** strategy.
|
|
245
|
+
*
|
|
246
|
+
* **Flow:**
|
|
247
|
+
* 1. Attempt a network fetch with a 3-second timeout (abort via `AbortController`)
|
|
248
|
+
* 2. If successful --> cache the response as `/` and return it
|
|
249
|
+
* 3. If failed --> serve the cached root HTML for offline use
|
|
250
|
+
* 4. If nothing cached --> return a minimal offline fallback page
|
|
251
|
+
*
|
|
252
|
+
* The 3-second timeout prevents the user from staring at a blank screen
|
|
253
|
+
* on flaky connections while still preferring fresh content.
|
|
254
|
+
*
|
|
255
|
+
* @param request - The navigation `Request` object.
|
|
256
|
+
* @returns A `Response` (from network, cache, or inline fallback).
|
|
257
|
+
*/
|
|
258
|
+
async function handleNavigationRequest(request) {
|
|
259
|
+
const cache = await caches.open(SHELL_CACHE);
|
|
260
|
+
try {
|
|
261
|
+
/* 3-second timeout — don't leave the user staring at a blank screen */
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
264
|
+
const response = await fetch(request, { signal: controller.signal });
|
|
265
|
+
clearTimeout(timeoutId);
|
|
266
|
+
if (response.ok) {
|
|
267
|
+
/* Cache the fresh HTML for offline fallback */
|
|
268
|
+
cache.put('/', response.clone());
|
|
269
|
+
return response;
|
|
270
|
+
}
|
|
271
|
+
throw new Error('Network response not ok');
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
/* Network failed or timed out — serve cached HTML */
|
|
275
|
+
console.log('[SW] Navigation offline, serving cache');
|
|
276
|
+
const cached = await cache.match('/');
|
|
277
|
+
if (cached)
|
|
278
|
+
return cached;
|
|
279
|
+
/* Try custom offline page (projects can create static/offline.html) */
|
|
280
|
+
const offlinePage = await cache.match('/offline.html');
|
|
281
|
+
if (offlinePage)
|
|
282
|
+
return offlinePage;
|
|
283
|
+
/* Last resort — minimal inline offline page */
|
|
284
|
+
return new Response(getOfflineHTML(), {
|
|
285
|
+
status: 200,
|
|
286
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// =============================================================================
|
|
291
|
+
// STRATEGY: IMMUTABLE ASSETS (CACHE-FIRST, PERMANENT)
|
|
292
|
+
// =============================================================================
|
|
293
|
+
/**
|
|
294
|
+
* Handles requests for immutable assets (`/_app/immutable/*`).
|
|
295
|
+
*
|
|
296
|
+
* **Strategy:** cache-first, NEVER revalidate. These files have content
|
|
297
|
+
* hashes baked into their filenames — if the content changes, the filename
|
|
298
|
+
* changes, so a cached version is always correct.
|
|
299
|
+
*
|
|
300
|
+
* Uses `ASSET_CACHE` which persists across SW versions (not cleared on
|
|
301
|
+
* deploy). Old entries are pruned by {@link cleanupOldAssets}.
|
|
302
|
+
*
|
|
303
|
+
* @param request - The `Request` for an immutable asset.
|
|
304
|
+
* @returns The cached `Response`, or a freshly-fetched one (then cached).
|
|
305
|
+
*/
|
|
306
|
+
async function handleImmutableAsset(request) {
|
|
307
|
+
const cache = await caches.open(ASSET_CACHE);
|
|
308
|
+
/* Check cache first — if we have it, it's guaranteed correct */
|
|
309
|
+
const cached = await cache.match(request);
|
|
310
|
+
if (cached) {
|
|
311
|
+
return cached;
|
|
312
|
+
}
|
|
313
|
+
/* Not cached yet — fetch from network and cache for next time */
|
|
314
|
+
try {
|
|
315
|
+
const response = await fetch(request);
|
|
316
|
+
if (response.ok) {
|
|
317
|
+
cache.put(request, response.clone());
|
|
318
|
+
}
|
|
319
|
+
return response;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
console.error('[SW] Failed to fetch immutable:', request.url);
|
|
323
|
+
return new Response('Asset not available offline', { status: 503 });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// =============================================================================
|
|
327
|
+
// STRATEGY: STATIC ASSETS (CACHE-FIRST)
|
|
328
|
+
// =============================================================================
|
|
329
|
+
/**
|
|
330
|
+
* Handles requests for general static assets (non-immutable JS, CSS, images,
|
|
331
|
+
* fonts, JSON files).
|
|
332
|
+
*
|
|
333
|
+
* **Strategy:** cache-first, NO background revalidation. This saves
|
|
334
|
+
* bandwidth — the shell cache is versioned per deploy, so stale assets
|
|
335
|
+
* are cleaned up automatically when the new SW activates.
|
|
336
|
+
*
|
|
337
|
+
* @param request - The `Request` for a static asset.
|
|
338
|
+
* @returns The cached `Response`, or a freshly-fetched one (then cached).
|
|
339
|
+
*/
|
|
340
|
+
async function handleStaticAsset(request) {
|
|
341
|
+
const cache = await caches.open(SHELL_CACHE);
|
|
342
|
+
/* Check cache first — return immediately, no background fetch */
|
|
343
|
+
const cached = await cache.match(request);
|
|
344
|
+
if (cached) {
|
|
345
|
+
return cached;
|
|
346
|
+
}
|
|
347
|
+
/* Not cached — fetch and store */
|
|
348
|
+
try {
|
|
349
|
+
const response = await fetch(request);
|
|
350
|
+
if (response.ok) {
|
|
351
|
+
cache.put(request, response.clone());
|
|
352
|
+
}
|
|
353
|
+
return response;
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
console.log('[SW] Static asset fetch failed:', request.url);
|
|
357
|
+
return new Response('Asset not available offline', { status: 503 });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// =============================================================================
|
|
361
|
+
// STRATEGY: OTHER REQUESTS (NETWORK-FIRST)
|
|
362
|
+
// =============================================================================
|
|
363
|
+
/**
|
|
364
|
+
* Handles all other same-origin GET requests with a **network-first** strategy.
|
|
365
|
+
*
|
|
366
|
+
* **Flow:**
|
|
367
|
+
* 1. Try the network
|
|
368
|
+
* 2. If successful --> cache and return
|
|
369
|
+
* 3. If failed --> return cached version
|
|
370
|
+
* 4. If nothing cached --> return a 503 "Offline" response
|
|
371
|
+
*
|
|
372
|
+
* @param request - The `Request` object.
|
|
373
|
+
* @returns A `Response` from network or cache.
|
|
374
|
+
*/
|
|
375
|
+
async function handleOtherRequest(request) {
|
|
376
|
+
const cache = await caches.open(SHELL_CACHE);
|
|
377
|
+
try {
|
|
378
|
+
const response = await fetch(request);
|
|
379
|
+
if (response.ok) {
|
|
380
|
+
cache.put(request, response.clone());
|
|
381
|
+
}
|
|
382
|
+
return response;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
const cached = await cache.match(request);
|
|
386
|
+
if (cached)
|
|
387
|
+
return cached;
|
|
388
|
+
return new Response('Offline', { status: 503 });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// =============================================================================
|
|
392
|
+
// BACKGROUND PRECACHING
|
|
393
|
+
// =============================================================================
|
|
394
|
+
/**
|
|
395
|
+
* Downloads all assets listed in `asset-manifest.json` that are NOT already
|
|
396
|
+
* cached. This makes the entire app available offline without blocking the
|
|
397
|
+
* install event.
|
|
398
|
+
*
|
|
399
|
+
* **Key behaviours:**
|
|
400
|
+
* - Fetches the manifest with a cache-busting query param (`?_=<timestamp>`)
|
|
401
|
+
* - Checks both `ASSET_CACHE` and `SHELL_CACHE` to avoid redundant downloads
|
|
402
|
+
* - Downloads in batches of 5 with a 50 ms pause between batches to avoid
|
|
403
|
+
* saturating the network
|
|
404
|
+
* - Notifies all open windows with `PRECACHE_COMPLETE` when done
|
|
405
|
+
*
|
|
406
|
+
* Triggered by sending `{ type: 'PRECACHE_ALL' }` to the service worker.
|
|
407
|
+
*
|
|
408
|
+
* @returns A promise that resolves when precaching is complete (or fails).
|
|
409
|
+
*
|
|
410
|
+
* @see {@link cleanupOldAssets} for removing assets no longer in the manifest
|
|
411
|
+
*/
|
|
412
|
+
async function backgroundPrecache() {
|
|
413
|
+
try {
|
|
414
|
+
const assetCache = await caches.open(ASSET_CACHE);
|
|
415
|
+
const shellCache = await caches.open(SHELL_CACHE);
|
|
416
|
+
/* Fetch manifest with cache-bust to ensure we get the latest version */
|
|
417
|
+
const manifestResponse = await fetch('/asset-manifest.json?_=' + Date.now(), {
|
|
418
|
+
cache: 'no-store'
|
|
419
|
+
});
|
|
420
|
+
if (!manifestResponse.ok) {
|
|
421
|
+
console.warn('[SW] Asset manifest not found');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const manifest = await manifestResponse.json();
|
|
425
|
+
const assets = manifest.assets || [];
|
|
426
|
+
if (assets.length === 0) {
|
|
427
|
+
console.warn('[SW] Asset manifest empty');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
/* ── Determine which assets still need caching ────────────────── */
|
|
431
|
+
const uncached = [];
|
|
432
|
+
for (const url of assets) {
|
|
433
|
+
/* Route to the correct cache based on whether the asset is immutable */
|
|
434
|
+
const isImmutable = url.includes('/_app/immutable/');
|
|
435
|
+
const cache = isImmutable ? assetCache : shellCache;
|
|
436
|
+
const cached = await cache.match(url);
|
|
437
|
+
if (!cached) {
|
|
438
|
+
uncached.push(url);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (uncached.length === 0) {
|
|
442
|
+
console.log('[SW] All assets already cached - full offline ready');
|
|
443
|
+
notifyClients({ type: 'PRECACHE_COMPLETE', cached: assets.length, total: assets.length });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
console.log(`[SW] Caching ${uncached.length} new assets (${assets.length - uncached.length} already cached)`);
|
|
447
|
+
/* ── Download in batches to avoid network saturation ──────────── */
|
|
448
|
+
let successCount = 0;
|
|
449
|
+
const batchSize = 5;
|
|
450
|
+
for (let i = 0; i < uncached.length; i += batchSize) {
|
|
451
|
+
const batch = uncached.slice(i, i + batchSize);
|
|
452
|
+
const results = await Promise.allSettled(batch.map((url) => {
|
|
453
|
+
const isImmutable = url.includes('/_app/immutable/');
|
|
454
|
+
const cache = isImmutable ? assetCache : shellCache;
|
|
455
|
+
return cache.add(url);
|
|
456
|
+
}));
|
|
457
|
+
results.forEach((r, idx) => {
|
|
458
|
+
if (r.status === 'fulfilled')
|
|
459
|
+
successCount++;
|
|
460
|
+
else
|
|
461
|
+
console.warn(`[SW] Failed to cache: ${batch[idx]}`);
|
|
462
|
+
});
|
|
463
|
+
/* Small delay between batches to be polite to the network */
|
|
464
|
+
if (i + batchSize < uncached.length) {
|
|
465
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const totalCached = assets.length - uncached.length + successCount;
|
|
469
|
+
console.log(`[SW] Precache complete: ${totalCached}/${assets.length}`);
|
|
470
|
+
notifyClients({ type: 'PRECACHE_COMPLETE', cached: totalCached, total: assets.length });
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
console.warn('[SW] Precache error:', e);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// =============================================================================
|
|
477
|
+
// OLD ASSET CLEANUP
|
|
478
|
+
// =============================================================================
|
|
479
|
+
/**
|
|
480
|
+
* Removes stale immutable assets from `ASSET_CACHE` that are no longer
|
|
481
|
+
* referenced in the current `asset-manifest.json`.
|
|
482
|
+
*
|
|
483
|
+
* Only targets `/_app/immutable/*` entries — the shell cache is already
|
|
484
|
+
* versioned and cleaned during the `activate` event.
|
|
485
|
+
*
|
|
486
|
+
* Triggered by sending `{ type: 'CLEANUP_OLD' }` to the service worker.
|
|
487
|
+
*
|
|
488
|
+
* @returns A promise that resolves when cleanup is complete.
|
|
489
|
+
*/
|
|
490
|
+
async function cleanupOldAssets() {
|
|
491
|
+
try {
|
|
492
|
+
const cache = await caches.open(ASSET_CACHE);
|
|
493
|
+
/* Fetch the current manifest to know which assets are still valid */
|
|
494
|
+
const manifestResponse = await fetch('/asset-manifest.json', { cache: 'no-store' });
|
|
495
|
+
if (!manifestResponse.ok)
|
|
496
|
+
return;
|
|
497
|
+
const manifest = await manifestResponse.json();
|
|
498
|
+
const currentAssets = new Set(manifest.assets || []);
|
|
499
|
+
/* Walk cached entries and delete any that aren't in the manifest */
|
|
500
|
+
const cachedRequests = await cache.keys();
|
|
501
|
+
let deletedCount = 0;
|
|
502
|
+
for (const request of cachedRequests) {
|
|
503
|
+
const url = new URL(request.url);
|
|
504
|
+
const pathname = url.pathname;
|
|
505
|
+
/* Only clean up immutable assets that are no longer referenced */
|
|
506
|
+
if (pathname.includes('/_app/immutable/') && !currentAssets.has(pathname)) {
|
|
507
|
+
await cache.delete(request);
|
|
508
|
+
deletedCount++;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (deletedCount > 0) {
|
|
512
|
+
console.log(`[SW] Cleaned up ${deletedCount} old assets`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (e) {
|
|
516
|
+
console.warn('[SW] Cleanup error:', e);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// =============================================================================
|
|
520
|
+
// CLIENT COMMUNICATION
|
|
521
|
+
// =============================================================================
|
|
522
|
+
/**
|
|
523
|
+
* Broadcasts a message to all open windows/tabs.
|
|
524
|
+
*
|
|
525
|
+
* @param message - The message object to send (e.g., `{ type: 'PRECACHE_COMPLETE', ... }`).
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```ts
|
|
529
|
+
* notifyClients({ type: 'PRECACHE_COMPLETE', cached: 42, total: 50 });
|
|
530
|
+
* ```
|
|
531
|
+
*/
|
|
532
|
+
function notifyClients(message) {
|
|
533
|
+
self.clients.matchAll({ type: 'window' }).then((clients) => {
|
|
534
|
+
clients.forEach((client) => client.postMessage(message));
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
// =============================================================================
|
|
538
|
+
// OFFLINE FALLBACK PAGE
|
|
539
|
+
// =============================================================================
|
|
540
|
+
/**
|
|
541
|
+
* Returns a minimal offline fallback HTML page. This is only used as a
|
|
542
|
+
* last resort when no cached HTML or custom `/offline.html` is available.
|
|
543
|
+
*
|
|
544
|
+
* Projects should create their own `static/offline.html` with custom
|
|
545
|
+
* styling — it will be precached and served automatically instead of
|
|
546
|
+
* this bare-bones fallback.
|
|
547
|
+
*
|
|
548
|
+
* @returns An unstyled HTML string for the offline fallback.
|
|
549
|
+
*/
|
|
550
|
+
function getOfflineHTML() {
|
|
551
|
+
return `<!DOCTYPE html>
|
|
552
|
+
<html lang="en">
|
|
553
|
+
<head>
|
|
554
|
+
<meta charset="UTF-8">
|
|
555
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
556
|
+
<title>Offline - __SW_NAME__</title>
|
|
557
|
+
</head>
|
|
558
|
+
<body>
|
|
559
|
+
<h1>You're Offline</h1>
|
|
560
|
+
<p>Please check your internet connection and try again.</p>
|
|
561
|
+
<button onclick="location.reload()">Try Again</button>
|
|
562
|
+
</body>
|
|
563
|
+
</html>`;
|
|
564
|
+
}
|
|
565
|
+
// =============================================================================
|
|
566
|
+
// MESSAGE HANDLER
|
|
567
|
+
// =============================================================================
|
|
568
|
+
/**
|
|
569
|
+
* Listens for messages from the app's client-side code.
|
|
570
|
+
*
|
|
571
|
+
* **Supported message types:**
|
|
572
|
+
* - `SKIP_WAITING` --> Immediately activate the waiting SW (user accepted update)
|
|
573
|
+
* - `GET_VERSION` --> Responds with the current `APP_VERSION` via `MessagePort`
|
|
574
|
+
* - `PRECACHE_ALL` --> Triggers {@link backgroundPrecache} to download all assets
|
|
575
|
+
* - `CLEANUP_OLD` --> Triggers {@link cleanupOldAssets} to remove stale cache entries
|
|
576
|
+
* - `CACHE_URLS` --> Caches a specific list of URLs (used for route prefetching)
|
|
577
|
+
* - `GET_CACHE_STATUS` --> Responds with cache completeness info via `MessagePort`
|
|
578
|
+
*
|
|
579
|
+
* @param event - The {@link ExtendableMessageEvent} from a client.
|
|
580
|
+
*/
|
|
581
|
+
self.addEventListener('message', (event) => {
|
|
582
|
+
const { type } = event.data || {};
|
|
583
|
+
/* ── Force-activate the waiting service worker ──────────────────── */
|
|
584
|
+
if (type === 'SKIP_WAITING') {
|
|
585
|
+
self.skipWaiting();
|
|
586
|
+
}
|
|
587
|
+
/* ── Return the current build version ───────────────────────────── */
|
|
588
|
+
if (type === 'GET_VERSION') {
|
|
589
|
+
event.ports[0]?.postMessage({ version: APP_VERSION });
|
|
590
|
+
}
|
|
591
|
+
/* ── Trigger background precache of all manifest assets ─────────── */
|
|
592
|
+
if (type === 'PRECACHE_ALL') {
|
|
593
|
+
backgroundPrecache();
|
|
594
|
+
}
|
|
595
|
+
/* ── Trigger cleanup of stale immutable assets ──────────────────── */
|
|
596
|
+
if (type === 'CLEANUP_OLD') {
|
|
597
|
+
cleanupOldAssets();
|
|
598
|
+
}
|
|
599
|
+
/* ── Cache specific URLs on demand (e.g., route prefetching) ────── */
|
|
600
|
+
if (type === 'CACHE_URLS') {
|
|
601
|
+
const urls = event.data.urls || [];
|
|
602
|
+
const assetCachePromise = caches.open(ASSET_CACHE);
|
|
603
|
+
const shellCachePromise = caches.open(SHELL_CACHE);
|
|
604
|
+
Promise.all([assetCachePromise, shellCachePromise]).then(([ac, sc]) => {
|
|
605
|
+
urls.forEach((url) => {
|
|
606
|
+
/* Route each URL to the correct cache based on immutability */
|
|
607
|
+
const isImmutable = url.includes('/_app/immutable/');
|
|
608
|
+
const cache = isImmutable ? ac : sc;
|
|
609
|
+
cache.add(url).catch(() => { });
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/* ── Report how many assets are cached vs. total ────────────────── */
|
|
614
|
+
if (type === 'GET_CACHE_STATUS') {
|
|
615
|
+
getCacheStatus().then((status) => {
|
|
616
|
+
event.ports[0]?.postMessage(status);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
// =============================================================================
|
|
621
|
+
// CACHE STATUS REPORTER
|
|
622
|
+
// =============================================================================
|
|
623
|
+
/**
|
|
624
|
+
* Computes the current cache completeness by comparing cached entries
|
|
625
|
+
* against the asset manifest.
|
|
626
|
+
*
|
|
627
|
+
* Attempts to find the manifest in cache first (for offline use), then
|
|
628
|
+
* falls back to a network fetch.
|
|
629
|
+
*
|
|
630
|
+
* @returns An object describing cache readiness:
|
|
631
|
+
* - `cached` — Number of manifest assets currently in cache
|
|
632
|
+
* - `total` — Total number of assets in the manifest
|
|
633
|
+
* - `ready` — `true` if every asset is cached (full offline support)
|
|
634
|
+
* - `version` — The manifest version string (if available)
|
|
635
|
+
* - `error` — Error message string (only present if something went wrong)
|
|
636
|
+
*/
|
|
637
|
+
async function getCacheStatus() {
|
|
638
|
+
try {
|
|
639
|
+
const assetCache = await caches.open(ASSET_CACHE);
|
|
640
|
+
const shellCache = await caches.open(SHELL_CACHE);
|
|
641
|
+
/* Try to find the manifest in cache first, then fall back to network */
|
|
642
|
+
const manifestResponse = (await shellCache.match('/asset-manifest.json')) ||
|
|
643
|
+
(await assetCache.match('/asset-manifest.json')) ||
|
|
644
|
+
(await fetch('/asset-manifest.json').catch(() => null));
|
|
645
|
+
if (!manifestResponse) {
|
|
646
|
+
return { cached: 0, total: 0, ready: false };
|
|
647
|
+
}
|
|
648
|
+
const manifest = await manifestResponse.clone().json();
|
|
649
|
+
const assets = manifest.assets || [];
|
|
650
|
+
/* Count how many manifest entries are already cached */
|
|
651
|
+
let cachedCount = 0;
|
|
652
|
+
for (const url of assets) {
|
|
653
|
+
const isImmutable = url.includes('/_app/immutable/');
|
|
654
|
+
const cache = isImmutable ? assetCache : shellCache;
|
|
655
|
+
if (await cache.match(url))
|
|
656
|
+
cachedCount++;
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
cached: cachedCount,
|
|
660
|
+
total: assets.length,
|
|
661
|
+
ready: cachedCount === assets.length,
|
|
662
|
+
version: manifest.version
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
return { cached: 0, total: 0, ready: false, error: e.message };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
export {};
|