@swoff/cli 0.3.13 → 0.3.14
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 +28 -32
- package/dist/index.js +2 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +3 -8
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +29 -87
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +16 -30
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +31 -6
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/init.js +188 -11
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +2 -4
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +34 -25
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/validator.js +202 -96
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/api-config.js +1 -1
- package/dist/lib/generators/file-generators/api-config.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-adapter.js +13 -0
- package/dist/lib/generators/file-generators/auth-adapter.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-check.js +40 -0
- package/dist/lib/generators/file-generators/auth-check.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-store.js +2 -5
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +1 -1
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -1
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector-bundle.js +15 -0
- package/dist/lib/generators/file-generators/client-injector-bundle.js.map +1 -0
- package/dist/lib/generators/file-generators/client-injector.js +2 -1
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/connectivity-manager.js +12 -0
- package/dist/lib/generators/file-generators/connectivity-manager.js.map +1 -0
- package/dist/lib/generators/file-generators/connectivity.js +12 -0
- package/dist/lib/generators/file-generators/connectivity.js.map +1 -0
- package/dist/lib/generators/file-generators/context.js +7 -0
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-framework-adapters.js +29 -20
- package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +2 -2
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +51 -1
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +2 -3
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +2 -2
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
- package/dist/lib/generators/file-generators/notification-generator.js +6 -0
- package/dist/lib/generators/file-generators/notification-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/open-db.js +6 -0
- package/dist/lib/generators/file-generators/open-db.js.map +1 -0
- package/dist/lib/generators/file-generators/push.js +1 -2
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/pwa-install.js +2 -6
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/server-push.js +1 -1
- package/dist/lib/generators/file-generators/server-push.js.map +1 -1
- package/dist/lib/generators/file-generators/storage-generator.js +6 -0
- package/dist/lib/generators/file-generators/storage-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/storage-notify.js +6 -0
- package/dist/lib/generators/file-generators/storage-notify.js.map +1 -0
- package/dist/lib/generators/file-generators/storage.js +6 -0
- package/dist/lib/generators/file-generators/storage.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-generator-build.js +96 -45
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +0 -4
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-template.js +8 -9
- package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-version-gen.js +32 -0
- package/dist/lib/generators/file-generators/sw-version-gen.js.map +1 -0
- package/dist/lib/generators/file-generators/swoff-api-bundle.js +51 -0
- package/dist/lib/generators/file-generators/swoff-api-bundle.js.map +1 -0
- package/dist/lib/generators/sw-build-utils.js +69 -0
- package/dist/lib/generators/sw-build-utils.js.map +1 -0
- package/dist/lib/generators/sw-generator.js +23 -34
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +74 -23
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +15 -94
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +74 -72
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js +102 -0
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js.map +1 -0
- package/dist/lib/generators/sw-sections/config-header.js +2 -3
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/default-template.js +59 -6
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +621 -495
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/install-handler.js +40 -23
- package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/message-handler.js +25 -8
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +10 -14
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/shared.js +68 -0
- package/dist/lib/generators/sw-sections/shared.js.map +1 -0
- package/dist/lib/generators/sw-sections/sw-push.js +2 -6
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -1
- package/dist/lib/generators/sw-sections/tag-management.js +55 -86
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +183 -59
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +67 -63
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/feature-registry.js +198 -0
- package/dist/lib/shared/feature-registry.js.map +1 -0
- package/dist/lib/utils/detect-framework.js +52 -9
- package/dist/lib/utils/detect-framework.js.map +1 -1
- package/dist/runtime/api-config.js +2 -2
- package/dist/runtime/api-config.js.map +1 -1
- package/dist/runtime/auth-adapter.js +179 -0
- package/dist/runtime/auth-adapter.js.map +1 -0
- package/dist/runtime/auth-state.js +16 -6
- package/dist/runtime/auth-state.js.map +1 -1
- package/dist/runtime/auth-store.js +219 -213
- package/dist/runtime/auth-store.js.map +1 -1
- package/dist/runtime/auth-user.js +5 -19
- package/dist/runtime/auth-user.js.map +1 -1
- package/dist/runtime/background-sync.js +5 -2
- package/dist/runtime/background-sync.js.map +1 -1
- package/dist/runtime/cache.js +91 -3
- package/dist/runtime/cache.js.map +1 -1
- package/dist/runtime/client-injector-bundle.js +314 -0
- package/dist/runtime/client-injector-bundle.js.map +1 -0
- package/dist/runtime/client-injector.js +173 -30
- package/dist/runtime/client-injector.js.map +1 -1
- package/dist/runtime/connectivity-manager.js +83 -0
- package/dist/runtime/connectivity-manager.js.map +1 -0
- package/dist/runtime/connectivity.js +83 -0
- package/dist/runtime/connectivity.js.map +1 -0
- package/dist/runtime/fetch-wrapper.js +26 -59
- package/dist/runtime/fetch-wrapper.js.map +1 -1
- package/dist/runtime/gql-wrapper.js +15 -9
- package/dist/runtime/gql-wrapper.js.map +1 -1
- package/dist/runtime/invalidation-tags.js +12 -89
- package/dist/runtime/invalidation-tags.js.map +1 -1
- package/dist/runtime/mutation-queue.js +83 -31
- package/dist/runtime/mutation-queue.js.map +1 -1
- package/dist/runtime/mutation-state.js +3 -3
- package/dist/runtime/mutation-state.js.map +1 -1
- package/dist/runtime/notification.js +65 -0
- package/dist/runtime/notification.js.map +1 -0
- package/dist/runtime/open-db.js +41 -0
- package/dist/runtime/open-db.js.map +1 -0
- package/dist/runtime/push.js +15 -27
- package/dist/runtime/push.js.map +1 -1
- package/dist/runtime/pwa-injector.js +3 -32
- package/dist/runtime/pwa-injector.js.map +1 -1
- package/dist/runtime/pwa-prompt.js +35 -3
- package/dist/runtime/pwa-prompt.js.map +1 -1
- package/dist/runtime/reset.js +39 -42
- package/dist/runtime/reset.js.map +1 -1
- package/dist/runtime/server-push.js +23 -13
- package/dist/runtime/server-push.js.map +1 -1
- package/dist/runtime/storage.js +32 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/runtime/sw-injector.js +19 -178
- package/dist/runtime/sw-injector.js.map +1 -1
- package/dist/runtime/sw-version.js +13 -0
- package/dist/runtime/sw-version.js.map +1 -0
- package/dist/runtime/swoff-api-bundle.js +1491 -0
- package/dist/runtime/swoff-api-bundle.js.map +1 -0
- package/dist/runtime/type-definitions.js +8 -7
- package/dist/runtime/type-definitions.js.map +1 -1
- package/dist/runtime/utils.js +4 -0
- package/dist/runtime/utils.js.map +1 -1
- package/dist/swoff-nkdhhmw8. +18 -0
- package/dist/swoff.js +5 -0
- package/package.json +1 -1
- package/templates/react/useAuth.jsx +7 -4
- package/templates/react/useAuth.tsx +14 -14
- package/templates/react/useBackgroundSync.jsx +5 -4
- package/templates/react/useBackgroundSync.tsx +5 -4
- package/templates/react/useCachedFetch.jsx +8 -0
- package/templates/react/useCachedFetch.tsx +8 -0
- package/templates/react/useMutation.jsx +60 -56
- package/templates/react/useMutation.tsx +65 -64
- package/templates/react/useMutationQueue.jsx +5 -1
- package/templates/react/useMutationQueue.tsx +8 -2
- package/templates/react/useMutationState.jsx +3 -4
- package/templates/react/useMutationState.tsx +4 -8
- package/templates/react/useNetworkStatus.jsx +57 -17
- package/templates/react/useNetworkStatus.tsx +42 -20
- package/templates/react/useOfflineAnalytics.jsx +42 -0
- package/templates/react/useOfflineAnalytics.tsx +50 -0
- package/templates/react/usePwaInstall.jsx +26 -0
- package/templates/react/usePwaInstall.tsx +26 -0
- package/templates/react/useSWUpdate.jsx +7 -52
- package/templates/react/useSWUpdate.tsx +12 -51
- package/templates/react/useStorageEstimate.jsx +56 -0
- package/templates/react/useStorageEstimate.tsx +67 -0
- package/dist/__tests__/assemble-sw.test.js +0 -145
- package/dist/__tests__/assemble-sw.test.js.map +0 -1
- package/dist/__tests__/cli-integration.test.js +0 -231
- package/dist/__tests__/cli-integration.test.js.map +0 -1
- package/dist/__tests__/config-types.test.js +0 -124
- package/dist/__tests__/config-types.test.js.map +0 -1
- package/dist/__tests__/detect-language.test.js +0 -51
- package/dist/__tests__/detect-language.test.js.map +0 -1
- package/dist/__tests__/file-generators.test.js +0 -228
- package/dist/__tests__/file-generators.test.js.map +0 -1
- package/dist/__tests__/glob-matcher.test.js +0 -86
- package/dist/__tests__/glob-matcher.test.js.map +0 -1
- package/dist/__tests__/invalidation-tags.test.js +0 -283
- package/dist/__tests__/invalidation-tags.test.js.map +0 -1
- package/dist/__tests__/loader.test.js +0 -77
- package/dist/__tests__/loader.test.js.map +0 -1
- package/dist/__tests__/validator.test.js +0 -340
- package/dist/__tests__/validator.test.js.map +0 -1
- package/templates/react/useCacheInvalidation.jsx +0 -19
- package/templates/react/useCacheInvalidation.tsx +0 -52
- package/templates/react/useIsFetching.jsx +0 -32
- package/templates/react/useIsFetching.tsx +0 -32
|
@@ -1,76 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
cleanStaleVersions();
|
|
30
|
-
if (staleVersions.has(cacheKey(request))) {
|
|
31
|
-
queueRefresh(cacheKey(request), new URL(request.url).href);
|
|
32
|
-
}`
|
|
33
|
-
: "";
|
|
34
|
-
const tagCode = tagInvalidation
|
|
35
|
-
? `
|
|
36
|
-
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
37
|
-
if (tagsHeader) {
|
|
38
|
-
const cacheKeyUrl = cacheKey(request);
|
|
39
|
-
const actualUrl = new URL(request.url).href;
|
|
40
|
-
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
41
|
-
await cacheTagUrl(cacheKeyUrl, actualUrl, tags);
|
|
42
|
-
}`
|
|
1
|
+
export function generateFetchHandler(swConfig, tagInvalidation, mutationQueueEnabled, authRoutePaths, serverPushEndpoint, debug) {
|
|
2
|
+
const { strategy: { default: defaultStrategy, patterns: strategies, normalizeKey: normalizeCacheKey, ignoreQueryParams, timeout: fetchTimeout = 10, maxRuntimeCacheAge: maxCacheAge, }, navigation: { mode: navMode = "spa", preload: navigationPreload, fallback: globalFallback = "", rules: navRules = [], }, } = swConfig;
|
|
3
|
+
const debugMode = debug === true;
|
|
4
|
+
const globalStaleTime = swConfig.strategy.reactive?.defaults?.staleTime;
|
|
5
|
+
const refetchRetry = swConfig.refetchQueue.retry;
|
|
6
|
+
const hasRules = navRules.length > 0;
|
|
7
|
+
const navModeCode = navMode === "ssr" ? '"ssr"' : navMode === "spa" ? '"spa"' : '"default"';
|
|
8
|
+
const fallbackCode = globalFallback ? `"${globalFallback}"` : '""';
|
|
9
|
+
const navRulesCode = hasRules
|
|
10
|
+
? `const NAV_RULES = ${JSON.stringify(navRules.map((r) => ({
|
|
11
|
+
match: r.match,
|
|
12
|
+
...(r.fallback ? { fallback: r.fallback } : {}),
|
|
13
|
+
})), null, 2)};
|
|
14
|
+
|
|
15
|
+
function matchRouteFallback(url) {
|
|
16
|
+
swLog("matchRouteFallback", "ENTER", url, 4);
|
|
17
|
+
const path = new URL(url).pathname;
|
|
18
|
+
for (const rule of NAV_RULES) {
|
|
19
|
+
if (matchGlob(path, rule.match)) {
|
|
20
|
+
swLog("matchRouteFallback", "MATCH rule=" + rule.match + " fallback=" + (rule.fallback || "null"), url, 4);
|
|
21
|
+
return rule.fallback || null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
swLog("matchRouteFallback", "NO MATCH", url, 4);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
`
|
|
43
29
|
: "";
|
|
44
|
-
const
|
|
45
|
-
staleTime: globalStaleTime,
|
|
46
|
-
refetchInterval: globalRefetchInterval,
|
|
47
|
-
refetchOnReconnect: globalRefetchOnReconnect,
|
|
48
|
-
refetchOnFocus: globalRefetchOnFocus,
|
|
49
|
-
};
|
|
50
|
-
const reactivePatterns = Object.entries(strategies)
|
|
51
|
-
.filter(([, entry]) => {
|
|
52
|
-
const resolved = typeof entry === "string" ? { strategy: entry } : entry;
|
|
53
|
-
return resolved.strategy === "reactive";
|
|
54
|
-
})
|
|
55
|
-
.map(([pattern, entry]) => {
|
|
56
|
-
const resolved = typeof entry === "string" ? { strategy: entry } : entry;
|
|
57
|
-
return {
|
|
58
|
-
pattern,
|
|
59
|
-
staleTime: resolved.staleTime ?? reactGlobalDefaults.staleTime,
|
|
60
|
-
refetchInterval: resolved.refetchInterval ?? reactGlobalDefaults.refetchInterval,
|
|
61
|
-
refetchOnReconnect: resolved.refetchOnReconnect ?? reactGlobalDefaults.refetchOnReconnect,
|
|
62
|
-
refetchOnFocus: resolved.refetchOnFocus ?? reactGlobalDefaults.refetchOnFocus,
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
const trimDecl = `const REFETCH_BATCH_SIZE = ${refetchBatchSize};
|
|
66
|
-
const REFETCH_BATCH_DELAY_MS = ${refetchBatchDelayMs};
|
|
67
|
-
const REFRESH_MAX_RETRIES = ${refetchMaxRetries};
|
|
68
|
-
const REFRESH_RETRY_DELAY_MS = ${refetchRetryDelayMs};`;
|
|
69
|
-
const GLOB_CODE = "\n" +
|
|
30
|
+
const globCode = "\n" +
|
|
70
31
|
"// --- Glob Pattern Matching ---\n" +
|
|
71
32
|
"\n" +
|
|
72
33
|
"function escapeGlobMeta(s) {\n" +
|
|
73
|
-
' return s.replace(/[.+^${}()|[\\]\\\\]/g, "
|
|
34
|
+
' return s.replace(/[.+^${}()|[\\]\\\\]/g, "\\\\$&");\n' +
|
|
74
35
|
"}\n" +
|
|
75
36
|
"\n" +
|
|
76
37
|
"function globPartRe(part) {\n" +
|
|
@@ -120,197 +81,59 @@ const REFRESH_RETRY_DELAY_MS = ${refetchRetryDelayMs};`;
|
|
|
120
81
|
" return pi === parts.length && ppi === pps.length;\n" +
|
|
121
82
|
"}\n" +
|
|
122
83
|
"\n";
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Use Map keyed by cacheKey for proper deduplication${tagInvalidation
|
|
131
|
-
? `
|
|
132
|
-
// Don't let a tagless (SWR/reactive) refresh override an invalidation-triggered entry with tags
|
|
133
|
-
if (_refreshQueue.has(cacheKeyUrl) && !tags) return;`
|
|
134
|
-
: ""}
|
|
135
|
-
_refreshQueue.set(cacheKeyUrl, { cacheKey: cacheKeyUrl, actualUrl: actualUrl || cacheKeyUrl, retryCount: 0${tagInvalidation ? ", tags: tags || null, method: method || null, body: body || null, contentType: contentType || null" : ""} });
|
|
136
|
-
if (!_refreshQueuePromise) {
|
|
137
|
-
_refreshQueuePromise = _processRefreshQueue();
|
|
138
|
-
}
|
|
139
|
-
return _refreshQueuePromise;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function resolveActualUrl(entry) {
|
|
143
|
-
if (entry.actualUrl) return entry.actualUrl;
|
|
144
|
-
const url = entry.cacheKey;
|
|
145
|
-
if (url.includes("/__swc/")) {
|
|
146
|
-
// Virtual cache key — look up the real URL from tag registry
|
|
147
|
-
try {
|
|
148
|
-
const db = await openTagDB();
|
|
149
|
-
const tx = db.transaction(TAG_STORE_NAME, "readonly");
|
|
150
|
-
const store = tx.objectStore(TAG_STORE_NAME);
|
|
151
|
-
const stored = await new Promise((resolve) => {
|
|
152
|
-
const req = store.get(url);
|
|
153
|
-
req.onsuccess = () => resolve(req.result);
|
|
154
|
-
req.onerror = () => resolve(null);
|
|
155
|
-
});
|
|
156
|
-
db.close();
|
|
157
|
-
if (stored?.actualUrl) return stored.actualUrl;
|
|
158
|
-
} catch {}
|
|
159
|
-
}
|
|
160
|
-
return url;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function _processRefreshQueue() {
|
|
164
|
-
try {
|
|
165
|
-
while (_refreshQueue.size > 0) {
|
|
166
|
-
const batch = [];
|
|
167
|
-
for (const [cacheKey, entry] of _refreshQueue) {
|
|
168
|
-
if (batch.length >= REFETCH_BATCH_SIZE) break;
|
|
169
|
-
if (entry.nextRetryAt && entry.nextRetryAt > Date.now()) continue;
|
|
170
|
-
batch.push(entry);
|
|
171
|
-
_refreshQueue.delete(cacheKey);
|
|
172
|
-
}
|
|
173
|
-
if (batch.length === 0) break;
|
|
174
|
-
|
|
175
|
-
const clients = await self.clients.matchAll();
|
|
176
|
-
|
|
177
|
-
await Promise.allSettled(batch.map(async (entry) => {
|
|
178
|
-
const fetchUrl = await resolveActualUrl(entry);
|
|
179
|
-
try {
|
|
180
|
-
const fetchOpts = {};
|
|
181
|
-
if (entry.method && entry.method !== "GET" && entry.method !== "HEAD") {
|
|
182
|
-
fetchOpts.method = entry.method;
|
|
183
|
-
if (entry.body) fetchOpts.body = entry.body;
|
|
184
|
-
if (entry.contentType) fetchOpts.headers = { "Content-Type": entry.contentType };
|
|
185
|
-
}
|
|
186
|
-
const response = await fetch(fetchUrl, fetchOpts);
|
|
187
|
-
if (response.ok) {
|
|
188
|
-
const request = new Request(entry.cacheKey);
|
|
189
|
-
await storeRuntime(request, response);${tagInvalidation
|
|
84
|
+
const inline503 = "function inline503Response() {\n" +
|
|
85
|
+
" return new Response(\n" +
|
|
86
|
+
' `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Offline</title><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f5f5f5}div{text-align:center}h1{font-size:2rem;color:#333}p{color:#666}</style></head><body><div><h1>You\\\'re offline</h1><p>Please check your connection and try again.</p></div></body></html>`,\n' +
|
|
87
|
+
' { status: 503, headers: { "Content-Type": "text/html", "Cache-Control": "no-store" } }\n' +
|
|
88
|
+
" );\n" +
|
|
89
|
+
"}\n";
|
|
90
|
+
const tagCode = tagInvalidation
|
|
190
91
|
? `
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
} catch {
|
|
204
|
-
// Refresh failed — retry with exponential backoff
|
|
205
|
-
if (entry.retryCount < REFRESH_MAX_RETRIES) {
|
|
206
|
-
entry.retryCount++;
|
|
207
|
-
const delay = REFRESH_RETRY_DELAY_MS * Math.pow(2, entry.retryCount - 1);
|
|
208
|
-
entry.nextRetryAt = Date.now() + delay;
|
|
209
|
-
_refreshQueue.set(entry.cacheKey, entry);
|
|
210
|
-
setTimeout(() => {
|
|
211
|
-
if (!_refreshQueuePromise) {
|
|
212
|
-
_refreshQueuePromise = _processRefreshQueue();
|
|
213
|
-
}
|
|
214
|
-
}, delay);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}));
|
|
218
|
-
|
|
219
|
-
if (_refreshQueue.size > 0 && REFETCH_BATCH_DELAY_MS > 0) {
|
|
220
|
-
await new Promise(r => setTimeout(r, REFETCH_BATCH_DELAY_MS));
|
|
221
|
-
}
|
|
92
|
+
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
93
|
+
if (tagsHeader) {
|
|
94
|
+
const cacheKeyUrl = cacheKey(request);
|
|
95
|
+
const actualUrl = new URL(request.url).href;
|
|
96
|
+
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
97
|
+
const method = request.method;
|
|
98
|
+
let body = null;
|
|
99
|
+
let contentType = null;
|
|
100
|
+
if (method !== "GET") {
|
|
101
|
+
contentType = request.headers.get("Content-Type");
|
|
102
|
+
try { body = await request.clone().text(); } catch {}
|
|
222
103
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
104
|
+
await cacheTagUrl(cacheKeyUrl, actualUrl, tags, method, body, contentType);
|
|
105
|
+
}`
|
|
106
|
+
: "";
|
|
107
|
+
return `// --- Mode & Strategy Configuration ---
|
|
108
|
+
|
|
109
|
+
const NAV_MODE = ${navModeCode};
|
|
110
|
+
const FALLBACK_PATH = ${fallbackCode};
|
|
111
|
+
const DEFAULT_STRATEGY = "${defaultStrategy}";
|
|
112
|
+
const CUSTOM_STRATEGIES = ${JSON.stringify(strategies)};
|
|
113
|
+
const REACTIVE_STALE_DEFAULT = ${globalStaleTime != null ? globalStaleTime : 0};
|
|
114
|
+
const FETCH_TIMEOUT_MS = ${fetchTimeout * 1000};
|
|
115
|
+
const SW_DEBUG = ${debugMode};
|
|
116
|
+
${authRoutePaths.length > 0 ? `const AUTH_ROUTES = ${JSON.stringify(authRoutePaths)};` : ""}
|
|
117
|
+
${serverPushEndpoint ? `const SERVER_PUSH_ENDPOINT = "${serverPushEndpoint}";` : ""}
|
|
118
|
+
|
|
119
|
+
${navRulesCode}// --- Debug Logging ---
|
|
120
|
+
|
|
121
|
+
function swLog(fn, msg, url, indent) {
|
|
122
|
+
if (!SW_DEBUG) return;
|
|
123
|
+
var prefix = "";
|
|
124
|
+
for (var i = 0; i < (indent || 0); i++) prefix += " ";
|
|
125
|
+
console.log("[SW]" + prefix, fn + ":", msg, url || "", Date.now());
|
|
226
126
|
}
|
|
227
127
|
|
|
228
|
-
// ---
|
|
229
|
-
|
|
230
|
-
const REACTIVE_PATTERNS = ${JSON.stringify(reactivePatterns)};
|
|
128
|
+
${globCode}// --- Navigation Detection ---
|
|
231
129
|
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
function shouldReactiveRefresh(cached, config) {
|
|
241
|
-
if (!config.staleTime && config.staleTime !== 0) return true;
|
|
242
|
-
if (config.staleTime === 0) return true;
|
|
243
|
-
return isStale(cached, config.staleTime);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// --- Reactive Interval Timers ---
|
|
247
|
-
|
|
248
|
-
${(() => {
|
|
249
|
-
const patternsWithInterval = reactivePatterns.filter((p) => !!p.refetchInterval && p.refetchInterval > 0);
|
|
250
|
-
if (patternsWithInterval.length === 0)
|
|
251
|
-
return "";
|
|
252
|
-
return `// Start intervals for reactive patterns with refetchInterval
|
|
253
|
-
(async () => {
|
|
254
|
-
${patternsWithInterval
|
|
255
|
-
.map((p) => ` setInterval(async () => {
|
|
256
|
-
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
257
|
-
const keys = await cache.keys();
|
|
258
|
-
for (const request of keys) {
|
|
259
|
-
const url = new URL(request.url);
|
|
260
|
-
if (url.pathname.startsWith("/__swc/")) continue;
|
|
261
|
-
if (!matchGlob(url.pathname, "${p.pattern}")) continue;
|
|
262
|
-
const config = findReactiveConfig(url.href);
|
|
263
|
-
if (!config) continue;
|
|
264
|
-
const cached = await cache.match(request);
|
|
265
|
-
if (cached && shouldReactiveRefresh(cached, config)) {
|
|
266
|
-
queueRefresh(request.url, url.href);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}, ${p.refetchInterval * 1000});`)
|
|
270
|
-
.join("\n")}
|
|
271
|
-
})();
|
|
272
|
-
`;
|
|
273
|
-
})()}
|
|
274
|
-
async function handleOnline() {
|
|
275
|
-
const clients = await self.clients.matchAll();
|
|
276
|
-
if (clients.length === 0) return;
|
|
277
|
-
|
|
278
|
-
// Step 1: Retry stale version entries (failed refetches after invalidation)
|
|
279
|
-
if (typeof staleVersions !== "undefined") {
|
|
280
|
-
const staleUrls = [...staleVersions.keys()];
|
|
281
|
-
for (const url of staleUrls) {
|
|
282
|
-
queueRefresh(url, url);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Step 2: Refresh reactive entries with refetchOnReconnect
|
|
287
|
-
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
288
|
-
const keys = await cache.keys();
|
|
289
|
-
for (const request of keys) {
|
|
290
|
-
const url = new URL(request.url);
|
|
291
|
-
if (url.pathname.startsWith("/__swc/")) continue;
|
|
292
|
-
const config = findReactiveConfig(url.href);
|
|
293
|
-
if (!config || !config.refetchOnReconnect) continue;
|
|
294
|
-
const cached = await cache.match(request);
|
|
295
|
-
if (cached && shouldReactiveRefresh(cached, config)) {
|
|
296
|
-
queueRefresh(request.url, url.href);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function handleOnFocus() {
|
|
302
|
-
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
303
|
-
const keys = await cache.keys();
|
|
304
|
-
for (const request of keys) {
|
|
305
|
-
const url = new URL(request.url);
|
|
306
|
-
if (url.pathname.startsWith("/__swc/")) continue;
|
|
307
|
-
const config = findReactiveConfig(url.href);
|
|
308
|
-
if (!config || !config.refetchOnFocus) continue;
|
|
309
|
-
const cached = await cache.match(request);
|
|
310
|
-
if (cached && shouldReactiveRefresh(cached, config)) {
|
|
311
|
-
queueRefresh(request.url, url.href);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
130
|
+
function isNavRequest(request) {
|
|
131
|
+
return (
|
|
132
|
+
request.mode === "navigate" ||
|
|
133
|
+
request.destination === "document" ||
|
|
134
|
+
(request.method === "GET" &&
|
|
135
|
+
request.headers.get("sec-fetch-mode") === "navigate")
|
|
136
|
+
);
|
|
314
137
|
}
|
|
315
138
|
|
|
316
139
|
// --- Cache Key ---
|
|
@@ -320,7 +143,6 @@ function cacheKey(request) {
|
|
|
320
143
|
if (key) return new URL("/__swc/" + key, self.location.origin).href;
|
|
321
144
|
const url = new URL(request.url);${normalizeCacheKey
|
|
322
145
|
? `
|
|
323
|
-
// Sort query params alphabetically for consistent cache keys
|
|
324
146
|
if (url.search) {
|
|
325
147
|
const params = new URLSearchParams(url.search);
|
|
326
148
|
params.sort();
|
|
@@ -328,10 +150,9 @@ function cacheKey(request) {
|
|
|
328
150
|
}`
|
|
329
151
|
: ""}${ignoreQueryParams && ignoreQueryParams.length > 0
|
|
330
152
|
? `
|
|
331
|
-
// Strip configured query params from cache key
|
|
332
|
-
const ignore = ${JSON.stringify(ignoreQueryParams)};
|
|
333
153
|
if (url.search) {
|
|
334
154
|
const params = new URLSearchParams(url.search);
|
|
155
|
+
const ignore = ${JSON.stringify(ignoreQueryParams)};
|
|
335
156
|
for (const key of ignore) params.delete(key);
|
|
336
157
|
url.search = params.toString();
|
|
337
158
|
}`
|
|
@@ -339,60 +160,216 @@ function cacheKey(request) {
|
|
|
339
160
|
return url.href;
|
|
340
161
|
}
|
|
341
162
|
|
|
342
|
-
// --- Cache
|
|
163
|
+
// --- Cache Lookup ---
|
|
343
164
|
|
|
344
165
|
async function fromPrecache(request) {
|
|
345
|
-
|
|
346
|
-
|
|
166
|
+
swLog("fromPrecache", "ENTER", request.url, 4);
|
|
167
|
+
const cache = await caches.open("precache");
|
|
168
|
+
const url = new URL(request.url);
|
|
169
|
+
url.search = "";
|
|
170
|
+
const result = await cache.match(url.href);
|
|
171
|
+
swLog("fromPrecache", result ? "HIT" : "MISS", request.url, 4);
|
|
172
|
+
return result;
|
|
347
173
|
}
|
|
348
174
|
|
|
349
175
|
async function fromRuntime(request) {
|
|
350
|
-
|
|
351
|
-
|
|
176
|
+
swLog("fromRuntime", "ENTER", request.url, 4);
|
|
177
|
+
const cache = await caches.open("swoff-runtime");
|
|
178
|
+
const result = await cache.match(cacheKey(request));
|
|
179
|
+
swLog("fromRuntime", result ? "HIT" : "MISS", request.url, 4);
|
|
180
|
+
return result;
|
|
352
181
|
}
|
|
353
182
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
183
|
+
/*
|
|
184
|
+
* serveFromCache returns a cached Response, or null if nothing is cached.
|
|
185
|
+
*
|
|
186
|
+
* Mode-specific behaviour for navigation requests:
|
|
187
|
+
* SPA: bypass cache entirely → return null
|
|
188
|
+
* SSR: precache → runtime-html → null
|
|
189
|
+
* Default: same as SSR
|
|
190
|
+
*
|
|
191
|
+
* Non-navigation requests:
|
|
192
|
+
* runtime → precache (content-type match only) → null
|
|
193
|
+
*
|
|
194
|
+
* Precache is content-type-gated for non-navigation requests to avoid
|
|
195
|
+
* serving HTML pages (stored with stripped extensions) for requests that
|
|
196
|
+
* expect a different content type (e.g. RSC payloads, JSON fetches).
|
|
197
|
+
*/
|
|
198
|
+
async function serveFromCache(request) {
|
|
199
|
+
swLog("serveFromCache", "ENTER", request.url, 3);
|
|
200
|
+
if (isNavRequest(request)) {
|
|
201
|
+
if (NAV_MODE === "spa") {
|
|
202
|
+
if (FALLBACK_PATH) {
|
|
203
|
+
const cache = await caches.open("precache");
|
|
204
|
+
const match = await cache.match(FALLBACK_PATH);
|
|
205
|
+
if (match) {
|
|
206
|
+
swLog("serveFromCache", "HIT fallback-path", request.url, 3);
|
|
207
|
+
return match;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
swLog("serveFromCache", "MISS (SPA nav)", request.url, 3);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const pc = await fromPrecache(request);
|
|
214
|
+
if (pc) {
|
|
215
|
+
swLog("serveFromCache", "HIT precache (nav)", request.url, 3);
|
|
216
|
+
return pc;
|
|
217
|
+
}
|
|
218
|
+
const htmlCache = await caches.open("swoff-runtime-html");
|
|
219
|
+
const htmlMatch = await htmlCache.match(cacheKey(request));
|
|
220
|
+
if (htmlMatch) {
|
|
221
|
+
swLog("serveFromCache", "HIT runtime-html", request.url, 3);
|
|
222
|
+
return htmlMatch;
|
|
223
|
+
}
|
|
224
|
+
swLog("serveFromCache", "MISS (SSR/Default nav)", request.url, 3);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const cached = await fromRuntime(request);
|
|
228
|
+
if (cached) {
|
|
229
|
+
swLog("serveFromCache", "HIT runtime", request.url, 3);
|
|
230
|
+
return cached;
|
|
231
|
+
}
|
|
232
|
+
const pc = await fromPrecache(request);
|
|
233
|
+
if (pc) {
|
|
234
|
+
const accept = request.headers.get("Accept") || "*/*";
|
|
235
|
+
const pcType = (pc.headers.get("Content-Type") || "").split(";")[0].trim();
|
|
236
|
+
if (accept.includes(pcType) || accept.includes("*/*") || !pcType) {
|
|
237
|
+
swLog("serveFromCache", "HIT precache (sub)", request.url, 3);
|
|
238
|
+
return pc;
|
|
239
|
+
}
|
|
240
|
+
swLog("serveFromCache", "SKIP precache (type mismatch)", request.url, 3);
|
|
241
|
+
}
|
|
242
|
+
swLog("serveFromCache", "MISS (subresource)", request.url, 3);
|
|
243
|
+
return null;
|
|
364
244
|
}
|
|
365
245
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
246
|
+
// --- Cache Store ---
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* cacheResponse stores a network response in the appropriate cache.
|
|
250
|
+
*
|
|
251
|
+
* If already in precache:
|
|
252
|
+
* - non-HTML with same content-type → update precache entry
|
|
253
|
+
* - otherwise → skip (no runtime duplicate)
|
|
254
|
+
* If not in precache:
|
|
255
|
+
* - HTML → runtime-html cache
|
|
256
|
+
* - non-HTML → runtime cache
|
|
257
|
+
*
|
|
258
|
+
* Tags are recorded in all cases.
|
|
259
|
+
*/
|
|
260
|
+
async function cacheResponse(response, request) {
|
|
261
|
+
swLog("cacheResponse", "ENTER", request.url, 3);
|
|
262
|
+
const key = cacheKey(request);
|
|
263
|
+
const ct = response.headers.get("Content-Type") || "";
|
|
264
|
+
var skipRuntime = false;
|
|
265
|
+
const precache = await caches.open("precache");
|
|
266
|
+
const url = new URL(key);
|
|
267
|
+
url.search = "";
|
|
268
|
+
const precached = await precache.match(url.href);
|
|
269
|
+
|
|
270
|
+
if (precached) {
|
|
271
|
+
if (!ct.startsWith("text/html")) {
|
|
272
|
+
const pct = precached.headers.get("Content-Type") || "";
|
|
273
|
+
if (pct.split(";")[0] === ct.split(";")[0]) {
|
|
274
|
+
try {
|
|
275
|
+
await precache.put(url.href, response.clone());
|
|
276
|
+
swLog("cacheResponse", "updated precache", request.url, 3);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
swLog("cacheResponse", "precache update failed", request.url, 3);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
skipRuntime = true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!skipRuntime) {
|
|
286
|
+
const cacheName = ct.startsWith("text/html") ? "swoff-runtime-html" : "swoff-runtime";
|
|
287
|
+
const cache = await caches.open(cacheName);
|
|
288
|
+
const headers = new Headers(response.headers);
|
|
289
|
+
headers.set("X-SW-Cached-At", String(Date.now()));
|
|
290
|
+
const putResponse = new Response(response.body, {
|
|
291
|
+
status: response.status,
|
|
292
|
+
statusText: response.statusText,
|
|
293
|
+
headers,
|
|
294
|
+
});
|
|
295
|
+
try {
|
|
296
|
+
await cache.put(key, putResponse);
|
|
297
|
+
swLog("cacheResponse", "stored in " + cacheName, request.url, 3);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
swLog("cacheResponse", "quota error, evicting stale entries", request.url, 3);
|
|
378
300
|
try {
|
|
379
|
-
|
|
301
|
+
// Evict stale runtime entries and retry once
|
|
302
|
+
for (const name of ["swoff-runtime", "swoff-runtime-html"]) {
|
|
303
|
+
const c = await caches.open(name);
|
|
304
|
+
const keys = await c.keys();
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
for (const req of keys) {
|
|
307
|
+
const res = await c.match(req);
|
|
308
|
+
const cachedAt = res ? Number(res.headers.get("X-SW-Cached-At") || 0) : 0;
|
|
309
|
+
if (!cachedAt || now - cachedAt > 3600000) await c.delete(req);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
await cache.put(key, putResponse.clone());
|
|
313
|
+
swLog("cacheResponse", "stored after eviction", request.url, 3);
|
|
380
314
|
} catch {}
|
|
381
315
|
}
|
|
382
|
-
|
|
383
|
-
}
|
|
316
|
+
}${tagCode}
|
|
384
317
|
}
|
|
385
318
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
319
|
+
// --- Fallback ---
|
|
320
|
+
|
|
321
|
+
/*
|
|
322
|
+
* Fallback hierarchy for when a strategy cannot serve a response:
|
|
323
|
+
* SSR / Default: precache → runtime-html → per-route → global → inline 503
|
|
324
|
+
* SPA: per-route → inline 503
|
|
325
|
+
*/
|
|
326
|
+
async function fallback(request) {
|
|
327
|
+
swLog("fallback", "ENTER", request.url, 3);
|
|
328
|
+
const pc = await fromPrecache(request);
|
|
329
|
+
if (pc) {
|
|
330
|
+
swLog("fallback", "HIT precache", request.url, 3);
|
|
331
|
+
return pc;
|
|
332
|
+
}
|
|
333
|
+
if (NAV_MODE !== "spa") {
|
|
334
|
+
const htmlCache = await caches.open("swoff-runtime-html");
|
|
335
|
+
const htmlMatch = await htmlCache.match(cacheKey(request));
|
|
336
|
+
if (htmlMatch) {
|
|
337
|
+
swLog("fallback", "HIT runtime-html", request.url, 3);
|
|
338
|
+
return htmlMatch;
|
|
339
|
+
}
|
|
340
|
+
}${hasRules
|
|
341
|
+
? `
|
|
342
|
+
const routeFallbackPath = matchRouteFallback(request.url);
|
|
343
|
+
if (routeFallbackPath) {
|
|
344
|
+
const cache = await caches.open("precache");
|
|
345
|
+
const match = await cache.match(routeFallbackPath);
|
|
346
|
+
if (match) {
|
|
347
|
+
swLog("fallback", "HIT per-route fallback", request.url, 3);
|
|
348
|
+
broadcastToClients("OFFLINE_FALLBACK_ACTIVATED", { detail: { route: new URL(request.url).pathname, fallbackLevel: "route-fallback", timestamp: Date.now() } });
|
|
349
|
+
return match;
|
|
350
|
+
}
|
|
351
|
+
}`
|
|
352
|
+
: ""}
|
|
353
|
+
if (NAV_MODE !== "spa") {
|
|
354
|
+
if (FALLBACK_PATH) {
|
|
355
|
+
const cache = await caches.open("precache");
|
|
356
|
+
const match = await cache.match(FALLBACK_PATH);
|
|
357
|
+
if (match) {
|
|
358
|
+
swLog("fallback", "HIT global fallback", request.url, 3);
|
|
359
|
+
broadcastToClients("OFFLINE_FALLBACK_ACTIVATED", { detail: { route: new URL(request.url).pathname, fallbackLevel: "offline-page", timestamp: Date.now() } });
|
|
360
|
+
return match;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
390
363
|
}
|
|
364
|
+
swLog("fallback", "HIT inline 503", request.url, 3);
|
|
365
|
+
broadcastToClients("OFFLINE_FALLBACK_ACTIVATED", { detail: { route: new URL(request.url).pathname, fallbackLevel: "inline-503", timestamp: Date.now() } });
|
|
366
|
+
return inline503Response();
|
|
391
367
|
}
|
|
392
368
|
|
|
393
|
-
// --- Response Helpers ---
|
|
369
|
+
${inline503}// --- Response Helpers ---
|
|
394
370
|
|
|
395
371
|
function markFromCache(response) {
|
|
372
|
+
swLog("markFromCache", "ENTER", response.url, 4);
|
|
396
373
|
const headers = new Headers(response.headers);
|
|
397
374
|
headers.set("X-SW-From-Cache", "true");
|
|
398
375
|
return new Response(response.body, {
|
|
@@ -403,94 +380,309 @@ function markFromCache(response) {
|
|
|
403
380
|
}
|
|
404
381
|
|
|
405
382
|
function isStale(response, staleTimeSeconds) {
|
|
406
|
-
if (staleTimeSeconds == null
|
|
383
|
+
if (staleTimeSeconds == null) return false;
|
|
384
|
+
if (staleTimeSeconds === 0) return true;
|
|
385
|
+
if (staleTimeSeconds < 0) return false;
|
|
407
386
|
const cachedAt = response.headers.get("X-SW-Cached-At");
|
|
408
387
|
if (!cachedAt) return false;
|
|
409
388
|
return Date.now() - Number(cachedAt) > staleTimeSeconds * 1000;
|
|
410
389
|
}
|
|
411
390
|
|
|
412
|
-
|
|
391
|
+
// --- Fetch Helpers ---
|
|
413
392
|
|
|
414
|
-
function
|
|
415
|
-
|
|
393
|
+
async function _fetchWithTimeout(_, request, timeoutMs) {
|
|
394
|
+
timeoutMs = timeoutMs || FETCH_TIMEOUT_MS;
|
|
395
|
+
swLog("_fetchWithTimeout", "ENTER timeout=" + timeoutMs, request.url, 3);
|
|
396
|
+
const controller = new AbortController();
|
|
397
|
+
const id = setTimeout(function() { controller.abort(); }, timeoutMs);
|
|
398
|
+
try {
|
|
399
|
+
const response = await fetch(request, { signal: controller.signal });
|
|
400
|
+
swLog("_fetchWithTimeout", "SUCCESS " + response.status, request.url, 3);
|
|
401
|
+
return response;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
swLog("_fetchWithTimeout", "FAILED", request.url, 3);
|
|
404
|
+
throw e;
|
|
405
|
+
} finally {
|
|
406
|
+
clearTimeout(id);
|
|
407
|
+
}
|
|
416
408
|
}
|
|
417
409
|
|
|
418
|
-
|
|
410
|
+
${navigationPreload
|
|
411
|
+
? `
|
|
412
|
+
async function fetchWithPreload(event, request, timeoutMs) {
|
|
413
|
+
swLog("fetchWithPreload", "ENTER", request.url, 3);
|
|
414
|
+
try {
|
|
415
|
+
const preload = await Promise.race([
|
|
416
|
+
event.preloadResponse,
|
|
417
|
+
new Promise(function(_, reject) {
|
|
418
|
+
setTimeout(function() { reject(new Error("preload timeout")); }, (timeoutMs || FETCH_TIMEOUT_MS) / 2);
|
|
419
|
+
}),
|
|
420
|
+
]);
|
|
421
|
+
if (preload) {
|
|
422
|
+
swLog("fetchWithPreload", "HIT preload", request.url, 3);
|
|
423
|
+
return preload;
|
|
424
|
+
}
|
|
425
|
+
} catch {}
|
|
426
|
+
swLog("fetchWithPreload", "MISS preload, fallback to fetch", request.url, 3);
|
|
427
|
+
return _fetchWithTimeout(event, request, timeoutMs);
|
|
428
|
+
}
|
|
429
|
+
`
|
|
430
|
+
: ""}const _fetch = ${navigationPreload ? "fetchWithPreload" : "_fetchWithTimeout"};
|
|
431
|
+
|
|
432
|
+
// --- Strategy Resolution ---
|
|
433
|
+
|
|
434
|
+
function determineCacheStrategy(request) {
|
|
419
435
|
const override = request.headers.get("X-SW-Strategy");
|
|
420
436
|
if (override) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
437
|
+
if (override === "reactive") {
|
|
438
|
+
const staleTimeHeader = request.headers.get("X-SW-Stale-Time");
|
|
439
|
+
return {
|
|
440
|
+
strategy: "reactive",
|
|
441
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
442
|
+
staleTime: staleTimeHeader != null ? Number(staleTimeHeader) : REACTIVE_STALE_DEFAULT,
|
|
443
|
+
refetchInterval: Number(request.headers.get("X-SW-Refetch-Interval") || 0),
|
|
444
|
+
refetchOnFocus: request.headers.get("X-SW-Refetch-On-Focus") === "true",
|
|
445
|
+
refetchOnReconnect: request.headers.get("X-SW-Refetch-On-Reconnect") === "true",
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return { strategy: override, timeoutMs: FETCH_TIMEOUT_MS };
|
|
429
449
|
}
|
|
450
|
+
|
|
430
451
|
const path = new URL(request.url).pathname;
|
|
431
|
-
for (const [pattern, entry] of Object.entries(
|
|
452
|
+
for (const [pattern, entry] of Object.entries(CUSTOM_STRATEGIES)) {
|
|
432
453
|
if (matchGlob(path, pattern)) {
|
|
433
|
-
const resolved =
|
|
434
|
-
|
|
454
|
+
const resolved = typeof entry === "string" ? { strategy: entry } : entry;
|
|
455
|
+
const timeoutMs = (resolved.timeout != null ? resolved.timeout * 1000 : FETCH_TIMEOUT_MS);
|
|
456
|
+
if (resolved.strategy === "reactive") {
|
|
457
|
+
return {
|
|
458
|
+
strategy: "reactive",
|
|
459
|
+
timeoutMs: timeoutMs,
|
|
460
|
+
staleTime: resolved.staleTime ?? REACTIVE_STALE_DEFAULT,
|
|
461
|
+
refetchInterval: resolved.refetchInterval ?? 0,
|
|
462
|
+
refetchOnFocus: resolved.refetchOnFocus ?? false,
|
|
463
|
+
refetchOnReconnect: resolved.refetchOnReconnect ?? false,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return { strategy: resolved.strategy, timeoutMs: timeoutMs };
|
|
435
467
|
}
|
|
436
468
|
}
|
|
437
|
-
|
|
469
|
+
|
|
470
|
+
if (DEFAULT_STRATEGY === "reactive") {
|
|
471
|
+
return {
|
|
472
|
+
strategy: "reactive",
|
|
473
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
474
|
+
staleTime: REACTIVE_STALE_DEFAULT,
|
|
475
|
+
refetchInterval: 0,
|
|
476
|
+
refetchOnFocus: false,
|
|
477
|
+
refetchOnReconnect: false,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return { strategy: DEFAULT_STRATEGY, timeoutMs: FETCH_TIMEOUT_MS };
|
|
438
481
|
}
|
|
439
482
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
483
|
+
// --- Reactive Entry Store ---
|
|
484
|
+
|
|
485
|
+
var REACTIVE_ENTRIES = new Map();
|
|
486
|
+
var REACTIVE_INTERVALS = new Map();
|
|
487
|
+
var clearAllReactive = function() {
|
|
488
|
+
REACTIVE_INTERVALS.forEach(function(id) { clearInterval(id); });
|
|
489
|
+
REACTIVE_INTERVALS.clear();
|
|
490
|
+
REACTIVE_ENTRIES.clear();
|
|
491
|
+
swLog("clearAllReactive", "done", "", 0);
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
function registerReactiveEntry(url, actualUrl, config) {
|
|
495
|
+
swLog("registerReactiveEntry", "ENTER", url, 4);
|
|
496
|
+
if (REACTIVE_ENTRIES.has(url)) {
|
|
497
|
+
swLog("registerReactiveEntry", "SKIP already registered", url, 4);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
REACTIVE_ENTRIES.set(url, {
|
|
501
|
+
actualUrl: actualUrl,
|
|
502
|
+
refetchOnFocus: !!config.refetchOnFocus,
|
|
503
|
+
refetchOnReconnect: !!config.refetchOnReconnect,
|
|
504
|
+
refetchInterval: config.refetchInterval || 0,
|
|
505
|
+
staleTime: config.staleTime || 0,
|
|
506
|
+
lastRefetch: 0,
|
|
507
|
+
});
|
|
508
|
+
if ((config.refetchInterval || 0) > 0 && !REACTIVE_INTERVALS.has(url)) {
|
|
509
|
+
var id = setInterval(function() { refetchEntry(url); }, (config.refetchInterval || 0) * 1000);
|
|
510
|
+
REACTIVE_INTERVALS.set(url, id);
|
|
511
|
+
swLog("registerReactiveEntry", "interval=" + config.refetchInterval + "s", url, 4);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function refetchEntry(url) {
|
|
516
|
+
swLog("refetchEntry", "ENTER", url, 4);
|
|
517
|
+
var entry = REACTIVE_ENTRIES.get(url);
|
|
518
|
+
var fetchUrl = entry ? entry.actualUrl || url : url;
|
|
519
|
+
var req = new Request(fetchUrl);
|
|
520
|
+
if (entry) {
|
|
521
|
+
var cached = await serveFromCache(req);
|
|
522
|
+
if (cached && !isStale(cached, entry.staleTime)) {
|
|
523
|
+
swLog("refetchEntry", "SKIP fresh in cache", url, 4);
|
|
524
|
+
entry.lastRefetch = Date.now();
|
|
525
|
+
return;
|
|
446
526
|
}
|
|
447
527
|
}
|
|
448
|
-
|
|
528
|
+
try {
|
|
529
|
+
var response = await fetchWithRetry(req, REFETCH_RETRY);
|
|
530
|
+
if (response && response.ok) {
|
|
531
|
+
await cacheResponse(response.clone(), req);
|
|
532
|
+
if (entry) entry.lastRefetch = Date.now();
|
|
533
|
+
swLog("refetchEntry", "SUCCESS", url, 4);
|
|
534
|
+
broadcastToClients("CACHE_UPDATED", { url: fetchUrl });
|
|
535
|
+
}
|
|
536
|
+
checkAuthFailure(response);
|
|
537
|
+
} catch {}
|
|
449
538
|
}
|
|
450
539
|
|
|
451
|
-
function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
540
|
+
function handleRefetch(prop) {
|
|
541
|
+
swLog("handleRefetch", "ENTER prop=" + prop + " entries=" + REACTIVE_ENTRIES.size, "", 0);
|
|
542
|
+
REACTIVE_ENTRIES.forEach(function(config, url) {
|
|
543
|
+
if (config[prop]) queueRefresh(url);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function checkAuthFailure(response) {
|
|
548
|
+
if (response && isAuthFailureResponse(response)) {
|
|
549
|
+
swLog("checkAuthFailure", "AUTH_FAILURE", response.url || "", 1);
|
|
550
|
+
broadcastToClients("AUTH_FAILURE");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// --- Strategies ---
|
|
555
|
+
|
|
556
|
+
/*
|
|
557
|
+
* Reactive: serve fresh from cache; if stale/miss → network → fallback
|
|
558
|
+
* Network-Only: network → fallback (cache ok responses)
|
|
559
|
+
* Network-First: network → cache (on fail) → fallback
|
|
560
|
+
* Cache-First: cache → network (on miss) → fallback
|
|
561
|
+
* Stale-While-Revalidate: stale cache immediately + bg network refresh → fallback
|
|
562
|
+
* Cache-Only: cache only → 404
|
|
563
|
+
*/
|
|
564
|
+
|
|
565
|
+
async function reactiveStrategy(event, request, config) {
|
|
566
|
+
swLog("reactiveStrategy", "ENTER", request.url, 2);
|
|
567
|
+
const staleTime = config.staleTime != null ? config.staleTime : REACTIVE_STALE_DEFAULT;
|
|
568
|
+
const cached = await serveFromCache(request);
|
|
569
|
+
if (cached && !isStale(cached, staleTime)) return markFromCache(cached);
|
|
570
|
+
|
|
571
|
+
if (cached && isStale(cached, staleTime)) {
|
|
572
|
+
event.waitUntil(queueRefresh(cacheKey(request)));
|
|
573
|
+
return markFromCache(cached);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
const response = await _fetch(event, request, config.timeoutMs);
|
|
578
|
+
checkAuthFailure(response);
|
|
579
|
+
if (response.ok) {
|
|
580
|
+
event.waitUntil(cacheResponse(response.clone(), request));
|
|
581
|
+
return response;
|
|
582
|
+
}
|
|
583
|
+
return fallback(request);
|
|
584
|
+
} catch {
|
|
585
|
+
return fallback(request);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function networkFirstStrategy(event, request, config) {
|
|
590
|
+
swLog("networkFirstStrategy", "ENTER", request.url, 2);
|
|
591
|
+
try {
|
|
592
|
+
const response = await _fetch(event, request, config.timeoutMs);
|
|
593
|
+
checkAuthFailure(response);
|
|
594
|
+
if (response.ok) {
|
|
595
|
+
event.waitUntil(cacheResponse(response.clone(), request));
|
|
596
|
+
return response;
|
|
597
|
+
}
|
|
598
|
+
const cached = await serveFromCache(request);
|
|
599
|
+
if (cached) return markFromCache(cached);
|
|
600
|
+
return fallback(request);
|
|
601
|
+
} catch {
|
|
602
|
+
const cached = await serveFromCache(request);
|
|
603
|
+
if (cached) return markFromCache(cached);
|
|
604
|
+
return fallback(request);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function cacheFirstStrategy(event, request, config) {
|
|
609
|
+
swLog("cacheFirstStrategy", "ENTER", request.url, 2);
|
|
610
|
+
const cached = await serveFromCache(request);
|
|
611
|
+
if (cached) return markFromCache(cached);
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
const response = await _fetch(event, request, config.timeoutMs);
|
|
615
|
+
checkAuthFailure(response);
|
|
616
|
+
if (response.ok) {
|
|
617
|
+
event.waitUntil(cacheResponse(response.clone(), request));
|
|
618
|
+
return response;
|
|
619
|
+
}
|
|
620
|
+
return fallback(request);
|
|
621
|
+
} catch {
|
|
622
|
+
return fallback(request);
|
|
467
623
|
}
|
|
468
624
|
}
|
|
469
625
|
|
|
626
|
+
async function staleWhileRevalidateStrategy(event, request, config) {
|
|
627
|
+
swLog("staleWhileRevalidateStrategy", "ENTER", request.url, 2);
|
|
628
|
+
const cached = await serveFromCache(request);
|
|
629
|
+
|
|
630
|
+
event.waitUntil(queueRefresh(cacheKey(request)));
|
|
631
|
+
|
|
632
|
+
if (cached) return markFromCache(cached);
|
|
633
|
+
try {
|
|
634
|
+
const response = await _fetch(event, request, config.timeoutMs);
|
|
635
|
+
checkAuthFailure(response);
|
|
636
|
+
if (response.ok) {
|
|
637
|
+
event.waitUntil(cacheResponse(response.clone(), request));
|
|
638
|
+
return response;
|
|
639
|
+
}
|
|
640
|
+
return fallback(request);
|
|
641
|
+
} catch {
|
|
642
|
+
return fallback(request);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function cacheOnlyStrategy(event, request, _config) {
|
|
647
|
+
swLog("cacheOnlyStrategy", "ENTER", request.url, 2);
|
|
648
|
+
const cached = await serveFromCache(request);
|
|
649
|
+
if (cached) return markFromCache(cached);
|
|
650
|
+
swLog("cacheOnlyStrategy", "MISS 404", request.url, 2);
|
|
651
|
+
return new Response("Not in cache", { status: 404 });
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const STRATEGY_HANDLERS = {
|
|
655
|
+
"reactive": reactiveStrategy,
|
|
656
|
+
"network-first": networkFirstStrategy,
|
|
657
|
+
"cache-first": cacheFirstStrategy,
|
|
658
|
+
"stale-while-revalidate": staleWhileRevalidateStrategy,
|
|
659
|
+
"cache-only": cacheOnlyStrategy,
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
async function _executeStrategy(event, request, config) {
|
|
663
|
+
swLog("_executeStrategy", "ENTER strategy=" + config.strategy, request.url, 1);
|
|
664
|
+
const handler = STRATEGY_HANDLERS[config.strategy] || cacheFirstStrategy;
|
|
665
|
+
try {
|
|
666
|
+
return await handler(event, request, config);
|
|
667
|
+
} catch {
|
|
668
|
+
swLog("_executeStrategy", "ERROR strategy=" + config.strategy, request.url, 1);
|
|
669
|
+
return fallback(request);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function applyStrategy(event, request, config) {
|
|
674
|
+
event.respondWith(_executeStrategy(event, request, config));
|
|
675
|
+
}
|
|
676
|
+
|
|
470
677
|
${mutationQueueEnabled
|
|
471
678
|
? `// --- Mutation Queue IDB Helpers ---
|
|
472
679
|
|
|
473
680
|
const MUTATION_DB_NAME = "swoff-queue";
|
|
474
681
|
const MUTATION_STORE_NAME = "mutations";
|
|
475
|
-
// Bump this when adding new indexes/stores for schema migration
|
|
476
682
|
const MUTATION_DB_VERSION = 1;
|
|
477
683
|
|
|
478
|
-
function openMutationQueueDB() {
|
|
479
|
-
return new Promise((resolve, reject) => {
|
|
480
|
-
const request = indexedDB.open(MUTATION_DB_NAME, MUTATION_DB_VERSION);
|
|
481
|
-
request.onupgradeneeded = (e) => {
|
|
482
|
-
const db = e.target.result;
|
|
483
|
-
if (!db.objectStoreNames.contains(MUTATION_STORE_NAME)) {
|
|
484
|
-
const store = db.createObjectStore(MUTATION_STORE_NAME, { keyPath: "id" });
|
|
485
|
-
store.createIndex("by-timestamp", "timestamp");
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
request.onsuccess = (e) => resolve(e.target.result);
|
|
489
|
-
request.onerror = (e) => reject(e.target.error);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
684
|
async function storeMutationInSW(request) {
|
|
685
|
+
swLog("storeMutationInSW", "ENTER", request.url, 4);
|
|
494
686
|
let body, bodyType;
|
|
495
687
|
try {
|
|
496
688
|
body = await request.clone().json();
|
|
@@ -503,9 +695,19 @@ async function storeMutationInSW(request) {
|
|
|
503
695
|
const tagsHeader = request.headers.get("X-SW-Invalidate-Tags");
|
|
504
696
|
const tags = tagsHeader ? tagsHeader.split(",").map(function(t) { return t.trim(); }) : [];
|
|
505
697
|
|
|
506
|
-
const db = await
|
|
698
|
+
const db = await openDB(MUTATION_DB_NAME, MUTATION_DB_VERSION, function(db) {
|
|
699
|
+
if (!db.objectStoreNames.contains(MUTATION_STORE_NAME)) {
|
|
700
|
+
const store = db.createObjectStore(MUTATION_STORE_NAME, { keyPath: "id" });
|
|
701
|
+
store.createIndex("by-timestamp", "timestamp");
|
|
702
|
+
}
|
|
703
|
+
});${maxCacheAge && maxCacheAge > 0
|
|
704
|
+
? `
|
|
705
|
+
// Prune expired entries in a separate transaction
|
|
706
|
+
await pruneStaleStore(db, MUTATION_STORE_NAME, Date.now() - MAX_RUNTIME_CACHE_AGE * 1000, "id");`
|
|
707
|
+
: ""}
|
|
507
708
|
const tx = db.transaction(MUTATION_STORE_NAME, "readwrite");
|
|
508
|
-
tx.objectStore(MUTATION_STORE_NAME)
|
|
709
|
+
const store = tx.objectStore(MUTATION_STORE_NAME);
|
|
710
|
+
store.add({
|
|
509
711
|
id: crypto.randomUUID(),
|
|
510
712
|
method: request.method,
|
|
511
713
|
url: new URL(request.url).href,
|
|
@@ -517,26 +719,54 @@ async function storeMutationInSW(request) {
|
|
|
517
719
|
nextRetryAt: 0,
|
|
518
720
|
tags,
|
|
519
721
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
722
|
+
try {
|
|
723
|
+
await new Promise(function(resolve, reject) {
|
|
724
|
+
tx.oncomplete = function() { resolve(); };
|
|
725
|
+
tx.onerror = function() { reject(tx.error); };
|
|
726
|
+
});
|
|
727
|
+
swLog("storeMutationInSW", "STORED", request.url, 4);
|
|
728
|
+
} finally {
|
|
729
|
+
db.close();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async function _fetchMutation(request) {
|
|
734
|
+
swLog("_fetchMutation", "ENTER", request.url, 4);
|
|
735
|
+
const controller = new AbortController();
|
|
736
|
+
const id = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
737
|
+
try {
|
|
738
|
+
const response = await fetch(request, { signal: controller.signal });
|
|
739
|
+
swLog("_fetchMutation", "SUCCESS " + response.status, request.url, 4);
|
|
740
|
+
return response;
|
|
741
|
+
} catch (e) {
|
|
742
|
+
swLog("_fetchMutation", "FAILED", request.url, 4);
|
|
743
|
+
throw e;
|
|
744
|
+
} finally {
|
|
745
|
+
clearTimeout(id);
|
|
746
|
+
}
|
|
524
747
|
}
|
|
525
748
|
|
|
526
749
|
async function handleMutation(event) {
|
|
750
|
+
swLog("handleMutation", "ENTER", event.request.url, 3);
|
|
527
751
|
const request = event.request;
|
|
528
752
|
if (request.headers.get("X-SW-No-Queue") === "true") {
|
|
529
|
-
|
|
753
|
+
swLog("handleMutation", "no-queue mode", request.url, 3);
|
|
754
|
+
try {
|
|
755
|
+
const response = await _fetchMutation(request.clone());
|
|
756
|
+
checkAuthFailure(response);
|
|
757
|
+
return response;
|
|
758
|
+
} catch {
|
|
759
|
+
throw new Error("Mutation failed (no-queue mode)");
|
|
760
|
+
}
|
|
530
761
|
}
|
|
531
762
|
try {
|
|
532
|
-
|
|
763
|
+
const response = await _fetchMutation(request.clone());
|
|
764
|
+
checkAuthFailure(response);
|
|
765
|
+
return response;
|
|
533
766
|
} catch {
|
|
767
|
+
swLog("handleMutation", "queuing mutation", request.url, 3);
|
|
534
768
|
await storeMutationInSW(request);
|
|
535
|
-
|
|
536
|
-
const clients = await self.clients.matchAll();
|
|
537
|
-
clients.forEach(function(client) {
|
|
538
|
-
client.postMessage({ type: "MUTATION_STORED" });
|
|
539
|
-
});
|
|
769
|
+
broadcastToClients("MUTATION_STORED");
|
|
540
770
|
const queuedBody = JSON.stringify({ queued: true });
|
|
541
771
|
return new Response(queuedBody, {
|
|
542
772
|
status: 202,
|
|
@@ -548,146 +778,42 @@ async function handleMutation(event) {
|
|
|
548
778
|
|
|
549
779
|
`
|
|
550
780
|
: ""}self.addEventListener("fetch", (event) => {
|
|
551
|
-
const { request } = event
|
|
781
|
+
const { request } = event;
|
|
782
|
+
swLog("fetch", "INCOMING", request.url, 0);${serverPushEndpoint
|
|
783
|
+
? `
|
|
784
|
+
if (SERVER_PUSH_ENDPOINT && new URL(request.url).pathname.includes(SERVER_PUSH_ENDPOINT)) {
|
|
785
|
+
swLog("fetch", "server-push-bypass", request.url, 0);
|
|
786
|
+
return;
|
|
787
|
+
}`
|
|
788
|
+
: ""}${authRoutePaths.length > 0
|
|
552
789
|
? `
|
|
553
|
-
if (
|
|
554
|
-
|
|
790
|
+
if (AUTH_ROUTES.some(function(route) { return new URL(request.url).pathname.includes(route); })) {
|
|
791
|
+
swLog("fetch", "auth-route-bypass", request.url, 0);
|
|
792
|
+
return;
|
|
793
|
+
}`
|
|
794
|
+
: ""}
|
|
795
|
+
if (request.headers.get("X-SW-Strategy") === "network-only") {
|
|
796
|
+
swLog("fetch", "strategy=network-only", request.url, 0);
|
|
797
|
+
return;
|
|
798
|
+
}${mutationQueueEnabled
|
|
799
|
+
? `
|
|
800
|
+
if (request.method !== "GET") {
|
|
801
|
+
if (request.headers.get("X-SW-Type") === "mutation") {
|
|
555
802
|
event.respondWith(handleMutation(event));
|
|
556
803
|
return;
|
|
557
804
|
}
|
|
558
|
-
if (!request.headers.get("X-SW-Cache-Key")) return;
|
|
805
|
+
if (!request.headers.get("X-SW-Cache-Key")) { return; }
|
|
559
806
|
}`
|
|
560
807
|
: `
|
|
561
|
-
if (request.method !== "GET"
|
|
562
|
-
if (!request.headers.get("X-SW-Cache-Key")) return;
|
|
808
|
+
if (request.method !== "GET") {
|
|
809
|
+
if (!request.headers.get("X-SW-Cache-Key")) { return; }
|
|
563
810
|
}`}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
// --- Strategies ---
|
|
569
|
-
|
|
570
|
-
${navigationPreload
|
|
571
|
-
? `
|
|
572
|
-
async function fetchWithPreload(event, request) {
|
|
573
|
-
try {
|
|
574
|
-
const preload = await event.preloadResponse;
|
|
575
|
-
if (preload) return preload;
|
|
576
|
-
} catch {}
|
|
577
|
-
return fetch(request);
|
|
578
|
-
}
|
|
579
|
-
`
|
|
580
|
-
: ""}const _fetch = ${navigationPreload ? "fetchWithPreload" : `(event, request) => fetch(request)`};
|
|
581
|
-
|
|
582
|
-
async function cacheFirst(event, request) {
|
|
583
|
-
const cached = await fromRuntime(request);
|
|
584
|
-
if (cached) {${staleVersionCode}
|
|
585
|
-
return markFromCache(cached);
|
|
811
|
+
const cfg = determineCacheStrategy(request);
|
|
812
|
+
swLog("fetch", "strategy=" + cfg.strategy, request.url, 0);
|
|
813
|
+
if (cfg.strategy === "reactive") {
|
|
814
|
+
registerReactiveEntry(cacheKey(request), request.url, cfg);
|
|
586
815
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (precached) return markFromCache(precached);
|
|
590
|
-
|
|
591
|
-
const fallback = await fromSpaFallback(request);
|
|
592
|
-
if (fallback) return fallback;
|
|
593
|
-
|
|
594
|
-
const reqForCache = request.clone();
|
|
595
|
-
const response = await _fetch(event, request);
|
|
596
|
-
if (response.ok) {
|
|
597
|
-
const responseToCache = response.clone();
|
|
598
|
-
event.waitUntil(cacheResponse(reqForCache, responseToCache));
|
|
599
|
-
}
|
|
600
|
-
return response;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async function networkFirst(event, request) {
|
|
604
|
-
try {
|
|
605
|
-
const reqForCache = request.clone();
|
|
606
|
-
const response = await _fetch(event, request);
|
|
607
|
-
if (response.ok) {
|
|
608
|
-
const responseToCache = response.clone();
|
|
609
|
-
event.waitUntil(cacheResponse(reqForCache, responseToCache));
|
|
610
|
-
}
|
|
611
|
-
return response;
|
|
612
|
-
} catch {
|
|
613
|
-
const cached = await fromRuntime(request);
|
|
614
|
-
if (cached) {
|
|
615
|
-
return markFromCache(cached);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
const precached = await fromPrecache(request);
|
|
619
|
-
if (precached) return markFromCache(precached);
|
|
620
|
-
|
|
621
|
-
const fallback = await fromSpaFallback(request);
|
|
622
|
-
if (fallback) return fallback;
|
|
623
|
-
|
|
624
|
-
throw new Error("Network request failed and no cached response available");
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
async function staleWhileRevalidate(event, request) {
|
|
629
|
-
const cached = await fromRuntime(request);
|
|
630
|
-
if (cached) {
|
|
631
|
-
event.waitUntil(queueRefresh(cacheKey(request), new URL(request.url).href));
|
|
632
|
-
return markFromCache(cached);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const precached = await fromPrecache(request);
|
|
636
|
-
if (precached) {
|
|
637
|
-
event.waitUntil(queueRefresh(cacheKey(request), new URL(request.url).href));
|
|
638
|
-
return markFromCache(precached);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const reqForCache = request.clone();
|
|
642
|
-
const response = await _fetch(event, request);
|
|
643
|
-
if (response.ok) {
|
|
644
|
-
const responseToCache = response.clone();
|
|
645
|
-
await cacheResponse(reqForCache, responseToCache);
|
|
646
|
-
}
|
|
647
|
-
return response;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async function reactiveStrategy(event, request, staleTime) {
|
|
651
|
-
const cached = await fromRuntime(request);
|
|
652
|
-
// Per-request staleTime header overrides config-level staleTime
|
|
653
|
-
const headerStale = request.headers.get("X-SW-Stale-Time");
|
|
654
|
-
const effectiveStaleTime = headerStale !== null ? Number(headerStale) : staleTime;
|
|
655
|
-
if (cached) {${staleVersionCode}
|
|
656
|
-
if (shouldReactiveRefresh(cached, { staleTime: effectiveStaleTime })) {
|
|
657
|
-
event.waitUntil(queueRefresh(cacheKey(request), new URL(request.url).href));
|
|
658
|
-
}
|
|
659
|
-
return markFromCache(cached);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const precached = await fromPrecache(request);
|
|
663
|
-
if (precached) return markFromCache(precached);
|
|
664
|
-
|
|
665
|
-
const fallback = await fromSpaFallback(request);
|
|
666
|
-
if (fallback) return fallback;
|
|
667
|
-
|
|
668
|
-
const reqForCache = request.clone();
|
|
669
|
-
const response = await _fetch(event, request);
|
|
670
|
-
if (response.ok) {
|
|
671
|
-
const responseToCache = response.clone();
|
|
672
|
-
await cacheResponse(reqForCache, responseToCache);
|
|
673
|
-
}
|
|
674
|
-
return response;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
async function cacheOnly(event, request) {
|
|
678
|
-
const cached = await fromRuntime(request);
|
|
679
|
-
if (cached) {${staleVersionCode}
|
|
680
|
-
return markFromCache(cached);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const precached = await fromPrecache(request);
|
|
684
|
-
if (precached) return markFromCache(precached);
|
|
685
|
-
|
|
686
|
-
return new Response("Not in cache", { status: 404 });
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
async function networkOnly(event, request) {
|
|
690
|
-
return _fetch(event, request);
|
|
691
|
-
}`;
|
|
816
|
+
applyStrategy(event, request, cfg);
|
|
817
|
+
});`;
|
|
692
818
|
}
|
|
693
819
|
//# sourceMappingURL=fetch-handler.js.map
|