@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
|
@@ -1,14 +1,160 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates auth-store.ts/js — token storage
|
|
3
|
-
*
|
|
2
|
+
* Generates auth-store.ts/js — token storage, auth headers, auth URL detection,
|
|
3
|
+
* and token refresh. All auth logic lives here so fetch-wrapper can import it
|
|
4
|
+
* without circular dependencies.
|
|
4
5
|
*
|
|
5
6
|
* Security: the Bearer token lives in memory only and is cleared on page
|
|
6
7
|
* refresh. Only { user, expiresAt } is persisted to IndexedDB for offline
|
|
7
8
|
* user display. After a page refresh, re-login is required.
|
|
8
9
|
*/
|
|
9
10
|
import { writeFile } from "./context.js";
|
|
11
|
+
function generateWithAuthHeaders(authType, ts, R, T) {
|
|
12
|
+
const a = T("AuthData | null");
|
|
13
|
+
const h = T("Headers");
|
|
14
|
+
const hd = T("Headers");
|
|
15
|
+
switch (authType) {
|
|
16
|
+
case "cookie":
|
|
17
|
+
return `/** Inject auth headers. For cookie auth, credentials are handled via AUTH_WITH_CREDENTIALS. */
|
|
18
|
+
export function withAuthHeaders(headers${hd}, _auth${a})${h}{
|
|
19
|
+
return headers;
|
|
20
|
+
}`;
|
|
21
|
+
case "bearer":
|
|
22
|
+
return `/** Inject Bearer token into request headers. */
|
|
23
|
+
export function withAuthHeaders(headers${hd}, auth${a})${h}{
|
|
24
|
+
if (auth?.token) {
|
|
25
|
+
headers.set("Authorization", \`Bearer \${auth.token}\`);
|
|
26
|
+
}
|
|
27
|
+
return headers;
|
|
28
|
+
}`;
|
|
29
|
+
case "custom":
|
|
30
|
+
return `/** Inject custom auth headers. Edit this function to match your backend. */
|
|
31
|
+
export function withAuthHeaders(headers${hd}, auth${a})${h}{
|
|
32
|
+
// --- EDIT THIS BLOCK FOR YOUR BACKEND ---
|
|
33
|
+
// if (auth?.token) {
|
|
34
|
+
// headers.set("X-Auth-Token", auth.token);
|
|
35
|
+
// }
|
|
36
|
+
// --- END OF EDITABLE BLOCK ---
|
|
37
|
+
return headers;
|
|
38
|
+
}`;
|
|
39
|
+
default:
|
|
40
|
+
return `export function withAuthHeaders(headers${hd}, auth${a})${h}{
|
|
41
|
+
if (auth?.token) {
|
|
42
|
+
headers.set("Authorization", \`Bearer \${auth.token}\`);
|
|
43
|
+
}
|
|
44
|
+
return headers;
|
|
45
|
+
}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function generateIsAuthUrl(refreshPath, userEndpoint, ts, R, T) {
|
|
49
|
+
return `/** Check if a URL is an auth endpoint that should bypass the SW cache. */
|
|
50
|
+
export function isAuthUrl(url${T("string")})${R("boolean")}{
|
|
51
|
+
const authPaths = [
|
|
52
|
+
"/login",
|
|
53
|
+
"/logout",
|
|
54
|
+
"/register",
|
|
55
|
+
"/api/login",
|
|
56
|
+
"/api/logout",
|
|
57
|
+
"/api/register",
|
|
58
|
+
"${refreshPath}",
|
|
59
|
+
"${userEndpoint}",
|
|
60
|
+
];
|
|
61
|
+
return authPaths.some((path) => url.includes(path));
|
|
62
|
+
}`;
|
|
63
|
+
}
|
|
64
|
+
function generateEnsureValidAuth(cookieAuth, ts, R, T, PT, refreshPath, ext) {
|
|
65
|
+
const cr = cookieAuth ? ` credentials: "include" as RequestCredentials,` : "";
|
|
66
|
+
return `/** Refresh the auth token via the refresh endpoint. Uses plain fetch to bypass SW cache. */
|
|
67
|
+
let refreshPromise${T("Promise<AuthData | null> | null")} = null;
|
|
68
|
+
|
|
69
|
+
export async function ensureValidAuth()${R("Promise<AuthData | null>")}{
|
|
70
|
+
const auth = await getAuth();
|
|
71
|
+
if (!auth) return null;
|
|
72
|
+
if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
|
|
73
|
+
|
|
74
|
+
if (!refreshPromise) {
|
|
75
|
+
refreshPromise = (async () => {
|
|
76
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
77
|
+
withAuthHeaders(headers, auth);
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch("${refreshPath}", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers,
|
|
82
|
+
${cr} });
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
await clearAuth();
|
|
86
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
const updated = { ...auth, token: data.token, expiresAt: data.expiresAt };
|
|
92
|
+
await setAuth(updated);
|
|
93
|
+
return updated;
|
|
94
|
+
} catch {
|
|
95
|
+
await clearAuth();
|
|
96
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return await refreshPromise;
|
|
104
|
+
} finally {
|
|
105
|
+
refreshPromise = null;
|
|
106
|
+
}
|
|
107
|
+
}`;
|
|
108
|
+
}
|
|
10
109
|
export function generateAuthStore(ctx) {
|
|
11
110
|
const ext = ctx.ext;
|
|
111
|
+
const ts = ext === "ts";
|
|
112
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
113
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
114
|
+
const PT = (type) => (ts ? `<${type}>` : "");
|
|
115
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
116
|
+
const authConfig = ctx.config.features.auth;
|
|
117
|
+
const { refreshPath, userEndpoint, type } = authConfig;
|
|
118
|
+
const authDataInterface = ts
|
|
119
|
+
? `export interface AuthData {
|
|
120
|
+
token?: string;
|
|
121
|
+
user?: Record<string, unknown>;
|
|
122
|
+
expiresAt?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface AuthResponse extends Record<string, unknown> {
|
|
126
|
+
// Uncomment and type the fields your backend's login response returns:
|
|
127
|
+
// token: string;
|
|
128
|
+
// user: Record<string, unknown>;
|
|
129
|
+
// expiresAt?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
`
|
|
133
|
+
: "";
|
|
134
|
+
const createAuthFromResponseBlock = ts
|
|
135
|
+
? `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
|
|
136
|
+
export function createAuthFromResponse(response: AuthResponse): AuthData {
|
|
137
|
+
// --- EDIT THIS TO MATCH YOUR BACKEND ---
|
|
138
|
+
const data = response as Record<string, unknown>;
|
|
139
|
+
return {
|
|
140
|
+
token: data.token as string,
|
|
141
|
+
user: data.user as Record<string, unknown> | undefined,
|
|
142
|
+
expiresAt: data.expiresAt as number | undefined,
|
|
143
|
+
};
|
|
144
|
+
// --- END OF EDITABLE BLOCK ---
|
|
145
|
+
}
|
|
146
|
+
`
|
|
147
|
+
: `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
|
|
148
|
+
export function createAuthFromResponse(response) {
|
|
149
|
+
// --- EDIT THIS TO MATCH YOUR BACKEND ---
|
|
150
|
+
return {
|
|
151
|
+
token: response.token,
|
|
152
|
+
user: response.user,
|
|
153
|
+
expiresAt: response.expiresAt,
|
|
154
|
+
};
|
|
155
|
+
// --- END OF EDITABLE BLOCK ---
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
12
158
|
const code = `/**
|
|
13
159
|
* Auth Store — Token in memory only; user info in IndexedDB for offline access.
|
|
14
160
|
*
|
|
@@ -18,110 +164,104 @@ export function generateAuthStore(ctx) {
|
|
|
18
164
|
* user info offline. After a page refresh, re-login is required.
|
|
19
165
|
*
|
|
20
166
|
* Usage:
|
|
21
|
-
* import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth
|
|
167
|
+
* import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth/store.${ext}";
|
|
22
168
|
*
|
|
23
169
|
* await setAuth({ token, user, expiresAt });
|
|
24
170
|
* const auth = await getAuth();
|
|
25
171
|
* await clearAuth();
|
|
26
172
|
*/
|
|
27
173
|
|
|
28
|
-
const DB_NAME = "swoff-auth";
|
|
174
|
+
${authDataInterface}const DB_NAME = "swoff-auth";
|
|
29
175
|
const STORE_NAME = "auth";
|
|
30
176
|
|
|
31
|
-
let memoryAuth = null;
|
|
177
|
+
let memoryAuth${T("AuthData | null")} = null;
|
|
32
178
|
|
|
33
|
-
|
|
34
|
-
|
|
179
|
+
${createAuthFromResponseBlock}
|
|
180
|
+
function openAuthDB()${R("Promise<IDBDatabase>")}{
|
|
181
|
+
return new Promise${PT("IDBDatabase")}((resolve, reject) => {
|
|
35
182
|
const request = indexedDB.open(DB_NAME, 1);
|
|
36
183
|
request.onupgradeneeded = (e) => {
|
|
37
|
-
const db = e.target.result;
|
|
184
|
+
const db = (e.target${AS("IDBOpenDBRequest")}).result;
|
|
38
185
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
39
186
|
db.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
40
187
|
}
|
|
41
188
|
};
|
|
42
|
-
request.onsuccess = (e) => resolve(e.target.result);
|
|
43
|
-
request.onerror = (e) => reject(e.target.error);
|
|
189
|
+
request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
|
|
190
|
+
request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
|
|
44
191
|
});
|
|
45
192
|
}
|
|
46
193
|
|
|
47
|
-
async function persistUserData(authData)
|
|
194
|
+
async function persistUserData(authData${T("AuthData | null")})${R("Promise<void>")}{
|
|
48
195
|
// Only persist { user, expiresAt } — never the token
|
|
49
196
|
const userData = { user: authData?.user, expiresAt: authData?.expiresAt };
|
|
50
197
|
const db = await openAuthDB();
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
198
|
+
return new Promise${PT("void")}((resolve, reject) => {
|
|
52
199
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
53
200
|
const store = tx.objectStore(STORE_NAME);
|
|
54
201
|
const request = store.put({ key: "session", value: userData });
|
|
55
202
|
request.onsuccess = () => resolve();
|
|
56
|
-
request.onerror = () => reject(request.error);
|
|
203
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
57
204
|
});
|
|
58
205
|
}
|
|
59
206
|
|
|
60
|
-
async function loadUserData() {
|
|
207
|
+
async function loadUserData()${R("Promise<{ user?: Record<string, unknown>; expiresAt?: number } | null>")}{
|
|
61
208
|
const db = await openAuthDB();
|
|
62
209
|
return new Promise((resolve, reject) => {
|
|
63
210
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
64
211
|
const store = tx.objectStore(STORE_NAME);
|
|
65
212
|
const request = store.get("session");
|
|
66
|
-
request.onsuccess = () => resolve(request.result?.value
|
|
67
|
-
request.onerror = () => reject(request.error);
|
|
213
|
+
request.onsuccess = () => resolve((request${AS("IDBRequest")}).result?.value ?? null);
|
|
214
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
68
215
|
});
|
|
69
216
|
}
|
|
70
217
|
|
|
71
|
-
async function clearPersistedData()
|
|
218
|
+
async function clearPersistedData()${R("Promise<void>")}{
|
|
72
219
|
const db = await openAuthDB();
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
220
|
+
return new Promise${PT("void")}((resolve, reject) => {
|
|
74
221
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
75
222
|
const store = tx.objectStore(STORE_NAME);
|
|
76
223
|
const request = store.delete("session");
|
|
77
224
|
request.onsuccess = () => resolve();
|
|
78
|
-
request.onerror = () => reject(request.error);
|
|
225
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
79
226
|
});
|
|
80
227
|
}
|
|
81
228
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
* Only user info and expiry are persisted to IndexedDB.
|
|
85
|
-
* @param {{ token?: string, user?: object, expiresAt?: number }} authData
|
|
86
|
-
*/
|
|
87
|
-
export async function setAuth(authData) {
|
|
229
|
+
/** Store auth data in memory and persist user info to IndexedDB for offline access. */
|
|
230
|
+
export async function setAuth(authData${T("AuthData")})${R("Promise<void>")}{
|
|
88
231
|
memoryAuth = authData;
|
|
89
232
|
await persistUserData(authData);
|
|
90
233
|
}
|
|
91
234
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
* falls back to IndexedDB (user info only, no token after refresh).
|
|
95
|
-
*/
|
|
96
|
-
export async function getAuth() {
|
|
235
|
+
/** Get auth data from memory (or IndexedDB if memory is empty after page refresh). */
|
|
236
|
+
export async function getAuth()${R("Promise<AuthData | null>")}{
|
|
97
237
|
if (memoryAuth) return memoryAuth;
|
|
98
238
|
|
|
99
239
|
const userData = await loadUserData();
|
|
100
240
|
if (userData) {
|
|
101
|
-
// After refresh: user info available, but no token — re-login required
|
|
102
241
|
memoryAuth = userData;
|
|
103
242
|
}
|
|
104
243
|
return memoryAuth;
|
|
105
244
|
}
|
|
106
245
|
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
*/
|
|
110
|
-
export async function clearAuth() {
|
|
246
|
+
/** Clear auth from memory and IndexedDB. Call on logout or 401. */
|
|
247
|
+
export async function clearAuth()${R("Promise<void>")}{
|
|
111
248
|
memoryAuth = null;
|
|
112
249
|
await clearPersistedData();
|
|
113
250
|
}
|
|
114
251
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
* @param {object | null} auth — The auth object from getAuth()
|
|
118
|
-
*/
|
|
119
|
-
export function isAuthValid(auth) {
|
|
252
|
+
/** Check if auth exists and has not expired. Returns true if no expiresAt is set. */
|
|
253
|
+
export function isAuthValid(auth${T("AuthData | null")})${R("boolean")}{
|
|
120
254
|
if (!auth) return false;
|
|
121
255
|
if (!auth.expiresAt) return true;
|
|
122
256
|
return Date.now() < auth.expiresAt;
|
|
123
257
|
}
|
|
258
|
+
|
|
259
|
+
${type === "cookie" ? `export const AUTH_WITH_CREDENTIALS = true;` : `export const AUTH_WITH_CREDENTIALS = false;`}
|
|
260
|
+
|
|
261
|
+
${generateWithAuthHeaders(type, ts, R, T)}
|
|
262
|
+
${generateIsAuthUrl(refreshPath, userEndpoint, ts, R, T)}
|
|
263
|
+
${generateEnsureValidAuth(type === "cookie", ts, R, T, PT, refreshPath, ext)}
|
|
124
264
|
`;
|
|
125
|
-
writeFile(ctx, `auth
|
|
265
|
+
writeFile(ctx, `auth/store.${ext}`, code);
|
|
126
266
|
}
|
|
127
267
|
//# sourceMappingURL=auth-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,SAAS,uBAAuB,CAAC,QAAgB,EAAE,EAAW,EAAE,CAAwB,EAAE,CAAwB;IAChH,MAAM,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IACxB,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,OAAO;yCAC4B,EAAE,UAAU,CAAC,IAAI,CAAC;;EAEzD,CAAC;QACC,KAAK,QAAQ;YACX,OAAO;yCAC4B,EAAE,SAAS,CAAC,IAAI,CAAC;;;;;EAKxD,CAAC;QACC,KAAK,QAAQ;YACX,OAAO;yCAC4B,EAAE,SAAS,CAAC,IAAI,CAAC;;;;;;;EAOxD,CAAC;QACC;YACE,OAAO,0CAA0C,EAAE,SAAS,CAAC,IAAI,CAAC;;;;;EAKtE,CAAC;IACD,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,WAAmB,EAAE,YAAoB,EAAE,EAAW,EAAE,CAAwB,EAAE,CAAwB;IACnI,OAAO;+BACsB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;;;;;;;;OAQnD,WAAW;OACX,YAAY;;;EAGjB,CAAC;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,UAAmB,EAAE,EAAW,EAAE,CAAwB,EAAE,CAAwB,EAAE,EAAyB,EAAE,WAAmB,EAAE,GAAW;IAChL,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,iDAAiD,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,OAAO;oBACW,CAAC,CAAC,iCAAiC,CAAC;;yCAEf,CAAC,CAAC,0BAA0B,CAAC;;;;;;;;;;wCAU9B,WAAW;;;EAGjD,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;EAyBF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IAExB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5C,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAEvD,MAAM,iBAAiB,GAAG,EAAE;QAC1B,CAAC,CAAC;;;;;;;;;;;;;CAaL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,2BAA2B,GAAG,EAAE;QACpC,CAAC,CAAC;;;;;;;;;;;CAWL;QACG,CAAC,CAAC;;;;;;;;;;CAUL,CAAC;IAEA,MAAM,IAAI,GAAG;;;;;;;;;8EAS+D,GAAG;;;;;;;EAO/E,iBAAiB;;;gBAGH,CAAC,CAAC,iBAAiB,CAAC;;EAElC,2BAA2B;uBACN,CAAC,CAAC,sBAAsB,CAAC;sBAC1B,EAAE,CAAC,aAAa,CAAC;;;4BAGX,EAAE,CAAC,kBAAkB,CAAC;;;;;kDAKA,EAAE,CAAC,kBAAkB,CAAC;+CACzB,EAAE,CAAC,YAAY,CAAC;;;;yCAItB,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;sBAI7D,EAAE,CAAC,MAAM,CAAC;;;;;6CAKa,EAAE,CAAC,YAAY,CAAC;;;;+BAI9B,CAAC,CAAC,wEAAwE,CAAC;;;;;;gDAM1D,EAAE,CAAC,YAAY,CAAC;6CACnB,EAAE,CAAC,YAAY,CAAC;;;;qCAIxB,CAAC,CAAC,eAAe,CAAC;;sBAEjC,EAAE,CAAC,MAAM,CAAC;;;;;6CAKa,EAAE,CAAC,YAAY,CAAC;;;;;wCAKrB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;iCAM1C,CAAC,CAAC,0BAA0B,CAAC;;;;;;;;;;;mCAW3B,CAAC,CAAC,eAAe,CAAC;;;;;;kCAMnB,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;;;;;;EAMpE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,6CAA6C;;EAEhH,uBAAuB,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;EACvC,iBAAiB,CAAC,WAAW,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;EACtD,uBAAuB,CAAC,IAAI,KAAK,QAAQ,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC;CAC3E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -4,43 +4,45 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateAuthUser(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 PT = (type) => (ts ? `<${type}>` : "");
|
|
11
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
7
12
|
const userEndpoint = ctx.config.features.auth.userEndpoint;
|
|
8
13
|
const code = `/**
|
|
9
14
|
* Current User — fetch, cache, and invalidate the current user for offline access.
|
|
10
15
|
*
|
|
11
16
|
* Usage:
|
|
12
|
-
* import { fetchCurrentUser, cacheUser, getCachedUser, clearCachedUser } from "./auth
|
|
17
|
+
* import { fetchCurrentUser, cacheUser, getCachedUser, clearCachedUser } from "./auth/user.${ext}";
|
|
13
18
|
*
|
|
14
19
|
* await fetchCurrentUser(); // Fetch from server & cache
|
|
15
20
|
* const user = await getCachedUser(); // Get cached (offline-capable)
|
|
16
21
|
* await clearCachedUser(); // Clear on logout
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
|
-
import {
|
|
24
|
+
import { fetchWithCache } from "../fetch-wrapper.${ext}";
|
|
20
25
|
|
|
21
26
|
const DB_NAME = "swoff-auth-user";
|
|
22
27
|
const STORE_NAME = "current-user";
|
|
23
28
|
|
|
24
|
-
function openAuthDB()
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
29
|
+
function openAuthDB()${R("Promise<IDBDatabase>")}{
|
|
30
|
+
return new Promise${PT("IDBDatabase")}((resolve, reject) => {
|
|
26
31
|
const request = indexedDB.open(DB_NAME, 1);
|
|
27
32
|
request.onupgradeneeded = (e) => {
|
|
28
|
-
const db = e.target.result;
|
|
33
|
+
const db = (e.target${AS("IDBOpenDBRequest")}).result;
|
|
29
34
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
30
35
|
db.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
31
36
|
}
|
|
32
37
|
};
|
|
33
|
-
request.onsuccess = (e) => resolve(e.target.result);
|
|
34
|
-
request.onerror = (e) => reject(e.target.error);
|
|
38
|
+
request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
|
|
39
|
+
request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
|
|
35
40
|
});
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*/
|
|
42
|
-
export async function fetchCurrentUser() {
|
|
43
|
-
const response = await authenticatedFetch("${userEndpoint}");
|
|
43
|
+
/** Fetch current user from the user endpoint and cache the result in IndexedDB. */
|
|
44
|
+
export async function fetchCurrentUser()${R("Promise<Record<string, unknown>>")}{
|
|
45
|
+
const { response } = await fetchWithCache("${userEndpoint}", { auth: true });
|
|
44
46
|
if (!response.ok) throw new Error("Failed to fetch user");
|
|
45
47
|
|
|
46
48
|
const user = await response.json();
|
|
@@ -48,12 +50,10 @@ export async function fetchCurrentUser() {
|
|
|
48
50
|
return user;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
export async function cacheUser(user) {
|
|
53
|
+
/** Persist user data to IndexedDB for offline access. */
|
|
54
|
+
export async function cacheUser(user${T("Record<string, unknown>")})${R("Promise<void>")}{
|
|
55
55
|
const db = await openAuthDB();
|
|
56
|
-
return new Promise((resolve, reject) => {
|
|
56
|
+
return new Promise<void>((resolve, reject) => {
|
|
57
57
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
58
58
|
const store = tx.objectStore(STORE_NAME);
|
|
59
59
|
store.put({ key: "user", value: user });
|
|
@@ -62,34 +62,30 @@ export async function cacheUser(user) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
*/
|
|
68
|
-
export async function getCachedUser() {
|
|
65
|
+
/** Load user data from IndexedDB cache (no token — only user object survives refresh). */
|
|
66
|
+
export async function getCachedUser()${R("Promise<Record<string, unknown> | null>")}{
|
|
69
67
|
const db = await openAuthDB();
|
|
70
|
-
return new Promise((resolve, reject) => {
|
|
68
|
+
return new Promise${PT("Record<string, unknown> | null")}((resolve, reject) => {
|
|
71
69
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
72
70
|
const store = tx.objectStore(STORE_NAME);
|
|
73
71
|
const request = store.get("user");
|
|
74
|
-
request.onsuccess = () => resolve(request.result?.value
|
|
75
|
-
request.onerror = () => reject(request.error);
|
|
72
|
+
request.onsuccess = () => resolve((request${AS("IDBRequest")}).result?.value ?? null);
|
|
73
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
76
74
|
});
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
*/
|
|
82
|
-
export async function clearCachedUser() {
|
|
77
|
+
/** Remove user data from IndexedDB cache. Call on logout. */
|
|
78
|
+
export async function clearCachedUser()${R("Promise<void>")}{
|
|
83
79
|
const db = await openAuthDB();
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
80
|
+
return new Promise${PT("void")}((resolve, reject) => {
|
|
85
81
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
86
82
|
const store = tx.objectStore(STORE_NAME);
|
|
87
83
|
store.delete("user");
|
|
88
84
|
tx.oncomplete = () => resolve();
|
|
89
|
-
tx.onerror = () => reject(tx.error);
|
|
85
|
+
tx.onerror = () => reject((tx${AS("IDBTransaction")}).error);
|
|
90
86
|
});
|
|
91
87
|
}
|
|
92
88
|
`;
|
|
93
|
-
writeFile(ctx, `auth
|
|
89
|
+
writeFile(ctx, `auth/user.${ext}`, code);
|
|
94
90
|
}
|
|
95
91
|
//# sourceMappingURL=auth-user.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-user.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,gBAAgB,CAAC,GAAqB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;IAE3D,MAAM,IAAI,GAAG;;;;gGAIiF,GAAG;;;;;;;mDAOhD,GAAG
|
|
1
|
+
{"version":3,"file":"auth-user.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,gBAAgB,CAAC,GAAqB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;IAE3D,MAAM,IAAI,GAAG;;;;gGAIiF,GAAG;;;;;;;mDAOhD,GAAG;;;;;uBAK/B,CAAC,CAAC,sBAAsB,CAAC;sBAC1B,EAAE,CAAC,aAAa,CAAC;;;4BAGX,EAAE,CAAC,kBAAkB,CAAC;;;;;kDAKA,EAAE,CAAC,kBAAkB,CAAC;+CACzB,EAAE,CAAC,YAAY,CAAC;;;;;0CAKrB,CAAC,CAAC,kCAAkC,CAAC;+CAChC,YAAY;;;;;;;;;sCASrB,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;uCAYjD,CAAC,CAAC,yCAAyC,CAAC;;sBAE7D,EAAE,CAAC,gCAAgC,CAAC;;;;gDAIV,EAAE,CAAC,YAAY,CAAC;6CACnB,EAAE,CAAC,YAAY,CAAC;;;;;yCAKpB,CAAC,CAAC,eAAe,CAAC;;sBAErC,EAAE,CAAC,MAAM,CAAC;;;;;mCAKG,EAAE,CAAC,gBAAgB,CAAC;;;CAGtD,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateBackgroundSync(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
7
10
|
const code = `/**
|
|
8
11
|
* Swoff Background Sync
|
|
9
12
|
* Register sync events for processing mutation queue after tab close.
|
|
@@ -17,8 +20,7 @@ export function generateBackgroundSync(ctx) {
|
|
|
17
20
|
* url: "/api/todos",
|
|
18
21
|
* body: { title: "Grocery" },
|
|
19
22
|
* tags: ["todos"],
|
|
20
|
-
*
|
|
21
|
-
* tempId: "temp_abc123",
|
|
23
|
+
* tags: ["todos"],
|
|
22
24
|
* });
|
|
23
25
|
*/
|
|
24
26
|
|
|
@@ -26,7 +28,7 @@ import { queueMutation, processMutationQueue, getPendingCount } from "./mutation
|
|
|
26
28
|
|
|
27
29
|
const SYNC_TAG = "sync-mutations";
|
|
28
30
|
|
|
29
|
-
async function registerSync()
|
|
31
|
+
async function registerSync()${R("Promise<void>")}{
|
|
30
32
|
if (!("serviceWorker" in navigator) || !("SyncManager" in window)) {
|
|
31
33
|
window.addEventListener("online", processMutationQueue, { once: true });
|
|
32
34
|
return;
|
|
@@ -40,12 +42,14 @@ async function registerSync() {
|
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
/** Queue a mutation and register a background sync event so it runs even after tab close. */
|
|
46
|
+
export async function syncWhenPossible(mutation${T("object")})${R("Promise<void>")}{
|
|
44
47
|
await queueMutation(mutation);
|
|
45
48
|
await registerSync();
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
/** Re-register a background sync if mutations are still pending. Called automatically after each sync cycle. */
|
|
52
|
+
export async function retrySync()${R("Promise<void>")}{
|
|
49
53
|
if (!("serviceWorker" in navigator) || !("SyncManager" in window)) return;
|
|
50
54
|
const count = await getPendingCount();
|
|
51
55
|
if (count > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/background-sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,sBAAsB,CAAC,GAAqB;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;;iEAMkD,GAAG
|
|
1
|
+
{"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/background-sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,sBAAsB,CAAC,GAAqB;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG;;;;;;iEAMkD,GAAG;;;;;;;;;;;yFAWqB,GAAG;;;;+BAI7D,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;iDAeA,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;mCAM/C,CAAC,CAAC,eAAe,CAAC;;;;;;;;;CASpD,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateCache(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
7
10
|
const code = `/**
|
|
8
11
|
* Swoff Cache Invalidation
|
|
9
12
|
* Framework-agnostic cache tag invalidation.
|
|
@@ -15,7 +18,8 @@ export function generateCache(ctx) {
|
|
|
15
18
|
* await invalidateByTag("todos");
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
/** Invalidate all cached responses tagged with the given tag. Dispatches cache-invalidated event. */
|
|
22
|
+
export async function invalidateByTag(tag${T("string")})${R("Promise<void>")}{
|
|
19
23
|
if (!navigator.serviceWorker?.controller) return;
|
|
20
24
|
|
|
21
25
|
navigator.serviceWorker.controller.postMessage({
|
|
@@ -28,10 +32,9 @@ export async function invalidateByTag(tag) {
|
|
|
28
32
|
);
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
+
/** Invalidate all cached responses matching any of the given tags. */
|
|
36
|
+
export async function invalidateByTags(tags${T("string[]")})${R("Promise<void>")}{
|
|
37
|
+
await Promise.all(tags.map((tag) => invalidateByTag(tag)));
|
|
35
38
|
}
|
|
36
39
|
`;
|
|
37
40
|
writeFile(ctx, `cache.${ext}`, code);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG;;;;;sDAKuC,GAAG;;;;;;;2CAOd,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;6CAc/B,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;CAG/E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates client-injector.{js|ts} - orchestrator that wires feature modules together.
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile } from "./context.js";
|
|
5
|
+
export function generateClientInjector(ctx) {
|
|
6
|
+
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const pwaEnabled = ctx.config.features.pwa.enabled;
|
|
9
|
+
const mutationQueueEnabled = ctx.config.features.mutationQueue;
|
|
10
|
+
const crossTabSync = ctx.config.features.crossTabSync;
|
|
11
|
+
const pwaImport = pwaEnabled
|
|
12
|
+
? `import { setupPwaInstall } from "./pwa/install.${ext}";
|
|
13
|
+
`
|
|
14
|
+
: "";
|
|
15
|
+
const pwaCall = pwaEnabled ? `setupPwaInstall();\n` : "";
|
|
16
|
+
const mutationImport = mutationQueueEnabled
|
|
17
|
+
? `import { processMutationQueue } from "./mutation-queue.${ext}";
|
|
18
|
+
`
|
|
19
|
+
: "";
|
|
20
|
+
const mutationOnlineListener = mutationQueueEnabled
|
|
21
|
+
? `
|
|
22
|
+
// --- Mutation Queue Online Listener ---
|
|
23
|
+
window.addEventListener("online", processMutationQueue);
|
|
24
|
+
`
|
|
25
|
+
: "";
|
|
26
|
+
const crossTabHandler = crossTabSync
|
|
27
|
+
? `
|
|
28
|
+
if (event.data.type === "TAG_INVALIDATED" && event.data.tag) {
|
|
29
|
+
window.dispatchEvent(
|
|
30
|
+
new CustomEvent("cache-invalidated", {
|
|
31
|
+
detail: { tags: [event.data.tag] },
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
`
|
|
36
|
+
: "";
|
|
37
|
+
const swImport = `import { initServiceWorker as swInit } from "./sw/injector.${ext}";
|
|
38
|
+
`;
|
|
39
|
+
const code = `/**
|
|
40
|
+
* Swoff Client Injector
|
|
41
|
+
* Orchestrates SW registration, PWA install, and cross-tab sync.
|
|
42
|
+
* Single entry point for all Swoff client-side features.
|
|
43
|
+
*
|
|
44
|
+
* Usage:
|
|
45
|
+
* import { initServiceWorker } from './swoff/client-injector.${ext}';
|
|
46
|
+
* initServiceWorker();
|
|
47
|
+
*
|
|
48
|
+
* Window events (dispatched by this module):
|
|
49
|
+
* sw-progress - Download progress (detail: { percent, downloaded, total })
|
|
50
|
+
* sw-ready - SW active and controlling page
|
|
51
|
+
* sw-error - SW registration failed
|
|
52
|
+
* cache-invalidated - Cache entries cleared (detail: { tags })
|
|
53
|
+
* mutation-sync-complete - Queued mutations synced (detail: { succeeded, failed })
|
|
54
|
+
* mutation-queue-changed - Queue modified
|
|
55
|
+
*
|
|
56
|
+
* Window events (dispatched by feature modules):
|
|
57
|
+
* pwa-installable - PWA can be installed (detail: { isInstallable: true })
|
|
58
|
+
* pwa-installed - User accepted install (detail: { outcome: 'accepted' })
|
|
59
|
+
* sw-auth-unauthorized - 401 response received
|
|
60
|
+
* sw-auth-state-change - Login or logout (detail: { authenticated: boolean })
|
|
61
|
+
* mutation-rollback - Mutation exhausted retries
|
|
62
|
+
* sw-update-available - New version ready (detail: { version })
|
|
63
|
+
* sw-version-detected - Version info available
|
|
64
|
+
*/
|
|
65
|
+
${pwaImport}${mutationImport}${swImport}
|
|
66
|
+
${pwaCall}${mutationOnlineListener}
|
|
67
|
+
// --- SW Message Listener ---
|
|
68
|
+
if (typeof window !== "undefined" && "serviceWorker" in navigator) {
|
|
69
|
+
navigator.serviceWorker.addEventListener("message", (event) => {
|
|
70
|
+
if (event.data.type === "SW_PROGRESS") {
|
|
71
|
+
const { percent, downloaded, total } = event.data;
|
|
72
|
+
window.dispatchEvent(
|
|
73
|
+
new CustomEvent("sw-progress", {
|
|
74
|
+
detail: { percent, downloaded, total },
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (event.data.type === "BACKGROUND_SYNC_COMPLETE") {
|
|
79
|
+
const { succeeded, failed, tags } = event.data.detail;
|
|
80
|
+
window.dispatchEvent(
|
|
81
|
+
new CustomEvent("mutation-sync-complete", {
|
|
82
|
+
detail: { succeeded, failed },
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
if (tags && tags.length > 0) {
|
|
86
|
+
window.dispatchEvent(
|
|
87
|
+
new CustomEvent("cache-invalidated", { detail: { tags } })
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
91
|
+
}${crossTabHandler} });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Initialize SW registration and all client-side features (PWA install, mutation queue, cross-tab sync). Call once at app startup. */
|
|
95
|
+
export async function initServiceWorker()${ts ? ": Promise<void>" : " "}{
|
|
96
|
+
await swInit();
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
writeFile(ctx, `client-injector.${ext}`, code);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=client-injector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-injector.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/client-injector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,sBAAsB,CAAC,GAAqB;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;IACnD,MAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC/D,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;IAEtD,MAAM,SAAS,GAAG,UAAU;QAC1B,CAAC,CAAC,kDAAkD,GAAG;CAC1D;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;IAEzD,MAAM,cAAc,GAAG,oBAAoB;QACzC,CAAC,CAAC,0DAA0D,GAAG;CAClE;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,sBAAsB,GAAG,oBAAoB;QACjD,CAAC,CAAC;;;CAGL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,eAAe,GAAG,YAAY;QAClC,CAAC,CAAC;;;;;;;;CAQL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,QAAQ,GAAG,8DAA8D,GAAG;CACnF,CAAC;IAEA,MAAM,IAAI,GAAG;;;;;;kEAMmD,GAAG;;;;;;;;;;;;;;;;;;;;EAoBnE,SAAS,GAAG,cAAc,GAAG,QAAQ;EACrC,OAAO,GAAG,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;OAyB3B,eAAe;;;;2CAIqB,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG;;;CAGtE,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
|