@swoff/cli 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -326
- package/bin/swoff +8 -14
- package/dist/__tests__/assemble-sw.test.js +6 -6
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +0 -1
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +9 -5
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +12 -23
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +0 -1
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +50 -2
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/add.js +7 -6
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +36 -9
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/info.js +45 -8
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +6 -2
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/config/validator.js +85 -4
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-fetch.js +3 -3
- package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +114 -5
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +2 -2
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +1 -3
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +5 -17
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +86 -20
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +28 -210
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +152 -0
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -0
- package/dist/lib/generators/file-generators/guide-generator.js +357 -36
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +130 -34
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +126 -0
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -0
- package/dist/lib/generators/file-generators/push.js +143 -0
- package/dist/lib/generators/file-generators/push.js.map +1 -0
- package/dist/lib/generators/file-generators/quick-readme.js +71 -11
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
- package/dist/lib/generators/file-generators/server-push.js +136 -0
- package/dist/lib/generators/file-generators/server-push.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-generator-build.js +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +1 -1
- package/dist/lib/generators/file-generators/sw-push.js +67 -0
- package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-template.js +2 -0
- package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +39 -11
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +8 -9
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +11 -2
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +22 -8
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +1 -1
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/default-template.js +2 -30
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +230 -120
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/install-handler.js +12 -10
- package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +114 -0
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -0
- package/dist/lib/generators/sw-sections/tag-management.js +17 -5
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +16 -10
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +44 -4
- package/dist/lib/shared/config-types.js.map +1 -1
- package/package.json +3 -2
- package/templates/hooks/useAuth.jsx +30 -0
- package/templates/hooks/useAuth.tsx +30 -0
- package/templates/hooks/useBackgroundSync.jsx +31 -0
- package/templates/hooks/useBackgroundSync.tsx +31 -0
- package/templates/hooks/useCacheInvalidation.jsx +19 -0
- package/templates/hooks/useCacheInvalidation.tsx +19 -0
- package/templates/hooks/useCachedFetch.jsx +94 -0
- package/templates/hooks/useCachedFetch.tsx +112 -0
- package/templates/hooks/useMutation.jsx +76 -0
- package/templates/hooks/useMutation.tsx +97 -0
- package/templates/hooks/useMutationQueue.jsx +42 -0
- package/templates/hooks/useMutationQueue.tsx +49 -0
- package/templates/hooks/useMutationState.jsx +27 -0
- package/templates/hooks/useMutationState.tsx +41 -0
- package/templates/hooks/useNetworkStatus.jsx +19 -0
- package/templates/hooks/useNetworkStatus.tsx +19 -0
- package/templates/hooks/usePrefetch.jsx +10 -0
- package/templates/hooks/usePrefetch.tsx +25 -0
- package/templates/hooks/usePushSubscription.jsx +67 -0
- package/templates/hooks/usePushSubscription.tsx +67 -0
- package/templates/hooks/useSWUpdate.jsx +76 -0
- package/templates/hooks/useSWUpdate.tsx +76 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-template.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/default-template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,kBAAkB;IAChC,OAAO
|
|
1
|
+
{"version":3,"file":"default-template.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/default-template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;2BAekB,CAAC;AAC5B,CAAC"}
|
|
@@ -1,211 +1,287 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates the SW fetch event handler with all caching strategies.
|
|
3
|
+
*
|
|
4
|
+
* Strategy dispatch tiers (highest to lowest priority):
|
|
5
|
+
* 1. X-SW-Strategy header — per-request override from fetchWithCache({ strategy })
|
|
6
|
+
* 2. URL pattern match — from features.serviceWorker.strategies in swoff.config.json
|
|
7
|
+
* 3. defaultStrategy — fallback from features.serviceWorker.defaultStrategy
|
|
8
|
+
*
|
|
9
|
+
* Each tier also carries: staleTime, maxCacheEntries, maxCacheAge
|
|
10
|
+
* Per-request values override route values, which override globals.
|
|
11
|
+
*
|
|
12
|
+
* With staleTime enabled, every strategy gains a "fresh" window:
|
|
13
|
+
* - cache-first: returns cached immediately, background refresh if stale
|
|
14
|
+
* - network-first: skips network if fresh, tries network if stale
|
|
15
|
+
* - stale-while-revalidate: skips refresh if fresh, refreshes if stale
|
|
16
|
+
* - cache-only: background refresh if stale (best effort)
|
|
17
|
+
* - network-only: unaffected (never caches)
|
|
3
18
|
*/
|
|
4
19
|
export function generateFetchHandler(swConfig, tagInvalidation) {
|
|
5
|
-
const { defaultStrategy, strategies, maxCacheEntries, maxCacheAge, navigationMode, spaEntry } = swConfig;
|
|
6
|
-
const hasTrim = (maxCacheEntries ?? 0) > 0 || (maxCacheAge ?? 0) > 0;
|
|
20
|
+
const { defaultStrategy, strategies, cacheStrategy = "all", staleTime, maxCacheEntries, maxCacheAge, navigationPreload, navigationMode, spaEntry } = swConfig;
|
|
7
21
|
const navMode = navigationMode ?? "spa";
|
|
8
22
|
const spaPath = spaEntry ?? "/index.html";
|
|
9
|
-
const navCode = navMode === "spa" ? `
|
|
10
|
-
if (request.mode === "navigate") {
|
|
11
|
-
const precache = await caches.open(CACHE_NAME);
|
|
12
|
-
const entry = await precache.match("${spaPath}");
|
|
13
|
-
if (entry) return entry;
|
|
14
|
-
}` : "";
|
|
15
|
-
const tagInvalidationCode = tagInvalidation ? `
|
|
16
|
-
const tagsHeader = event.request.headers.get("X-SW-Cache-Tags");
|
|
17
|
-
if (tagsHeader) {
|
|
18
|
-
const url = new URL(event.request.url).href;
|
|
19
|
-
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
20
|
-
await cacheTagUrl(url, tags);
|
|
21
|
-
}` : "";
|
|
22
23
|
const staleVersionCode = tagInvalidation ? `
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
cleanStaleVersions();
|
|
25
|
+
if (staleVersions.has(cacheKey(request))) {
|
|
26
|
+
event.waitUntil(refreshCache(request).then(() => staleVersions.delete(cacheKey(request))));
|
|
25
27
|
}` : "";
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const cache = await caches.open(cacheName);
|
|
40
|
-
|
|
41
|
-
if (MAX_CACHE_ENTRIES > 0) {
|
|
42
|
-
const keys = await cache.keys();
|
|
43
|
-
if (keys.length >= MAX_CACHE_ENTRIES) {
|
|
44
|
-
const toDelete = keys.slice(0, keys.length - MAX_CACHE_ENTRIES + 1);
|
|
45
|
-
await Promise.all(toDelete.map((key) => cache.delete(key)));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
28
|
+
const tagCode = tagInvalidation ? `
|
|
29
|
+
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
30
|
+
if (tagsHeader) {
|
|
31
|
+
const cacheKeyUrl = cacheKey(request);
|
|
32
|
+
const actualUrl = new URL(request.url).href;
|
|
33
|
+
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
34
|
+
await cacheTagUrl(cacheKeyUrl, actualUrl, tags);
|
|
35
|
+
}` : "";
|
|
36
|
+
const trimDecl = `const GLOBAL_MAX_ENTRIES = ${maxCacheEntries ?? 0};
|
|
37
|
+
const GLOBAL_MAX_AGE = ${maxCacheAge ?? 0};
|
|
38
|
+
const GLOBAL_STALE_TIME = ${staleTime ?? 0};`;
|
|
39
|
+
return `${trimDecl}
|
|
40
|
+
// --- Cache Key ---
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const response = await cache.match(request);
|
|
54
|
-
const dateHeader = response?.headers.get("date");
|
|
55
|
-
if (dateHeader) {
|
|
56
|
-
const age = now - new Date(dateHeader).getTime();
|
|
57
|
-
if (age > MAX_CACHE_AGE) {
|
|
58
|
-
await cache.delete(request);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
42
|
+
function cacheKey(request) {
|
|
43
|
+
const key = request.headers.get("X-SW-Cache-Key");
|
|
44
|
+
if (key) return new URL("/__swc/" + key, self.location.origin).href;
|
|
45
|
+
return new URL(request.url).href;
|
|
63
46
|
}
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
|
|
48
|
+
// --- Cache Helpers ---
|
|
49
|
+
|
|
66
50
|
async function fromPrecache(request) {
|
|
67
51
|
const cache = await caches.open(CACHE_NAME);
|
|
68
|
-
return cache.match(request);
|
|
52
|
+
return cache.match(new URL(request.url).pathname);
|
|
69
53
|
}
|
|
70
54
|
|
|
71
|
-
function
|
|
55
|
+
async function fromRuntime(request) {
|
|
56
|
+
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
57
|
+
return cache.match(cacheKey(request));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function storeRuntime(request, response) {
|
|
61
|
+
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
72
62
|
const headers = new Headers(response.headers);
|
|
73
|
-
headers.set("X-SW-
|
|
74
|
-
|
|
63
|
+
headers.set("X-SW-Cached-At", String(Date.now()));
|
|
64
|
+
const cloned = new Response(response.body, {
|
|
75
65
|
status: response.status,
|
|
76
66
|
statusText: response.statusText,
|
|
77
67
|
headers,
|
|
78
68
|
});
|
|
69
|
+
await cache.put(cacheKey(request), cloned);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
|
-
function
|
|
82
|
-
|
|
72
|
+
async function cacheResponse(request, response) {
|
|
73
|
+
await storeRuntime(request, response);${tagCode}
|
|
83
74
|
}
|
|
84
75
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
async function fromSpaFallback(request) {
|
|
77
|
+
if (request.mode === "navigate") {
|
|
78
|
+
const cache = await caches.open(CACHE_NAME);
|
|
79
|
+
return cache.match("${spaPath}");
|
|
89
80
|
}
|
|
90
|
-
return defaultStrategy;
|
|
91
81
|
}
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
if (!isReadRequest(event.request)) return;
|
|
83
|
+
// --- Response Helpers ---
|
|
95
84
|
|
|
96
|
-
|
|
85
|
+
function markFromCache(response) {
|
|
86
|
+
const headers = new Headers(response.headers);
|
|
87
|
+
headers.set("X-SW-From-Cache", "true");
|
|
88
|
+
return new Response(response.body, {
|
|
89
|
+
status: response.status,
|
|
90
|
+
statusText: response.statusText,
|
|
91
|
+
headers,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
function isStale(response, staleTimeSeconds) {
|
|
96
|
+
if (!staleTimeSeconds || staleTimeSeconds <= 0) return false;
|
|
97
|
+
const cachedAt = response.headers.get("X-SW-Cached-At");
|
|
98
|
+
if (!cachedAt) return false;
|
|
99
|
+
return Date.now() - Number(cachedAt) > staleTimeSeconds * 1000;
|
|
100
|
+
}
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
102
|
+
// --- Strategy Selection (3-tier config resolution) ---
|
|
103
|
+
|
|
104
|
+
function resolveStrategyEntry(entry) {
|
|
105
|
+
return typeof entry === "string" ? { strategy: entry } : entry;
|
|
106
|
+
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
function determineCacheStrategy(request, customStrategies, globalDefaults) {
|
|
109
|
+
const override = request.headers.get("X-SW-Strategy");
|
|
110
|
+
if (override) {
|
|
111
|
+
return {
|
|
112
|
+
strategy: override,
|
|
113
|
+
staleTime: Number(request.headers.get("X-SW-Stale-Time")) || globalDefaults.staleTime,
|
|
114
|
+
maxCacheEntries: Number(request.headers.get("X-SW-Max-Entries")) || globalDefaults.maxCacheEntries,
|
|
115
|
+
maxCacheAge: Number(request.headers.get("X-SW-Max-Age")) || globalDefaults.maxCacheAge,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const path = new URL(request.url).pathname;
|
|
119
|
+
for (const [pattern, entry] of Object.entries(customStrategies)) {
|
|
120
|
+
if (path.startsWith(pattern.replace("*", ""))) {
|
|
121
|
+
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
|
+
};
|
|
128
|
+
}
|
|
111
129
|
}
|
|
130
|
+
return {
|
|
131
|
+
strategy: globalDefaults.defaultStrategy,
|
|
132
|
+
staleTime: globalDefaults.staleTime,
|
|
133
|
+
maxCacheEntries: globalDefaults.maxCacheEntries,
|
|
134
|
+
maxCacheAge: globalDefaults.maxCacheAge,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
112
137
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
function applyStrategy(event, request, config) {
|
|
139
|
+
const { strategy, staleTime, maxCacheEntries, maxCacheAge } = config;
|
|
140
|
+
if (strategy === "stale-while-revalidate") {
|
|
141
|
+
event.respondWith(staleWhileRevalidate(event, request, staleTime, maxCacheEntries, maxCacheAge));
|
|
142
|
+
} else if (strategy === "network-first") {
|
|
143
|
+
event.respondWith(networkFirst(event, request, staleTime, maxCacheEntries, maxCacheAge));
|
|
144
|
+
} else if (strategy === "cache-only") {
|
|
145
|
+
event.respondWith(cacheOnly(event, request, staleTime, maxCacheEntries, maxCacheAge));
|
|
146
|
+
} else if (strategy === "network-only") {
|
|
147
|
+
event.respondWith(networkOnly(event, request));
|
|
148
|
+
} else {
|
|
149
|
+
event.respondWith(cacheFirst(event, request, staleTime, maxCacheEntries, maxCacheAge));
|
|
116
150
|
}
|
|
151
|
+
}
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
self.addEventListener("fetch", (event) => {
|
|
154
|
+
const { request } = event;
|
|
155
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
156
|
+
if (!request.headers.get("X-SW-Cache-Key")) return;
|
|
157
|
+
}
|
|
158
|
+
${cacheStrategy === "explicit-only" ? `if (!request.headers.get("X-SW-Cache-Strategy")) return;` : ""}
|
|
159
|
+
applyStrategy(event, request, determineCacheStrategy(event.request, ${JSON.stringify(strategies)}, { defaultStrategy: "${defaultStrategy}", staleTime: GLOBAL_STALE_TIME, maxCacheEntries: GLOBAL_MAX_ENTRIES, maxCacheAge: GLOBAL_MAX_AGE }));
|
|
120
160
|
});
|
|
121
161
|
|
|
122
|
-
|
|
123
|
-
|
|
162
|
+
// --- Strategies ---
|
|
163
|
+
|
|
164
|
+
${navigationPreload ? `
|
|
165
|
+
async function fetchWithPreload(event, request) {
|
|
166
|
+
try {
|
|
167
|
+
const preload = await event.preloadResponse;
|
|
168
|
+
if (preload) return preload;
|
|
169
|
+
} catch {}
|
|
170
|
+
return fetch(request);
|
|
171
|
+
}
|
|
172
|
+
` : ""}const _fetch = ${navigationPreload ? "fetchWithPreload" : `(event, request) => fetch(request)`};
|
|
124
173
|
|
|
125
|
-
|
|
174
|
+
${generateTrimCode(maxCacheEntries, maxCacheAge)}
|
|
175
|
+
function _trim(cacheName, maxEntries, maxAge) {
|
|
176
|
+
${maxCacheEntries || maxCacheAge ? ` trimRuntimeCache(cacheName, maxEntries, maxAge);` : ""}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function cacheFirst(event, request, staleTime, maxEntries, maxAge) {
|
|
180
|
+
const cached = await fromRuntime(request);
|
|
126
181
|
if (cached) {${staleVersionCode}
|
|
182
|
+
if (isStale(cached, staleTime)) {
|
|
183
|
+
event.waitUntil(refreshCache(request));
|
|
184
|
+
}
|
|
127
185
|
return markFromCache(cached);
|
|
128
186
|
}
|
|
129
187
|
|
|
130
188
|
const precached = await fromPrecache(request);
|
|
131
189
|
if (precached) return markFromCache(precached);
|
|
132
|
-
|
|
133
|
-
const
|
|
190
|
+
|
|
191
|
+
const fallback = await fromSpaFallback(request);
|
|
192
|
+
if (fallback) return fallback;
|
|
193
|
+
|
|
194
|
+
const response = await _fetch(event, request);
|
|
134
195
|
if (response.ok) {
|
|
135
|
-
const cloned = response.clone();
|
|
136
196
|
event.waitUntil(
|
|
137
197
|
(async () => {
|
|
138
|
-
await
|
|
139
|
-
|
|
198
|
+
await cacheResponse(request, response);
|
|
199
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
200
|
+
})(),
|
|
140
201
|
);
|
|
141
202
|
}
|
|
142
203
|
return response;
|
|
143
204
|
}
|
|
144
205
|
|
|
145
|
-
async function networkFirst(event, request) {
|
|
146
|
-
|
|
206
|
+
async function networkFirst(event, request, staleTime, maxEntries, maxAge) {
|
|
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
|
+
}
|
|
147
214
|
|
|
148
215
|
try {
|
|
149
|
-
const response = await
|
|
216
|
+
const response = await _fetch(event, request);
|
|
150
217
|
if (response.ok) {
|
|
151
|
-
const cloned = response.clone();
|
|
152
218
|
event.waitUntil(
|
|
153
219
|
(async () => {
|
|
154
|
-
await
|
|
155
|
-
|
|
220
|
+
await cacheResponse(request, response);
|
|
221
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
222
|
+
})(),
|
|
156
223
|
);
|
|
157
224
|
}
|
|
158
225
|
return response;
|
|
159
226
|
} catch {
|
|
160
|
-
const cached = await
|
|
227
|
+
const cached = await fromRuntime(request);
|
|
161
228
|
if (cached) return cached;
|
|
162
229
|
|
|
163
230
|
const precached = await fromPrecache(request);
|
|
164
231
|
if (precached) return precached;
|
|
165
|
-
|
|
232
|
+
|
|
233
|
+
const fallback = await fromSpaFallback(request);
|
|
234
|
+
if (fallback) return fallback;
|
|
235
|
+
|
|
166
236
|
throw new Error("Request failed and no cached response available");
|
|
167
237
|
}
|
|
168
238
|
}
|
|
169
239
|
|
|
170
|
-
async function staleWhileRevalidate(event, request) {
|
|
171
|
-
const
|
|
172
|
-
const cached = await runtimeCache.match(request);
|
|
173
|
-
|
|
240
|
+
async function staleWhileRevalidate(event, request, staleTime, maxEntries, maxAge) {
|
|
241
|
+
const cached = await fromRuntime(request);
|
|
174
242
|
if (cached) {
|
|
175
|
-
|
|
243
|
+
// Skip background refresh if still fresh
|
|
244
|
+
if (!isStale(cached, staleTime)) {
|
|
245
|
+
return markFromCache(cached);
|
|
246
|
+
}
|
|
247
|
+
event.waitUntil(refreshCache(request));
|
|
176
248
|
return markFromCache(cached);
|
|
177
249
|
}
|
|
178
250
|
|
|
179
251
|
const precached = await fromPrecache(request);
|
|
180
252
|
if (precached) {
|
|
181
|
-
event.waitUntil(refreshCache(
|
|
253
|
+
event.waitUntil(refreshCache(request));
|
|
182
254
|
return markFromCache(precached);
|
|
183
255
|
}
|
|
184
256
|
|
|
185
|
-
const response = await
|
|
257
|
+
const response = await _fetch(event, request);
|
|
186
258
|
if (response.ok) {
|
|
187
|
-
await
|
|
188
|
-
|
|
259
|
+
await cacheResponse(request, response);
|
|
260
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
261
|
+
}
|
|
189
262
|
return response;
|
|
190
263
|
}
|
|
191
264
|
|
|
192
|
-
async function refreshCache(
|
|
265
|
+
async function refreshCache(request) {
|
|
193
266
|
try {
|
|
194
|
-
const
|
|
267
|
+
const key = request.headers.get("X-SW-Cache-Key");
|
|
268
|
+
const fetchRequest = key ? new Request(new URL(request.url).href, { method: request.method, headers: request.headers, body: request.method !== "GET" ? request.body : undefined }) : request;
|
|
269
|
+
const response = await fetch(fetchRequest);
|
|
195
270
|
if (response.ok) {
|
|
196
|
-
await
|
|
271
|
+
await storeRuntime(request, response);
|
|
197
272
|
}
|
|
198
273
|
} catch {
|
|
199
274
|
// Background refresh failed - stale cache remains usable
|
|
200
275
|
}
|
|
201
276
|
}
|
|
202
277
|
|
|
203
|
-
async function cacheOnly(event, request) {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
278
|
+
async function cacheOnly(event, request, staleTime, maxEntries, maxAge) {
|
|
279
|
+
const cached = await fromRuntime(request);
|
|
280
|
+
if (cached) {${staleVersionCode}
|
|
281
|
+
if (isStale(cached, staleTime)) {
|
|
282
|
+
event.waitUntil(refreshCache(request));
|
|
283
|
+
}
|
|
284
|
+
return markFromCache(cached);
|
|
209
285
|
}
|
|
210
286
|
|
|
211
287
|
const precached = await fromPrecache(request);
|
|
@@ -215,7 +291,41 @@ async function cacheOnly(event, request) {
|
|
|
215
291
|
}
|
|
216
292
|
|
|
217
293
|
async function networkOnly(event, request) {
|
|
218
|
-
return
|
|
294
|
+
return _fetch(event, request);
|
|
219
295
|
}`;
|
|
220
296
|
}
|
|
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
|
+
}
|
|
221
331
|
//# sourceMappingURL=fetch-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/fetch-handler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fetch-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/fetch-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,UAAU,oBAAoB,CAClC,QAA0V,EAC1V,eAAwB;IAExB,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,GAAG,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAE9J,MAAM,OAAO,GAAG,cAAc,IAAI,KAAK,CAAC;IACxC,MAAM,OAAO,GAAG,QAAQ,IAAI,aAAa,CAAC;IAE1C,MAAM,gBAAgB,GAAG,eAAe,CAAC,CAAC,CAAC;;;;MAIvC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEV,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC;;;;;;;IAOhC,CAAC,CAAC,CAAC,EAAE,CAAC;IAER,MAAM,QAAQ,GAAG,8BAA8B,eAAe,IAAI,CAAC;yBAC5C,WAAW,IAAI,CAAC;4BACb,SAAS,IAAI,CAAC,GAAG,CAAC;IAE5C,OAAO,GAAG,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0CAkCsB,OAAO;;;;;;0BAMvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA+E7B,aAAa,KAAK,eAAe,CAAC,CAAC,CAAC,0DAA0D,CAAC,CAAC,CAAC,EAAE;wEAC/B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,yBAAyB,eAAe;;;;;EAKxI,iBAAiB,CAAC,CAAC,CAAC;;;;;;;;CAQrB,CAAC,CAAC,CAAC,EAAE,kBAAkB,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,oCAAoC;;EAEnG,gBAAgB,CAAC,eAAe,EAAE,WAAW,CAAC;;EAE9C,eAAe,IAAI,WAAW,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC,CAAC,EAAE;;;;;iBAK3E,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmGhB,gBAAgB;;;;;;;;;;;;;;;EAe/B,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,eAAmC,EAAE,WAA+B;IAC5F,IAAI,CAAC,eAAe,IAAI,CAAC,WAAW;QAAE,OAAO,EAAE,CAAC;IAEhD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BR,CAAC;AACF,CAAC"}
|
|
@@ -8,24 +8,26 @@ self.addEventListener("install", (event) => {
|
|
|
8
8
|
(async () => {
|
|
9
9
|
const cache = await caches.open(CACHE_NAME);
|
|
10
10
|
let downloaded = 0;
|
|
11
|
+
let attempted = 0;
|
|
11
12
|
for (const asset of ASSETS_TO_CACHE) {
|
|
13
|
+
attempted++;
|
|
12
14
|
try {
|
|
13
15
|
const request = new Request(asset.url, asset.options);
|
|
14
16
|
await cache.add(request);
|
|
15
17
|
downloaded++;
|
|
16
|
-
const percent = Math.round((downloaded / ASSETS_TO_CACHE.length) * 100);
|
|
17
|
-
const clients = await self.clients.matchAll({ includeUncontrolled: true });
|
|
18
|
-
clients.forEach((client) => {
|
|
19
|
-
client.postMessage({
|
|
20
|
-
type: "SW_PROGRESS",
|
|
21
|
-
percent,
|
|
22
|
-
downloaded,
|
|
23
|
-
total: ASSETS_TO_CACHE.length,
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
18
|
} catch (err) {
|
|
27
19
|
console.error(\`Failed to cache \${asset.url}:\`, err);
|
|
28
20
|
}
|
|
21
|
+
const percent = Math.round((attempted / ASSETS_TO_CACHE.length) * 100);
|
|
22
|
+
const clients = await self.clients.matchAll({ includeUncontrolled: true });
|
|
23
|
+
clients.forEach((client) => {
|
|
24
|
+
client.postMessage({
|
|
25
|
+
type: "SW_PROGRESS",
|
|
26
|
+
percent,
|
|
27
|
+
downloaded,
|
|
28
|
+
total: ASSETS_TO_CACHE.length,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
32
|
if (AUTO_SKIP_WAITING) self.skipWaiting();
|
|
31
33
|
})(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/install-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,sBAAsB;IACpC,OAAO
|
|
1
|
+
{"version":3,"file":"install-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/install-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8BL,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the SW push event connection handler.
|
|
3
|
+
* Opens an SSE (fetch+stream) or WebSocket connection from the SW,
|
|
4
|
+
* listens for "invalidate" events, and calls invalidateByTag().
|
|
5
|
+
*/
|
|
6
|
+
export function generateServerPushHandler(type, endpoint, reconnectDelayMs) {
|
|
7
|
+
if (type === "websocket") {
|
|
8
|
+
return `
|
|
9
|
+
// --- Server Push Events (WebSocket) ---
|
|
10
|
+
|
|
11
|
+
let pushWs = null;
|
|
12
|
+
let pushReconnectTimer = null;
|
|
13
|
+
|
|
14
|
+
async function connectPushEvents() {
|
|
15
|
+
try {
|
|
16
|
+
pushWs = new WebSocket("${endpoint}");
|
|
17
|
+
pushWs.onmessage = (event) => {
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(event.data);
|
|
20
|
+
if (data.type === "invalidate" && data.tags) {
|
|
21
|
+
data.tags.forEach((tag) => invalidateByTag(tag));
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
};
|
|
25
|
+
pushWs.onclose = () => {
|
|
26
|
+
pushWs = null;
|
|
27
|
+
scheduleReconnect();
|
|
28
|
+
};
|
|
29
|
+
pushWs.onerror = () => {
|
|
30
|
+
pushWs?.close();
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
scheduleReconnect();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function scheduleReconnect() {
|
|
38
|
+
if (pushReconnectTimer) clearTimeout(pushReconnectTimer);
|
|
39
|
+
pushReconnectTimer = setTimeout(connectPushEvents, ${reconnectDelayMs});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
self.addEventListener("activate", (event) => {
|
|
43
|
+
event.waitUntil(connectPushEvents());
|
|
44
|
+
});
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
// SSE: fetch + ReadableStream reader
|
|
48
|
+
return `
|
|
49
|
+
// --- Server Push Events (SSE) ---
|
|
50
|
+
|
|
51
|
+
let pushReconnectTimer = null;
|
|
52
|
+
let pushAbortController = null;
|
|
53
|
+
|
|
54
|
+
async function connectPushEvents() {
|
|
55
|
+
try {
|
|
56
|
+
pushAbortController = new AbortController();
|
|
57
|
+
const response = await fetch("${endpoint}", {
|
|
58
|
+
headers: { Accept: "text/event-stream" },
|
|
59
|
+
signal: pushAbortController.signal,
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok || !response.body) {
|
|
62
|
+
scheduleReconnect();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const reader = response.body.getReader();
|
|
66
|
+
const decoder = new TextDecoder();
|
|
67
|
+
let buffer = "";
|
|
68
|
+
let eventType = "";
|
|
69
|
+
let dataStr = "";
|
|
70
|
+
|
|
71
|
+
while (true) {
|
|
72
|
+
const { done, value } = await reader.read();
|
|
73
|
+
if (done) break;
|
|
74
|
+
buffer += decoder.decode(value, { stream: true });
|
|
75
|
+
|
|
76
|
+
const lines = buffer.split("\\n");
|
|
77
|
+
buffer = lines.pop() || "";
|
|
78
|
+
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (line.startsWith("event: ")) {
|
|
81
|
+
eventType = line.slice(7).trim();
|
|
82
|
+
} else if (line.startsWith("data: ")) {
|
|
83
|
+
dataStr = line.slice(6);
|
|
84
|
+
} else if (line === "") {
|
|
85
|
+
if (eventType === "invalidate" && dataStr) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(dataStr);
|
|
88
|
+
if (parsed.tags) {
|
|
89
|
+
parsed.tags.forEach((tag) => invalidateByTag(tag));
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
eventType = "";
|
|
94
|
+
dataStr = "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Connection lost or aborted
|
|
100
|
+
}
|
|
101
|
+
scheduleReconnect();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function scheduleReconnect() {
|
|
105
|
+
if (pushReconnectTimer) clearTimeout(pushReconnectTimer);
|
|
106
|
+
pushReconnectTimer = setTimeout(connectPushEvents, ${reconnectDelayMs});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
self.addEventListener("activate", (event) => {
|
|
110
|
+
event.waitUntil(connectPushEvents());
|
|
111
|
+
});
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=server-push-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-push-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/server-push-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,yBAAyB,CACvC,IAAyB,EACzB,QAAgB,EAChB,gBAAwB;IAExB,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO;;;;;;;;8BAQmB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;uDAuBiB,gBAAgB;;;;;;CAMtE,CAAC;IACA,CAAC;IAED,qCAAqC;IACrC,OAAO;;;;;;;;;oCAS2B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uDAiDW,gBAAgB;;;;;;CAMtE,CAAC;AACF,CAAC"}
|