@swoff/cli 0.3.5 → 0.3.7
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 +610 -24
- package/bin/swoff +8 -14
- package/dist/__tests__/assemble-sw.test.js +17 -3
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +9 -9
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +10 -9
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +65 -68
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -14
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +41 -22
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +3 -3
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +11 -5
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/clean.js +5 -0
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +43 -13
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +7 -3
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +149 -32
- 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/commands/validate.js +3 -1
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/validator.js +25 -16
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
- package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-state.js +8 -5
- package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +181 -41
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +28 -32
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +9 -5
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +8 -5
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +101 -0
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
- package/dist/lib/generators/file-generators/context.js +7 -6
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +196 -41
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +23 -151
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +450 -0
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +65 -87
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
- package/dist/lib/generators/file-generators/mutation-reconcile.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/pwa-install.js +41 -9
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +88 -0
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
- package/dist/lib/generators/file-generators/reconcile.js +7 -12
- package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
- package/dist/lib/generators/file-generators/store.js +30 -20
- package/dist/lib/generators/file-generators/store.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +38 -17
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +82 -111
- package/dist/lib/generators/file-generators/sw-injector.js.map +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 +3 -1
- package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +45 -22
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +27 -19
- 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 +36 -12
- package/dist/lib/generators/sw-sections/assemble-sw.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 +1 -30
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +176 -90
- 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/tag-management.js +36 -5
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +14 -12
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +26 -9
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/utils/build-script.js +35 -0
- package/dist/lib/utils/build-script.js.map +1 -0
- 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 +55 -0
- package/templates/hooks/useCachedFetch.tsx +69 -0
- package/templates/hooks/useMutationQueue.jsx +34 -0
- package/templates/hooks/useMutationQueue.tsx +34 -0
- package/templates/hooks/useNetworkStatus.jsx +19 -0
- package/templates/hooks/useNetworkStatus.tsx +19 -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
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
* Contains config, paths, and the generated files tracker.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
export function
|
|
8
|
-
if (!existsSync(
|
|
9
|
-
mkdirSync(
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
export function ensureDir(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
10
|
}
|
|
11
11
|
export function writeFile(ctx, filename, code) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const fullPath = join(ctx.swoffDir, filename);
|
|
13
|
+
ensureDir(dirname(fullPath));
|
|
14
|
+
writeFileSync(fullPath, code);
|
|
14
15
|
ctx.generatedFiles.push(`swoff/${filename}`);
|
|
15
16
|
}
|
|
16
17
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAYrC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAqB,EAAE,QAAgB,EAAE,IAAY;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -1,79 +1,234 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates fetch-wrapper.js
|
|
2
|
+
* Generates fetch-wrapper.ts/js — unified fetch with caching, auth, offline queue, and auto-invalidation.
|
|
3
3
|
*/
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateFetchWrapper(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const G = (type) => (ts ? `<${type}>` : "");
|
|
11
|
+
const tagInvalidation = ctx.config.features.tagInvalidation;
|
|
12
|
+
const authEnabled = ctx.config.features.auth.enabled;
|
|
13
|
+
const mutationQueue = ctx.config.features.mutationQueue;
|
|
14
|
+
const importLines = [
|
|
15
|
+
tagInvalidation
|
|
16
|
+
? `import { generateTags } from "./invalidation-tags.${ext}";`
|
|
17
|
+
: "",
|
|
18
|
+
tagInvalidation ? `import { invalidateByTags } from "./cache.${ext}";` : "",
|
|
19
|
+
authEnabled
|
|
20
|
+
? `import { getAuth, clearAuth, withAuthHeaders, isAuthUrl, AUTH_WITH_CREDENTIALS } from "./auth/store.${ext}";`
|
|
21
|
+
: "",
|
|
22
|
+
mutationQueue
|
|
23
|
+
? `import { queueMutation } from "./mutation-queue.${ext}";`
|
|
24
|
+
: "",
|
|
25
|
+
]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join("\n");
|
|
28
|
+
const interfaceBlock = ts
|
|
29
|
+
? `
|
|
30
|
+
export interface FetchWithCacheResult${G("T")} {
|
|
31
|
+
response${T("Response & { json(): Promise<T> }")};
|
|
32
|
+
fromCache${T("boolean")};
|
|
33
|
+
}
|
|
34
|
+
`
|
|
35
|
+
: "";
|
|
36
|
+
const optionsType = ts ? `FetchWithCacheOptions` : `options`;
|
|
37
|
+
const optionsInterface = ts
|
|
38
|
+
? `
|
|
39
|
+
export interface FetchWithCacheOptions extends RequestInit {
|
|
40
|
+
tags?: string[];
|
|
41
|
+
staleWhileRevalidate?: boolean;
|
|
42
|
+
auth?: boolean;
|
|
43
|
+
queueOffline?: boolean;
|
|
44
|
+
invalidate?: 'auto' | string[] | false;
|
|
45
|
+
type?: 'read' | 'mutation';
|
|
46
|
+
strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only';
|
|
47
|
+
}
|
|
48
|
+
`
|
|
49
|
+
: "";
|
|
50
|
+
const authBlock = authEnabled
|
|
51
|
+
? `
|
|
52
|
+
if (options.auth) {
|
|
53
|
+
const auth = await getAuth();
|
|
54
|
+
withAuthHeaders(headers, auth);
|
|
55
|
+
}`
|
|
56
|
+
: "";
|
|
57
|
+
const authUrlsBlock = authEnabled
|
|
58
|
+
? `
|
|
59
|
+
// Auth endpoints bypass SW cache
|
|
60
|
+
if (options.auth && isAuthUrl(url) && !headers.has("X-SW-Cache-Strategy")) {
|
|
61
|
+
headers.set("X-SW-Cache-Strategy", "mutation");
|
|
62
|
+
}`
|
|
63
|
+
: "";
|
|
64
|
+
const authCredentialsBlock = authEnabled
|
|
65
|
+
? `
|
|
66
|
+
if (AUTH_WITH_CREDENTIALS) {
|
|
67
|
+
fetchOptions.credentials = "include";
|
|
68
|
+
}`
|
|
69
|
+
: "";
|
|
70
|
+
const auth401Block = authEnabled
|
|
71
|
+
? `
|
|
72
|
+
if (options.auth && response.status === 401) {
|
|
73
|
+
await clearAuth();
|
|
74
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
75
|
+
}`
|
|
76
|
+
: "";
|
|
77
|
+
const offlineReadBlock = ` if (isRead) {
|
|
78
|
+
const cached = await caches.match(input);
|
|
79
|
+
if (cached) return { response: cached, fromCache: true };
|
|
80
|
+
throw new Error("Offline: no cached data available");
|
|
81
|
+
}`;
|
|
82
|
+
const offlineWriteBlock = mutationQueue
|
|
83
|
+
? ` // Write offline — auto-queue
|
|
84
|
+
if (options.queueOffline !== false) {
|
|
85
|
+
const headerObj: Record<string, string> = {};
|
|
86
|
+
headers.forEach((value, key) => {
|
|
87
|
+
headerObj[key] = value;
|
|
88
|
+
});
|
|
89
|
+
await queueMutation({
|
|
90
|
+
method,
|
|
91
|
+
url,
|
|
92
|
+
body: options.body,
|
|
93
|
+
headers: headerObj,
|
|
94
|
+
tags: options.tags || [],
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
response: new Response(null, { status: 202, statusText: "Queued" }),
|
|
99
|
+
fromCache: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
throw new Error("Offline: write operation failed");`
|
|
103
|
+
: ` throw new Error("Offline: write operation failed");`;
|
|
104
|
+
const autoInvalidateBlock = tagInvalidation
|
|
105
|
+
? `
|
|
106
|
+
// Auto-invalidate after mutation success
|
|
107
|
+
if (!isRead && response.ok) {
|
|
108
|
+
const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
|
|
109
|
+
if (invalidateSetting !== false) {
|
|
110
|
+
const tagsToInvalidate${T("string[]")} = Array.isArray(invalidateSetting)
|
|
111
|
+
? invalidateSetting
|
|
112
|
+
: (options.tags || generateTags(url));
|
|
113
|
+
if (tagsToInvalidate.length > 0) {
|
|
114
|
+
await invalidateByTags(tagsToInvalidate);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}`
|
|
118
|
+
: "";
|
|
119
|
+
const autoTagsBlock = tagInvalidation
|
|
120
|
+
? `
|
|
121
|
+
// Auto-generate tags from URL if not provided
|
|
122
|
+
if (!options.tags && isRead) {
|
|
123
|
+
const urlTags = generateTags(url);
|
|
124
|
+
if (urlTags.length > 0) {
|
|
125
|
+
headers.set("X-SW-Cache-Tags", urlTags.join(","));
|
|
126
|
+
}
|
|
127
|
+
}`
|
|
128
|
+
: "";
|
|
7
129
|
const code = `/**
|
|
8
130
|
* Swoff Fetch Wrapper
|
|
9
|
-
*
|
|
131
|
+
* Unified fetch with caching, auth, offline queue, auto-invalidation, and
|
|
132
|
+
* per-request strategy override.
|
|
10
133
|
*
|
|
11
134
|
* Usage:
|
|
12
135
|
* import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
|
|
13
136
|
*
|
|
14
|
-
* // GET
|
|
15
|
-
* const
|
|
137
|
+
* // GET — cached with auto-generated tags
|
|
138
|
+
* const { response } = await fetchWithCache("/api/todos");
|
|
139
|
+
* const data = await response.json();
|
|
16
140
|
*
|
|
17
|
-
* // POST
|
|
141
|
+
* // POST — mutation with auto-invalidation
|
|
18
142
|
* await fetchWithCache("/api/todos", {
|
|
19
143
|
* method: "POST",
|
|
20
144
|
* body: JSON.stringify({ title: "New task" }),
|
|
21
145
|
* });
|
|
22
146
|
*
|
|
23
|
-
* //
|
|
24
|
-
* const
|
|
147
|
+
* // Authenticated request (works with bearer, cookie, custom)
|
|
148
|
+
* const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
|
|
149
|
+
*
|
|
150
|
+
* // Custom tags + stale-while-revalidate
|
|
151
|
+
* const { response: staleRes, fromCache } = await fetchWithCache("/api/data", {
|
|
25
152
|
* tags: ["data"],
|
|
26
153
|
* staleWhileRevalidate: true,
|
|
27
|
-
* })
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* // Override caching strategy per-request (highest priority)
|
|
157
|
+
* await fetchWithCache("/api/checkout", {
|
|
158
|
+
* method: "POST",
|
|
159
|
+
* type: "read",
|
|
160
|
+
* strategy: "network-only",
|
|
161
|
+
* });
|
|
162
|
+
*
|
|
163
|
+
* // Override method-based caching with explicit type
|
|
164
|
+
* // Use type: "mutation" for POST-based reads (search, GraphQL)
|
|
165
|
+
* await fetchWithCache("/api/search", {
|
|
166
|
+
* method: "POST",
|
|
167
|
+
* type: "read",
|
|
168
|
+
* body: JSON.stringify({ query: "hello" }),
|
|
169
|
+
* });
|
|
170
|
+
*
|
|
171
|
+
* // Offline: auto-queues writes (disable with queueOffline: false)
|
|
172
|
+
* await fetchWithCache("/api/todos", {
|
|
173
|
+
* method: "POST",
|
|
174
|
+
* body: JSON.stringify({ title: "Offline task" }),
|
|
175
|
+
* });
|
|
176
|
+
* // When back online, processMutationQueue() replays them
|
|
28
177
|
*/
|
|
29
178
|
|
|
30
|
-
|
|
179
|
+
${importLines}
|
|
180
|
+
${interfaceBlock}${optionsInterface}
|
|
181
|
+
const inFlightRequests = new Map${G("string, Promise<Response>")}();
|
|
182
|
+
|
|
183
|
+
/** Fetch with caching, auth, offline queue, auto-invalidation, and per-request strategy override. Returns { response, fromCache }. Use { auth: true } for authenticated requests — works with bearer, cookie, and custom auth types. */
|
|
184
|
+
export async function fetchWithCache${G("T")}(input${T("RequestInfo")}, options${T("RequestInit & { tags?: string[]; staleWhileRevalidate?: boolean; auth?: boolean; queueOffline?: boolean; invalidate?: 'auto' | string[] | false; type?: 'read' | 'mutation'; strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only' }")} = {})${R("Promise<FetchWithCacheResult<T>>")}{
|
|
185
|
+
const method = (options.method || "GET").toUpperCase();
|
|
186
|
+
const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD"));
|
|
187
|
+
const url = typeof input === "string" ? input : input.url;
|
|
31
188
|
|
|
32
|
-
export async function fetchWithCache(input, options = {}) {
|
|
33
189
|
const headers = new Headers(options.headers);
|
|
34
|
-
const method = options.method || "GET";
|
|
35
190
|
|
|
191
|
+
// Set cache strategy
|
|
36
192
|
if (!headers.has("X-SW-Cache-Strategy")) {
|
|
37
|
-
headers.set(
|
|
38
|
-
"X-SW-Cache-Strategy",
|
|
39
|
-
method === "GET" || method === "HEAD" ? "read" : "mutation"
|
|
40
|
-
);
|
|
193
|
+
headers.set("X-SW-Cache-Strategy", isRead ? "read" : "mutation");
|
|
41
194
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
headers.set("X-SW-Stale", "true");
|
|
45
|
-
}
|
|
46
|
-
|
|
195
|
+
${autoTagsBlock}
|
|
196
|
+
// Custom tags override auto-generated
|
|
47
197
|
if (options.tags && options.tags.length > 0) {
|
|
48
198
|
headers.set("X-SW-Cache-Tags", options.tags.join(","));
|
|
49
199
|
}
|
|
50
200
|
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
if (inFlightRequests.has(url)) {
|
|
54
|
-
return inFlightRequests.get(url).then((r) => r.clone());
|
|
55
|
-
}
|
|
56
|
-
const promise = fetch(input, { ...options, headers }).finally(() => {
|
|
57
|
-
inFlightRequests.delete(url);
|
|
58
|
-
});
|
|
59
|
-
inFlightRequests.set(url, promise);
|
|
60
|
-
return promise;
|
|
201
|
+
if (options.staleWhileRevalidate && !options.strategy) {
|
|
202
|
+
headers.set("X-SW-Strategy", "stale-while-revalidate");
|
|
61
203
|
}
|
|
204
|
+
if (options.strategy) {
|
|
205
|
+
headers.set("X-SW-Strategy", options.strategy);
|
|
206
|
+
}
|
|
207
|
+
${authBlock}${authUrlsBlock}
|
|
208
|
+
const fetchOptions${T("RequestInit")} = { ...options, headers };
|
|
209
|
+
${authCredentialsBlock}
|
|
62
210
|
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function fetchWithCacheOrQueue(input, options = {}) {
|
|
211
|
+
// Offline handling
|
|
67
212
|
if (!navigator.onLine) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
213
|
+
${offlineReadBlock}
|
|
214
|
+
${offlineWriteBlock}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Deduplicate in-flight GET requests
|
|
218
|
+
let responsePromise${T("Promise<Response>")};
|
|
219
|
+
if (isRead && inFlightRequests.has(url)) {
|
|
220
|
+
responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
|
|
221
|
+
} else {
|
|
222
|
+
responsePromise = fetch(input, fetchOptions);
|
|
223
|
+
if (isRead) {
|
|
224
|
+
inFlightRequests.set(url, responsePromise.finally(() => inFlightRequests.delete(url)));
|
|
74
225
|
}
|
|
75
226
|
}
|
|
76
|
-
|
|
227
|
+
|
|
228
|
+
const response = await responsePromise;
|
|
229
|
+
${autoInvalidateBlock}${auth401Block}
|
|
230
|
+
const fromCache = response.headers.get("X-SW-From-Cache") === "true";
|
|
231
|
+
return { response, fromCache };
|
|
77
232
|
}
|
|
78
233
|
`;
|
|
79
234
|
writeFile(ctx, `fetch-wrapper.${ext}`, code);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "path";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const templatesDir = join(__dirname, "../../../../templates/hooks");
|
|
6
|
+
function copyHook(hooksDir, name, ext) {
|
|
7
|
+
const src = join(templatesDir, `${name}.${ext}x`);
|
|
8
|
+
copyFileSync(src, join(hooksDir, `${name}.${ext}x`));
|
|
9
|
+
}
|
|
3
10
|
export function generateHooks(ctx) {
|
|
4
11
|
const { config, swoffDir, ext, frameworkName } = ctx;
|
|
5
12
|
if (frameworkName !== "react")
|
|
@@ -8,160 +15,25 @@ export function generateHooks(ctx) {
|
|
|
8
15
|
if (!existsSync(hooksDir)) {
|
|
9
16
|
mkdirSync(hooksDir, { recursive: true });
|
|
10
17
|
}
|
|
18
|
+
copyHook(hooksDir, "useNetworkStatus", ext);
|
|
19
|
+
copyHook(hooksDir, "useCachedFetch", ext);
|
|
11
20
|
if (config.features.pwa.enabled) {
|
|
12
|
-
|
|
21
|
+
copyHook(hooksDir, "useSWUpdate", ext);
|
|
13
22
|
}
|
|
14
23
|
if (config.features.auth.enabled) {
|
|
15
|
-
|
|
24
|
+
copyHook(hooksDir, "useAuth", ext);
|
|
16
25
|
}
|
|
17
26
|
if (config.features.mutationQueue) {
|
|
18
|
-
|
|
27
|
+
copyHook(hooksDir, "useMutationQueue", ext);
|
|
28
|
+
}
|
|
29
|
+
if (config.features.pushNotifications?.enabled) {
|
|
30
|
+
copyHook(hooksDir, "usePushSubscription", ext);
|
|
31
|
+
}
|
|
32
|
+
if (config.features.backgroundSync) {
|
|
33
|
+
copyHook(hooksDir, "useBackgroundSync", ext);
|
|
34
|
+
}
|
|
35
|
+
if (config.features.tagInvalidation) {
|
|
36
|
+
copyHook(hooksDir, "useCacheInvalidation", ext);
|
|
19
37
|
}
|
|
20
|
-
}
|
|
21
|
-
function writePWAHooks(dir, ext) {
|
|
22
|
-
writeFileSync(join(dir, `usePWAUpdate.${ext}x`), `import { useState, useEffect, useCallback } from "react";
|
|
23
|
-
|
|
24
|
-
export function usePWAUpdate() {
|
|
25
|
-
const [state, setState] = useState({
|
|
26
|
-
updateStatus: "idle",
|
|
27
|
-
currentVersion: null as string | null,
|
|
28
|
-
availableVersion: null as string | null,
|
|
29
|
-
progress: 0,
|
|
30
|
-
forceUpdate: false,
|
|
31
|
-
error: null as string | null,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
setState((s) => ({ ...s, currentVersion: (window as any).currentSWVersion || null }));
|
|
36
|
-
|
|
37
|
-
const onAvailable = (e: CustomEvent) => setState((s) => ({
|
|
38
|
-
...s,
|
|
39
|
-
updateStatus: "available" as const,
|
|
40
|
-
availableVersion: e.detail.version,
|
|
41
|
-
forceUpdate: (window as any).swUpdateRequired || false,
|
|
42
|
-
}));
|
|
43
|
-
const onProgress = (e: CustomEvent) => setState((s) => ({
|
|
44
|
-
...s,
|
|
45
|
-
updateStatus: "downloading" as const,
|
|
46
|
-
progress: e.detail.percent,
|
|
47
|
-
}));
|
|
48
|
-
const onReady = () => setState((s) => ({ ...s, updateStatus: "idle" as const, progress: 0 }));
|
|
49
|
-
const onError = () => setState((s) => ({ ...s, error: "SW registration failed" }));
|
|
50
|
-
|
|
51
|
-
window.addEventListener("sw-update-available", onAvailable as EventListener);
|
|
52
|
-
window.addEventListener("sw-progress", onProgress as EventListener);
|
|
53
|
-
window.addEventListener("sw-ready", onReady);
|
|
54
|
-
window.addEventListener("sw-error", onError);
|
|
55
|
-
return () => {
|
|
56
|
-
window.removeEventListener("sw-update-available", onAvailable as EventListener);
|
|
57
|
-
window.removeEventListener("sw-progress", onProgress as EventListener);
|
|
58
|
-
window.removeEventListener("sw-ready", onReady);
|
|
59
|
-
window.removeEventListener("sw-error", onError);
|
|
60
|
-
};
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const acceptUpdate = useCallback(async () => {
|
|
64
|
-
if (!state.availableVersion) return;
|
|
65
|
-
const { handleUpdateApproved } = await import("../sw-injector.${ext}");
|
|
66
|
-
await handleUpdateApproved(state.availableVersion);
|
|
67
|
-
}, [state.availableVersion]);
|
|
68
|
-
|
|
69
|
-
const dismissUpdate = useCallback(() => {
|
|
70
|
-
sessionStorage.setItem("sw-dismissed-update", "true");
|
|
71
|
-
setState((s) => ({ ...s, updateStatus: "idle" }));
|
|
72
|
-
}, []);
|
|
73
|
-
|
|
74
|
-
return { ...state, acceptUpdate, dismissUpdate };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function useSWProgress() {
|
|
78
|
-
const [state, setState] = useState({
|
|
79
|
-
status: "idle" as "idle" | "installing",
|
|
80
|
-
progress: 0,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
const onProgress = (e: CustomEvent) => setState({
|
|
85
|
-
status: "installing",
|
|
86
|
-
progress: e.detail.percent,
|
|
87
|
-
});
|
|
88
|
-
const onReady = () => setState({ status: "idle", progress: 0 });
|
|
89
|
-
|
|
90
|
-
window.addEventListener("sw-progress", onProgress as EventListener);
|
|
91
|
-
window.addEventListener("sw-ready", onReady);
|
|
92
|
-
return () => {
|
|
93
|
-
window.removeEventListener("sw-progress", onProgress as EventListener);
|
|
94
|
-
window.removeEventListener("sw-ready", onReady);
|
|
95
|
-
};
|
|
96
|
-
}, []);
|
|
97
|
-
|
|
98
|
-
return state;
|
|
99
|
-
}
|
|
100
|
-
`);
|
|
101
|
-
// useSWProgress is included in the same file
|
|
102
|
-
}
|
|
103
|
-
function writeAuthHook(dir, ext) {
|
|
104
|
-
writeFileSync(join(dir, `useAuth.${ext}x`), `import { useState, useEffect } from "react";
|
|
105
|
-
import { getAuthState } from "../auth-state.${ext}";
|
|
106
|
-
|
|
107
|
-
export function useAuth() {
|
|
108
|
-
const [state, setState] = useState({
|
|
109
|
-
authenticated: false,
|
|
110
|
-
user: null as Record<string, unknown> | null,
|
|
111
|
-
online: navigator.onLine,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
getAuthState().then(setState);
|
|
116
|
-
|
|
117
|
-
const onOnline = () => setState((s) => ({ ...s, online: true }));
|
|
118
|
-
const onOffline = () => setState((s) => ({ ...s, online: false }));
|
|
119
|
-
const onAuthChange = () => getAuthState().then(setState);
|
|
120
|
-
|
|
121
|
-
window.addEventListener("online", onOnline);
|
|
122
|
-
window.addEventListener("offline", onOffline);
|
|
123
|
-
window.addEventListener("sw-auth-state-change", onAuthChange);
|
|
124
|
-
|
|
125
|
-
return () => {
|
|
126
|
-
window.removeEventListener("online", onOnline);
|
|
127
|
-
window.removeEventListener("offline", onOffline);
|
|
128
|
-
window.removeEventListener("sw-auth-state-change", onAuthChange);
|
|
129
|
-
};
|
|
130
|
-
}, []);
|
|
131
|
-
|
|
132
|
-
return state;
|
|
133
|
-
}
|
|
134
|
-
`);
|
|
135
|
-
}
|
|
136
|
-
function writeMutationQueueHook(dir, ext) {
|
|
137
|
-
writeFileSync(join(dir, `useMutationQueue.${ext}x`), `import { useState, useEffect } from "react";
|
|
138
|
-
import { getPendingCount } from "../mutation-queue.${ext}";
|
|
139
|
-
|
|
140
|
-
export function useMutationQueue() {
|
|
141
|
-
const [state, setState] = useState<{
|
|
142
|
-
pending: number;
|
|
143
|
-
lastSync: { succeeded: number; failed: number } | null;
|
|
144
|
-
}>({ pending: 0, lastSync: null });
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
|
|
148
|
-
|
|
149
|
-
const onSync = (e: CustomEvent) => setState({
|
|
150
|
-
pending: 0,
|
|
151
|
-
lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
|
|
152
|
-
});
|
|
153
|
-
const onChange = async () => setState((s) => ({ ...s, pending: await getPendingCount() }));
|
|
154
|
-
|
|
155
|
-
window.addEventListener("mutation-sync-complete", onSync as EventListener);
|
|
156
|
-
window.addEventListener("mutation-queue-changed", onChange);
|
|
157
|
-
return () => {
|
|
158
|
-
window.removeEventListener("mutation-sync-complete", onSync as EventListener);
|
|
159
|
-
window.removeEventListener("mutation-queue-changed", onChange);
|
|
160
|
-
};
|
|
161
|
-
}, []);
|
|
162
|
-
|
|
163
|
-
return state;
|
|
164
|
-
}
|
|
165
|
-
`);
|
|
166
38
|
}
|
|
167
39
|
//# sourceMappingURL=generate-hooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAGpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,6BAA6B,CAAC,CAAC;AAEpE,SAAS,QAAQ,CAAC,QAAgB,EAAE,IAAY,EAAE,GAAW;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC;IAClD,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC;IACrD,IAAI,aAAa,KAAK,OAAO;QAAE,OAAO;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAE1C,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,EAAE,CAAC;QAC/C,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;QACnC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC;QACpC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
|