@swoff/cli 0.3.13 → 0.3.15
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 +28 -32
- package/dist/index.js +4 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +4 -9
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/clean.js +8 -17
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +16 -30
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +33 -7
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/init.js +259 -23
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +2 -4
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +34 -25
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/minimal-config.js +64 -0
- package/dist/lib/config/minimal-config.js.map +1 -0
- package/dist/lib/config/validator.js +219 -110
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/api-config.js +1 -1
- package/dist/lib/generators/file-generators/api-config.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-adapter.js +13 -0
- package/dist/lib/generators/file-generators/auth-adapter.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-check.js +40 -0
- package/dist/lib/generators/file-generators/auth-check.js.map +1 -0
- package/dist/lib/generators/file-generators/auth-store.js +2 -5
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +1 -1
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -1
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector-bundle.js +15 -0
- package/dist/lib/generators/file-generators/client-injector-bundle.js.map +1 -0
- package/dist/lib/generators/file-generators/client-injector.js +2 -1
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/connectivity.js +12 -0
- package/dist/lib/generators/file-generators/connectivity.js.map +1 -0
- package/dist/lib/generators/file-generators/context.js +7 -0
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-framework-adapters.js +29 -20
- package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +2 -2
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +51 -1
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +2 -3
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +2 -2
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
- package/dist/lib/generators/file-generators/open-db.js +6 -0
- package/dist/lib/generators/file-generators/open-db.js.map +1 -0
- package/dist/lib/generators/file-generators/push.js +1 -2
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/pwa-install.js +2 -6
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/server-push.js +1 -1
- package/dist/lib/generators/file-generators/server-push.js.map +1 -1
- package/dist/lib/generators/file-generators/storage.js +6 -0
- package/dist/lib/generators/file-generators/storage.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-generator-build.js +105 -48
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +0 -4
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-template.js +8 -9
- package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
- package/dist/lib/generators/file-generators/swoff-api-bundle.js +51 -0
- package/dist/lib/generators/file-generators/swoff-api-bundle.js.map +1 -0
- package/dist/lib/generators/sw-build-utils.js +69 -0
- package/dist/lib/generators/sw-build-utils.js.map +1 -0
- package/dist/lib/generators/sw-generator.js +23 -34
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +77 -23
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +20 -95
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-precache.js +126 -0
- package/dist/lib/generators/sw-sections/background-precache.js.map +1 -0
- package/dist/lib/generators/sw-sections/background-sync-handler.js +74 -72
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js +102 -0
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js.map +1 -0
- package/dist/lib/generators/sw-sections/config-header.js +2 -3
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/default-template.js +64 -7
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +621 -495
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/install-handler.js +41 -23
- package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/message-handler.js +34 -8
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +10 -14
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/shared.js +70 -0
- package/dist/lib/generators/sw-sections/shared.js.map +1 -0
- package/dist/lib/generators/sw-sections/sw-push.js +2 -6
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -1
- package/dist/lib/generators/sw-sections/tag-management.js +55 -86
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +183 -59
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +84 -79
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/feature-registry.js +198 -0
- package/dist/lib/shared/feature-registry.js.map +1 -0
- package/dist/lib/utils/detect-framework.js +52 -9
- package/dist/lib/utils/detect-framework.js.map +1 -1
- package/dist/runtime/api-config.js +2 -2
- package/dist/runtime/api-config.js.map +1 -1
- package/dist/runtime/auth-adapter.js +179 -0
- package/dist/runtime/auth-adapter.js.map +1 -0
- package/dist/runtime/auth-state.js +16 -6
- package/dist/runtime/auth-state.js.map +1 -1
- package/dist/runtime/auth-store.js +219 -213
- package/dist/runtime/auth-store.js.map +1 -1
- package/dist/runtime/background-sync.js +5 -2
- package/dist/runtime/background-sync.js.map +1 -1
- package/dist/runtime/cache.js +91 -3
- package/dist/runtime/cache.js.map +1 -1
- package/dist/runtime/client-injector-bundle.js +314 -0
- package/dist/runtime/client-injector-bundle.js.map +1 -0
- package/dist/runtime/client-injector.js +188 -30
- package/dist/runtime/client-injector.js.map +1 -1
- package/dist/runtime/connectivity.js +83 -0
- package/dist/runtime/connectivity.js.map +1 -0
- package/dist/runtime/fetch-wrapper.js +26 -59
- package/dist/runtime/fetch-wrapper.js.map +1 -1
- package/dist/runtime/gql-wrapper.js +15 -9
- package/dist/runtime/gql-wrapper.js.map +1 -1
- package/dist/runtime/invalidation-tags.js +12 -89
- package/dist/runtime/invalidation-tags.js.map +1 -1
- package/dist/runtime/mutation-queue.js +83 -31
- package/dist/runtime/mutation-queue.js.map +1 -1
- package/dist/runtime/mutation-state.js +3 -3
- package/dist/runtime/mutation-state.js.map +1 -1
- package/dist/runtime/open-db.js +41 -0
- package/dist/runtime/open-db.js.map +1 -0
- package/dist/runtime/push.js +15 -27
- package/dist/runtime/push.js.map +1 -1
- package/dist/runtime/pwa-prompt.js +35 -3
- package/dist/runtime/pwa-prompt.js.map +1 -1
- package/dist/runtime/reset.js +39 -42
- package/dist/runtime/reset.js.map +1 -1
- package/dist/runtime/server-push.js +23 -13
- package/dist/runtime/server-push.js.map +1 -1
- package/dist/runtime/storage.js +32 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/runtime/sw-injector.js +19 -178
- package/dist/runtime/sw-injector.js.map +1 -1
- package/dist/runtime/swoff-api-bundle.js +1491 -0
- package/dist/runtime/swoff-api-bundle.js.map +1 -0
- package/dist/runtime/type-definitions.js +8 -7
- package/dist/runtime/type-definitions.js.map +1 -1
- package/dist/runtime/utils.js +4 -0
- package/dist/runtime/utils.js.map +1 -1
- package/package.json +9 -3
- package/templates/react/useAuth.jsx +7 -4
- package/templates/react/useAuth.tsx +14 -14
- package/templates/react/useBackgroundSync.jsx +5 -4
- package/templates/react/useBackgroundSync.tsx +5 -4
- package/templates/react/useCachedFetch.jsx +8 -0
- package/templates/react/useCachedFetch.tsx +8 -0
- package/templates/react/useMutation.jsx +60 -56
- package/templates/react/useMutation.tsx +65 -64
- package/templates/react/useMutationQueue.jsx +5 -1
- package/templates/react/useMutationQueue.tsx +8 -2
- package/templates/react/useMutationState.jsx +3 -4
- package/templates/react/useMutationState.tsx +4 -8
- package/templates/react/useNetworkStatus.jsx +57 -17
- package/templates/react/useNetworkStatus.tsx +42 -20
- package/templates/react/useOfflineAnalytics.jsx +42 -0
- package/templates/react/useOfflineAnalytics.tsx +50 -0
- package/templates/react/usePwaInstall.jsx +26 -0
- package/templates/react/usePwaInstall.tsx +26 -0
- package/templates/react/useSWUpdate.jsx +7 -52
- package/templates/react/useSWUpdate.tsx +12 -51
- package/templates/react/useStorageEstimate.jsx +56 -0
- package/templates/react/useStorageEstimate.tsx +67 -0
- package/dist/__tests__/assemble-sw.test.js +0 -145
- package/dist/__tests__/assemble-sw.test.js.map +0 -1
- package/dist/__tests__/cli-integration.test.js +0 -231
- package/dist/__tests__/cli-integration.test.js.map +0 -1
- package/dist/__tests__/config-types.test.js +0 -124
- package/dist/__tests__/config-types.test.js.map +0 -1
- package/dist/__tests__/detect-language.test.js +0 -51
- package/dist/__tests__/detect-language.test.js.map +0 -1
- package/dist/__tests__/file-generators.test.js +0 -228
- package/dist/__tests__/file-generators.test.js.map +0 -1
- package/dist/__tests__/glob-matcher.test.js +0 -86
- package/dist/__tests__/glob-matcher.test.js.map +0 -1
- package/dist/__tests__/invalidation-tags.test.js +0 -283
- package/dist/__tests__/invalidation-tags.test.js.map +0 -1
- package/dist/__tests__/loader.test.js +0 -77
- package/dist/__tests__/loader.test.js.map +0 -1
- package/dist/__tests__/validator.test.js +0 -340
- package/dist/__tests__/validator.test.js.map +0 -1
- package/dist/lib/commands/add.js +0 -120
- package/dist/lib/commands/add.js.map +0 -1
- package/dist/lib/commands/assets.js +0 -45
- package/dist/lib/commands/assets.js.map +0 -1
- package/dist/lib/commands/generate-assets.js +0 -44
- package/dist/lib/commands/generate-assets.js.map +0 -1
- package/dist/lib/commands/info.js +0 -183
- package/dist/lib/commands/info.js.map +0 -1
- package/dist/lib/generators/asset-generator/dependency-resolver.js +0 -85
- package/dist/lib/generators/asset-generator/dependency-resolver.js.map +0 -1
- package/dist/lib/generators/asset-generator/generate.js +0 -111
- package/dist/lib/generators/asset-generator/generate.js.map +0 -1
- package/dist/lib/generators/asset-generator/guide.js +0 -43
- package/dist/lib/generators/asset-generator/guide.js.map +0 -1
- package/dist/lib/generators/asset-generator/html-patcher.js +0 -65
- package/dist/lib/generators/asset-generator/html-patcher.js.map +0 -1
- package/dist/lib/generators/asset-generator/ico-encoder.js +0 -29
- package/dist/lib/generators/asset-generator/ico-encoder.js.map +0 -1
- package/dist/lib/generators/asset-generator/manifest-patcher.js +0 -23
- package/dist/lib/generators/asset-generator/manifest-patcher.js.map +0 -1
- package/dist/lib/generators/asset-generator/maskable.js +0 -15
- package/dist/lib/generators/asset-generator/maskable.js.map +0 -1
- package/dist/lib/generators/asset-generator/rasterize.js +0 -32
- package/dist/lib/generators/asset-generator/rasterize.js.map +0 -1
- package/dist/lib/generators/asset-generator/sizes.js +0 -25
- package/dist/lib/generators/asset-generator/sizes.js.map +0 -1
- package/dist/lib/generators/file-generators/auth-fetch.js +0 -151
- package/dist/lib/generators/file-generators/auth-fetch.js.map +0 -1
- package/dist/lib/generators/file-generators/auth-user.js +0 -13
- package/dist/lib/generators/file-generators/auth-user.js.map +0 -1
- package/dist/lib/generators/file-generators/fetch-state.js +0 -6
- package/dist/lib/generators/file-generators/fetch-state.js.map +0 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +0 -44
- package/dist/lib/generators/file-generators/generate-hooks.js.map +0 -1
- package/dist/lib/generators/file-generators/indexeddb.js +0 -74
- package/dist/lib/generators/file-generators/indexeddb.js.map +0 -1
- package/dist/lib/generators/file-generators/manifest.js +0 -46
- package/dist/lib/generators/file-generators/manifest.js.map +0 -1
- package/dist/lib/generators/file-generators/mutation-reconcile.js +0 -57
- package/dist/lib/generators/file-generators/mutation-reconcile.js.map +0 -1
- package/dist/lib/generators/file-generators/quick-readme.js +0 -21
- package/dist/lib/generators/file-generators/quick-readme.js.map +0 -1
- package/dist/lib/generators/file-generators/reconcile.js +0 -50
- package/dist/lib/generators/file-generators/reconcile.js.map +0 -1
- package/dist/lib/generators/file-generators/store.js +0 -86
- package/dist/lib/generators/file-generators/store.js.map +0 -1
- package/dist/lib/generators/file-generators/sw-push.js +0 -67
- package/dist/lib/generators/file-generators/sw-push.js.map +0 -1
- package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +0 -1
- package/dist/lib/utils/build-script.js +0 -35
- package/dist/lib/utils/build-script.js.map +0 -1
- package/dist/lib/utils/tty-status.js +0 -21
- package/dist/lib/utils/tty-status.js.map +0 -1
- package/dist/runtime/auth-user.js +0 -168
- package/dist/runtime/auth-user.js.map +0 -1
- package/dist/runtime/fetch-state.js +0 -42
- package/dist/runtime/fetch-state.js.map +0 -1
- package/dist/runtime/pwa-index.js +0 -11
- package/dist/runtime/pwa-index.js.map +0 -1
- package/dist/runtime/pwa-injector.js +0 -44
- package/dist/runtime/pwa-injector.js.map +0 -1
- package/dist/runtime/pwa-install.js +0 -83
- package/dist/runtime/pwa-install.js.map +0 -1
- package/templates/react/useCacheInvalidation.jsx +0 -19
- package/templates/react/useCacheInvalidation.tsx +0 -52
- package/templates/react/useIsFetching.jsx +0 -32
- package/templates/react/useIsFetching.tsx +0 -32
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { T, R, PT, AS } from "./utils.js";
|
|
2
|
-
export function generateMutationQueueCode(ctx, authEnabled, batchSize, batchDelayMs, maxRetries, retryBackoffMs) {
|
|
2
|
+
export function generateMutationQueueCode(ctx, authEnabled, batchSize, batchDelayMs, maxRetries, retryBackoffMs, retryMaxBackoffMs, retryJitterMs) {
|
|
3
3
|
const { ext, ts } = ctx;
|
|
4
|
-
const
|
|
5
|
-
? `import { getAuth } from "../auth/store.${ext}";
|
|
4
|
+
const authImports = authEnabled
|
|
5
|
+
? `import { getAuth, ensureValidAuth, clearAuth } from "../auth/store.${ext}";
|
|
6
6
|
`
|
|
7
7
|
: "";
|
|
8
|
-
const additionalImports = `import { invalidateByTags } from "../cache/
|
|
8
|
+
const additionalImports = `import { invalidateByTags } from "../cache/invalidate.${ext}";
|
|
9
9
|
${ts
|
|
10
10
|
? `import type { MutationQueueItem } from "../swoff.d.ts";
|
|
11
11
|
`
|
|
@@ -18,6 +18,41 @@ ${ts
|
|
|
18
18
|
const authHeaderSpread = authEnabled
|
|
19
19
|
? ` ...authHeader,\n`
|
|
20
20
|
: " ";
|
|
21
|
+
const auth401ReplayBlock = authEnabled
|
|
22
|
+
? ` if (response.status === 401) {
|
|
23
|
+
// Auth failure — try silent refresh (/refresh endpoint)
|
|
24
|
+
const refreshed = await ensureValidAuth();
|
|
25
|
+
if (refreshed?.token) {
|
|
26
|
+
// Refresh succeeded — retry the mutation once
|
|
27
|
+
const retryHeader${T(ts, "Record<string, string>")} = refreshed?.token ? { Authorization: \`Bearer \${refreshed.token}\` } : {};
|
|
28
|
+
const retryResponse = await fetch(item.url, {
|
|
29
|
+
method: item.method,
|
|
30
|
+
headers: {
|
|
31
|
+
...(contentType ? { "Content-Type": contentType } : {}),
|
|
32
|
+
...item.headers,
|
|
33
|
+
...retryHeader,
|
|
34
|
+
},
|
|
35
|
+
...(replayBody != null ? { body: replayBody } : {}),
|
|
36
|
+
});
|
|
37
|
+
if (retryResponse.ok) {
|
|
38
|
+
// Success after auth refresh — normal flow
|
|
39
|
+
if (item.tags && item.tags.length > 0) {
|
|
40
|
+
await invalidateByTags(item.tags);
|
|
41
|
+
}
|
|
42
|
+
await removeFromQueue(item.id, db);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Retry still failed — authenticated but not authorized for this resource
|
|
46
|
+
// Drop this mutation immediately (don't retry)
|
|
47
|
+
await removeFromQueue(item.id, db);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// Refresh failed — session expired. ClearAuth cascades to clear queue + caches.
|
|
51
|
+
await clearAuth();
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
: "";
|
|
21
56
|
const code = `/**
|
|
22
57
|
* Swoff Mutation Queue
|
|
23
58
|
* Queue offline writes and sync when connection returns.
|
|
@@ -44,33 +79,36 @@ ${ts
|
|
|
44
79
|
* batchDelayMs: ${batchDelayMs} — delay between mutations (rate limiting)
|
|
45
80
|
* maxRetries: ${maxRetries} — max retries before dropping
|
|
46
81
|
* retryBackoffMs: ${retryBackoffMs} — exponential backoff base
|
|
82
|
+
* retryMaxBackoffMs: ${retryMaxBackoffMs} — max backoff cap
|
|
83
|
+
* retryJitterMs: ${retryJitterMs} — jitter for randomization
|
|
47
84
|
*/
|
|
48
85
|
|
|
49
|
-
${
|
|
86
|
+
${authImports}${additionalImports}const DB_NAME = "swoff-queue";
|
|
50
87
|
const STORE_NAME = "mutations";
|
|
51
|
-
// Bump this when adding new indexes/stores for schema migration
|
|
52
|
-
const DB_VERSION = 1;
|
|
53
88
|
const BATCH_SIZE = ${batchSize};
|
|
54
89
|
const BATCH_DELAY_MS = ${batchDelayMs};
|
|
55
90
|
const MAX_RETRIES = ${maxRetries};
|
|
56
91
|
const RETRY_BACKOFF_MS = ${retryBackoffMs};
|
|
92
|
+
const RETRY_MAX_BACKOFF_MS = ${retryMaxBackoffMs};
|
|
93
|
+
const RETRY_JITTER_MS = ${retryJitterMs};
|
|
94
|
+
|
|
95
|
+
function backoffDelay(attempt${T(ts, "number")}) {
|
|
96
|
+
const delay = Math.min(RETRY_BACKOFF_MS * Math.pow(2, attempt), RETRY_MAX_BACKOFF_MS);
|
|
97
|
+
return delay + (RETRY_JITTER_MS > 0 ? Math.random() * RETRY_JITTER_MS : 0);
|
|
98
|
+
}
|
|
57
99
|
|
|
58
100
|
function sleep(ms${T(ts, "number")})${R(ts, "Promise<void>")}{
|
|
59
101
|
return new Promise((r) => setTimeout(r, ms));
|
|
60
102
|
}
|
|
61
103
|
|
|
104
|
+
import { openDB } from "../db.${ext}";
|
|
105
|
+
|
|
62
106
|
function openQueueDB()${R(ts, "Promise<IDBDatabase>")}{
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
69
|
-
store.createIndex("by-timestamp", "timestamp");
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
|
|
73
|
-
request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
|
|
107
|
+
return openDB(DB_NAME, STORE_NAME, "id", (db) => {
|
|
108
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
109
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
110
|
+
store.createIndex("by-timestamp", "timestamp");
|
|
111
|
+
}
|
|
74
112
|
});
|
|
75
113
|
}
|
|
76
114
|
|
|
@@ -114,8 +152,8 @@ export async function queueMutation(mutation${T(ts, "Partial<MutationQueueItem>"
|
|
|
114
152
|
});
|
|
115
153
|
|
|
116
154
|
await new Promise<void>((resolve, reject) => {
|
|
117
|
-
tx.oncomplete = () => resolve();
|
|
118
|
-
tx.onerror = () => reject(tx.error);
|
|
155
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
156
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
119
157
|
});
|
|
120
158
|
|
|
121
159
|
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
@@ -151,7 +189,7 @@ ${authHeaderSpread} },
|
|
|
151
189
|
...(replayBody != null ? { body: replayBody } : {}),
|
|
152
190
|
});
|
|
153
191
|
|
|
154
|
-
if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
|
|
192
|
+
${auth401ReplayBlock} if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
|
|
155
193
|
|
|
156
194
|
if (item.tags && item.tags.length > 0) {
|
|
157
195
|
await invalidateByTags(item.tags);
|
|
@@ -161,18 +199,30 @@ ${authHeaderSpread} },
|
|
|
161
199
|
return true;
|
|
162
200
|
} catch {
|
|
163
201
|
item.retryCount++;
|
|
164
|
-
item.nextRetryAt = Date.now() +
|
|
202
|
+
item.nextRetryAt = Date.now() + backoffDelay(item.retryCount - 1);
|
|
165
203
|
await updateInQueue(item, db);
|
|
166
204
|
return false;
|
|
167
205
|
}
|
|
168
206
|
}
|
|
169
207
|
|
|
208
|
+
/** Clear all queued mutations. Called when auth context changes (logout, session expiry) — queued operations belong to the old session and must not leak. */
|
|
209
|
+
export async function clearQueue()${R(ts, "Promise<void>")}{
|
|
210
|
+
const db = await openQueueDB();
|
|
211
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
212
|
+
tx.objectStore(STORE_NAME).clear();
|
|
213
|
+
await new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
214
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
215
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
216
|
+
});
|
|
217
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
218
|
+
}
|
|
219
|
+
|
|
170
220
|
/** Process all queued mutations in order. Sends them to the server. Runs automatically when mutations are queued or on reconnection. Respects batchSize for progress reporting and batchDelayMs for rate limiting. */
|
|
171
221
|
export async function processMutationQueue()${R(ts, "Promise<void>")}{
|
|
172
222
|
if (isSyncing) return;
|
|
173
|
-
isSyncing = true;
|
|
174
223
|
|
|
175
224
|
const db = await openQueueDB();
|
|
225
|
+
isSyncing = true;
|
|
176
226
|
try {
|
|
177
227
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
178
228
|
const store = tx.objectStore(STORE_NAME);
|
|
@@ -218,8 +268,8 @@ export async function processMutationQueue()${R(ts, "Promise<void>")}{
|
|
|
218
268
|
// Emit progress after every BATCH_SIZE mutations
|
|
219
269
|
if ((succeeded + failed) % BATCH_SIZE === 0 || succeeded + failed === total) {
|
|
220
270
|
window.dispatchEvent(
|
|
221
|
-
new CustomEvent("mutation-sync-
|
|
222
|
-
detail: { succeeded, failed, total
|
|
271
|
+
new CustomEvent("mutation-sync-progress", {
|
|
272
|
+
detail: { succeeded, failed, total },
|
|
223
273
|
})
|
|
224
274
|
);
|
|
225
275
|
}
|
|
@@ -250,22 +300,24 @@ export async function flushMutations()${R(ts, "Promise<void>")}{
|
|
|
250
300
|
}
|
|
251
301
|
|
|
252
302
|
async function removeFromQueue(id${T(ts, "string")}, db${T(ts, "IDBDatabase | undefined")})${R(ts, "Promise<void>")}{
|
|
253
|
-
|
|
303
|
+
var ownDb = false;
|
|
304
|
+
if (!db) { db = await openQueueDB(); ownDb = true; }
|
|
254
305
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
255
306
|
tx.objectStore(STORE_NAME).delete(id);
|
|
256
307
|
await new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
257
|
-
tx.oncomplete = () => resolve();
|
|
258
|
-
tx.onerror = () => reject(tx.error);
|
|
308
|
+
tx.oncomplete = () => { if (ownDb) db.close(); resolve(); };
|
|
309
|
+
tx.onerror = () => { if (ownDb) db.close(); reject(tx.error); };
|
|
259
310
|
});
|
|
260
311
|
}
|
|
261
312
|
|
|
262
313
|
async function updateInQueue(item${T(ts, "MutationQueueItem")}, db${T(ts, "IDBDatabase | undefined")})${R(ts, "Promise<void>")}{
|
|
263
|
-
|
|
314
|
+
var ownDb = false;
|
|
315
|
+
if (!db) { db = await openQueueDB(); ownDb = true; }
|
|
264
316
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
265
317
|
tx.objectStore(STORE_NAME).put(item);
|
|
266
318
|
await new Promise${PT(ts, "void")}((resolve, reject) => {
|
|
267
|
-
tx.oncomplete = () => resolve();
|
|
268
|
-
tx.onerror = () => reject(tx.error);
|
|
319
|
+
tx.oncomplete = () => { if (ownDb) db.close(); resolve(); };
|
|
320
|
+
tx.onerror = () => { if (ownDb) db.close(); reject(tx.error); };
|
|
269
321
|
});
|
|
270
322
|
}
|
|
271
323
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../src/runtime/mutation-queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,UAAU,yBAAyB,CACvC,GAAmB,EACnB,WAAoB,EACpB,SAAiB,EACjB,YAAoB,EACpB,UAAkB,EAClB,cAAsB;
|
|
1
|
+
{"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../src/runtime/mutation-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T, R } from "./utils.js";
|
|
1
|
+
import { T, R, G } from "./utils.js";
|
|
2
2
|
export function generateMutationStateCode(ctx) {
|
|
3
3
|
const { ext, ts } = ctx;
|
|
4
4
|
return `/**
|
|
@@ -31,10 +31,10 @@ export interface MutationState {
|
|
|
31
31
|
timestamp${T(ts, "number")};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const mutations = new Map
|
|
34
|
+
const mutations = new Map${G(ts, "string, MutationState")}();
|
|
35
35
|
|
|
36
36
|
let listenerId = 0;
|
|
37
|
-
const listeners = new Map
|
|
37
|
+
const listeners = new Map${G(ts, "number, (state: MutationState) => void")}();
|
|
38
38
|
|
|
39
39
|
/** Track a mutation with the given ID and initial status. */
|
|
40
40
|
export function trackMutation(id${T(ts, "string")}, status${T(ts, "MutationStatus")} = "idle")${R(ts, "MutationState")}{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutation-state.js","sourceRoot":"","sources":["../../src/runtime/mutation-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"mutation-state.js","sourceRoot":"","sources":["../../src/runtime/mutation-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAErC,MAAM,UAAU,yBAAyB,CAAC,GAAmB;IAC3D,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;mGAM0F,GAAG;;;;;;;;;;;;;;;;;MAiBhG,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;UACX,CAAC,CAAC,EAAE,EAAE,gBAAgB,CAAC;SACxB,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC;QACtB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;aACX,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;;;2BAGD,CAAC,CAAC,EAAE,EAAE,uBAAuB,CAAC;;;2BAG9B,CAAC,CAAC,EAAE,EAAE,wCAAwC,CAAC;;;kCAGxC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;wCAe9E,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,wBAAwB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,sBAAsB,CAAC;;;;;;;;;;;oCAW/F,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;mCAKjE,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;qCAKtD,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,2BAA2B,CAAC;;;;;uCAKnD,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;wCAK/B,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC;;;;;yCAKvB,CAAC,CAAC,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;;;;;;;;;gDASnC,CAAC,CAAC,EAAE,EAAE,gCAAgC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC;;;;;;gCAM9E,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;;;;CAStE,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { T, R, PT, AS, O } from "./utils.js";
|
|
2
|
+
export function generateOpenDBCode(ctx) {
|
|
3
|
+
const { ts } = ctx;
|
|
4
|
+
return `/**
|
|
5
|
+
* Swoff IndexedDB Helper
|
|
6
|
+
* Generic promise-based IndexedDB open helper used by auth, queue, and push modules.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { openDB } from "./db.${ctx.ext}";
|
|
10
|
+
*
|
|
11
|
+
* const db = await openDB("my-db", "my-store", "id");
|
|
12
|
+
* // optional upgrade callback for additional indexes:
|
|
13
|
+
* const db = await openDB("my-db", "my-store", "id", (db) => {
|
|
14
|
+
* db.createIndex("by-timestamp", "timestamp");
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export function openDB(
|
|
19
|
+
name${T(ts, "string")},
|
|
20
|
+
storeName${T(ts, "string")},
|
|
21
|
+
keyPath${T(ts, "string | string[]")},
|
|
22
|
+
upgradeCallback${O(ts, "(db: IDBDatabase) => void")},
|
|
23
|
+
version${O(ts, "number")}
|
|
24
|
+
)${R(ts, "Promise<IDBDatabase>")}{
|
|
25
|
+
return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
|
|
26
|
+
const request = indexedDB.open(name, version ?? 1);
|
|
27
|
+
request.onupgradeneeded = (e) => {
|
|
28
|
+
const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
|
|
29
|
+
if (upgradeCallback) {
|
|
30
|
+
upgradeCallback(db);
|
|
31
|
+
} else if (!db.objectStoreNames.contains(storeName)) {
|
|
32
|
+
db.createObjectStore(storeName, { keyPath: keyPath${AS(ts, 'IDBObjectStoreParameters["keyPath"]')} });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
|
|
36
|
+
request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=open-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-db.js","sourceRoot":"","sources":["../../src/runtime/open-db.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IACpD,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACnB,OAAO;;;;;oCAK2B,GAAG,CAAC,GAAG;;;;;;;;;;QAUnC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;aACV,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;WACjB,CAAC,CAAC,EAAE,EAAE,mBAAmB,CAAC;mBAClB,CAAC,CAAC,EAAE,EAAE,2BAA2B,CAAC;WAC1C,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;GACvB,CAAC,CAAC,EAAE,EAAE,sBAAsB,CAAC;sBACV,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC;;;4BAGf,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC;;;;4DAIM,EAAE,CAAC,EAAE,EAAE,qCAAqC,CAAC;;;kDAGvD,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC;+CAC7B,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC;;;CAGlE,CAAC;AACF,CAAC"}
|
package/dist/runtime/push.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { T, R,
|
|
1
|
+
import { T, R, AS } from "./utils.js";
|
|
2
2
|
export function generatePushCode(ctx) {
|
|
3
3
|
const { ext, ts } = ctx;
|
|
4
|
-
const vapidKey = ctx.vapidKey;
|
|
5
4
|
return `/**
|
|
6
5
|
* Swoff Push Notifications
|
|
7
6
|
* Push subscription management with IndexedDB persistence.
|
|
8
7
|
*
|
|
9
8
|
* Usage:
|
|
10
|
-
* import { subscribeToPush, unsubscribeFromPush, isSubscribed } from './swoff/
|
|
9
|
+
* import { subscribeToPush, unsubscribeFromPush, isSubscribed } from './swoff/push-notification/index.${ext}';
|
|
10
|
+
*
|
|
11
|
+
* // Replace with your VAPID public key:
|
|
12
|
+
* const VAPID_PUBLIC_KEY = "YOUR_VAPID_PUBLIC_KEY_HERE";
|
|
11
13
|
*
|
|
12
14
|
* // Enable (triggers permission prompt)
|
|
13
15
|
* const subscription = await subscribeToPush();
|
|
@@ -24,28 +26,14 @@ export function generatePushCode(ctx) {
|
|
|
24
26
|
* push-subscription-changed - Subscribed/unsubscribed (detail: { subscribed })
|
|
25
27
|
*/
|
|
26
28
|
|
|
29
|
+
import { openDB } from "../db.${ext}";
|
|
30
|
+
|
|
27
31
|
const SUBSCRIPTION_DB = "swoff-push";
|
|
28
32
|
const SUBSCRIPTION_STORE = "subscription";
|
|
29
|
-
|
|
30
|
-
const DB_VERSION = 1;
|
|
31
|
-
const VAPID_PUBLIC_KEY = ${JSON.stringify(vapidKey)};
|
|
33
|
+
const VAPID_PUBLIC_KEY = "";
|
|
32
34
|
|
|
33
35
|
let permissionState${T(ts, "NotificationPermission | undefined")} = typeof Notification !== "undefined" ? Notification.permission : undefined;
|
|
34
36
|
|
|
35
|
-
function openPushDB()${R(ts, "Promise<IDBDatabase>")}{
|
|
36
|
-
return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
|
|
37
|
-
const request = indexedDB.open(SUBSCRIPTION_DB, DB_VERSION);
|
|
38
|
-
request.onupgradeneeded = (e) => {
|
|
39
|
-
const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
|
|
40
|
-
if (!db.objectStoreNames.contains(SUBSCRIPTION_STORE)) {
|
|
41
|
-
db.createObjectStore(SUBSCRIPTION_STORE, { keyPath: "id" });
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
|
|
45
|
-
request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
37
|
/** Request notification permission from the user. Returns true if granted. */
|
|
50
38
|
export async function requestNotificationPermission()${R(ts, "Promise<boolean>")}{
|
|
51
39
|
if (permissionState === "granted") return true;
|
|
@@ -69,7 +57,7 @@ export async function getPushSubscription()${R(ts, "Promise<PushSubscription | n
|
|
|
69
57
|
}
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
/** Subscribe to push notifications. Returns the subscription or null if permission denied.
|
|
60
|
+
/** Subscribe to push notifications. Returns the subscription or null if permission denied. Update VAPID_PUBLIC_KEY at the top with your key. */
|
|
73
61
|
export async function subscribeToPush()${R(ts, "Promise<PushSubscription | null>")}{
|
|
74
62
|
const granted = await requestNotificationPermission();
|
|
75
63
|
if (!granted) return null;
|
|
@@ -80,7 +68,7 @@ export async function subscribeToPush()${R(ts, "Promise<PushSubscription | null>
|
|
|
80
68
|
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)${AS(ts, "BufferSource")},
|
|
81
69
|
});
|
|
82
70
|
|
|
83
|
-
const db = await
|
|
71
|
+
const db = await openDB(SUBSCRIPTION_DB, SUBSCRIPTION_STORE, "id");
|
|
84
72
|
const tx = db.transaction(SUBSCRIPTION_STORE, "readwrite");
|
|
85
73
|
tx.objectStore(SUBSCRIPTION_STORE).put({
|
|
86
74
|
id: "current",
|
|
@@ -89,8 +77,8 @@ export async function subscribeToPush()${R(ts, "Promise<PushSubscription | null>
|
|
|
89
77
|
subscribedAt: Date.now(),
|
|
90
78
|
});
|
|
91
79
|
await new Promise<void>((resolve, reject) => {
|
|
92
|
-
tx.oncomplete = () => resolve();
|
|
93
|
-
tx.onerror = () => reject(tx.error);
|
|
80
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
81
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
94
82
|
});
|
|
95
83
|
|
|
96
84
|
window.dispatchEvent(
|
|
@@ -107,12 +95,12 @@ export async function unsubscribeFromPush()${R(ts, "Promise<void>")}{
|
|
|
107
95
|
|
|
108
96
|
await subscription.unsubscribe();
|
|
109
97
|
|
|
110
|
-
const db = await
|
|
98
|
+
const db = await openDB(SUBSCRIPTION_DB, SUBSCRIPTION_STORE, "id");
|
|
111
99
|
const tx = db.transaction(SUBSCRIPTION_STORE, "readwrite");
|
|
112
100
|
tx.objectStore(SUBSCRIPTION_STORE).delete("current");
|
|
113
101
|
await new Promise<void>((resolve, reject) => {
|
|
114
|
-
tx.oncomplete = () => resolve();
|
|
115
|
-
tx.onerror = () => reject(tx.error);
|
|
102
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
103
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
116
104
|
});
|
|
117
105
|
|
|
118
106
|
window.dispatchEvent(
|
package/dist/runtime/push.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/runtime/push.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/runtime/push.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAM,EAAE,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,UAAU,gBAAgB,CAAC,GAAmB;IAClD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;2GAKkG,GAAG;;;;;;;;;;;;;;;;;;;;gCAoB9E,GAAG;;;;;;qBAMd,CAAC,CAAC,EAAE,EAAE,oCAAoC,CAAC;;;uDAGT,CAAC,CAAC,EAAE,EAAE,kBAAkB,CAAC;;;;;;;;;;;;;6CAanC,CAAC,CAAC,EAAE,EAAE,kCAAkC,CAAC;;;;;;;;;;yCAU7C,CAAC,CAAC,EAAE,EAAE,kCAAkC,CAAC;;;;;;;mEAOf,EAAE,CAAC,EAAE,EAAE,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;6CAwB5C,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;sCAoB7B,CAAC,CAAC,EAAE,EAAE,kBAAkB,CAAC;;;;;;6CAMlB,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC;;;;;;CAMlF,CAAC;AACF,CAAC"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { R } from "./utils.js";
|
|
1
|
+
import { R, AS } from "./utils.js";
|
|
2
2
|
export function generatePwaPromptCode(ctx) {
|
|
3
3
|
const { ext, ts } = ctx;
|
|
4
|
+
const preventLine = ctx.preventDefaultInstall
|
|
5
|
+
? " e.preventDefault();\n"
|
|
6
|
+
: "";
|
|
4
7
|
return `/**
|
|
5
8
|
* Swoff PWA Install Prompt
|
|
6
|
-
* Installability check
|
|
9
|
+
* Installability check, manual prompt trigger, and event listener setup.
|
|
7
10
|
*
|
|
8
11
|
* Usage:
|
|
9
|
-
* import { isInstallable, promptInstall } from './swoff/pwa/prompt.${ext}';
|
|
12
|
+
* import { setupPwaInstall, isInstallable, promptInstall } from './swoff/pwa/prompt.${ext}';
|
|
13
|
+
*
|
|
14
|
+
* setupPwaInstall(); // sets up beforeinstallprompt and appinstalled listeners
|
|
10
15
|
*
|
|
11
16
|
* if (isInstallable()) {
|
|
12
17
|
* const result = await promptInstall();
|
|
@@ -16,8 +21,35 @@ export function generatePwaPromptCode(ctx) {
|
|
|
16
21
|
* window.deferredInstallPrompt - The captured BeforeInstallPromptEvent
|
|
17
22
|
*/
|
|
18
23
|
|
|
24
|
+
/** Set up beforeinstallprompt and appinstalled event listeners. Call once at app startup. */
|
|
25
|
+
export function setupPwaInstall()${R(ts, "void")}{
|
|
26
|
+
window.addEventListener("beforeinstallprompt", (e) => {
|
|
27
|
+
window.deferredInstallPrompt = e${AS(ts, "BeforeInstallPromptEvent")};
|
|
28
|
+
window.pwaInstallable = true;
|
|
29
|
+
|
|
30
|
+
${preventLine}
|
|
31
|
+
window.dispatchEvent(
|
|
32
|
+
new CustomEvent("pwa-installable", {
|
|
33
|
+
detail: { isInstallable: true },
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
window.addEventListener("appinstalled", () => {
|
|
39
|
+
window.deferredInstallPrompt = null;
|
|
40
|
+
window.pwaInstallable = false;
|
|
41
|
+
|
|
42
|
+
window.dispatchEvent(
|
|
43
|
+
new CustomEvent("pwa-installed", {
|
|
44
|
+
detail: { outcome: "accepted" },
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
/** Check if the PWA install prompt is available (user has met install criteria). */
|
|
20
51
|
export function isInstallable()${R(ts, "boolean")}{
|
|
52
|
+
if (typeof window === "undefined") return false;
|
|
21
53
|
return !!window.deferredInstallPrompt;
|
|
22
54
|
}
|
|
23
55
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pwa-prompt.js","sourceRoot":"","sources":["../../src/runtime/pwa-prompt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"pwa-prompt.js","sourceRoot":"","sources":["../../src/runtime/pwa-prompt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,UAAU,qBAAqB,CACnC,GAAyD;IAEzD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,MAAM,WAAW,GAAG,GAAG,CAAC,qBAAqB;QAC3C,CAAC,CAAC,2BAA2B;QAC7B,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;yFAKgF,GAAG;;;;;;;;;;;;;mCAazD,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;sCAEV,EAAE,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;EAGtE,WAAW;;;;;;;;;;;;;;;;;;;;;iCAqBoB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;;;;;uCAMV,CAAC,CAAC,EAAE,EAAE,8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B3E,CAAC;AACF,CAAC"}
|
package/dist/runtime/reset.js
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
import { T, R } from "./utils.js";
|
|
1
|
+
import { T, O, R } from "./utils.js";
|
|
2
2
|
export function generateResetCode(ctx) {
|
|
3
3
|
const { ext, ts } = ctx;
|
|
4
4
|
return `/**
|
|
5
5
|
* Swoff Reset
|
|
6
6
|
* Nuclear option: wipe all swoff data (caches, IndexedDB, localStorage),
|
|
7
|
-
*
|
|
7
|
+
* then instruct the service worker to clear and re-precache all assets.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the old approach of unregister + re-register (which often failed to
|
|
10
|
+
* trigger the install event for byte-identical scripts), this sends a
|
|
11
|
+
* RESET_CACHE message to the active SW, which handles cache clearing and
|
|
12
|
+
* re-precaching internally.
|
|
8
13
|
*
|
|
9
14
|
* Usage:
|
|
10
15
|
* import { resetSwoff } from './swoff/reset.${ext}';
|
|
11
16
|
*
|
|
12
17
|
* // Full reset (default)
|
|
13
18
|
* const result = await resetSwoff();
|
|
14
|
-
* console.log(result.
|
|
19
|
+
* console.log(result.warnings);
|
|
15
20
|
*
|
|
16
21
|
* // Selective reset — keep caches but reset IDB + SW
|
|
17
22
|
* await resetSwoff({ clearCache: false });
|
|
18
23
|
*
|
|
19
24
|
* Events dispatched:
|
|
20
25
|
* swoff:reset-start — before any cleanup begins
|
|
21
|
-
* swoff:reset-complete — after everything finishes (detail: {
|
|
26
|
+
* swoff:reset-complete — after everything finishes (detail: { warnings })
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
|
-
import { initServiceWorker } from "./sw/injector.${ext}";
|
|
25
|
-
|
|
26
29
|
export interface ResetSwoffOptions {
|
|
27
30
|
/** Clear all Cache Storage caches (default: true) */
|
|
28
|
-
clearCache${
|
|
31
|
+
clearCache${O(ts, "boolean")};
|
|
29
32
|
/** Delete all swoff-* IndexedDB databases (default: true) */
|
|
30
|
-
clearIdb${
|
|
33
|
+
clearIdb${O(ts, "boolean")};
|
|
31
34
|
/** Clear localStorage keys like swRegisteredVersion (default: true) */
|
|
32
|
-
clearStorage${
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
+
clearStorage${O(ts, "boolean")};
|
|
36
|
+
/** Reset SW caches (clear + re-precache) (default: true) */
|
|
37
|
+
resetSwCache${O(ts, "boolean")};
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
const DEFAULT_OPTIONS: ResetSwoffOptions = {
|
|
38
41
|
clearCache: true,
|
|
39
42
|
clearIdb: true,
|
|
40
43
|
clearStorage: true,
|
|
41
|
-
|
|
44
|
+
resetSwCache: true,
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
const KNOWN_DB_NAMES = [
|
|
45
48
|
"swoff-auth",
|
|
46
|
-
"swoff-auth-user",
|
|
47
49
|
"swoff-queue",
|
|
48
50
|
"swoff-cache-tags",
|
|
49
51
|
"swoff-push",
|
|
@@ -74,14 +76,14 @@ async function deleteSwoffDatabases(warnings${T(ts, "string[]")})${R(ts, "Promis
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/** Nuclear reset: wipes all swoff-persisted data
|
|
78
|
-
export async function resetSwoff(opts${T(ts, "
|
|
79
|
+
/** Nuclear reset: wipes all swoff-persisted data and resets the SW cache. Returns detailed results. */
|
|
80
|
+
export async function resetSwoff(opts${T(ts, "ResetSwoffOptions")} = {})${R(ts, "Promise<{ warnings: string[] }>")}{
|
|
79
81
|
const options${T(ts, "ResetSwoffOptions")} = { ...DEFAULT_OPTIONS, ...opts };
|
|
80
82
|
const warnings${T(ts, "string[]")} = [];
|
|
81
83
|
|
|
82
84
|
window.dispatchEvent(new CustomEvent("swoff:reset-start"));
|
|
83
85
|
|
|
84
|
-
// 1. Clear Cache Storage
|
|
86
|
+
// 1. Clear Cache Storage (belt-and-suspenders — SW also clears them)
|
|
85
87
|
if (options.clearCache) {
|
|
86
88
|
try {
|
|
87
89
|
if (typeof caches !== "undefined") {
|
|
@@ -107,39 +109,34 @@ export async function resetSwoff(opts${T(ts, "Partial<ResetSwoffOptions>")} = {}
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
// 4.
|
|
111
|
-
|
|
112
|
-
if (options.unregisterSW) {
|
|
112
|
+
// 4. Tell the SW to clear its caches and re-precache all assets
|
|
113
|
+
if (options.resetSwCache) {
|
|
113
114
|
try {
|
|
114
|
-
if ("serviceWorker" in navigator) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
115
|
+
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
116
|
+
const registration = await navigator.serviceWorker.ready;
|
|
117
|
+
if (registration.active) {
|
|
118
|
+
await new Promise<void>((resolve) => {
|
|
119
|
+
const channel = new MessageChannel();
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
channel.port1.close();
|
|
122
|
+
resolve();
|
|
123
|
+
}, 10000);
|
|
124
|
+
channel.port1.onmessage = (event) => {
|
|
125
|
+
if (event.data.type === "RESET_CACHE_COMPLETE") {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
registration.active?.postMessage({ type: "RESET_CACHE" }, [channel.port2]);
|
|
131
|
+
});
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
} catch (e) {
|
|
121
|
-
warnings.push(\`Failed to
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Always clear the version marker before re-registering so initServiceWorker
|
|
125
|
-
// detects a fresh install rather than trying to reuse a stale registration
|
|
126
|
-
try {
|
|
127
|
-
localStorage.removeItem("swRegisteredVersion");
|
|
128
|
-
} catch { /* ignore */ }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 5. Re-register SW
|
|
132
|
-
let reregistered = false;
|
|
133
|
-
if (options.unregisterSW) {
|
|
134
|
-
try {
|
|
135
|
-
await initServiceWorker();
|
|
136
|
-
reregistered = true;
|
|
137
|
-
} catch (e) {
|
|
138
|
-
warnings.push(\`Failed to re-register SW: \${e}\`);
|
|
135
|
+
warnings.push(\`Failed to reset SW cache: \${e}\`);
|
|
139
136
|
}
|
|
140
137
|
}
|
|
141
138
|
|
|
142
|
-
const result = {
|
|
139
|
+
const result = { warnings };
|
|
143
140
|
window.dispatchEvent(new CustomEvent("swoff:reset-complete", { detail: result }));
|
|
144
141
|
return result;
|
|
145
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/runtime/reset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/runtime/reset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAErC,MAAM,UAAU,iBAAiB,CAAC,GAAmB;IACnD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;;;;;;iDAWwC,GAAG;;;;;;;;;;;;;;;;cAgBtC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;YAElB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;gBAEZ,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;gBAEhB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;;8CAkBc,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;uCAyBlD,CAAC,CAAC,EAAE,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,iCAAiC,CAAC;iBACjG,CAAC,CAAC,EAAE,EAAE,mBAAmB,CAAC;kBACzB,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6DlC,CAAC;AACF,CAAC"}
|