@swoff/cli 0.3.13 → 0.3.14
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 +2 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +3 -8
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +29 -87
- package/dist/lib/commands/add.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 +31 -6
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/init.js +188 -11
- 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/validator.js +202 -96
- 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-manager.js +12 -0
- package/dist/lib/generators/file-generators/connectivity-manager.js.map +1 -0
- 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/notification-generator.js +6 -0
- package/dist/lib/generators/file-generators/notification-generator.js.map +1 -0
- 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-generator.js +6 -0
- package/dist/lib/generators/file-generators/storage-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/storage-notify.js +6 -0
- package/dist/lib/generators/file-generators/storage-notify.js.map +1 -0
- 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 +96 -45
- 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/sw-version-gen.js +32 -0
- package/dist/lib/generators/file-generators/sw-version-gen.js.map +1 -0
- 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 +74 -23
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +15 -94
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- 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 +59 -6
- 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 +40 -23
- package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/message-handler.js +25 -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 +68 -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 +67 -63
- 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/auth-user.js +5 -19
- package/dist/runtime/auth-user.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 +173 -30
- package/dist/runtime/client-injector.js.map +1 -1
- package/dist/runtime/connectivity-manager.js +83 -0
- package/dist/runtime/connectivity-manager.js.map +1 -0
- 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/notification.js +65 -0
- package/dist/runtime/notification.js.map +1 -0
- 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-injector.js +3 -32
- package/dist/runtime/pwa-injector.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/sw-version.js +13 -0
- package/dist/runtime/sw-version.js.map +1 -0
- 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/dist/swoff-nkdhhmw8. +18 -0
- package/dist/swoff.js +5 -0
- package/package.json +1 -1
- 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/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
|
@@ -0,0 +1,1491 @@
|
|
|
1
|
+
export function generateSwoffApiBundleCode(ctx, flags) {
|
|
2
|
+
const cascadingCode = flags.tagInvalidationCascading && Object.keys(flags.tagInvalidationCascading).length > 0
|
|
3
|
+
? JSON.stringify(flags.tagInvalidationCascading)
|
|
4
|
+
: "null";
|
|
5
|
+
const prefixesCode = JSON.stringify(flags.tagInvalidationSkipPrefixes);
|
|
6
|
+
const singularizationCode = flags.tagInvalidationSingularization && Object.keys(flags.tagInvalidationSingularization).length > 0
|
|
7
|
+
? JSON.stringify(flags.tagInvalidationSingularization)
|
|
8
|
+
: "null";
|
|
9
|
+
const authCode = flags.authEnabled ? generateAuthSection(flags) : "";
|
|
10
|
+
const mutationCode = flags.mutationQueueEnabled ? generateMutationSection(flags) : "";
|
|
11
|
+
const pwaCode = flags.pwaEnabled ? generatePwaSection(flags) : "";
|
|
12
|
+
const gqlCode = flags.gqlEnabled ? generateGqlSection(flags) : "";
|
|
13
|
+
const pushCode = flags.pushNotificationsEnabled ? generatePushSection() : "";
|
|
14
|
+
const serverPushCode = flags.serverPushEnabled ? generateServerPushSection(flags) : "";
|
|
15
|
+
return `(function () {
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
// ── Constants ──
|
|
19
|
+
var API_BASE = "";
|
|
20
|
+
var CASCADING = ${cascadingCode};
|
|
21
|
+
var SKIP_PREFIXES = ${prefixesCode};
|
|
22
|
+
var SINGULARIZATION = ${singularizationCode};
|
|
23
|
+
var BATCH_WINDOW_MS = ${flags.requestBatchWindowMs};
|
|
24
|
+
|
|
25
|
+
// ── IndexedDB Helper ──
|
|
26
|
+
function openDB(name, storeName, keyPath, upgradeCallback, version) {
|
|
27
|
+
return new Promise(function (resolve, reject) {
|
|
28
|
+
var request = indexedDB.open(name, version || 1);
|
|
29
|
+
request.onupgradeneeded = function (e) {
|
|
30
|
+
var db = e.target.result;
|
|
31
|
+
if (upgradeCallback) {
|
|
32
|
+
upgradeCallback(db);
|
|
33
|
+
} else if (!db.objectStoreNames.contains(storeName)) {
|
|
34
|
+
db.createObjectStore(storeName, { keyPath: keyPath });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
request.onsuccess = function (e) { resolve(e.target.result); };
|
|
38
|
+
request.onerror = function (e) { reject(e.target.error); };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Cache Tags ──
|
|
43
|
+
var TAG_PATTERNS = ${compilePatterns(flags.tagInvalidationPatterns)};
|
|
44
|
+
|
|
45
|
+
function generateTags(url) {
|
|
46
|
+
var base = typeof window !== "undefined" ? window.location.origin : "";
|
|
47
|
+
var parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = typeof url === "string" ? new URL(url, base) : url;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
var path = parsed.pathname;
|
|
54
|
+
|
|
55
|
+
for (var i = 0; i < TAG_PATTERNS.length; i++) {
|
|
56
|
+
var entry = TAG_PATTERNS[i];
|
|
57
|
+
var match = path.match(entry.re);
|
|
58
|
+
if (match) {
|
|
59
|
+
var params = {};
|
|
60
|
+
for (var j = 0; j < entry.params.length; j++) {
|
|
61
|
+
params[entry.params[j]] = match[j + 1];
|
|
62
|
+
}
|
|
63
|
+
return entry.templates.map(function (tmpl) {
|
|
64
|
+
return tmpl.replace(/\\{(\\w+)\\}/g, function (_, key) { return params[key] || ""; });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var segments = path.split("/").filter(Boolean);
|
|
70
|
+
if (segments.length === 0) return ["root"];
|
|
71
|
+
|
|
72
|
+
var startIdx = 0;
|
|
73
|
+
while (startIdx < segments.length && SKIP_PREFIXES.indexOf(segments[startIdx]) !== -1) {
|
|
74
|
+
startIdx++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var resourceSegments = segments.slice(startIdx);
|
|
78
|
+
if (resourceSegments.length === 0) return ["root"];
|
|
79
|
+
|
|
80
|
+
var tags = [];
|
|
81
|
+
tags.push(resourceSegments[0]);
|
|
82
|
+
|
|
83
|
+
if (resourceSegments.length >= 2 && !isNaN(Number(resourceSegments[1]))) {
|
|
84
|
+
var collection = resourceSegments[0];
|
|
85
|
+
var id = resourceSegments[1];
|
|
86
|
+
var singular = SINGULARIZATION && SINGULARIZATION[collection] !== undefined
|
|
87
|
+
? SINGULARIZATION[collection]
|
|
88
|
+
: collection.replace(/s$/, "");
|
|
89
|
+
tags.push(singular + ":" + id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (var k = 2; k < resourceSegments.length; k++) {
|
|
93
|
+
if (isNaN(Number(resourceSegments[k]))) {
|
|
94
|
+
tags.push(resourceSegments[k]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return tags;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function generateTagsFromMethod(method, url) {
|
|
102
|
+
var tags = generateTags(url);
|
|
103
|
+
if (method === "GET" || method === "HEAD") return tags;
|
|
104
|
+
return tags.map(function (tag) { return method.toLowerCase() + "-" + tag; });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function expandCascading(tags) {
|
|
108
|
+
if (!CASCADING) return tags.slice();
|
|
109
|
+
var result = new Set(tags);
|
|
110
|
+
for (var i = 0; i < tags.length; i++) {
|
|
111
|
+
var deps = CASCADING[tags[i]];
|
|
112
|
+
if (deps) {
|
|
113
|
+
for (var j = 0; j < deps.length; j++) {
|
|
114
|
+
result.add(deps[j]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Array.from(result);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Cache Invalidation ──
|
|
122
|
+
function invalidateByTag(tag) {
|
|
123
|
+
if (!navigator.serviceWorker || !navigator.serviceWorker.controller) return Promise.resolve();
|
|
124
|
+
navigator.serviceWorker.controller.postMessage({ type: "INVALIDATE_TAG", tag: tag });
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function invalidateByTags(tags) {
|
|
129
|
+
return Promise.all(tags.map(function (tag) { return invalidateByTag(tag); }));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function invalidateUrl(url) {
|
|
133
|
+
var tags = generateTags(url);
|
|
134
|
+
var allTags = CASCADING ? expandCascading(tags) : tags;
|
|
135
|
+
return invalidateByTags(allTags);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function invalidateByMethod(method, url) {
|
|
139
|
+
var tags = generateTagsFromMethod(method, url);
|
|
140
|
+
var allTags = CASCADING ? expandCascading(tags) : tags;
|
|
141
|
+
return invalidateByTags(allTags);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function invalidateMatching(glob) {
|
|
145
|
+
var controller = navigator.serviceWorker && navigator.serviceWorker.controller;
|
|
146
|
+
if (!controller) return Promise.resolve();
|
|
147
|
+
controller.postMessage({ type: "INVALIDATE_MATCHING", glob: glob });
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getUrlsForTag(tag) {
|
|
152
|
+
var controller = navigator.serviceWorker && navigator.serviceWorker.controller;
|
|
153
|
+
if (!controller) return Promise.resolve([]);
|
|
154
|
+
return new Promise(function (resolve) {
|
|
155
|
+
var channel = new MessageChannel();
|
|
156
|
+
var timer = setTimeout(function () { channel.port1.close(); resolve([]); }, 5000);
|
|
157
|
+
channel.port1.onmessage = function (event) {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
resolve(event.data.urls || []);
|
|
160
|
+
};
|
|
161
|
+
controller.postMessage({ type: "GET_URLS_FOR_TAG", tag: tag }, [channel.port2]);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getTagsForUrl(url) {
|
|
166
|
+
var controller = navigator.serviceWorker && navigator.serviceWorker.controller;
|
|
167
|
+
if (!controller) return Promise.resolve([]);
|
|
168
|
+
return new Promise(function (resolve) {
|
|
169
|
+
var channel = new MessageChannel();
|
|
170
|
+
var timer = setTimeout(function () { channel.port1.close(); resolve([]); }, 5000);
|
|
171
|
+
channel.port1.onmessage = function (event) {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
resolve(event.data.tags || []);
|
|
174
|
+
};
|
|
175
|
+
controller.postMessage({ type: "GET_TAGS_FOR_URL", url: url }, [channel.port2]);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Storage ──
|
|
180
|
+
function getStorageEstimate() {
|
|
181
|
+
if (!navigator.storage || !navigator.storage.estimate) {
|
|
182
|
+
return Promise.resolve({ usage: 0, quota: 0, percentUsed: 0 });
|
|
183
|
+
}
|
|
184
|
+
return navigator.storage.estimate().then(function (est) {
|
|
185
|
+
var usage = est.usage || 0;
|
|
186
|
+
var quota = est.quota || 0;
|
|
187
|
+
var percentUsed = quota > 0 ? Math.round((usage / quota) * 100) : 0;
|
|
188
|
+
return { usage: usage, quota: quota, percentUsed: percentUsed };
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatBytes(bytes) {
|
|
193
|
+
if (bytes === 0) return "0 B";
|
|
194
|
+
var units = ["B", "KB", "MB", "GB"];
|
|
195
|
+
var i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
196
|
+
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Connectivity ──
|
|
200
|
+
function onOnline(callback) {
|
|
201
|
+
if (typeof window === "undefined") return;
|
|
202
|
+
window.addEventListener("online", callback);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onOffline(callback) {
|
|
206
|
+
if (typeof window === "undefined") return;
|
|
207
|
+
window.addEventListener("offline", callback);
|
|
208
|
+
}
|
|
209
|
+
${authCode}${mutationCode}${pwaCode}${gqlCode}${pushCode}${serverPushCode}
|
|
210
|
+
// ── Fetch with Cache ──
|
|
211
|
+
var inFlightRequests = new Map();
|
|
212
|
+
var pendingBatches = new Map();
|
|
213
|
+
|
|
214
|
+
function fetchWithCache(input, options) {
|
|
215
|
+
options = options || {};
|
|
216
|
+
var method = (options.method || "GET").toUpperCase();
|
|
217
|
+
var isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD" || method === "OPTIONS"));
|
|
218
|
+
var resolvedInput = input;
|
|
219
|
+
var url = typeof resolvedInput === "string" ? resolvedInput : resolvedInput.url;
|
|
220
|
+
|
|
221
|
+
var headers = new Headers(options.headers);
|
|
222
|
+
|
|
223
|
+
if (!headers.has("X-SW-Type")) {
|
|
224
|
+
headers.set("X-SW-Type", isRead ? "read" : "mutation");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!options.tags && isRead) {
|
|
228
|
+
var urlTags = generateTags(url);
|
|
229
|
+
if (urlTags.length > 0) {
|
|
230
|
+
headers.set("X-SW-Cache-Tags", urlTags.join(","));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.tags && options.tags.length > 0) {
|
|
235
|
+
headers.set("X-SW-Cache-Tags", options.tags.join(","));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (options.strategy) {
|
|
239
|
+
headers.set("X-SW-Strategy", options.strategy);
|
|
240
|
+
}
|
|
241
|
+
if (options.staleTime !== undefined) {
|
|
242
|
+
headers.set("X-SW-Stale-Time", String(options.staleTime));
|
|
243
|
+
}
|
|
244
|
+
if (options.refetchInterval !== undefined) {
|
|
245
|
+
headers.set("X-SW-Refetch-Interval", String(options.refetchInterval));
|
|
246
|
+
}
|
|
247
|
+
if (options.refetchOnFocus !== undefined) {
|
|
248
|
+
headers.set("X-SW-Refetch-On-Focus", String(options.refetchOnFocus));
|
|
249
|
+
}
|
|
250
|
+
if (options.refetchOnReconnect !== undefined) {
|
|
251
|
+
headers.set("X-SW-Refetch-On-Reconnect", String(options.refetchOnReconnect));
|
|
252
|
+
}
|
|
253
|
+
if (options.queueOffline === false) {
|
|
254
|
+
headers.set("X-SW-No-Queue", "true");
|
|
255
|
+
}
|
|
256
|
+
${flags.authEnabled ? generateAuthFetchBlock(flags) : ""}
|
|
257
|
+
var fetchOptions = Object.assign({}, options, { headers: headers });
|
|
258
|
+
${flags.authEnabled && flags.authType === "cookie" ? " fetchOptions.credentials = \"include\";\n" : ""}
|
|
259
|
+
if (options.signal && options.signal.aborted) {
|
|
260
|
+
return Promise.reject(new DOMException("The operation was aborted", "AbortError"));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
var mutationTags = [];
|
|
264
|
+
if (!isRead) {
|
|
265
|
+
var invalidateSetting = options.invalidate !== false ? (options.invalidate || "auto") : false;
|
|
266
|
+
if (invalidateSetting !== false) {
|
|
267
|
+
if (Array.isArray(invalidateSetting)) {
|
|
268
|
+
mutationTags = invalidateSetting;
|
|
269
|
+
} else {
|
|
270
|
+
mutationTags = generateTags(url);
|
|
271
|
+
mutationTags = expandCascading(mutationTags);
|
|
272
|
+
}
|
|
273
|
+
headers.set("X-SW-Invalidate-Tags", mutationTags.join(","));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
var responsePromise;
|
|
278
|
+
if (isRead && inFlightRequests.has(url)) {
|
|
279
|
+
responsePromise = inFlightRequests.get(url).then(function (r) { return r.clone(); });
|
|
280
|
+
} else if (isRead && pendingBatches.has(url)) {
|
|
281
|
+
responsePromise = new Promise(function (resolve, reject) {
|
|
282
|
+
pendingBatches.get(url).resolvers.push(resolve);
|
|
283
|
+
pendingBatches.get(url).rejectors.push(reject);
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
var abortHandler = function () {
|
|
287
|
+
inFlightRequests.delete(url);
|
|
288
|
+
if (pendingBatches.has(url)) {
|
|
289
|
+
var batch = pendingBatches.get(url);
|
|
290
|
+
clearTimeout(batch.timer);
|
|
291
|
+
pendingBatches.delete(url);
|
|
292
|
+
batch.rejectors.forEach(function (rej) { rej(new DOMException("The operation was aborted", "AbortError")); });
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
if (options.signal) {
|
|
296
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
297
|
+
}
|
|
298
|
+
if (isRead && BATCH_WINDOW_MS > 0) {
|
|
299
|
+
var batch = { resolvers: [], rejectors: [], timer: 0 };
|
|
300
|
+
pendingBatches.set(url, batch);
|
|
301
|
+
batch.timer = setTimeout(function () {
|
|
302
|
+
pendingBatches.delete(url);
|
|
303
|
+
var promise = fetch(resolvedInput, fetchOptions);
|
|
304
|
+
var cleanup = function () {
|
|
305
|
+
inFlightRequests.delete(url);
|
|
306
|
+
if (options.signal) {
|
|
307
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
inFlightRequests.set(url, promise.then(function (r) { cleanup(); return r; }, function (e) { cleanup(); throw e; }));
|
|
311
|
+
promise.then(function (r) {
|
|
312
|
+
batch.resolvers.forEach(function (res) { res(r.clone()); });
|
|
313
|
+
}).catch(function (err) {
|
|
314
|
+
batch.rejectors.forEach(function (rej) { rej(err); });
|
|
315
|
+
});
|
|
316
|
+
}, BATCH_WINDOW_MS);
|
|
317
|
+
responsePromise = new Promise(function (resolve, reject) {
|
|
318
|
+
batch.resolvers.push(resolve);
|
|
319
|
+
batch.rejectors.push(reject);
|
|
320
|
+
});
|
|
321
|
+
} else {
|
|
322
|
+
responsePromise = fetch(resolvedInput, fetchOptions).then(function (r) {
|
|
323
|
+
inFlightRequests.delete(url);
|
|
324
|
+
if (options.signal) {
|
|
325
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
326
|
+
}
|
|
327
|
+
return r;
|
|
328
|
+
}, function (e) {
|
|
329
|
+
inFlightRequests.delete(url);
|
|
330
|
+
if (options.signal) {
|
|
331
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
332
|
+
}
|
|
333
|
+
throw e;
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return responsePromise.then(function (response) {
|
|
339
|
+
if (!isRead) {
|
|
340
|
+
var mutationSuccess = options.validateSuccess ? options.validateSuccess(response) : response.ok;
|
|
341
|
+
var mutationQueued = response.headers.get("X-SW-Mutation-Queued") === "true";
|
|
342
|
+
if (mutationSuccess && !mutationQueued) {
|
|
343
|
+
var invalidateSetting = options.invalidate !== false ? (options.invalidate || "auto") : false;
|
|
344
|
+
if (invalidateSetting !== false) {
|
|
345
|
+
var invalidateTarget = options.invalidateUrl || url;
|
|
346
|
+
if (Array.isArray(invalidateSetting)) {
|
|
347
|
+
invalidateByTags(invalidateSetting);
|
|
348
|
+
} else {
|
|
349
|
+
invalidateUrl(invalidateTarget);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
var fromCache = response.headers.get("X-SW-From-Cache") === "true";
|
|
355
|
+
var queued = response.headers.get("X-SW-Mutation-Queued") === "true";
|
|
356
|
+
return { response: response, fromCache: fromCache, queued: queued };
|
|
357
|
+
}).catch(function (err) {
|
|
358
|
+
if (err instanceof TypeError) {
|
|
359
|
+
if (isRead) {
|
|
360
|
+
if (options.signal && options.signal.aborted) throw new DOMException("The operation was aborted", "AbortError");
|
|
361
|
+
return caches.match(resolvedInput).then(function (cached) {
|
|
362
|
+
if (cached) return { response: cached, fromCache: true, queued: false };
|
|
363
|
+
throw new Error("Offline: no cached data available");
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
throw err;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function prefetchCache(input, options) {
|
|
372
|
+
fetchWithCache(input, options).catch(function () {});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Reset ──
|
|
376
|
+
var KNOWN_DB_NAMES = ["swoff-auth", "swoff-queue", "swoff-cache-tags", "swoff-push"];
|
|
377
|
+
|
|
378
|
+
function deleteSwoffDatabases(warnings) {
|
|
379
|
+
var dbNames = KNOWN_DB_NAMES.slice();
|
|
380
|
+
try {
|
|
381
|
+
if (indexedDB.databases) {
|
|
382
|
+
return indexedDB.databases().then(function (allDbs) {
|
|
383
|
+
for (var i = 0; i < allDbs.length; i++) {
|
|
384
|
+
if (allDbs[i].name && allDbs[i].name.indexOf("swoff-") === 0 && dbNames.indexOf(allDbs[i].name) === -1) {
|
|
385
|
+
dbNames.push(allDbs[i].name);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return deleteAll(dbNames, warnings);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
} catch (e) {}
|
|
392
|
+
return deleteAll(dbNames, warnings);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function deleteAll(dbNames, warnings) {
|
|
396
|
+
for (var i = 0; i < dbNames.length; i++) {
|
|
397
|
+
try {
|
|
398
|
+
indexedDB.deleteDatabase(dbNames[i]);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
warnings.push("Failed to delete database \\"" + dbNames[i] + "\\": " + e);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return Promise.resolve();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function resetSwoff(opts) {
|
|
407
|
+
opts = opts || {};
|
|
408
|
+
var options = {
|
|
409
|
+
clearCache: opts.clearCache !== false,
|
|
410
|
+
clearIdb: opts.clearIdb !== false,
|
|
411
|
+
clearStorage: opts.clearStorage !== false,
|
|
412
|
+
resetSwCache: opts.resetSwCache !== false,
|
|
413
|
+
};
|
|
414
|
+
var warnings = [];
|
|
415
|
+
window.dispatchEvent(new CustomEvent("swoff:reset-start"));
|
|
416
|
+
|
|
417
|
+
var promises = [];
|
|
418
|
+
|
|
419
|
+
if (options.clearCache) {
|
|
420
|
+
try {
|
|
421
|
+
if (typeof caches !== "undefined") {
|
|
422
|
+
promises.push(caches.keys().then(function (keys) {
|
|
423
|
+
return Promise.all(keys.map(function (k) { return caches.delete(k); }));
|
|
424
|
+
}).catch(function (e) {
|
|
425
|
+
warnings.push("Failed to clear caches: " + e);
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
} catch (e) {
|
|
429
|
+
warnings.push("Failed to clear caches: " + e);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (options.clearIdb) {
|
|
434
|
+
promises.push(deleteSwoffDatabases(warnings));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (options.clearStorage) {
|
|
438
|
+
try {
|
|
439
|
+
localStorage.removeItem("swRegisteredVersion");
|
|
440
|
+
} catch (e) {
|
|
441
|
+
warnings.push("Failed to clear localStorage: " + e);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (options.resetSwCache) {
|
|
446
|
+
try {
|
|
447
|
+
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
448
|
+
promises.push(navigator.serviceWorker.ready.then(function (registration) {
|
|
449
|
+
if (registration.active) {
|
|
450
|
+
return new Promise(function (resolve) {
|
|
451
|
+
var channel = new MessageChannel();
|
|
452
|
+
var timer = setTimeout(function () { channel.port1.close(); resolve(); }, 10000);
|
|
453
|
+
channel.port1.onmessage = function (event) {
|
|
454
|
+
if (event.data.type === "RESET_CACHE_COMPLETE") {
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
resolve();
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
registration.active.postMessage({ type: "RESET_CACHE" }, [channel.port2]);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}).catch(function (e) {
|
|
463
|
+
warnings.push("Failed to reset SW cache: " + e);
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
} catch (e) {
|
|
467
|
+
warnings.push("Failed to reset SW cache: " + e);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return Promise.all(promises).then(function () {
|
|
472
|
+
var result = { warnings: warnings };
|
|
473
|
+
window.dispatchEvent(new CustomEvent("swoff:reset-complete", { detail: result }));
|
|
474
|
+
return result;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ── Utilities ──
|
|
479
|
+
function skipWaiting() {
|
|
480
|
+
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
|
|
481
|
+
navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function getCurrentOnlineStatus() {
|
|
486
|
+
return typeof navigator !== "undefined" ? navigator.onLine : true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ── Configuration ──
|
|
490
|
+
function configure(opts) {
|
|
491
|
+
if (!opts) return;
|
|
492
|
+
if (opts.apiBase !== undefined) API_BASE = opts.apiBase;
|
|
493
|
+
if (opts.auth) {
|
|
494
|
+
if (opts.auth.type !== undefined) adapter.type = opts.auth.type;
|
|
495
|
+
if (opts.auth.getHeaders) adapter.getHeaders = opts.auth.getHeaders;
|
|
496
|
+
if (opts.auth.refresh) adapter.refresh = opts.auth.refresh;
|
|
497
|
+
if (opts.auth.fetchUser) adapter.fetchUser = opts.auth.fetchUser;
|
|
498
|
+
}
|
|
499
|
+
if (opts.push) {
|
|
500
|
+
if (opts.push.vapidPublicKey !== undefined) VAPID_PUBLIC_KEY = opts.push.vapidPublicKey;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ── Assembly ──
|
|
505
|
+
var api = {
|
|
506
|
+
configure: configure,
|
|
507
|
+
fetchWithCache: fetchWithCache,
|
|
508
|
+
prefetchCache: prefetchCache,
|
|
509
|
+
invalidateByTag: invalidateByTag,
|
|
510
|
+
invalidateByTags: invalidateByTags,
|
|
511
|
+
invalidateUrl: invalidateUrl,
|
|
512
|
+
invalidateByMethod: invalidateByMethod,
|
|
513
|
+
invalidateMatching: invalidateMatching,
|
|
514
|
+
getUrlsForTag: getUrlsForTag,
|
|
515
|
+
getTagsForUrl: getTagsForUrl,
|
|
516
|
+
generateTags: generateTags,
|
|
517
|
+
getStorageEstimate: getStorageEstimate,
|
|
518
|
+
formatBytes: formatBytes,
|
|
519
|
+
getCurrentOnlineStatus: getCurrentOnlineStatus,
|
|
520
|
+
onOnline: onOnline,
|
|
521
|
+
onOffline: onOffline,
|
|
522
|
+
resetSwoff: resetSwoff,
|
|
523
|
+
skipWaiting: skipWaiting,
|
|
524
|
+
forceRetry: typeof window !== "undefined" && typeof window.__SWOFF_FORCE_RETRY === "function" ? window.__SWOFF_FORCE_RETRY : function () { return Promise.resolve(); },
|
|
525
|
+
${flags.authEnabled ? " setAuth: setAuth,\n getAuth: getAuth,\n clearAuth: clearAuth,\n ensureValidAuth: ensureValidAuth,\n clearMemoryAuth: clearMemoryAuth,\n getAuthState: getAuthState," : ""}
|
|
526
|
+
${flags.mutationQueueEnabled ? " queueMutation: queueMutation,\n flushMutations: flushMutations,\n clearQueue: clearQueue,\n getPendingCount: getPendingCount,\n getQueueItems: getQueueItems,\n getQueuePosition: getQueuePosition,\n syncWhenPossible: syncWhenPossible,\n retrySync: retrySync," : ""}
|
|
527
|
+
${flags.pwaEnabled ? " promptInstall: promptInstall,\n isInstallable: isInstallable," : ""}
|
|
528
|
+
${flags.gqlEnabled ? " fetchWithGql: fetchWithGql,\n queryGql: queryGql,\n mutateGql: mutateGql," : ""}
|
|
529
|
+
${flags.pushNotificationsEnabled ? " requestNotificationPermission: requestNotificationPermission,\n getPushSubscription: getPushSubscription,\n subscribeToPush: subscribeToPush,\n unsubscribeFromPush: unsubscribeFromPush,\n isSubscribed: isSubscribed," : ""}
|
|
530
|
+
${flags.serverPushEnabled ? " startPushEvents: startPushEvents,\n stopPushEvents: stopPushEvents,\n isPushConnected: isPushConnected," : ""}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
if (typeof window !== "undefined") {
|
|
534
|
+
window.swoff = api;
|
|
535
|
+
}
|
|
536
|
+
})();
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
function generatePushSection() {
|
|
540
|
+
return `
|
|
541
|
+
// ── Push Notifications ──
|
|
542
|
+
var pushPermissionState = typeof Notification !== "undefined" ? Notification.permission : undefined;
|
|
543
|
+
|
|
544
|
+
function requestNotificationPermission() {
|
|
545
|
+
if (pushPermissionState === "granted") return Promise.resolve(true);
|
|
546
|
+
if (pushPermissionState === "denied") return Promise.resolve(false);
|
|
547
|
+
return Notification.requestPermission().then(function (result) {
|
|
548
|
+
pushPermissionState = result;
|
|
549
|
+
window.dispatchEvent(new CustomEvent("push-permission-changed", { detail: { permission: result } }));
|
|
550
|
+
return result === "granted";
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function getPushSubscription() {
|
|
555
|
+
try {
|
|
556
|
+
return navigator.serviceWorker.ready.then(function (registration) {
|
|
557
|
+
return registration.pushManager.getSubscription();
|
|
558
|
+
});
|
|
559
|
+
} catch (e) {
|
|
560
|
+
return Promise.resolve(null);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function subscribeToPush() {
|
|
565
|
+
return requestNotificationPermission().then(function (granted) {
|
|
566
|
+
if (!granted) return null;
|
|
567
|
+
return navigator.serviceWorker.ready.then(function (registration) {
|
|
568
|
+
return registration.pushManager.subscribe({
|
|
569
|
+
userVisibleOnly: true,
|
|
570
|
+
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
|
|
571
|
+
});
|
|
572
|
+
}).then(function (subscription) {
|
|
573
|
+
return openDB("swoff-push", "subscription", "id").then(function (db) {
|
|
574
|
+
var tx = db.transaction("subscription", "readwrite");
|
|
575
|
+
tx.objectStore("subscription").put({
|
|
576
|
+
id: "current",
|
|
577
|
+
endpoint: subscription.endpoint,
|
|
578
|
+
keys: subscription.toJSON().keys,
|
|
579
|
+
subscribedAt: Date.now(),
|
|
580
|
+
});
|
|
581
|
+
return new Promise(function (resolve, reject) {
|
|
582
|
+
tx.oncomplete = function () { db.close(); resolve(); };
|
|
583
|
+
tx.onerror = function () { db.close(); reject(tx.error); };
|
|
584
|
+
});
|
|
585
|
+
}).then(function () {
|
|
586
|
+
window.dispatchEvent(new CustomEvent("push-subscription-changed", { detail: { subscribed: true } }));
|
|
587
|
+
return subscription;
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function unsubscribeFromPush() {
|
|
594
|
+
return getPushSubscription().then(function (subscription) {
|
|
595
|
+
if (!subscription) return;
|
|
596
|
+
return subscription.unsubscribe().then(function () {
|
|
597
|
+
return openDB("swoff-push", "subscription", "id").then(function (db) {
|
|
598
|
+
var tx = db.transaction("subscription", "readwrite");
|
|
599
|
+
tx.objectStore("subscription").delete("current");
|
|
600
|
+
return new Promise(function (resolve, reject) {
|
|
601
|
+
tx.oncomplete = function () { db.close(); resolve(); };
|
|
602
|
+
tx.onerror = function () { db.close(); reject(tx.error); };
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}).then(function () {
|
|
607
|
+
window.dispatchEvent(new CustomEvent("push-subscription-changed", { detail: { subscribed: false } }));
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function isSubscribed() {
|
|
612
|
+
return getPushSubscription().then(function (sub) {
|
|
613
|
+
return sub !== null;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function urlBase64ToUint8Array(base64String) {
|
|
618
|
+
var padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
|
619
|
+
var base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
620
|
+
var rawData = atob(base64);
|
|
621
|
+
return Uint8Array.from(rawData, function (c) { return c.charCodeAt(0); });
|
|
622
|
+
}
|
|
623
|
+
`;
|
|
624
|
+
}
|
|
625
|
+
function generateServerPushSection(flags) {
|
|
626
|
+
const connectImpl = flags.serverPushType === "sse"
|
|
627
|
+
? `
|
|
628
|
+
return new Promise(function (resolve) {
|
|
629
|
+
fetch(API_BASE + SERVER_PUSH_ENDPOINT, {
|
|
630
|
+
headers: { Accept: "text/event-stream" },
|
|
631
|
+
credentials: "include",
|
|
632
|
+
signal: options.signal,
|
|
633
|
+
}).then(function (response) {
|
|
634
|
+
serverPushFetchController = null;
|
|
635
|
+
if (!serverPushActive) { resolve(); return; }
|
|
636
|
+
if (!response.ok || !response.body) { resolve(); return; }
|
|
637
|
+
serverPushNotifyStatus(true);
|
|
638
|
+
var reader = response.body.getReader();
|
|
639
|
+
var decoder = new TextDecoder();
|
|
640
|
+
var buffer = "";
|
|
641
|
+
var eventType = "";
|
|
642
|
+
var dataStr = "";
|
|
643
|
+
function readNext() {
|
|
644
|
+
reader.read().then(function (result) {
|
|
645
|
+
if (result.done) {
|
|
646
|
+
serverPushNotifyStatus(false);
|
|
647
|
+
resolve();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
651
|
+
var lines = buffer.split("\\n");
|
|
652
|
+
buffer = lines.pop() || "";
|
|
653
|
+
for (var i = 0; i < lines.length; i++) {
|
|
654
|
+
var line = lines[i];
|
|
655
|
+
if (line.indexOf("event: ") === 0) eventType = line.slice(7).trim();
|
|
656
|
+
else if (line.indexOf("data: ") === 0) dataStr = line.slice(6);
|
|
657
|
+
else if (line === "" && eventType === "invalidate" && dataStr) {
|
|
658
|
+
try {
|
|
659
|
+
var p = JSON.parse(dataStr);
|
|
660
|
+
if (p.tags) serverPushHandleInvalidation(p.tags);
|
|
661
|
+
} catch (e) {}
|
|
662
|
+
eventType = "";
|
|
663
|
+
dataStr = "";
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
readNext();
|
|
667
|
+
}, function () {
|
|
668
|
+
serverPushNotifyStatus(false);
|
|
669
|
+
resolve();
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
readNext();
|
|
673
|
+
}, function () {
|
|
674
|
+
serverPushNotifyStatus(false);
|
|
675
|
+
resolve();
|
|
676
|
+
});
|
|
677
|
+
});`
|
|
678
|
+
: `
|
|
679
|
+
return new Promise(function (resolve) {
|
|
680
|
+
try {
|
|
681
|
+
var ws = new WebSocket(API_BASE + SERVER_PUSH_ENDPOINT);
|
|
682
|
+
serverPushWs = ws;
|
|
683
|
+
ws.onopen = function () { serverPushNotifyStatus(true); };
|
|
684
|
+
ws.onmessage = function (event) {
|
|
685
|
+
try {
|
|
686
|
+
var d = JSON.parse(event.data);
|
|
687
|
+
if (d.type === "invalidate" && d.tags) serverPushHandleInvalidation(d.tags);
|
|
688
|
+
} catch (e) {}
|
|
689
|
+
};
|
|
690
|
+
ws.onclose = function () { serverPushWs = null; serverPushNotifyStatus(false); resolve(); };
|
|
691
|
+
ws.onerror = function () { serverPushWs = null; resolve(); };
|
|
692
|
+
if (options.signal) {
|
|
693
|
+
options.signal.addEventListener("abort", function () { ws.close(); serverPushWs = null; });
|
|
694
|
+
}
|
|
695
|
+
} catch (e) { resolve(); }
|
|
696
|
+
});`;
|
|
697
|
+
return `
|
|
698
|
+
// ── Server Push Events ──
|
|
699
|
+
var SERVER_PUSH_ENDPOINT = ${JSON.stringify(flags.serverPushEndpoint)};
|
|
700
|
+
var SERVER_PUSH_RECONNECT_DELAY_MS = ${flags.serverPushReconnectDelayMs};
|
|
701
|
+
var serverPushActive = false;
|
|
702
|
+
var serverPushSwConnected = false;
|
|
703
|
+
var serverPushReconnectTimer = null;
|
|
704
|
+
var serverPushWs = null;
|
|
705
|
+
var serverPushFetchController = null;
|
|
706
|
+
|
|
707
|
+
function serverPushHandleInvalidation(tags) {
|
|
708
|
+
var i;
|
|
709
|
+
for (i = 0; i < tags.length; i++) {
|
|
710
|
+
invalidateByTag(tags[i]);
|
|
711
|
+
}
|
|
712
|
+
window.dispatchEvent(new CustomEvent("cache-invalidated", { detail: { tags: tags } }));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function serverPushNotifyStatus(connected) {
|
|
716
|
+
window.dispatchEvent(new CustomEvent("push-events-status", { detail: { connected: connected } }));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
|
|
720
|
+
navigator.serviceWorker.addEventListener("message", function (event) {
|
|
721
|
+
if (event.data && event.data.type === "SSE_STATUS") {
|
|
722
|
+
serverPushSwConnected = event.data.connected;
|
|
723
|
+
serverPushNotifyStatus(serverPushSwConnected);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function serverPushConnect() {
|
|
729
|
+
var controller = new AbortController();
|
|
730
|
+
serverPushFetchController = controller;
|
|
731
|
+
var options = { signal: controller.signal };
|
|
732
|
+
${connectImpl}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function startPushEvents() {
|
|
736
|
+
if (serverPushActive) return;
|
|
737
|
+
if (navigator.serviceWorker && navigator.serviceWorker.controller) return;
|
|
738
|
+
var onControllerChange = function () { stopPushEvents(); };
|
|
739
|
+
if (navigator.serviceWorker) {
|
|
740
|
+
navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
|
|
741
|
+
}
|
|
742
|
+
serverPushActive = true;
|
|
743
|
+
var delay = Math.max(1000, SERVER_PUSH_RECONNECT_DELAY_MS);
|
|
744
|
+
function serverPushLoop() {
|
|
745
|
+
if (!serverPushActive) {
|
|
746
|
+
if (navigator.serviceWorker) {
|
|
747
|
+
navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
|
|
748
|
+
}
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
serverPushConnect().then(function () {
|
|
752
|
+
if (!serverPushActive) {
|
|
753
|
+
if (navigator.serviceWorker) {
|
|
754
|
+
navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
serverPushReconnectTimer = setTimeout(function () {
|
|
759
|
+
delay = Math.min(delay * 1.5, 30000);
|
|
760
|
+
serverPushLoop();
|
|
761
|
+
}, delay);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
serverPushLoop();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function stopPushEvents() {
|
|
768
|
+
serverPushActive = false;
|
|
769
|
+
if (serverPushReconnectTimer) { clearTimeout(serverPushReconnectTimer); serverPushReconnectTimer = null; }
|
|
770
|
+
if (serverPushWs) { serverPushWs.close(); serverPushWs = null; }
|
|
771
|
+
if (serverPushFetchController) { serverPushFetchController.abort(); serverPushFetchController = null; }
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function isPushConnected() {
|
|
775
|
+
return serverPushActive || serverPushSwConnected;
|
|
776
|
+
}
|
|
777
|
+
`;
|
|
778
|
+
}
|
|
779
|
+
function compilePatterns(patterns) {
|
|
780
|
+
const entries = [];
|
|
781
|
+
for (const [pattern, templates] of Object.entries(patterns)) {
|
|
782
|
+
const compiled = compilePatternEntry(pattern, templates);
|
|
783
|
+
if (compiled) {
|
|
784
|
+
entries.push(` { re: new RegExp("${compiled.regex.replace(/\\/g, "\\\\")}"), params: ${JSON.stringify(compiled.params)}, templates: ${JSON.stringify(compiled.templates)} }`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return "[\n" + entries.join(",\n") + "\n]";
|
|
788
|
+
}
|
|
789
|
+
function compilePatternEntry(rawPattern, tagTemplates) {
|
|
790
|
+
const rawParts = rawPattern.split("/");
|
|
791
|
+
const parts = rawParts.filter((p) => p !== "");
|
|
792
|
+
const paramNames = [];
|
|
793
|
+
const regexParts = [];
|
|
794
|
+
for (let i = 0; i < parts.length; i++) {
|
|
795
|
+
const part = parts[i];
|
|
796
|
+
if (part === "**") {
|
|
797
|
+
regexParts.push("(?:\\/[^/]+)*");
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (regexParts.length > 0) {
|
|
801
|
+
regexParts.push("\\/");
|
|
802
|
+
}
|
|
803
|
+
if (part === "*") {
|
|
804
|
+
regexParts.push("[^/]+");
|
|
805
|
+
}
|
|
806
|
+
else if (part.startsWith(":")) {
|
|
807
|
+
paramNames.push(part.slice(1));
|
|
808
|
+
regexParts.push("([^/]+)");
|
|
809
|
+
}
|
|
810
|
+
else if (part.includes("{")) {
|
|
811
|
+
const close = part.indexOf("}");
|
|
812
|
+
if (close !== -1) {
|
|
813
|
+
const inner = part.slice(part.indexOf("{") + 1, close);
|
|
814
|
+
const alternatives = inner.split(",").map((s) => s.trim().replace(/[.+^${}()|[\]\\]/g, "\\$&"));
|
|
815
|
+
regexParts.push("(?:" + alternatives.join("|") + ")");
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
regexParts.push(part.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
regexParts.push(part.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (regexParts.length === 0)
|
|
826
|
+
return null;
|
|
827
|
+
const hasLeadingSlash = rawPattern.startsWith("/");
|
|
828
|
+
const regex = (hasLeadingSlash ? "^\\/" : "^") + regexParts.join("") + "$";
|
|
829
|
+
const templatePlaceholders = new Set();
|
|
830
|
+
for (const tmpl of tagTemplates) {
|
|
831
|
+
const matches = tmpl.match(/\{(\w+)\}/g);
|
|
832
|
+
if (matches) {
|
|
833
|
+
for (const m of matches) {
|
|
834
|
+
templatePlaceholders.add(m.slice(1, -1));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
for (const ph of templatePlaceholders) {
|
|
839
|
+
if (!paramNames.includes(ph)) {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return { regex, params: paramNames, templates: tagTemplates };
|
|
844
|
+
}
|
|
845
|
+
function generateAuthSection(flags) {
|
|
846
|
+
const isCookie = flags.authType === "cookie";
|
|
847
|
+
return `
|
|
848
|
+
// ── Auth Store ──
|
|
849
|
+
var AUTH_DB_NAME = "swoff-auth";
|
|
850
|
+
var AUTH_STORE_NAME = "auth";
|
|
851
|
+
var memoryAuth = null;
|
|
852
|
+
var _fetchingUser = false;
|
|
853
|
+
|
|
854
|
+
var adapter = {
|
|
855
|
+
type: ${JSON.stringify(flags.authType)},
|
|
856
|
+
getHeaders: function (auth) {
|
|
857
|
+
if (!auth || !auth.token) return {};
|
|
858
|
+
return { Authorization: "Bearer " + auth.token };
|
|
859
|
+
},
|
|
860
|
+
getAuth: function () { return Promise.resolve(null); },
|
|
861
|
+
refresh: function () { return Promise.resolve(null); },
|
|
862
|
+
fetchUser: function () { return Promise.resolve(null); },
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
function persistUserData(authData) {
|
|
866
|
+
var userData = { user: authData ? authData.user : null, expiresAt: authData ? authData.expiresAt : null };
|
|
867
|
+
return openDB(AUTH_DB_NAME, AUTH_STORE_NAME, "key").then(function (db) {
|
|
868
|
+
return new Promise(function (resolve, reject) {
|
|
869
|
+
var tx = db.transaction(AUTH_STORE_NAME, "readwrite");
|
|
870
|
+
var store = tx.objectStore(AUTH_STORE_NAME);
|
|
871
|
+
var request = store.put({ key: "session", value: userData });
|
|
872
|
+
request.onsuccess = function () { resolve(); };
|
|
873
|
+
request.onerror = function () { reject(request.error); };
|
|
874
|
+
}).then(function () { db.close(); });
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function loadUserData() {
|
|
879
|
+
return openDB(AUTH_DB_NAME, AUTH_STORE_NAME, "key").then(function (db) {
|
|
880
|
+
return new Promise(function (resolve, reject) {
|
|
881
|
+
var tx = db.transaction(AUTH_STORE_NAME, "readonly");
|
|
882
|
+
var store = tx.objectStore(AUTH_STORE_NAME);
|
|
883
|
+
var request = store.get("session");
|
|
884
|
+
request.onsuccess = function () { resolve(request.result ? request.result.value : null); };
|
|
885
|
+
request.onerror = function () { reject(request.error); };
|
|
886
|
+
}).then(function (result) { db.close(); return result; });
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function clearPersistedData() {
|
|
891
|
+
return openDB(AUTH_DB_NAME, AUTH_STORE_NAME, "key").then(function (db) {
|
|
892
|
+
return new Promise(function (resolve, reject) {
|
|
893
|
+
var tx = db.transaction(AUTH_STORE_NAME, "readwrite");
|
|
894
|
+
var store = tx.objectStore(AUTH_STORE_NAME);
|
|
895
|
+
var request = store.delete("session");
|
|
896
|
+
request.onsuccess = function () { resolve(); };
|
|
897
|
+
request.onerror = function () { reject(request.error); };
|
|
898
|
+
}).then(function () { db.close(); });
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function setAuth(authData) {
|
|
903
|
+
memoryAuth = authData;
|
|
904
|
+
return persistUserData(authData);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function getAuth() {
|
|
908
|
+
if (memoryAuth) return Promise.resolve(memoryAuth);
|
|
909
|
+
return adapter.getAuth().then(function (adapterAuth) {
|
|
910
|
+
if (adapterAuth) {
|
|
911
|
+
memoryAuth = adapterAuth;
|
|
912
|
+
return persistUserData(adapterAuth).then(function () { return memoryAuth; });
|
|
913
|
+
}
|
|
914
|
+
return null;
|
|
915
|
+
}).catch(function () {
|
|
916
|
+
return loadUserData().then(function (userData) {
|
|
917
|
+
if (userData) {
|
|
918
|
+
memoryAuth = userData;
|
|
919
|
+
return memoryAuth;
|
|
920
|
+
}
|
|
921
|
+
if (_fetchingUser) return null;
|
|
922
|
+
_fetchingUser = true;
|
|
923
|
+
return adapter.fetchUser().then(function (fetched) {
|
|
924
|
+
if (fetched) {
|
|
925
|
+
memoryAuth = fetched;
|
|
926
|
+
return persistUserData(fetched).then(function () { return memoryAuth; });
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
}).catch(function () { return null; }).then(function (result) { _fetchingUser = false; return result; });
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function clearAuth(options) {
|
|
935
|
+
options = options || {};
|
|
936
|
+
if (options.broadcast !== false) {
|
|
937
|
+
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
|
|
938
|
+
navigator.serviceWorker.controller.postMessage({ type: "AUTH_CLEARED" });
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
memoryAuth = null;
|
|
942
|
+
return clearPersistedData().then(function () {
|
|
943
|
+
try {
|
|
944
|
+
return caches.keys().then(function (keys) {
|
|
945
|
+
return Promise.all(keys.filter(function (name) { return name.indexOf("swoff-runtime") === 0; }).map(function (name) { return caches.delete(name); }));
|
|
946
|
+
});
|
|
947
|
+
} catch (e) { return Promise.resolve(); }
|
|
948
|
+
}).then(function () {
|
|
949
|
+
if (typeof clearQueue === "function") return clearQueue();
|
|
950
|
+
}).then(function () {
|
|
951
|
+
window.dispatchEvent(new CustomEvent("sw-auth-state-change", { detail: { type: "clear" } }));
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
${isCookie ? `
|
|
955
|
+
function ensureValidAuth() {
|
|
956
|
+
return getAuth();
|
|
957
|
+
}
|
|
958
|
+
` : `
|
|
959
|
+
function tryRestoreSession() {
|
|
960
|
+
return getAuth().then(function (auth) {
|
|
961
|
+
if (!auth) return null;
|
|
962
|
+
return adapter.refresh(auth).then(function (refreshed) {
|
|
963
|
+
if (refreshed) { return setAuth(refreshed).then(function () { return refreshed; }); }
|
|
964
|
+
return null;
|
|
965
|
+
});
|
|
966
|
+
}).catch(function () { return null; });
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
var restorePromise = null;
|
|
970
|
+
var refreshPromise = null;
|
|
971
|
+
|
|
972
|
+
function ensureValidAuth() {
|
|
973
|
+
return getAuth().then(function (auth) {
|
|
974
|
+
if (!auth) return null;
|
|
975
|
+
if (!auth.token) {
|
|
976
|
+
if (restorePromise) return restorePromise.then(function (r) { restorePromise = null; return r; });
|
|
977
|
+
restorePromise = tryRestoreSession();
|
|
978
|
+
return restorePromise.then(function (r) { restorePromise = null; return r; });
|
|
979
|
+
}
|
|
980
|
+
if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
|
|
981
|
+
if (!refreshPromise) {
|
|
982
|
+
refreshPromise = adapter.refresh(auth).then(function (refreshed) {
|
|
983
|
+
if (refreshed) { return setAuth(refreshed).then(function () { return refreshed; }); }
|
|
984
|
+
return clearAuth().then(function () { return null; });
|
|
985
|
+
}).catch(function () {
|
|
986
|
+
return clearAuth().then(function () { return null; });
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return refreshPromise.then(function (r) { refreshPromise = null; return r; });
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
`}
|
|
993
|
+
|
|
994
|
+
function clearMemoryAuth() {
|
|
995
|
+
memoryAuth = null;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function getAuthState() {
|
|
999
|
+
return getAuth().then(function (auth) {
|
|
1000
|
+
return {
|
|
1001
|
+
authenticated: !!(auth && (auth.token || auth.user)),
|
|
1002
|
+
auth: auth,
|
|
1003
|
+
online: getCurrentOnlineStatus(),
|
|
1004
|
+
};
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
`;
|
|
1008
|
+
}
|
|
1009
|
+
function generateAuthFetchBlock(flags) {
|
|
1010
|
+
return `
|
|
1011
|
+
if (options.auth) {
|
|
1012
|
+
return getAuth().then(function (auth) {
|
|
1013
|
+
if (auth && auth.token) {
|
|
1014
|
+
headers.set("Authorization", "Bearer " + auth.token);
|
|
1015
|
+
}
|
|
1016
|
+
}).then(function () {});
|
|
1017
|
+
}
|
|
1018
|
+
`;
|
|
1019
|
+
}
|
|
1020
|
+
function generateMutationSection(flags) {
|
|
1021
|
+
return `
|
|
1022
|
+
// ── Mutation Queue ──
|
|
1023
|
+
var QUEUE_DB_NAME = "swoff-queue";
|
|
1024
|
+
var QUEUE_STORE_NAME = "mutations";
|
|
1025
|
+
var QUEUE_BATCH_SIZE = ${flags.mutationQueueBatchSize};
|
|
1026
|
+
var QUEUE_BATCH_DELAY_MS = ${flags.mutationQueueBatchDelayMs};
|
|
1027
|
+
var QUEUE_MAX_RETRIES = ${flags.mutationQueueMaxRetries};
|
|
1028
|
+
var QUEUE_RETRY_BACKOFF_MS = ${flags.mutationQueueRetryBackoffMs};
|
|
1029
|
+
var QUEUE_RETRY_MAX_BACKOFF_MS = ${flags.mutationQueueRetryMaxBackoffMs};
|
|
1030
|
+
var QUEUE_RETRY_JITTER_MS = ${flags.mutationQueueRetryJitterMs};
|
|
1031
|
+
|
|
1032
|
+
function queueBackoffDelay(attempt) {
|
|
1033
|
+
var delay = Math.min(QUEUE_RETRY_BACKOFF_MS * Math.pow(2, attempt), QUEUE_RETRY_MAX_BACKOFF_MS);
|
|
1034
|
+
return delay + (QUEUE_RETRY_JITTER_MS > 0 ? Math.random() * QUEUE_RETRY_JITTER_MS : 0);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
var isSyncing = false;
|
|
1038
|
+
|
|
1039
|
+
function openQueueDB() {
|
|
1040
|
+
return openDB(QUEUE_DB_NAME, QUEUE_STORE_NAME, "id", function (db) {
|
|
1041
|
+
if (!db.objectStoreNames.contains(QUEUE_STORE_NAME)) {
|
|
1042
|
+
var store = db.createObjectStore(QUEUE_STORE_NAME, { keyPath: "id" });
|
|
1043
|
+
store.createIndex("by-timestamp", "timestamp");
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function queueMutation(mutation) {
|
|
1049
|
+
return openQueueDB().then(function (db) {
|
|
1050
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readwrite");
|
|
1051
|
+
var store = tx.objectStore(QUEUE_STORE_NAME);
|
|
1052
|
+
|
|
1053
|
+
var body = mutation.body;
|
|
1054
|
+
var bodyType = "json";
|
|
1055
|
+
if (typeof body === "string") {
|
|
1056
|
+
bodyType = "text";
|
|
1057
|
+
} else if (body instanceof FormData) {
|
|
1058
|
+
bodyType = "formdata";
|
|
1059
|
+
body = Array.from(body.entries());
|
|
1060
|
+
} else if (body instanceof Blob) {
|
|
1061
|
+
bodyType = "blob";
|
|
1062
|
+
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
1063
|
+
bodyType = "buffer";
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
var safeHeaders = Object.assign({}, mutation.headers || {});
|
|
1067
|
+
delete safeHeaders["authorization"];
|
|
1068
|
+
delete safeHeaders["Authorization"];
|
|
1069
|
+
|
|
1070
|
+
store.add({
|
|
1071
|
+
id: crypto.randomUUID(),
|
|
1072
|
+
method: mutation.method,
|
|
1073
|
+
url: mutation.url,
|
|
1074
|
+
body: body,
|
|
1075
|
+
bodyType: bodyType,
|
|
1076
|
+
headers: safeHeaders,
|
|
1077
|
+
timestamp: Date.now(),
|
|
1078
|
+
retryCount: 0,
|
|
1079
|
+
nextRetryAt: 0,
|
|
1080
|
+
tags: mutation.tags || [],
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
return new Promise(function (resolve, reject) {
|
|
1084
|
+
tx.oncomplete = function () { db.close(); resolve(); };
|
|
1085
|
+
tx.onerror = function () { db.close(); reject(tx.error); };
|
|
1086
|
+
});
|
|
1087
|
+
}).then(function () {
|
|
1088
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function removeFromQueue(id, db) {
|
|
1093
|
+
var ownDb = false;
|
|
1094
|
+
var p;
|
|
1095
|
+
if (!db) {
|
|
1096
|
+
p = openQueueDB().then(function (d) { db = d; ownDb = true; return db; });
|
|
1097
|
+
} else {
|
|
1098
|
+
p = Promise.resolve(db);
|
|
1099
|
+
}
|
|
1100
|
+
return p.then(function (db) {
|
|
1101
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readwrite");
|
|
1102
|
+
tx.objectStore(QUEUE_STORE_NAME).delete(id);
|
|
1103
|
+
return new Promise(function (resolve, reject) {
|
|
1104
|
+
tx.oncomplete = function () { if (ownDb) db.close(); resolve(); };
|
|
1105
|
+
tx.onerror = function () { if (ownDb) db.close(); reject(tx.error); };
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function updateInQueue(item, db) {
|
|
1111
|
+
var ownDb = false;
|
|
1112
|
+
var p;
|
|
1113
|
+
if (!db) {
|
|
1114
|
+
p = openQueueDB().then(function (d) { db = d; ownDb = true; return db; });
|
|
1115
|
+
} else {
|
|
1116
|
+
p = Promise.resolve(db);
|
|
1117
|
+
}
|
|
1118
|
+
return p.then(function (db) {
|
|
1119
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readwrite");
|
|
1120
|
+
tx.objectStore(QUEUE_STORE_NAME).put(item);
|
|
1121
|
+
return new Promise(function (resolve, reject) {
|
|
1122
|
+
tx.oncomplete = function () { if (ownDb) db.close(); resolve(); };
|
|
1123
|
+
tx.onerror = function () { if (ownDb) db.close(); reject(tx.error); };
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function clearQueue() {
|
|
1129
|
+
return openQueueDB().then(function (db) {
|
|
1130
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readwrite");
|
|
1131
|
+
tx.objectStore(QUEUE_STORE_NAME).clear();
|
|
1132
|
+
return new Promise(function (resolve, reject) {
|
|
1133
|
+
tx.oncomplete = function () { db.close(); resolve(); };
|
|
1134
|
+
tx.onerror = function () { db.close(); reject(tx.error); };
|
|
1135
|
+
});
|
|
1136
|
+
}).then(function () {
|
|
1137
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function processMutationQueue() {
|
|
1142
|
+
if (isSyncing) return Promise.resolve();
|
|
1143
|
+
return openQueueDB().then(function (db) {
|
|
1144
|
+
isSyncing = true;
|
|
1145
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readonly");
|
|
1146
|
+
var store = tx.objectStore(QUEUE_STORE_NAME);
|
|
1147
|
+
var index = store.index("by-timestamp");
|
|
1148
|
+
return new Promise(function (resolve, reject) {
|
|
1149
|
+
var request = index.getAll();
|
|
1150
|
+
request.onsuccess = function () { resolve(request.result); };
|
|
1151
|
+
request.onerror = function () { reject(request.error); };
|
|
1152
|
+
}).then(function (queue) {
|
|
1153
|
+
if (queue.length === 0) { isSyncing = false; db.close(); return; }
|
|
1154
|
+
|
|
1155
|
+
var succeeded = 0;
|
|
1156
|
+
var failed = 0;
|
|
1157
|
+
var total = queue.length;
|
|
1158
|
+
var earliestRetry = Infinity;
|
|
1159
|
+
|
|
1160
|
+
function processNext(index) {
|
|
1161
|
+
if (index >= queue.length) {
|
|
1162
|
+
window.dispatchEvent(new CustomEvent("mutation-sync-complete", { detail: { succeeded: succeeded, failed: failed, total: total } }));
|
|
1163
|
+
if (earliestRetry < Infinity && earliestRetry > Date.now()) {
|
|
1164
|
+
setTimeout(function () { if (!isSyncing) processMutationQueue(); }, earliestRetry - Date.now());
|
|
1165
|
+
}
|
|
1166
|
+
isSyncing = false;
|
|
1167
|
+
db.close();
|
|
1168
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
var item = queue[index];
|
|
1173
|
+
|
|
1174
|
+
if (item.retryCount >= QUEUE_MAX_RETRIES) {
|
|
1175
|
+
removeFromQueue(item.id, db).then(function () {
|
|
1176
|
+
failed++; emitProgress(succeeded, failed, total);
|
|
1177
|
+
if (QUEUE_BATCH_DELAY_MS > 0 && succeeded + failed < total) {
|
|
1178
|
+
setTimeout(function () { processNext(index + 1); }, QUEUE_BATCH_DELAY_MS);
|
|
1179
|
+
} else { processNext(index + 1); }
|
|
1180
|
+
});
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (item.nextRetryAt && Date.now() < item.nextRetryAt) {
|
|
1185
|
+
if (item.nextRetryAt < earliestRetry) earliestRetry = item.nextRetryAt;
|
|
1186
|
+
processNext(index + 1);
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
replayMutation(item, db).then(function (ok) {
|
|
1191
|
+
if (ok) { succeeded++; } else { failed++; }
|
|
1192
|
+
emitProgress(succeeded, failed, total);
|
|
1193
|
+
if (QUEUE_BATCH_DELAY_MS > 0 && succeeded + failed < total) {
|
|
1194
|
+
setTimeout(function () { processNext(index + 1); }, QUEUE_BATCH_DELAY_MS);
|
|
1195
|
+
} else { processNext(index + 1); }
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
processNext(0);
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function emitProgress(succeeded, failed, total) {
|
|
1205
|
+
if ((succeeded + failed) % QUEUE_BATCH_SIZE === 0 || succeeded + failed === total) {
|
|
1206
|
+
window.dispatchEvent(new CustomEvent("mutation-sync-progress", { detail: { succeeded: succeeded, failed: failed, total: total } }));
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function replayMutation(item, db) {
|
|
1211
|
+
var authPromise = ${flags.authEnabled
|
|
1212
|
+
? "getAuth().then(function (auth) { return auth; })"
|
|
1213
|
+
: "Promise.resolve(null)"};
|
|
1214
|
+
return authPromise.then(function (auth) {
|
|
1215
|
+
var authHeader = {};
|
|
1216
|
+
if (auth && auth.token) {
|
|
1217
|
+
authHeader = { Authorization: "Bearer " + auth.token };
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
var replayBody = null;
|
|
1221
|
+
var contentType = undefined;
|
|
1222
|
+
var bt = item.bodyType || "json";
|
|
1223
|
+
|
|
1224
|
+
if (bt === "formdata") {
|
|
1225
|
+
replayBody = new FormData();
|
|
1226
|
+
for (var i = 0; i < (item.body || []).length; i++) {
|
|
1227
|
+
replayBody.append(item.body[i][0], item.body[i][1]);
|
|
1228
|
+
}
|
|
1229
|
+
} else if (bt === "blob" || bt === "buffer") {
|
|
1230
|
+
replayBody = item.body;
|
|
1231
|
+
} else if (bt === "text") {
|
|
1232
|
+
replayBody = item.body;
|
|
1233
|
+
} else if (item.body != null) {
|
|
1234
|
+
replayBody = JSON.stringify(item.body);
|
|
1235
|
+
contentType = "application/json";
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return fetch(item.url, {
|
|
1239
|
+
method: item.method,
|
|
1240
|
+
headers: Object.assign({}, contentType ? { "Content-Type": contentType } : {}, item.headers, authHeader),
|
|
1241
|
+
body: replayBody,
|
|
1242
|
+
}).then(function (response) {
|
|
1243
|
+
return handleReplayResponse(response, item, db);
|
|
1244
|
+
}).catch(function () {
|
|
1245
|
+
item.retryCount++;
|
|
1246
|
+
item.nextRetryAt = Date.now() + queueBackoffDelay(item.retryCount - 1);
|
|
1247
|
+
return updateInQueue(item, db).then(function () { return false; });
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function handleReplayResponse(response, item, db) {
|
|
1253
|
+
if (response.ok) {
|
|
1254
|
+
if (item.tags && item.tags.length > 0) {
|
|
1255
|
+
return invalidateByTags(item.tags).then(function () {
|
|
1256
|
+
return removeFromQueue(item.id, db).then(function () { return true; });
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
return removeFromQueue(item.id, db).then(function () { return true; });
|
|
1260
|
+
}
|
|
1261
|
+
if (response.status === 401) {
|
|
1262
|
+
return ensureValidAuth().then(function (refreshed) {
|
|
1263
|
+
if (refreshed && refreshed.token) {
|
|
1264
|
+
var retryHeader = { Authorization: "Bearer " + refreshed.token };
|
|
1265
|
+
return fetch(item.url, {
|
|
1266
|
+
method: item.method,
|
|
1267
|
+
headers: Object.assign({}, item.headers, retryHeader),
|
|
1268
|
+
body: item.body ? JSON.stringify(item.body) : null,
|
|
1269
|
+
}).then(function (retryResponse) {
|
|
1270
|
+
if (retryResponse.ok) {
|
|
1271
|
+
if (item.tags && item.tags.length > 0) {
|
|
1272
|
+
return invalidateByTags(item.tags);
|
|
1273
|
+
}
|
|
1274
|
+
return removeFromQueue(item.id, db).then(function () { return true; });
|
|
1275
|
+
}
|
|
1276
|
+
return removeFromQueue(item.id, db).then(function () { return true; });
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
return clearAuth().then(function () { return false; });
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
item.retryCount++;
|
|
1283
|
+
item.nextRetryAt = Date.now() + queueBackoffDelay(item.retryCount - 1);
|
|
1284
|
+
return updateInQueue(item, db).then(function () { return false; });
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function flushMutations() {
|
|
1288
|
+
return processMutationQueue();
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function getPendingCount() {
|
|
1292
|
+
return openQueueDB().then(function (db) {
|
|
1293
|
+
return new Promise(function (resolve, reject) {
|
|
1294
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readonly");
|
|
1295
|
+
var request = tx.objectStore(QUEUE_STORE_NAME).count();
|
|
1296
|
+
request.onsuccess = function () { db.close(); resolve(request.result); };
|
|
1297
|
+
request.onerror = function () { db.close(); reject(request.error); };
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function getQueueItems() {
|
|
1303
|
+
return openQueueDB().then(function (db) {
|
|
1304
|
+
var tx = db.transaction(QUEUE_STORE_NAME, "readonly");
|
|
1305
|
+
var store = tx.objectStore(QUEUE_STORE_NAME);
|
|
1306
|
+
var index = store.index("by-timestamp");
|
|
1307
|
+
return new Promise(function (resolve, reject) {
|
|
1308
|
+
var request = index.getAll();
|
|
1309
|
+
request.onsuccess = function () { resolve(request.result); };
|
|
1310
|
+
request.onerror = function () { reject(request.error); };
|
|
1311
|
+
}).then(function (result) { db.close(); return result; });
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function getQueuePosition(id) {
|
|
1316
|
+
return getQueueItems().then(function (items) {
|
|
1317
|
+
for (var i = 0; i < items.length; i++) {
|
|
1318
|
+
if (items[i].id === id) return i;
|
|
1319
|
+
}
|
|
1320
|
+
return -1;
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function registerSync() {
|
|
1325
|
+
if (!("SyncManager" in window)) {
|
|
1326
|
+
window.addEventListener("online", processMutationQueue, { once: true });
|
|
1327
|
+
return Promise.resolve();
|
|
1328
|
+
}
|
|
1329
|
+
return navigator.serviceWorker.ready.then(function (registration) {
|
|
1330
|
+
return registration.sync.register("sync-mutations").catch(function () {
|
|
1331
|
+
window.addEventListener("online", processMutationQueue, { once: true });
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function syncWhenPossible(mutation) {
|
|
1337
|
+
return queueMutation(mutation).then(function () {
|
|
1338
|
+
return registerSync();
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function retrySync() {
|
|
1343
|
+
window.addEventListener("mutation-sync-complete", retrySync, { once: true });
|
|
1344
|
+
if (!("SyncManager" in window)) return Promise.resolve();
|
|
1345
|
+
return getPendingCount().then(function (count) {
|
|
1346
|
+
if (count > 0) return registerSync();
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
`;
|
|
1350
|
+
}
|
|
1351
|
+
function generatePwaSection(flags) {
|
|
1352
|
+
const preventLine = flags.pwaPreventDefaultInstall
|
|
1353
|
+
? " e.preventDefault();\n"
|
|
1354
|
+
: "";
|
|
1355
|
+
return `
|
|
1356
|
+
// ── PWA Install Prompt ──
|
|
1357
|
+
function setupPwaInstall() {
|
|
1358
|
+
window.addEventListener("beforeinstallprompt", function (e) {
|
|
1359
|
+
window.deferredInstallPrompt = e;
|
|
1360
|
+
window.pwaInstallable = true;
|
|
1361
|
+
${preventLine} window.dispatchEvent(new CustomEvent("pwa-installable", { detail: { isInstallable: true } }));
|
|
1362
|
+
});
|
|
1363
|
+
window.addEventListener("appinstalled", function () {
|
|
1364
|
+
window.deferredInstallPrompt = null;
|
|
1365
|
+
window.pwaInstallable = false;
|
|
1366
|
+
window.dispatchEvent(new CustomEvent("pwa-installed", { detail: { outcome: "accepted" } }));
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") { setupPwaInstall(); }
|
|
1370
|
+
|
|
1371
|
+
function isInstallable() {
|
|
1372
|
+
if (typeof window === "undefined") return false;
|
|
1373
|
+
return !!window.deferredInstallPrompt;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function promptInstall() {
|
|
1377
|
+
if (!window.deferredInstallPrompt) {
|
|
1378
|
+
return Promise.reject(new Error("Install prompt not available"));
|
|
1379
|
+
}
|
|
1380
|
+
var promptEvent = window.deferredInstallPrompt;
|
|
1381
|
+
return promptEvent.prompt().then(function () {
|
|
1382
|
+
return promptEvent.userChoice;
|
|
1383
|
+
}).then(function (choice) {
|
|
1384
|
+
if (choice.outcome === "accepted") {
|
|
1385
|
+
window.dispatchEvent(new CustomEvent("pwa-installed", { detail: { outcome: "accepted" } }));
|
|
1386
|
+
} else {
|
|
1387
|
+
window.dispatchEvent(new CustomEvent("pwa-dismissed", { detail: { outcome: "dismissed" } }));
|
|
1388
|
+
}
|
|
1389
|
+
window.deferredInstallPrompt = null;
|
|
1390
|
+
return choice;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
`;
|
|
1394
|
+
}
|
|
1395
|
+
function generateGqlSection(flags) {
|
|
1396
|
+
const endpointsCode = JSON.stringify(flags.gqlEndpoints);
|
|
1397
|
+
return `
|
|
1398
|
+
// ── GraphQL Wrapper ──
|
|
1399
|
+
var GQL_ENDPOINTS = ${endpointsCode};
|
|
1400
|
+
|
|
1401
|
+
function getOperationName(query) {
|
|
1402
|
+
var match = query.match(/(query|mutation|subscription)\\s+(\\w+)/);
|
|
1403
|
+
return match ? match[2] : null;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function isReadOperation(query) {
|
|
1407
|
+
var trimmed = query.trim();
|
|
1408
|
+
if (trimmed.startsWith("mutation") || trimmed.startsWith("subscription")) return false;
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function simpleHash(str) {
|
|
1413
|
+
var hash = 0;
|
|
1414
|
+
for (var i = 0; i < str.length; i++) {
|
|
1415
|
+
var char = str.charCodeAt(i);
|
|
1416
|
+
hash = ((hash << 5) - hash) + char;
|
|
1417
|
+
hash = hash & hash;
|
|
1418
|
+
}
|
|
1419
|
+
return Math.abs(hash).toString(16).slice(0, 16);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function bodyHash(obj) {
|
|
1423
|
+
var json = JSON.stringify(obj);
|
|
1424
|
+
if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
|
|
1425
|
+
try {
|
|
1426
|
+
var bytes = new TextEncoder().encode(json);
|
|
1427
|
+
return crypto.subtle.digest("SHA-256", bytes).then(function (hash) {
|
|
1428
|
+
return Array.from(new Uint8Array(hash)).map(function (b) { return b.toString(16).padStart(2, "0"); }).join("").slice(0, 16);
|
|
1429
|
+
});
|
|
1430
|
+
} catch (e) {}
|
|
1431
|
+
}
|
|
1432
|
+
return Promise.resolve(simpleHash(json));
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function tagsFromOpName(name) {
|
|
1436
|
+
if (!name) return [];
|
|
1437
|
+
var stripped = name.replace(/^(get|fetch|list|all|query)/i, "").replace(/^(create|set|add|new|update|delete|remove)/i, "");
|
|
1438
|
+
if (!stripped) return [name.toLowerCase()];
|
|
1439
|
+
var tag = stripped.toLowerCase();
|
|
1440
|
+
var plural = tag.replace(/s$/, "") + "s";
|
|
1441
|
+
return [plural, tag];
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function fetchWithGql(query, options, endpointIndex) {
|
|
1445
|
+
options = options || {};
|
|
1446
|
+
endpointIndex = endpointIndex || 0;
|
|
1447
|
+
var isRead = isReadOperation(query);
|
|
1448
|
+
var opName = getOperationName(query);
|
|
1449
|
+
var variables = options.variables;
|
|
1450
|
+
var tags = options.tags || tagsFromOpName(opName);
|
|
1451
|
+
var endpoint = GQL_ENDPOINTS[endpointIndex] || GQL_ENDPOINTS[0];
|
|
1452
|
+
|
|
1453
|
+
return bodyHash({ query: query, variables: variables }).then(function (hash) {
|
|
1454
|
+
return fetchWithCache(endpoint, {
|
|
1455
|
+
method: "POST",
|
|
1456
|
+
body: JSON.stringify({ query: query, variables: variables }),
|
|
1457
|
+
headers: {
|
|
1458
|
+
"Content-Type": "application/json",
|
|
1459
|
+
"X-SW-Cache-Key": "gql:" + hash,
|
|
1460
|
+
},
|
|
1461
|
+
tags: tags,
|
|
1462
|
+
type: isRead ? "read" : "mutation",
|
|
1463
|
+
auth: options.auth,
|
|
1464
|
+
queueOffline: options.queueOffline,
|
|
1465
|
+
invalidate: options.invalidate,
|
|
1466
|
+
});
|
|
1467
|
+
}).then(function (result) {
|
|
1468
|
+
var response = result.response;
|
|
1469
|
+
if (!response.ok) {
|
|
1470
|
+
throw new Error("GraphQL request failed with status " + response.status);
|
|
1471
|
+
}
|
|
1472
|
+
return response.json().then(function (json) {
|
|
1473
|
+
return { data: json.data, fromCache: result.fromCache };
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function queryGql(query, variables, options, endpointIndex) {
|
|
1479
|
+
options = options || {};
|
|
1480
|
+
options.variables = variables;
|
|
1481
|
+
return fetchWithGql(query, options, endpointIndex);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function mutateGql(mutation, variables, options, endpointIndex) {
|
|
1485
|
+
options = options || {};
|
|
1486
|
+
options.variables = variables;
|
|
1487
|
+
return fetchWithGql(mutation, options, endpointIndex);
|
|
1488
|
+
}
|
|
1489
|
+
`;
|
|
1490
|
+
}
|
|
1491
|
+
//# sourceMappingURL=swoff-api-bundle.js.map
|