@swoff/cli 0.3.8 → 0.3.10
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 +47 -33
- package/dist/__tests__/assemble-sw.test.js +14 -53
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +119 -11
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +44 -17
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +3 -3
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/glob-matcher.test.js +86 -0
- package/dist/__tests__/glob-matcher.test.js.map +1 -0
- package/dist/__tests__/invalidation-tags.test.js +207 -23
- package/dist/__tests__/invalidation-tags.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +16 -12
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +140 -100
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +39 -34
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +12 -12
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +84 -36
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/assets.js +37 -0
- package/dist/lib/commands/assets.js.map +1 -0
- package/dist/lib/commands/clean.js +32 -30
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-assets.js +44 -0
- package/dist/lib/commands/generate-assets.js.map +1 -0
- package/dist/lib/commands/generate-guide.js +22 -29
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +17 -24
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +41 -48
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +27 -13
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +4 -4
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +25 -23
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/validator.js +169 -91
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
- package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
- package/dist/lib/generators/asset-generator/generate.js +111 -0
- package/dist/lib/generators/asset-generator/generate.js.map +1 -0
- package/dist/lib/generators/asset-generator/guide.js +43 -0
- package/dist/lib/generators/asset-generator/guide.js.map +1 -0
- package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
- package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
- package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
- package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
- package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
- package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
- package/dist/lib/generators/asset-generator/maskable.js +15 -0
- package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
- package/dist/lib/generators/asset-generator/rasterize.js +32 -0
- package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
- package/dist/lib/generators/asset-generator/sizes.js +25 -0
- package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
- package/dist/lib/generators/file-generators/api-config.js +6 -0
- package/dist/lib/generators/file-generators/api-config.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-state.js +2 -34
- package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +2 -250
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +3 -81
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +2 -57
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -38
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +2 -91
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-state.js +6 -0
- package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
- package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -267
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
- package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
- package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +3 -145
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +24 -630
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +18 -86
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/manifest.js +15 -5
- package/dist/lib/generators/file-generators/manifest.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +3 -285
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +2 -117
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
- package/dist/lib/generators/file-generators/push.js +6 -139
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/pwa-install.js +11 -84
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +8 -125
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
- package/dist/lib/generators/file-generators/reset.js +6 -0
- package/dist/lib/generators/file-generators/reset.js.map +1 -0
- package/dist/lib/generators/file-generators/server-push.js +3 -124
- package/dist/lib/generators/file-generators/server-push.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +10 -230
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +2 -114
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +10 -20
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +20 -16
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +68 -19
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +2 -2
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +506 -144
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
- package/dist/lib/generators/sw-sections/message-handler.js +55 -15
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/sw-push.js +67 -0
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
- package/dist/lib/generators/sw-sections/tag-management.js +65 -23
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +66 -45
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +182 -71
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/glob-matcher.js +77 -0
- package/dist/lib/shared/glob-matcher.js.map +1 -0
- package/dist/lib/utils/tty-status.js +21 -0
- package/dist/lib/utils/tty-status.js.map +1 -0
- package/dist/runtime/api-config.js +18 -0
- package/dist/runtime/api-config.js.map +1 -0
- package/dist/runtime/auth-state.js +35 -0
- package/dist/runtime/auth-state.js.map +1 -0
- package/dist/runtime/auth-store.js +273 -0
- package/dist/runtime/auth-store.js.map +1 -0
- package/dist/runtime/auth-user.js +168 -0
- package/dist/runtime/auth-user.js.map +1 -0
- package/dist/runtime/background-sync.js +59 -0
- package/dist/runtime/background-sync.js.map +1 -0
- package/dist/runtime/cache.js +30 -0
- package/dist/runtime/cache.js.map +1 -0
- package/dist/runtime/client-injector.js +136 -0
- package/dist/runtime/client-injector.js.map +1 -0
- package/dist/runtime/fetch-state.js +42 -0
- package/dist/runtime/fetch-state.js.map +1 -0
- package/dist/runtime/fetch-wrapper.js +355 -0
- package/dist/runtime/fetch-wrapper.js.map +1 -0
- package/dist/runtime/gql-wrapper.js +135 -0
- package/dist/runtime/gql-wrapper.js.map +1 -0
- package/dist/runtime/invalidation-tags.js +260 -0
- package/dist/runtime/invalidation-tags.js.map +1 -0
- package/dist/runtime/mutation-queue.js +304 -0
- package/dist/runtime/mutation-queue.js.map +1 -0
- package/dist/runtime/mutation-state.js +117 -0
- package/dist/runtime/mutation-state.js.map +1 -0
- package/dist/runtime/push.js +138 -0
- package/dist/runtime/push.js.map +1 -0
- package/dist/runtime/pwa-index.js +11 -0
- package/dist/runtime/pwa-index.js.map +1 -0
- package/dist/runtime/pwa-injector.js +44 -0
- package/dist/runtime/pwa-injector.js.map +1 -0
- package/dist/runtime/pwa-install.js +83 -0
- package/dist/runtime/pwa-install.js.map +1 -0
- package/dist/runtime/pwa-prompt.js +54 -0
- package/dist/runtime/pwa-prompt.js.map +1 -0
- package/dist/runtime/reset.js +148 -0
- package/dist/runtime/reset.js.map +1 -0
- package/dist/runtime/server-push.js +148 -0
- package/dist/runtime/server-push.js.map +1 -0
- package/dist/runtime/sw-injector.js +236 -0
- package/dist/runtime/sw-injector.js.map +1 -0
- package/dist/runtime/type-definitions.js +138 -0
- package/dist/runtime/type-definitions.js.map +1 -0
- package/dist/runtime/utils.js +27 -0
- package/dist/runtime/utils.js.map +1 -0
- package/package.json +3 -1
- package/templates/react/useAuth.jsx +106 -0
- package/templates/react/useAuth.tsx +118 -0
- package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
- package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
- package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
- package/templates/react/useCacheInvalidation.tsx +52 -0
- package/templates/react/useCachedFetch.jsx +140 -0
- package/templates/react/useCachedFetch.tsx +160 -0
- package/templates/react/useIsFetching.jsx +32 -0
- package/templates/react/useIsFetching.tsx +32 -0
- package/templates/react/useMutation.jsx +141 -0
- package/templates/react/useMutation.tsx +180 -0
- package/templates/react/useMutationQueue.jsx +69 -0
- package/templates/react/useMutationQueue.tsx +83 -0
- package/templates/react/useMutationState.jsx +13 -0
- package/templates/react/useMutationState.tsx +25 -0
- package/templates/react/useNetworkStatus.jsx +61 -0
- package/templates/react/useNetworkStatus.tsx +76 -0
- package/templates/react/usePrefetch.jsx +32 -0
- package/templates/react/usePrefetch.tsx +42 -0
- package/templates/{hooks → react}/usePushSubscription.jsx +7 -7
- package/templates/{hooks → react}/usePushSubscription.tsx +7 -7
- package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
- package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
- package/templates/react/useSwoffReset.jsx +32 -0
- package/templates/react/useSwoffReset.tsx +39 -0
- package/templates/hooks/useAuth.jsx +0 -30
- package/templates/hooks/useAuth.tsx +0 -30
- package/templates/hooks/useCacheInvalidation.tsx +0 -19
- package/templates/hooks/useCachedFetch.jsx +0 -94
- package/templates/hooks/useCachedFetch.tsx +0 -112
- package/templates/hooks/useMutation.jsx +0 -76
- package/templates/hooks/useMutation.tsx +0 -97
- package/templates/hooks/useMutationQueue.jsx +0 -42
- package/templates/hooks/useMutationQueue.tsx +0 -49
- package/templates/hooks/useMutationState.jsx +0 -27
- package/templates/hooks/useMutationState.tsx +0 -41
- package/templates/hooks/useNetworkStatus.jsx +0 -19
- package/templates/hooks/useNetworkStatus.tsx +0 -19
- package/templates/hooks/usePrefetch.jsx +0 -10
- package/templates/hooks/usePrefetch.tsx +0 -25
|
@@ -6,43 +6,337 @@
|
|
|
6
6
|
* 2. URL pattern match — from features.serviceWorker.strategies in swoff.config.json
|
|
7
7
|
* 3. defaultStrategy — fallback from features.serviceWorker.defaultStrategy
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Strategies:
|
|
10
|
+
* - cache-first: Serve cache if exists, fetch if missing. No bg refresh.
|
|
11
|
+
* - network-first: Try network first, fall back to cache on failure.
|
|
12
|
+
* - stale-while-revalidate: Serve cache + always refresh in background.
|
|
13
|
+
* - cache-only: Never fetch. Cache hit or 404.
|
|
14
|
+
* - network-only: Never cache. Always fetch.
|
|
15
|
+
* - reactive: Serve cache + refresh on configurable triggers (interval,
|
|
16
|
+
* onFocus, onReconnect). staleTime gates ALL triggers.
|
|
11
17
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - network-only: unaffected (never caches)
|
|
18
|
+
* staleTime, refetchInterval, refetchOnFocus, refetchOnReconnect are
|
|
19
|
+
* reactive-only fields. Non-reactive strategies ignore them.
|
|
20
|
+
*
|
|
21
|
+
* Background refreshes use a shared batch queue with rate limiting.
|
|
22
|
+
* On successful refresh, CACHE_UPDATED is posted to all clients.
|
|
18
23
|
*/
|
|
19
|
-
export function generateFetchHandler(swConfig, tagInvalidation) {
|
|
20
|
-
const { defaultStrategy, strategies, cacheStrategy = "all",
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
+
export function generateFetchHandler(swConfig, tagInvalidation, mutationQueueEnabled) {
|
|
25
|
+
const { strategy: { default: defaultStrategy, patterns: strategies, mode: cacheStrategy = "all", normalizeKey: normalizeCacheKey, ignoreQueryParams, }, navigation: { mode: navMode = "spa", preload: navigationPreload, fallback: spaPath = "/index.html", }, refetchQueue: { batchSize: refetchBatchSize = 5, batchDelayMs: refetchBatchDelayMs = 1000, maxRetries: refetchMaxRetries = 3, retryDelayMs: refetchRetryDelayMs = 1000, }, } = swConfig;
|
|
26
|
+
const { staleTime: globalStaleTime, refetchInterval: globalRefetchInterval, refetchOnReconnect: globalRefetchOnReconnect, refetchOnFocus: globalRefetchOnFocus, } = swConfig.strategy.reactive?.defaults || {};
|
|
27
|
+
const staleVersionCode = tagInvalidation
|
|
28
|
+
? `
|
|
24
29
|
cleanStaleVersions();
|
|
25
30
|
if (staleVersions.has(cacheKey(request))) {
|
|
26
|
-
|
|
27
|
-
}`
|
|
28
|
-
|
|
31
|
+
queueRefresh(cacheKey(request), new URL(request.url).href);
|
|
32
|
+
}`
|
|
33
|
+
: "";
|
|
34
|
+
const tagCode = tagInvalidation
|
|
35
|
+
? `
|
|
29
36
|
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
30
37
|
if (tagsHeader) {
|
|
31
38
|
const cacheKeyUrl = cacheKey(request);
|
|
32
39
|
const actualUrl = new URL(request.url).href;
|
|
33
40
|
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
34
41
|
await cacheTagUrl(cacheKeyUrl, actualUrl, tags);
|
|
35
|
-
}`
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
42
|
+
}`
|
|
43
|
+
: "";
|
|
44
|
+
const reactGlobalDefaults = {
|
|
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" +
|
|
70
|
+
"// --- Glob Pattern Matching ---\n" +
|
|
71
|
+
"\n" +
|
|
72
|
+
"function escapeGlobMeta(s) {\n" +
|
|
73
|
+
' return s.replace(/[.+^${}()|[\\]\\\\]/g, "\\\\$$&");\n' +
|
|
74
|
+
"}\n" +
|
|
75
|
+
"\n" +
|
|
76
|
+
"function globPartRe(part) {\n" +
|
|
77
|
+
' for (var o = "", i = 0; i < part.length; i++) {\n' +
|
|
78
|
+
" var c = part[i];\n" +
|
|
79
|
+
' if (c === "*") { o += "[^/]*"; }\n' +
|
|
80
|
+
' else if (c === "?") { o += "[^/]"; }\n' +
|
|
81
|
+
' else if (c === "{") {\n' +
|
|
82
|
+
' var cl = part.indexOf("}", i);\n' +
|
|
83
|
+
' if (cl === -1) { o += "\\\\" + c; }\n' +
|
|
84
|
+
" else {\n" +
|
|
85
|
+
' o += "(?:" + part.slice(i + 1, cl).split(",").map(function(s) { return escapeGlobMeta(s.trim()); }).join("|") + ")";\n' +
|
|
86
|
+
" i = cl;\n" +
|
|
87
|
+
" }\n" +
|
|
88
|
+
' } else { o += "\\\\" + c; }\n' +
|
|
89
|
+
" }\n" +
|
|
90
|
+
" return o;\n" +
|
|
91
|
+
"}\n" +
|
|
92
|
+
"\n" +
|
|
93
|
+
"function matchGlob(path, pattern) {\n" +
|
|
94
|
+
' if (pattern.charAt(0) === "!") return !matchGlob(path, pattern.slice(1));\n' +
|
|
95
|
+
' var parts = pattern.split("/").filter(Boolean);\n' +
|
|
96
|
+
' var pps = path.split("/").filter(Boolean);\n' +
|
|
97
|
+
" var pi = 0, ppi = 0;\n" +
|
|
98
|
+
" while (pi < parts.length && ppi < pps.length) {\n" +
|
|
99
|
+
" var part = parts[pi];\n" +
|
|
100
|
+
' if (part === "**") {\n' +
|
|
101
|
+
" if (pi === parts.length - 1) return true;\n" +
|
|
102
|
+
" var nxt = parts[pi + 1];\n" +
|
|
103
|
+
" var found = -1;\n" +
|
|
104
|
+
" for (var j = ppi; j < pps.length; j++) {\n" +
|
|
105
|
+
' if (nxt.indexOf("*") > -1 || nxt.indexOf("?") > -1 || nxt.indexOf("{") > -1) {\n' +
|
|
106
|
+
' if (new RegExp("^" + globPartRe(nxt) + "$").test(pps[j])) { found = j; break; }\n' +
|
|
107
|
+
" } else if (nxt === pps[j]) { found = j; break; }\n" +
|
|
108
|
+
" }\n" +
|
|
109
|
+
" if (found === -1) return false;\n" +
|
|
110
|
+
" ppi = found;\n" +
|
|
111
|
+
" pi++;\n" +
|
|
112
|
+
" continue;\n" +
|
|
113
|
+
" }\n" +
|
|
114
|
+
' if (part.indexOf("*") > -1 || part.indexOf("?") > -1 || part.indexOf("{") > -1) {\n' +
|
|
115
|
+
' if (!new RegExp("^" + globPartRe(part) + "$").test(pps[ppi])) return false;\n' +
|
|
116
|
+
" } else if (part !== pps[ppi]) return false;\n" +
|
|
117
|
+
" pi++;\n" +
|
|
118
|
+
" ppi++;\n" +
|
|
119
|
+
" }\n" +
|
|
120
|
+
" return pi === parts.length && ppi === pps.length;\n" +
|
|
121
|
+
"}\n" +
|
|
122
|
+
"\n";
|
|
39
123
|
return `${trimDecl}
|
|
124
|
+
// --- Batch Refresh Queue ---
|
|
125
|
+
|
|
126
|
+
const _refreshQueue = new Map();
|
|
127
|
+
let _refreshQueuePromise = null;
|
|
128
|
+
|
|
129
|
+
function queueRefresh(cacheKeyUrl, actualUrl${tagInvalidation ? ", tags" : ""}${tagInvalidation ? ", method, body, contentType" : ""}) {
|
|
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
|
|
190
|
+
? `
|
|
191
|
+
if (entry.tags && typeof cacheTagUrl !== "undefined") {
|
|
192
|
+
await cacheTagUrl(entry.cacheKey, fetchUrl, entry.tags, entry.method, entry.body, entry.contentType);
|
|
193
|
+
}`
|
|
194
|
+
: ""}
|
|
195
|
+
// Clean up stale version tracking on successful refresh
|
|
196
|
+
if (typeof staleVersions !== "undefined" && staleVersions.has(entry.cacheKey)) {
|
|
197
|
+
staleVersions.delete(entry.cacheKey);
|
|
198
|
+
}
|
|
199
|
+
for (const client of clients) {
|
|
200
|
+
client.postMessage({ type: "CACHE_UPDATED", url: fetchUrl });
|
|
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
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} finally {
|
|
224
|
+
_refreshQueuePromise = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- Reactive Patterns (extracted for interval/focus/reconnect) ---
|
|
229
|
+
|
|
230
|
+
const REACTIVE_PATTERNS = ${JSON.stringify(reactivePatterns)};
|
|
231
|
+
|
|
232
|
+
function findReactiveConfig(url) {
|
|
233
|
+
const path = new URL(url).pathname;
|
|
234
|
+
for (const cfg of REACTIVE_PATTERNS) {
|
|
235
|
+
if (matchGlob(path, cfg.pattern)) return cfg;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
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
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
40
316
|
// --- Cache Key ---
|
|
41
317
|
|
|
42
318
|
function cacheKey(request) {
|
|
43
319
|
const key = request.headers.get("X-SW-Cache-Key");
|
|
44
320
|
if (key) return new URL("/__swc/" + key, self.location.origin).href;
|
|
45
|
-
|
|
321
|
+
const url = new URL(request.url);${normalizeCacheKey
|
|
322
|
+
? `
|
|
323
|
+
// Sort query params alphabetically for consistent cache keys
|
|
324
|
+
if (url.search) {
|
|
325
|
+
const params = new URLSearchParams(url.search);
|
|
326
|
+
params.sort();
|
|
327
|
+
url.search = params.toString();
|
|
328
|
+
}`
|
|
329
|
+
: ""}${ignoreQueryParams && ignoreQueryParams.length > 0
|
|
330
|
+
? `
|
|
331
|
+
// Strip configured query params from cache key
|
|
332
|
+
const ignore = ${JSON.stringify(ignoreQueryParams)};
|
|
333
|
+
if (url.search) {
|
|
334
|
+
const params = new URLSearchParams(url.search);
|
|
335
|
+
for (const key of ignore) params.delete(key);
|
|
336
|
+
url.search = params.toString();
|
|
337
|
+
}`
|
|
338
|
+
: ""}
|
|
339
|
+
return url.href;
|
|
46
340
|
}
|
|
47
341
|
|
|
48
342
|
// --- Cache Helpers ---
|
|
@@ -70,7 +364,23 @@ async function storeRuntime(request, response) {
|
|
|
70
364
|
}
|
|
71
365
|
|
|
72
366
|
async function cacheResponse(request, response) {
|
|
73
|
-
await storeRuntime(request, response)
|
|
367
|
+
await storeRuntime(request, response);
|
|
368
|
+
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
369
|
+
if (tagsHeader) {
|
|
370
|
+
const cacheKeyUrl = cacheKey(request);
|
|
371
|
+
const actualUrl = new URL(request.url).href;
|
|
372
|
+
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
373
|
+
const method = request.method;
|
|
374
|
+
let body = null;
|
|
375
|
+
let contentType = null;
|
|
376
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
377
|
+
contentType = request.headers.get("Content-Type");
|
|
378
|
+
try {
|
|
379
|
+
body = await request.clone().text();
|
|
380
|
+
} catch {}
|
|
381
|
+
}
|
|
382
|
+
await cacheTagUrl(cacheKeyUrl, actualUrl, tags, method, body, contentType);
|
|
383
|
+
}
|
|
74
384
|
}
|
|
75
385
|
|
|
76
386
|
async function fromSpaFallback(request) {
|
|
@@ -93,13 +403,13 @@ function markFromCache(response) {
|
|
|
93
403
|
}
|
|
94
404
|
|
|
95
405
|
function isStale(response, staleTimeSeconds) {
|
|
96
|
-
if (
|
|
406
|
+
if (staleTimeSeconds == null || staleTimeSeconds <= 0) return false;
|
|
97
407
|
const cachedAt = response.headers.get("X-SW-Cached-At");
|
|
98
408
|
if (!cachedAt) return false;
|
|
99
409
|
return Date.now() - Number(cachedAt) > staleTimeSeconds * 1000;
|
|
100
410
|
}
|
|
101
411
|
|
|
102
|
-
// --- Strategy Selection (3-tier config resolution) ---
|
|
412
|
+
${GLOB_CODE}// --- Strategy Selection (3-tier config resolution) ---
|
|
103
413
|
|
|
104
414
|
function resolveStrategyEntry(entry) {
|
|
105
415
|
return typeof entry === "string" ? { strategy: entry } : entry;
|
|
@@ -108,60 +418,157 @@ function resolveStrategyEntry(entry) {
|
|
|
108
418
|
function determineCacheStrategy(request, customStrategies, globalDefaults) {
|
|
109
419
|
const override = request.headers.get("X-SW-Strategy");
|
|
110
420
|
if (override) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
421
|
+
const cfg = { strategy: override };
|
|
422
|
+
const hStale = request.headers.get("X-SW-Stale-Time");
|
|
423
|
+
if (hStale !== null) cfg.staleTime = Number(hStale);
|
|
424
|
+
const hFocus = request.headers.get("X-SW-Refetch-On-Focus");
|
|
425
|
+
if (hFocus !== null) cfg.refetchOnFocus = hFocus !== "false";
|
|
426
|
+
const hReconnect = request.headers.get("X-SW-Refetch-On-Reconnect");
|
|
427
|
+
if (hReconnect !== null) cfg.refetchOnReconnect = hReconnect !== "false";
|
|
428
|
+
return cfg;
|
|
117
429
|
}
|
|
118
430
|
const path = new URL(request.url).pathname;
|
|
119
431
|
for (const [pattern, entry] of Object.entries(customStrategies)) {
|
|
120
|
-
if (path
|
|
432
|
+
if (matchGlob(path, pattern)) {
|
|
121
433
|
const resolved = resolveStrategyEntry(entry);
|
|
122
|
-
return {
|
|
123
|
-
strategy: resolved.strategy,
|
|
124
|
-
staleTime: resolved.staleTime ?? globalDefaults.staleTime,
|
|
125
|
-
maxCacheEntries: resolved.maxCacheEntries ?? globalDefaults.maxCacheEntries,
|
|
126
|
-
maxCacheAge: resolved.maxCacheAge ?? globalDefaults.maxCacheAge,
|
|
127
|
-
};
|
|
434
|
+
return { strategy: resolved.strategy };
|
|
128
435
|
}
|
|
129
436
|
}
|
|
130
|
-
return {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
437
|
+
return { strategy: globalDefaults.defaultStrategy };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function determineCacheStrategyForUrl(url, customStrategies, globalDefaults) {
|
|
441
|
+
const path = new URL(url).pathname;
|
|
442
|
+
for (const [pattern, entry] of Object.entries(customStrategies)) {
|
|
443
|
+
if (matchGlob(path, pattern)) {
|
|
444
|
+
const resolved = resolveStrategyEntry(entry);
|
|
445
|
+
return { strategy: resolved.strategy };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return { strategy: globalDefaults.defaultStrategy };
|
|
136
449
|
}
|
|
137
450
|
|
|
138
451
|
function applyStrategy(event, request, config) {
|
|
139
|
-
const { strategy
|
|
140
|
-
if (strategy === "
|
|
141
|
-
|
|
452
|
+
const { strategy } = config;
|
|
453
|
+
if (strategy === "reactive") {
|
|
454
|
+
const reactiveCfg = findReactiveConfig(new URL(request.url).href);
|
|
455
|
+
const staleTime = config.staleTime !== undefined ? config.staleTime : reactiveCfg?.staleTime;
|
|
456
|
+
event.respondWith(reactiveStrategy(event, request, staleTime));
|
|
457
|
+
} else if (strategy === "stale-while-revalidate") {
|
|
458
|
+
event.respondWith(staleWhileRevalidate(event, request));
|
|
142
459
|
} else if (strategy === "network-first") {
|
|
143
|
-
event.respondWith(networkFirst(event, request
|
|
460
|
+
event.respondWith(networkFirst(event, request));
|
|
144
461
|
} else if (strategy === "cache-only") {
|
|
145
|
-
event.respondWith(cacheOnly(event, request
|
|
462
|
+
event.respondWith(cacheOnly(event, request));
|
|
146
463
|
} else if (strategy === "network-only") {
|
|
147
464
|
event.respondWith(networkOnly(event, request));
|
|
148
465
|
} else {
|
|
149
|
-
event.respondWith(cacheFirst(event, request
|
|
466
|
+
event.respondWith(cacheFirst(event, request));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
${mutationQueueEnabled
|
|
471
|
+
? `// --- Mutation Queue IDB Helpers ---
|
|
472
|
+
|
|
473
|
+
const MUTATION_DB_NAME = "swoff-queue";
|
|
474
|
+
const MUTATION_STORE_NAME = "mutations";
|
|
475
|
+
// Bump this when adding new indexes/stores for schema migration
|
|
476
|
+
const MUTATION_DB_VERSION = 1;
|
|
477
|
+
|
|
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
|
+
async function storeMutationInSW(request) {
|
|
494
|
+
let body, bodyType;
|
|
495
|
+
try {
|
|
496
|
+
body = await request.clone().json();
|
|
497
|
+
bodyType = "json";
|
|
498
|
+
} catch {
|
|
499
|
+
body = await request.clone().text();
|
|
500
|
+
bodyType = "text";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const tagsHeader = request.headers.get("X-SW-Invalidate-Tags");
|
|
504
|
+
const tags = tagsHeader ? tagsHeader.split(",").map(function(t) { return t.trim(); }) : [];
|
|
505
|
+
|
|
506
|
+
const db = await openMutationQueueDB();
|
|
507
|
+
const tx = db.transaction(MUTATION_STORE_NAME, "readwrite");
|
|
508
|
+
tx.objectStore(MUTATION_STORE_NAME).add({
|
|
509
|
+
id: crypto.randomUUID(),
|
|
510
|
+
method: request.method,
|
|
511
|
+
url: new URL(request.url).href,
|
|
512
|
+
body,
|
|
513
|
+
bodyType,
|
|
514
|
+
headers: request.headers.get("Content-Type") ? { "Content-Type": request.headers.get("Content-Type") } : {},
|
|
515
|
+
timestamp: Date.now(),
|
|
516
|
+
retryCount: 0,
|
|
517
|
+
nextRetryAt: 0,
|
|
518
|
+
tags,
|
|
519
|
+
});
|
|
520
|
+
await new Promise(function(resolve, reject) {
|
|
521
|
+
tx.oncomplete = function() { resolve(); };
|
|
522
|
+
tx.onerror = function() { reject(tx.error); };
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function handleMutation(event) {
|
|
527
|
+
const request = event.request;
|
|
528
|
+
if (request.headers.get("X-SW-No-Queue") === "true") {
|
|
529
|
+
return fetch(request.clone());
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
return await fetch(request.clone());
|
|
533
|
+
} catch {
|
|
534
|
+
await storeMutationInSW(request);
|
|
535
|
+
// Notify open clients that a mutation was stored so they can try to process
|
|
536
|
+
const clients = await self.clients.matchAll();
|
|
537
|
+
clients.forEach(function(client) {
|
|
538
|
+
client.postMessage({ type: "MUTATION_STORED" });
|
|
539
|
+
});
|
|
540
|
+
const queuedBody = JSON.stringify({ queued: true });
|
|
541
|
+
return new Response(queuedBody, {
|
|
542
|
+
status: 202,
|
|
543
|
+
statusText: "Accepted",
|
|
544
|
+
headers: { "Content-Type": "application/json", "X-SW-Mutation-Queued": "true" },
|
|
545
|
+
});
|
|
150
546
|
}
|
|
151
547
|
}
|
|
152
548
|
|
|
153
|
-
|
|
154
|
-
|
|
549
|
+
`
|
|
550
|
+
: ""}self.addEventListener("fetch", (event) => {
|
|
551
|
+
const { request } = event;${mutationQueueEnabled
|
|
552
|
+
? `
|
|
155
553
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
554
|
+
if (request.headers.get("X-SW-Cache-Strategy") === "mutation") {
|
|
555
|
+
event.respondWith(handleMutation(event));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
156
558
|
if (!request.headers.get("X-SW-Cache-Key")) return;
|
|
157
|
-
}
|
|
559
|
+
}`
|
|
560
|
+
: `
|
|
561
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
562
|
+
if (!request.headers.get("X-SW-Cache-Key")) return;
|
|
563
|
+
}`}
|
|
158
564
|
${cacheStrategy === "explicit-only" ? `if (!request.headers.get("X-SW-Cache-Strategy")) return;` : ""}
|
|
159
|
-
applyStrategy(event, request, determineCacheStrategy(event.request, ${JSON.stringify(strategies)}, { defaultStrategy: "${defaultStrategy}"
|
|
565
|
+
applyStrategy(event, request, determineCacheStrategy(event.request, ${JSON.stringify(strategies)}, { defaultStrategy: "${defaultStrategy}" }));
|
|
160
566
|
});
|
|
161
567
|
|
|
162
568
|
// --- Strategies ---
|
|
163
569
|
|
|
164
|
-
${navigationPreload
|
|
570
|
+
${navigationPreload
|
|
571
|
+
? `
|
|
165
572
|
async function fetchWithPreload(event, request) {
|
|
166
573
|
try {
|
|
167
574
|
const preload = await event.preloadResponse;
|
|
@@ -169,19 +576,12 @@ async function fetchWithPreload(event, request) {
|
|
|
169
576
|
} catch {}
|
|
170
577
|
return fetch(request);
|
|
171
578
|
}
|
|
172
|
-
`
|
|
173
|
-
|
|
174
|
-
${generateTrimCode(maxCacheEntries, maxCacheAge)}
|
|
175
|
-
function _trim(cacheName, maxEntries, maxAge) {
|
|
176
|
-
${maxCacheEntries || maxCacheAge ? ` trimRuntimeCache(cacheName, maxEntries, maxAge);` : ""}
|
|
177
|
-
}
|
|
579
|
+
`
|
|
580
|
+
: ""}const _fetch = ${navigationPreload ? "fetchWithPreload" : `(event, request) => fetch(request)`};
|
|
178
581
|
|
|
179
|
-
async function cacheFirst(event, request
|
|
582
|
+
async function cacheFirst(event, request) {
|
|
180
583
|
const cached = await fromRuntime(request);
|
|
181
584
|
if (cached) {${staleVersionCode}
|
|
182
|
-
if (isStale(cached, staleTime)) {
|
|
183
|
-
event.waitUntil(refreshCache(request));
|
|
184
|
-
}
|
|
185
585
|
return markFromCache(cached);
|
|
186
586
|
}
|
|
187
587
|
|
|
@@ -191,96 +591,92 @@ async function cacheFirst(event, request, staleTime, maxEntries, maxAge) {
|
|
|
191
591
|
const fallback = await fromSpaFallback(request);
|
|
192
592
|
if (fallback) return fallback;
|
|
193
593
|
|
|
594
|
+
const reqForCache = request.clone();
|
|
194
595
|
const response = await _fetch(event, request);
|
|
195
596
|
if (response.ok) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
await cacheResponse(request, response);
|
|
199
|
-
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
200
|
-
})(),
|
|
201
|
-
);
|
|
597
|
+
const responseToCache = response.clone();
|
|
598
|
+
event.waitUntil(cacheResponse(reqForCache, responseToCache));
|
|
202
599
|
}
|
|
203
600
|
return response;
|
|
204
601
|
}
|
|
205
602
|
|
|
206
|
-
async function networkFirst(event, request
|
|
207
|
-
// If cached and fresh (within staleTime), skip network entirely
|
|
208
|
-
if (staleTime > 0) {
|
|
209
|
-
const cached = await fromRuntime(request);
|
|
210
|
-
if (cached && !isStale(cached, staleTime)) {
|
|
211
|
-
return markFromCache(cached);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
603
|
+
async function networkFirst(event, request) {
|
|
215
604
|
try {
|
|
605
|
+
const reqForCache = request.clone();
|
|
216
606
|
const response = await _fetch(event, request);
|
|
217
607
|
if (response.ok) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await cacheResponse(request, response);
|
|
221
|
-
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
222
|
-
})(),
|
|
223
|
-
);
|
|
608
|
+
const responseToCache = response.clone();
|
|
609
|
+
event.waitUntil(cacheResponse(reqForCache, responseToCache));
|
|
224
610
|
}
|
|
225
611
|
return response;
|
|
226
612
|
} catch {
|
|
227
613
|
const cached = await fromRuntime(request);
|
|
228
|
-
if (cached)
|
|
614
|
+
if (cached) {
|
|
615
|
+
return markFromCache(cached);
|
|
616
|
+
}
|
|
229
617
|
|
|
230
618
|
const precached = await fromPrecache(request);
|
|
231
|
-
if (precached) return precached;
|
|
619
|
+
if (precached) return markFromCache(precached);
|
|
232
620
|
|
|
233
621
|
const fallback = await fromSpaFallback(request);
|
|
234
622
|
if (fallback) return fallback;
|
|
235
623
|
|
|
236
|
-
throw new Error("
|
|
624
|
+
throw new Error("Network request failed and no cached response available");
|
|
237
625
|
}
|
|
238
626
|
}
|
|
239
627
|
|
|
240
|
-
async function staleWhileRevalidate(event, request
|
|
628
|
+
async function staleWhileRevalidate(event, request) {
|
|
241
629
|
const cached = await fromRuntime(request);
|
|
242
630
|
if (cached) {
|
|
243
|
-
|
|
244
|
-
if (!isStale(cached, staleTime)) {
|
|
245
|
-
return markFromCache(cached);
|
|
246
|
-
}
|
|
247
|
-
event.waitUntil(refreshCache(request));
|
|
631
|
+
event.waitUntil(queueRefresh(cacheKey(request), new URL(request.url).href));
|
|
248
632
|
return markFromCache(cached);
|
|
249
633
|
}
|
|
250
634
|
|
|
251
635
|
const precached = await fromPrecache(request);
|
|
252
636
|
if (precached) {
|
|
253
|
-
event.waitUntil(
|
|
637
|
+
event.waitUntil(queueRefresh(cacheKey(request), new URL(request.url).href));
|
|
254
638
|
return markFromCache(precached);
|
|
255
639
|
}
|
|
256
640
|
|
|
641
|
+
const reqForCache = request.clone();
|
|
257
642
|
const response = await _fetch(event, request);
|
|
258
643
|
if (response.ok) {
|
|
259
|
-
|
|
260
|
-
|
|
644
|
+
const responseToCache = response.clone();
|
|
645
|
+
await cacheResponse(reqForCache, responseToCache);
|
|
261
646
|
}
|
|
262
647
|
return response;
|
|
263
648
|
}
|
|
264
649
|
|
|
265
|
-
async function
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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));
|
|
272
658
|
}
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
275
673
|
}
|
|
674
|
+
return response;
|
|
276
675
|
}
|
|
277
676
|
|
|
278
|
-
async function cacheOnly(event, request
|
|
677
|
+
async function cacheOnly(event, request) {
|
|
279
678
|
const cached = await fromRuntime(request);
|
|
280
679
|
if (cached) {${staleVersionCode}
|
|
281
|
-
if (isStale(cached, staleTime)) {
|
|
282
|
-
event.waitUntil(refreshCache(request));
|
|
283
|
-
}
|
|
284
680
|
return markFromCache(cached);
|
|
285
681
|
}
|
|
286
682
|
|
|
@@ -294,38 +690,4 @@ async function networkOnly(event, request) {
|
|
|
294
690
|
return _fetch(event, request);
|
|
295
691
|
}`;
|
|
296
692
|
}
|
|
297
|
-
function generateTrimCode(maxCacheEntries, maxCacheAge) {
|
|
298
|
-
if (!maxCacheEntries && !maxCacheAge)
|
|
299
|
-
return "";
|
|
300
|
-
return `
|
|
301
|
-
async function trimRuntimeCache(cacheName, maxEntries, maxAge) {
|
|
302
|
-
const _maxEntries = maxEntries ?? GLOBAL_MAX_ENTRIES;
|
|
303
|
-
const _maxAge = maxAge ?? GLOBAL_MAX_AGE;
|
|
304
|
-
const cache = await caches.open(cacheName);
|
|
305
|
-
|
|
306
|
-
if (_maxEntries > 0) {
|
|
307
|
-
const keys = await cache.keys();
|
|
308
|
-
if (keys.length >= _maxEntries) {
|
|
309
|
-
const toDelete = keys.slice(0, keys.length - _maxEntries + 1);
|
|
310
|
-
await Promise.all(toDelete.map((key) => cache.delete(key)));
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (_maxAge > 0) {
|
|
315
|
-
const keys = await cache.keys();
|
|
316
|
-
const now = Date.now();
|
|
317
|
-
for (const request of keys) {
|
|
318
|
-
const response = await cache.match(request);
|
|
319
|
-
const dateHeader = response?.headers.get("date");
|
|
320
|
-
if (dateHeader) {
|
|
321
|
-
const age = now - new Date(dateHeader).getTime();
|
|
322
|
-
if (age > _maxAge) {
|
|
323
|
-
await cache.delete(request);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
`;
|
|
330
|
-
}
|
|
331
693
|
//# sourceMappingURL=fetch-handler.js.map
|