@swoff/cli 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -33
- package/dist/__tests__/assemble-sw.test.js +14 -53
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +119 -11
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +44 -17
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +3 -3
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/glob-matcher.test.js +86 -0
- package/dist/__tests__/glob-matcher.test.js.map +1 -0
- package/dist/__tests__/invalidation-tags.test.js +207 -23
- package/dist/__tests__/invalidation-tags.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +16 -12
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +140 -100
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +39 -34
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +12 -12
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +84 -36
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/assets.js +37 -0
- package/dist/lib/commands/assets.js.map +1 -0
- package/dist/lib/commands/clean.js +32 -30
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-assets.js +44 -0
- package/dist/lib/commands/generate-assets.js.map +1 -0
- package/dist/lib/commands/generate-guide.js +22 -29
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +17 -24
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +41 -48
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +27 -13
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +4 -4
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +25 -23
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/validator.js +169 -91
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
- package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
- package/dist/lib/generators/asset-generator/generate.js +111 -0
- package/dist/lib/generators/asset-generator/generate.js.map +1 -0
- package/dist/lib/generators/asset-generator/guide.js +43 -0
- package/dist/lib/generators/asset-generator/guide.js.map +1 -0
- package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
- package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
- package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
- package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
- package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
- package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
- package/dist/lib/generators/asset-generator/maskable.js +15 -0
- package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
- package/dist/lib/generators/asset-generator/rasterize.js +32 -0
- package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
- package/dist/lib/generators/asset-generator/sizes.js +25 -0
- package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
- package/dist/lib/generators/file-generators/api-config.js +6 -0
- package/dist/lib/generators/file-generators/api-config.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-state.js +2 -34
- package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +2 -250
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +3 -81
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +2 -57
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -38
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +2 -91
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-state.js +6 -0
- package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
- package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -267
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
- package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
- package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +3 -145
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +24 -630
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +18 -86
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/manifest.js +15 -5
- package/dist/lib/generators/file-generators/manifest.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +3 -285
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +2 -117
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
- package/dist/lib/generators/file-generators/push.js +6 -139
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/pwa-install.js +11 -84
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +8 -125
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
- package/dist/lib/generators/file-generators/reset.js +6 -0
- package/dist/lib/generators/file-generators/reset.js.map +1 -0
- package/dist/lib/generators/file-generators/server-push.js +3 -124
- package/dist/lib/generators/file-generators/server-push.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +10 -230
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +2 -114
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +10 -20
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +20 -16
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +68 -19
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +2 -2
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +506 -144
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
- package/dist/lib/generators/sw-sections/message-handler.js +55 -15
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/sw-push.js +67 -0
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
- package/dist/lib/generators/sw-sections/tag-management.js +65 -23
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +66 -45
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +182 -71
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/glob-matcher.js +77 -0
- package/dist/lib/shared/glob-matcher.js.map +1 -0
- package/dist/lib/utils/tty-status.js +21 -0
- package/dist/lib/utils/tty-status.js.map +1 -0
- package/dist/runtime/api-config.js +18 -0
- package/dist/runtime/api-config.js.map +1 -0
- package/dist/runtime/auth-state.js +35 -0
- package/dist/runtime/auth-state.js.map +1 -0
- package/dist/runtime/auth-store.js +273 -0
- package/dist/runtime/auth-store.js.map +1 -0
- package/dist/runtime/auth-user.js +168 -0
- package/dist/runtime/auth-user.js.map +1 -0
- package/dist/runtime/background-sync.js +59 -0
- package/dist/runtime/background-sync.js.map +1 -0
- package/dist/runtime/cache.js +30 -0
- package/dist/runtime/cache.js.map +1 -0
- package/dist/runtime/client-injector.js +136 -0
- package/dist/runtime/client-injector.js.map +1 -0
- package/dist/runtime/fetch-state.js +42 -0
- package/dist/runtime/fetch-state.js.map +1 -0
- package/dist/runtime/fetch-wrapper.js +355 -0
- package/dist/runtime/fetch-wrapper.js.map +1 -0
- package/dist/runtime/gql-wrapper.js +135 -0
- package/dist/runtime/gql-wrapper.js.map +1 -0
- package/dist/runtime/invalidation-tags.js +260 -0
- package/dist/runtime/invalidation-tags.js.map +1 -0
- package/dist/runtime/mutation-queue.js +304 -0
- package/dist/runtime/mutation-queue.js.map +1 -0
- package/dist/runtime/mutation-state.js +117 -0
- package/dist/runtime/mutation-state.js.map +1 -0
- package/dist/runtime/push.js +138 -0
- package/dist/runtime/push.js.map +1 -0
- package/dist/runtime/pwa-index.js +11 -0
- package/dist/runtime/pwa-index.js.map +1 -0
- package/dist/runtime/pwa-injector.js +44 -0
- package/dist/runtime/pwa-injector.js.map +1 -0
- package/dist/runtime/pwa-install.js +83 -0
- package/dist/runtime/pwa-install.js.map +1 -0
- package/dist/runtime/pwa-prompt.js +54 -0
- package/dist/runtime/pwa-prompt.js.map +1 -0
- package/dist/runtime/reset.js +148 -0
- package/dist/runtime/reset.js.map +1 -0
- package/dist/runtime/server-push.js +148 -0
- package/dist/runtime/server-push.js.map +1 -0
- package/dist/runtime/sw-injector.js +236 -0
- package/dist/runtime/sw-injector.js.map +1 -0
- package/dist/runtime/type-definitions.js +138 -0
- package/dist/runtime/type-definitions.js.map +1 -0
- package/dist/runtime/utils.js +27 -0
- package/dist/runtime/utils.js.map +1 -0
- package/package.json +3 -1
- package/templates/react/useAuth.jsx +106 -0
- package/templates/react/useAuth.tsx +118 -0
- package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
- package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
- package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
- package/templates/react/useCacheInvalidation.tsx +52 -0
- package/templates/react/useCachedFetch.jsx +140 -0
- package/templates/react/useCachedFetch.tsx +160 -0
- package/templates/react/useIsFetching.jsx +32 -0
- package/templates/react/useIsFetching.tsx +32 -0
- package/templates/react/useMutation.jsx +141 -0
- package/templates/react/useMutation.tsx +180 -0
- package/templates/react/useMutationQueue.jsx +69 -0
- package/templates/react/useMutationQueue.tsx +83 -0
- package/templates/react/useMutationState.jsx +13 -0
- package/templates/react/useMutationState.tsx +25 -0
- package/templates/react/useNetworkStatus.jsx +61 -0
- package/templates/react/useNetworkStatus.tsx +76 -0
- package/templates/react/usePrefetch.jsx +32 -0
- package/templates/react/usePrefetch.tsx +42 -0
- package/templates/{hooks → react}/usePushSubscription.jsx +7 -7
- package/templates/{hooks → react}/usePushSubscription.tsx +7 -7
- package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
- package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
- package/templates/react/useSwoffReset.jsx +32 -0
- package/templates/react/useSwoffReset.tsx +39 -0
- package/templates/hooks/useAuth.jsx +0 -30
- package/templates/hooks/useAuth.tsx +0 -30
- package/templates/hooks/useCacheInvalidation.tsx +0 -19
- package/templates/hooks/useCachedFetch.jsx +0 -94
- package/templates/hooks/useCachedFetch.tsx +0 -112
- package/templates/hooks/useMutation.jsx +0 -76
- package/templates/hooks/useMutation.tsx +0 -97
- package/templates/hooks/useMutationQueue.jsx +0 -42
- package/templates/hooks/useMutationQueue.tsx +0 -49
- package/templates/hooks/useMutationState.jsx +0 -27
- package/templates/hooks/useMutationState.tsx +0 -41
- package/templates/hooks/useNetworkStatus.jsx +0 -19
- package/templates/hooks/useNetworkStatus.tsx +0 -19
- package/templates/hooks/usePrefetch.jsx +0 -10
- package/templates/hooks/usePrefetch.tsx +0 -25
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { T, R, PT, AS } from "./utils.js";
|
|
2
|
+
function generateWithAuthHeaders(authType, ts) {
|
|
3
|
+
const hd = T(ts, "Headers");
|
|
4
|
+
const a = T(ts, "AuthData | null");
|
|
5
|
+
switch (authType) {
|
|
6
|
+
case "cookie":
|
|
7
|
+
return `/** Inject auth headers. For cookie auth, credentials are handled via AUTH_WITH_CREDENTIALS. */
|
|
8
|
+
export function withAuthHeaders(headers${hd}, _auth${a})${hd}{
|
|
9
|
+
return headers;
|
|
10
|
+
}`;
|
|
11
|
+
case "bearer":
|
|
12
|
+
return `/** Inject Bearer token into request headers. */
|
|
13
|
+
export function withAuthHeaders(headers${hd}, auth${a})${hd}{
|
|
14
|
+
if (auth?.token) {
|
|
15
|
+
headers.set("Authorization", \`Bearer \${auth.token}\`);
|
|
16
|
+
}
|
|
17
|
+
return headers;
|
|
18
|
+
}`;
|
|
19
|
+
case "custom":
|
|
20
|
+
return `/** Inject custom auth headers. Edit this function to match your backend. */
|
|
21
|
+
export function withAuthHeaders(headers${hd}, auth${a})${hd}{
|
|
22
|
+
// --- EDIT THIS BLOCK FOR YOUR BACKEND ---
|
|
23
|
+
// if (auth?.token) {
|
|
24
|
+
// headers.set("X-Auth-Token", auth.token);
|
|
25
|
+
// }
|
|
26
|
+
// --- END OF EDITABLE BLOCK ---
|
|
27
|
+
return headers;
|
|
28
|
+
}`;
|
|
29
|
+
default:
|
|
30
|
+
return `export function withAuthHeaders(headers${hd}, auth${a})${hd}{
|
|
31
|
+
if (auth?.token) {
|
|
32
|
+
headers.set("Authorization", \`Bearer \${auth.token}\`);
|
|
33
|
+
}
|
|
34
|
+
return headers;
|
|
35
|
+
}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function generateIsAuthUrl(refreshPath, userEndpoint, ts) {
|
|
39
|
+
return `/** Check if a URL is an auth endpoint that should bypass the SW cache. */
|
|
40
|
+
export function isAuthUrl(url${T(ts, "string")})${R(ts, "boolean")}{
|
|
41
|
+
const authPaths = [
|
|
42
|
+
"/login",
|
|
43
|
+
"/logout",
|
|
44
|
+
"/register",
|
|
45
|
+
"/api/login",
|
|
46
|
+
"/api/logout",
|
|
47
|
+
"/api/register",
|
|
48
|
+
"${refreshPath}",
|
|
49
|
+
"${userEndpoint}",
|
|
50
|
+
];
|
|
51
|
+
return authPaths.some((path) => url.includes(path));
|
|
52
|
+
}`;
|
|
53
|
+
}
|
|
54
|
+
function generateEnsureValidAuth(cookieAuth, ts, refreshPath, ext) {
|
|
55
|
+
const tokenArg = cookieAuth ? "" : "auth?.token";
|
|
56
|
+
return `/** Try to restore the session after page refresh — delegates to refreshSession() in ./user.${ext}. */
|
|
57
|
+
let restorePromise${T(ts, "Promise<AuthData | null> | null")} = null;
|
|
58
|
+
|
|
59
|
+
async function tryRestoreSession()${R(ts, "Promise<AuthData | null>")}{
|
|
60
|
+
if (restorePromise) {
|
|
61
|
+
try { return await restorePromise; } finally { restorePromise = null; }
|
|
62
|
+
}
|
|
63
|
+
restorePromise = (async () => {
|
|
64
|
+
try {
|
|
65
|
+
const response = await refreshSession();
|
|
66
|
+
if (!response.ok) return null;
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
const userData = await loadUserData();
|
|
69
|
+
const auth = { ...userData, token: data.token, expiresAt: data.expiresAt };
|
|
70
|
+
await setAuth(auth);
|
|
71
|
+
return auth;
|
|
72
|
+
} catch { return null; }
|
|
73
|
+
})();
|
|
74
|
+
try { return await restorePromise; } finally { restorePromise = null; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let refreshPromise${T(ts, "Promise<AuthData | null> | null")} = null;
|
|
78
|
+
|
|
79
|
+
export async function ensureValidAuth()${R(ts, "Promise<AuthData | null>")}{
|
|
80
|
+
const auth = await getAuth();
|
|
81
|
+
if (!auth) return null;
|
|
82
|
+
|
|
83
|
+
// Token missing after page refresh — try silent session restoration
|
|
84
|
+
if (!auth.token) {
|
|
85
|
+
return tryRestoreSession();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
|
|
89
|
+
|
|
90
|
+
if (!refreshPromise) {
|
|
91
|
+
refreshPromise = (async () => {
|
|
92
|
+
try {
|
|
93
|
+
const response = await refreshSession(${tokenArg});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
await clearAuth();
|
|
96
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const data = await response.json();
|
|
101
|
+
const updated = { ...auth, token: data.token, expiresAt: data.expiresAt };
|
|
102
|
+
await setAuth(updated);
|
|
103
|
+
return updated;
|
|
104
|
+
} catch {
|
|
105
|
+
await clearAuth();
|
|
106
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
return await refreshPromise;
|
|
114
|
+
} finally {
|
|
115
|
+
refreshPromise = null;
|
|
116
|
+
}
|
|
117
|
+
}`;
|
|
118
|
+
}
|
|
119
|
+
export function generateAuthStoreCode(ctx, authType, refreshPath, userEndpoint) {
|
|
120
|
+
const { ext, ts } = ctx;
|
|
121
|
+
const authDataInterface = ts
|
|
122
|
+
? `export interface AuthData {
|
|
123
|
+
token?: string;
|
|
124
|
+
user?: Record<string, unknown>;
|
|
125
|
+
expiresAt?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface AuthResponse extends Record<string, unknown> {
|
|
129
|
+
// Uncomment and type the fields your backend's login response returns:
|
|
130
|
+
// token: string;
|
|
131
|
+
// user: Record<string, unknown>;
|
|
132
|
+
// expiresAt?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
`
|
|
136
|
+
: "";
|
|
137
|
+
const createAuthFromResponseBlock = ts
|
|
138
|
+
? `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
|
|
139
|
+
export function createAuthFromResponse(response: AuthResponse): AuthData {
|
|
140
|
+
// --- EDIT THIS TO MATCH YOUR BACKEND ---
|
|
141
|
+
const data = response as Record<string, unknown>;
|
|
142
|
+
return {
|
|
143
|
+
token: data.token as string,
|
|
144
|
+
user: data.user as Record<string, unknown> | undefined,
|
|
145
|
+
expiresAt: data.expiresAt as number | undefined,
|
|
146
|
+
};
|
|
147
|
+
// --- END OF EDITABLE BLOCK ---
|
|
148
|
+
}
|
|
149
|
+
`
|
|
150
|
+
: `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
|
|
151
|
+
export function createAuthFromResponse(response) {
|
|
152
|
+
// --- EDIT THIS TO MATCH YOUR BACKEND ---
|
|
153
|
+
return {
|
|
154
|
+
token: response.token,
|
|
155
|
+
user: response.user,
|
|
156
|
+
expiresAt: response.expiresAt,
|
|
157
|
+
};
|
|
158
|
+
// --- END OF EDITABLE BLOCK ---
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
return `/**
|
|
162
|
+
* Auth Store — Token in memory only; user info in IndexedDB for offline access.
|
|
163
|
+
*
|
|
164
|
+
* Security:
|
|
165
|
+
* The Bearer token lives in JavaScript memory and is cleared on page refresh.
|
|
166
|
+
* Only { user, expiresAt } is persisted to IndexedDB so the app can display
|
|
167
|
+
* user info offline. After a page refresh, re-login is required.
|
|
168
|
+
*
|
|
169
|
+
* Usage:
|
|
170
|
+
* import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth/store.${ext}";
|
|
171
|
+
*
|
|
172
|
+
* await setAuth({ token, user, expiresAt });
|
|
173
|
+
* const auth = await getAuth();
|
|
174
|
+
* await clearAuth();
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
import { refreshSession } from "./user.${ext}";
|
|
178
|
+
|
|
179
|
+
${authDataInterface}const DB_NAME = "swoff-auth";
|
|
180
|
+
const STORE_NAME = "auth";
|
|
181
|
+
// Bump this when adding new indexes/stores for schema migration
|
|
182
|
+
const DB_VERSION = 1;
|
|
183
|
+
|
|
184
|
+
let memoryAuth${T(ts, "AuthData | null")} = null;
|
|
185
|
+
|
|
186
|
+
${createAuthFromResponseBlock}
|
|
187
|
+
function openAuthDB()${R(ts, "Promise<IDBDatabase>")}{
|
|
188
|
+
return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
|
|
189
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
190
|
+
request.onupgradeneeded = (e) => {
|
|
191
|
+
const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
|
|
192
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
193
|
+
db.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
|
|
197
|
+
request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function persistUserData(authData${T(ts, "AuthData | null")})${R(ts, "Promise<void>")}{
|
|
202
|
+
// Only persist { user, expiresAt } — never the token
|
|
203
|
+
const userData = { user: authData?.user, expiresAt: authData?.expiresAt };
|
|
204
|
+
const db = await openAuthDB();
|
|
205
|
+
return new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
206
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
207
|
+
const store = tx.objectStore(STORE_NAME);
|
|
208
|
+
const request = store.put({ key: "session", value: userData });
|
|
209
|
+
request.onsuccess = () => resolve();
|
|
210
|
+
request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function loadUserData()${R(ts, "Promise<{ user?: Record<string, unknown>; expiresAt?: number } | null>")}{
|
|
215
|
+
const db = await openAuthDB();
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
218
|
+
const store = tx.objectStore(STORE_NAME);
|
|
219
|
+
const request = store.get("session");
|
|
220
|
+
request.onsuccess = () => resolve((request${AS(ts, "IDBRequest")}).result?.value ?? null);
|
|
221
|
+
request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function clearPersistedData()${R(ts, "Promise<void>")}{
|
|
226
|
+
const db = await openAuthDB();
|
|
227
|
+
return new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
228
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
229
|
+
const store = tx.objectStore(STORE_NAME);
|
|
230
|
+
const request = store.delete("session");
|
|
231
|
+
request.onsuccess = () => resolve();
|
|
232
|
+
request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Store auth data in memory and persist user info to IndexedDB for offline access. */
|
|
237
|
+
export async function setAuth(authData${T(ts, "AuthData")})${R(ts, "Promise<void>")}{
|
|
238
|
+
memoryAuth = authData;
|
|
239
|
+
await persistUserData(authData);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Get auth data from memory (or IndexedDB if memory is empty after page refresh). */
|
|
243
|
+
export async function getAuth()${R(ts, "Promise<AuthData | null>")}{
|
|
244
|
+
if (memoryAuth) return memoryAuth;
|
|
245
|
+
|
|
246
|
+
const userData = await loadUserData();
|
|
247
|
+
if (userData) {
|
|
248
|
+
memoryAuth = userData;
|
|
249
|
+
}
|
|
250
|
+
return memoryAuth;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Clear auth from memory and IndexedDB. Call on logout or 401. */
|
|
254
|
+
export async function clearAuth()${R(ts, "Promise<void>")}{
|
|
255
|
+
memoryAuth = null;
|
|
256
|
+
await clearPersistedData();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Check if auth exists and has not expired. Returns true if no expiresAt is set. */
|
|
260
|
+
export function isAuthValid(auth${T(ts, "AuthData | null")})${R(ts, "boolean")}{
|
|
261
|
+
if (!auth) return false;
|
|
262
|
+
if (!auth.expiresAt) return true;
|
|
263
|
+
return Date.now() < auth.expiresAt;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
${authType === "cookie" ? `export const AUTH_WITH_CREDENTIALS = true;` : `export const AUTH_WITH_CREDENTIALS = false;`}
|
|
267
|
+
|
|
268
|
+
${generateWithAuthHeaders(authType, ts)}
|
|
269
|
+
${generateIsAuthUrl(refreshPath, userEndpoint, ts)}
|
|
270
|
+
${generateEnsureValidAuth(authType === "cookie", ts, refreshPath, ext)}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=auth-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../src/runtime/auth-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { T, R, PT, AS } from "./utils.js";
|
|
2
|
+
export function generateAuthUserCode(ctx, refreshPath, userEndpoint, authType) {
|
|
3
|
+
const { ext, ts } = ctx;
|
|
4
|
+
const cookieAuth = authType === "cookie";
|
|
5
|
+
const authTypeName = cookieAuth ? "cookie" : "bearer";
|
|
6
|
+
const refreshDocs = cookieAuth
|
|
7
|
+
? ` *
|
|
8
|
+
* Cookie auth: credentials ("include") are automatically sent with the request.
|
|
9
|
+
* The browser attaches the session cookie — no token management needed.
|
|
10
|
+
* Change method, URL, headers, or body below if your server expects something different.
|
|
11
|
+
* Example: if your refresh endpoint expects a specific content-type, add:
|
|
12
|
+
* headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
13
|
+
* Example: if your endpoint uses PUT instead of POST:
|
|
14
|
+
* method: "PUT",`
|
|
15
|
+
: ` *
|
|
16
|
+
* Bearer auth: the optional \`token\` parameter is passed by auth/store.ts when the user
|
|
17
|
+
* has an existing session. The generated template adds "Authorization: Bearer <token>".
|
|
18
|
+
* If your refresh endpoint doesn't need the token (e.g., uses a refresh token cookie),
|
|
19
|
+
* simply remove the \`token\` parameter and the header logic below.
|
|
20
|
+
* To send a refresh token in the body instead:
|
|
21
|
+
* body: JSON.stringify({ refreshToken: "..." }),
|
|
22
|
+
* To change the content type:
|
|
23
|
+
* headers: { "Content-Type": "application/x-www-form-urlencoded" },`;
|
|
24
|
+
const userDocs = cookieAuth
|
|
25
|
+
? ` *
|
|
26
|
+
* Cookie auth: credentials ("include") sends the session cookie automatically.
|
|
27
|
+
* Edit the URL, method, or headers below if your user endpoint differs.
|
|
28
|
+
* Example: if your server uses /api/v1/me with a different method:
|
|
29
|
+
* const response = await fetch(API_BASE + "/api/v1/me", {
|
|
30
|
+
* method: "GET",
|
|
31
|
+
* credentials: "include",
|
|
32
|
+
* });`
|
|
33
|
+
: ` *
|
|
34
|
+
* Bearer auth: add the Authorization header using the user's token.
|
|
35
|
+
* Get the token from auth/store.ts:
|
|
36
|
+
* import { getAuth } from "./store.${ext}";
|
|
37
|
+
* const auth = await getAuth();
|
|
38
|
+
* const response = await fetch(API_BASE + "${userEndpoint}", {
|
|
39
|
+
* headers: { "Authorization": \`Bearer \${auth?.token}\` },
|
|
40
|
+
* });
|
|
41
|
+
* If your endpoint uses a different method or body, edit accordingly below.`;
|
|
42
|
+
return `/**
|
|
43
|
+
* Auth User — refresh session and fetch current user.
|
|
44
|
+
*
|
|
45
|
+
* This file is YOUR control panel for auth HTTP calls. swoff generates sensible
|
|
46
|
+
* defaults based on your swoff.config.json auth type (cookie/bearer), then you
|
|
47
|
+
* edit the functions below to match your server exactly.
|
|
48
|
+
*
|
|
49
|
+
* Both functions are imported by swoff's auth store (auth/store.${ext}) so your
|
|
50
|
+
* edits flow through to session refresh, 401 retry, and user caching.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* import { refreshSession, fetchCurrentUser, cacheUser, getCachedUser, clearCachedUser } from "./auth/user.${ext}";
|
|
54
|
+
*
|
|
55
|
+
* // Refresh the session (called automatically by the auth store)
|
|
56
|
+
* const response = await refreshSession();
|
|
57
|
+
* const data = await response.json();
|
|
58
|
+
*
|
|
59
|
+
* // Fetch and cache the current user
|
|
60
|
+
* const user = await fetchCurrentUser();
|
|
61
|
+
*
|
|
62
|
+
* // Get cached user (offline-capable)
|
|
63
|
+
* const cached = await getCachedUser();
|
|
64
|
+
*
|
|
65
|
+
* // Clear on logout
|
|
66
|
+
* await clearCachedUser();
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
import { API_BASE } from "../config.${ext}";
|
|
70
|
+
|
|
71
|
+
const DB_NAME = "swoff-auth-user";
|
|
72
|
+
const STORE_NAME = "current-user";
|
|
73
|
+
// Bump this when adding new indexes/stores for schema migration
|
|
74
|
+
const DB_VERSION = 1;
|
|
75
|
+
|
|
76
|
+
function openAuthDB()${R(ts, "Promise<IDBDatabase>")}{
|
|
77
|
+
return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
|
|
78
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
79
|
+
request.onupgradeneeded = (e) => {
|
|
80
|
+
const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
|
|
81
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
82
|
+
db.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
|
|
86
|
+
request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Refresh the auth session.
|
|
92
|
+
*
|
|
93
|
+
* Called automatically by auth/store.{{ext}} when the access token expires
|
|
94
|
+
* (ensureValidAuth) or to restore a session after page reload (tryRestoreSession).
|
|
95
|
+
*
|
|
96
|
+
* ${authTypeName} defaults:${refreshDocs}
|
|
97
|
+
*
|
|
98
|
+
* ${cookieAuth ? "" : "@param token - Bearer token from the existing session (only passed for bearer auth)."}
|
|
99
|
+
* @returns A fetch Response. The auth store reads response.json() to extract
|
|
100
|
+
* { token, expiresAt } and merges them with cached user data.
|
|
101
|
+
*/
|
|
102
|
+
export async function refreshSession(${cookieAuth ? "" : "token" + T(ts, "string")})${R(ts, "Promise<Response>")}{
|
|
103
|
+
${cookieAuth
|
|
104
|
+
? `return fetch(API_BASE + "${refreshPath}", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
credentials: "include" as RequestCredentials,
|
|
107
|
+
});`
|
|
108
|
+
: `const headers${T(ts, "Record<string, string>")} = { "Content-Type": "application/json" };
|
|
109
|
+
if (token) headers["Authorization"] = \`Bearer \${token}\`;
|
|
110
|
+
return fetch(API_BASE + "${refreshPath}", {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers,
|
|
113
|
+
});`}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**${userDocs}
|
|
117
|
+
*/
|
|
118
|
+
export async function fetchCurrentUser()${R(ts, "Promise<Record<string, unknown>>")}{
|
|
119
|
+
${cookieAuth
|
|
120
|
+
? `const response = await fetch(API_BASE + "${userEndpoint}", {
|
|
121
|
+
credentials: "include" as RequestCredentials,
|
|
122
|
+
});`
|
|
123
|
+
: `const response = await fetch(API_BASE + "${userEndpoint}");`}
|
|
124
|
+
if (!response.ok) throw new Error("Failed to fetch user");
|
|
125
|
+
|
|
126
|
+
const user = await response.json();
|
|
127
|
+
await cacheUser(user);
|
|
128
|
+
return user;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Persist user data to IndexedDB for offline access. */
|
|
132
|
+
export async function cacheUser(user${T(ts, "Record<string, unknown>")})${R(ts, "Promise<void>")}{
|
|
133
|
+
const db = await openAuthDB();
|
|
134
|
+
return new Promise<void>((resolve, reject) => {
|
|
135
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
136
|
+
const store = tx.objectStore(STORE_NAME);
|
|
137
|
+
store.put({ key: "user", value: user });
|
|
138
|
+
tx.oncomplete = () => resolve();
|
|
139
|
+
tx.onerror = () => reject(tx.error);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Load user data from IndexedDB cache (no token — only user object survives refresh). */
|
|
144
|
+
export async function getCachedUser()${R(ts, "Promise<Record<string, unknown> | null>")}{
|
|
145
|
+
const db = await openAuthDB();
|
|
146
|
+
return new Promise${PT(ts, "Record<string, unknown> | null")}((resolve, reject) => {
|
|
147
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
148
|
+
const store = tx.objectStore(STORE_NAME);
|
|
149
|
+
const request = store.get("user");
|
|
150
|
+
request.onsuccess = () => resolve((request${AS(ts, "IDBRequest")}).result?.value ?? null);
|
|
151
|
+
request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Remove user data from IndexedDB cache. Call on logout. */
|
|
156
|
+
export async function clearCachedUser()${R(ts, "Promise<void>")}{
|
|
157
|
+
const db = await openAuthDB();
|
|
158
|
+
return new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
159
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
160
|
+
const store = tx.objectStore(STORE_NAME);
|
|
161
|
+
store.delete("user");
|
|
162
|
+
tx.oncomplete = () => resolve();
|
|
163
|
+
tx.onerror = () => reject((tx${AS(ts, "IDBTransaction")}).error);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=auth-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-user.js","sourceRoot":"","sources":["../../src/runtime/auth-user.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,UAAU,oBAAoB,CAClC,GAAmB,EACnB,WAAmB,EACnB,YAAoB,EACpB,QAAgB;IAEhB,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,MAAM,UAAU,GAAG,QAAQ,KAAK,QAAQ,CAAC;IACzC,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEtD,MAAM,WAAW,GAAG,UAAU;QAC5B,CAAC,CAAC;;;;;;;sBAOgB;QAClB,CAAC,CAAC;;;;;;;;yEAQmE,CAAC;IAExE,MAAM,QAAQ,GAAG,UAAU;QACzB,CAAC,CAAC;;;;;;;WAOK;QACP,CAAC,CAAC;;;0CAGoC,GAAG;;kDAEK,YAAY;;;+EAGiB,CAAC;IAE9E,OAAO;;;;;;;mEAO0D,GAAG;;;;gHAI0C,GAAG;;;;;;;;;;;;;;;;sCAgB7E,GAAG;;;;;;;uBAOlB,CAAC,CAAC,EAAE,EAAE,sBAAsB,CAAC;sBAC9B,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC;;;4BAGf,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC;;;;;kDAKJ,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC;+CAC7B,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC;;;;;;;;;;KAU9D,YAAY,aAAa,WAAW;;KAEpC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,sFAAsF;;;;uCAItE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,GAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,mBAAmB,CAAC;IAC1G,UAAU;QACV,CAAC,CAAC,4BAA4B,WAAW;;;MAGvC;QACF,CAAC,CAAC,gBAAgB,CAAC,CAAC,EAAE,EAAE,wBAAwB,CAAC;;6BAExB,WAAW;;;MAGlC;;;KAGD,QAAQ;;0CAE6B,CAAC,CAAC,EAAE,EAAE,kCAAkC,CAAC;IAC/E,UAAU;QACV,CAAC,CAAC,4CAA4C,YAAY;;MAExD;QACF,CAAC,CAAC,4CAA4C,YAAY,KAAK;;;;;;;;;sCAS7B,CAAC,CAAC,EAAE,EAAE,yBAAyB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;uCAYzD,CAAC,CAAC,EAAE,EAAE,yCAAyC,CAAC;;sBAEjE,EAAE,CAAC,EAAE,EAAE,gCAAgC,CAAC;;;;gDAId,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC;6CACvB,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC;;;;;yCAKxB,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;sBAEzC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;mCAKD,EAAE,CAAC,EAAE,EAAE,gBAAgB,CAAC;;;CAG1D,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { T, R } from "./utils.js";
|
|
2
|
+
export function generateBackgroundSyncCode(ctx) {
|
|
3
|
+
const { ext, ts } = ctx;
|
|
4
|
+
return `/**
|
|
5
|
+
* Swoff Background Sync
|
|
6
|
+
* Register sync events for processing mutation queue after tab close.
|
|
7
|
+
* Falls back to online event listener in unsupported browsers.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { syncWhenPossible } from './swoff/background-sync.${ext}';
|
|
11
|
+
*
|
|
12
|
+
* await syncWhenPossible({
|
|
13
|
+
* method: "POST",
|
|
14
|
+
* url: "/api/todos",
|
|
15
|
+
* body: { title: "Grocery" },
|
|
16
|
+
* tags: ["todos"],
|
|
17
|
+
* tags: ["todos"],
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { queueMutation, processMutationQueue, getPendingCount } from "./queue.${ext}";
|
|
22
|
+
|
|
23
|
+
const SYNC_TAG = "sync-mutations";
|
|
24
|
+
|
|
25
|
+
async function registerSync()${R(ts, "Promise<void>")}{
|
|
26
|
+
if (!("serviceWorker" in navigator) || !("SyncManager" in window)) {
|
|
27
|
+
window.addEventListener("online", processMutationQueue, { once: true });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const registration = await navigator.serviceWorker.ready;
|
|
33
|
+
await registration.sync.register(SYNC_TAG);
|
|
34
|
+
} catch {
|
|
35
|
+
window.addEventListener("online", processMutationQueue, { once: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Queue a mutation and register a background sync event so it runs even after tab close. */
|
|
40
|
+
export async function syncWhenPossible(mutation${T(ts, "object")})${R(ts, "Promise<void>")}{
|
|
41
|
+
await queueMutation(mutation);
|
|
42
|
+
await registerSync();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Re-register a background sync if mutations are still pending. Called automatically after each sync cycle. */
|
|
46
|
+
export async function retrySync()${R(ts, "Promise<void>")}{
|
|
47
|
+
if (!("serviceWorker" in navigator) || !("SyncManager" in window)) return;
|
|
48
|
+
const count = await getPendingCount();
|
|
49
|
+
if (count > 0) {
|
|
50
|
+
await registerSync();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof window !== "undefined") {
|
|
55
|
+
window.addEventListener("mutation-sync-complete", retrySync);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=background-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../src/runtime/background-sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,0BAA0B,CAAC,GAAmB;IAC5D,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;iEAMwD,GAAG;;;;;;;;;;;gFAWY,GAAG;;;;+BAIpD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;iDAeJ,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;mCAMvD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;CAWxD,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { T, R } from "./utils.js";
|
|
2
|
+
export function generateCacheCode(ctx) {
|
|
3
|
+
const { ext, ts } = ctx;
|
|
4
|
+
return `/**
|
|
5
|
+
* Swoff Cache Invalidation
|
|
6
|
+
* Framework-agnostic cache tag invalidation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { invalidateByTag, invalidateByTags } from './swoff/cache.${ext}';
|
|
10
|
+
*
|
|
11
|
+
* // After a mutation, invalidate related cache
|
|
12
|
+
* await invalidateByTag("todos");
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Invalidate all cached responses tagged with the given tag. Sends INVALIDATE_TAG to the SW; the client-injector dispatches cache-invalidated on SW confirmation. */
|
|
16
|
+
export async function invalidateByTag(tag${T(ts, "string")})${R(ts, "Promise<void>")}{
|
|
17
|
+
if (!navigator.serviceWorker?.controller) return;
|
|
18
|
+
navigator.serviceWorker.controller.postMessage({
|
|
19
|
+
type: "INVALIDATE_TAG",
|
|
20
|
+
tag,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Invalidate all cached responses matching any of the given tags. */
|
|
25
|
+
export async function invalidateByTags(tags${T(ts, "string[]")})${R(ts, "Promise<void>")}{
|
|
26
|
+
await Promise.all(tags.map((tag) => invalidateByTag(tag)));
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,iBAAiB,CAAC,GAAmB;IACnD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;wEAK+D,GAAG;;;;;;;2CAOhC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;6CASvC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;CAGvF,CAAC;AACF,CAAC"}
|