@sweidos/eidos 1.1.0 → 2.0.0
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 +127 -32
- package/dist/action.js +223 -112
- package/dist/action.js.map +1 -0
- package/dist/async-storage-adapter.js.map +1 -0
- package/dist/cli.js +102 -0
- package/dist/devtools.js +208 -71
- package/dist/eidos-sw.js +283 -188
- package/dist/eidos.cjs +2 -2
- package/dist/eidos.cjs.map +1 -0
- package/dist/idb.js.map +1 -0
- package/dist/index.d.ts +160 -26
- package/dist/index.js +45 -41
- package/dist/push.cjs +123 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +116 -0
- package/dist/query.cjs +1 -1
- package/dist/query.d.ts +1 -2
- package/dist/query.js +1 -1
- package/dist/queue-storage.js.map +1 -0
- package/dist/queue-sync.js +34 -0
- package/dist/queue-sync.js.map +1 -0
- package/dist/react/Provider.js.map +1 -0
- package/dist/react/hooks.js +23 -23
- package/dist/react/hooks.js.map +1 -0
- package/dist/replay.js.map +1 -0
- package/dist/resource.js +121 -107
- package/dist/resource.js.map +1 -0
- package/dist/runtime.js +37 -19
- package/dist/runtime.js.map +1 -0
- package/dist/store-slices.js.map +1 -0
- package/dist/store.js.map +1 -0
- package/dist/stores.js +23 -31
- package/dist/stores.js.map +1 -0
- package/dist/sw-bridge.js +44 -31
- package/dist/sw-bridge.js.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -0
- package/package.json +11 -4
package/dist/eidos-sw.js
CHANGED
|
@@ -1,213 +1,308 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
//#region ../core/src/internal/url-base64.ts
|
|
2
|
+
/** Decodes a base64url string (e.g. a VAPID public key) into raw bytes. */
|
|
3
|
+
function urlBase64ToUint8Array(base64Url) {
|
|
4
|
+
const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
5
|
+
const raw = atob(base64);
|
|
6
|
+
return Uint8Array.from(raw, (c) => c.charCodeAt(0));
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/sw.ts
|
|
10
|
+
var CACHE_VERSION = "v1";
|
|
11
|
+
var CACHE_PREFIX = "eidos";
|
|
12
|
+
var runtimeConfig = {
|
|
13
|
+
resources: /* @__PURE__ */ new Map(),
|
|
14
|
+
simulateOffline: false
|
|
6
15
|
};
|
|
7
16
|
self.addEventListener("install", (event) => {
|
|
8
|
-
|
|
17
|
+
event.waitUntil(self.skipWaiting());
|
|
9
18
|
});
|
|
10
19
|
self.addEventListener("activate", (event) => {
|
|
11
|
-
|
|
12
|
-
Promise.all([
|
|
13
|
-
self.clients.claim(),
|
|
14
|
-
// Purge stale caches from previous versions
|
|
15
|
-
caches.keys().then(
|
|
16
|
-
(keys) => Promise.all(
|
|
17
|
-
keys.filter((k) => k.startsWith(CACHE_PREFIX) && !k.endsWith(CACHE_VERSION)).map((k) => caches.delete(k))
|
|
18
|
-
)
|
|
19
|
-
)
|
|
20
|
-
])
|
|
21
|
-
);
|
|
20
|
+
event.waitUntil(Promise.all([self.clients.claim(), caches.keys().then((keys) => Promise.all(keys.filter((k) => k.startsWith(CACHE_PREFIX) && !k.endsWith(CACHE_VERSION)).map((k) => caches.delete(k))))]));
|
|
22
21
|
});
|
|
23
22
|
self.addEventListener("message", (event) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
23
|
+
const data = event.data;
|
|
24
|
+
if (!data?.type) return;
|
|
25
|
+
switch (data.type) {
|
|
26
|
+
case "EIDOS_REGISTER_RESOURCE": {
|
|
27
|
+
const url = data.url;
|
|
28
|
+
const patternSrc = data.pattern;
|
|
29
|
+
runtimeConfig.resources.set(url, {
|
|
30
|
+
strategy: data.strategy,
|
|
31
|
+
cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
|
|
32
|
+
...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
|
|
33
|
+
});
|
|
34
|
+
event.source?.postMessage({
|
|
35
|
+
type: "EIDOS_RESOURCE_REGISTERED",
|
|
36
|
+
url
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "EIDOS_UNREGISTER_RESOURCE":
|
|
41
|
+
runtimeConfig.resources.delete(data.url);
|
|
42
|
+
break;
|
|
43
|
+
case "EIDOS_SIMULATE_OFFLINE":
|
|
44
|
+
runtimeConfig.simulateOffline = data.enabled;
|
|
45
|
+
break;
|
|
46
|
+
case "EIDOS_CLEAR_CACHE": {
|
|
47
|
+
const targetUrl = data.url;
|
|
48
|
+
const reg = targetUrl ? runtimeConfig.resources.get(targetUrl) : void 0;
|
|
49
|
+
const cacheName = reg?.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`;
|
|
50
|
+
caches.open(cacheName).then(async (cache) => {
|
|
51
|
+
if (targetUrl) {
|
|
52
|
+
const keys = await cache.keys();
|
|
53
|
+
const isCrossOrigin = targetUrl.startsWith("http");
|
|
54
|
+
await Promise.all(keys.filter((req) => {
|
|
55
|
+
const reqUrl = req.url;
|
|
56
|
+
const p = new URL(reqUrl).pathname;
|
|
57
|
+
if (reg?.pattern) return reg.pattern.test(isCrossOrigin ? reqUrl : p);
|
|
58
|
+
return isCrossOrigin ? reqUrl === targetUrl : p === targetUrl;
|
|
59
|
+
}).map((req) => cache.delete(req)));
|
|
60
|
+
} else await cache.keys().then((keys) => Promise.all(keys.map((k) => cache.delete(k))));
|
|
61
|
+
notifyClients({
|
|
62
|
+
type: "EIDOS_CACHE_CLEARED",
|
|
63
|
+
url: targetUrl
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "EIDOS_PING":
|
|
69
|
+
event.source?.postMessage({ type: "EIDOS_PONG" });
|
|
70
|
+
break;
|
|
71
|
+
case "EIDOS_CACHE_VAPID_KEY":
|
|
72
|
+
idbSet("vapidPublicKey", data.key);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
75
|
});
|
|
76
76
|
self.addEventListener("fetch", (event) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
event.respondWith(handleFetch(event.request, pathname, reg));
|
|
77
|
+
if (event.request.method !== "GET") return;
|
|
78
|
+
const requestUrl = event.request.url;
|
|
79
|
+
const pathname = new URL(requestUrl).pathname;
|
|
80
|
+
let reg = runtimeConfig.resources.get(requestUrl) ?? runtimeConfig.resources.get(pathname);
|
|
81
|
+
if (!reg) for (const [key, registration] of runtimeConfig.resources) {
|
|
82
|
+
if (!registration.pattern) continue;
|
|
83
|
+
const target = key.startsWith("http") ? requestUrl : pathname;
|
|
84
|
+
if (registration.pattern.test(target)) {
|
|
85
|
+
reg = registration;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!reg) return;
|
|
90
|
+
if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
|
|
91
|
+
event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg.cacheName));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
event.respondWith(handleFetch(event.request, pathname, reg));
|
|
97
95
|
});
|
|
98
96
|
async function handleFetch(request, pathname, reg) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return staleWhileRevalidate(null, request, pathname, reg.cacheName);
|
|
107
|
-
case "network-first":
|
|
108
|
-
return networkFirst(request, pathname, reg.cacheName);
|
|
109
|
-
default:
|
|
110
|
-
return fetch(request);
|
|
111
|
-
}
|
|
97
|
+
if (runtimeConfig.simulateOffline) return serveOffline(request, pathname, reg.cacheName);
|
|
98
|
+
switch (reg.strategy) {
|
|
99
|
+
case "cache-first": return cacheFirst(request, pathname, reg.cacheName);
|
|
100
|
+
case "stale-while-revalidate": return staleWhileRevalidate(null, request, pathname, reg.cacheName);
|
|
101
|
+
case "network-first": return networkFirst(request, pathname, reg.cacheName);
|
|
102
|
+
default: return fetch(request);
|
|
103
|
+
}
|
|
112
104
|
}
|
|
113
105
|
async function cacheFirst(request, pathname, cacheName) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
106
|
+
const cache = await caches.open(cacheName);
|
|
107
|
+
const cached = await cache.match(request);
|
|
108
|
+
if (cached) {
|
|
109
|
+
notifyClients({
|
|
110
|
+
type: "EIDOS_CACHE_HIT",
|
|
111
|
+
url: pathname,
|
|
112
|
+
strategy: "cache-first"
|
|
113
|
+
});
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(request);
|
|
118
|
+
if (response.ok) {
|
|
119
|
+
await cache.put(request, response.clone());
|
|
120
|
+
notifyClients({
|
|
121
|
+
type: "EIDOS_CACHE_UPDATED",
|
|
122
|
+
url: pathname,
|
|
123
|
+
strategy: "cache-first"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return response;
|
|
127
|
+
} catch {
|
|
128
|
+
notifyClients({
|
|
129
|
+
type: "EIDOS_NETWORK_ERROR",
|
|
130
|
+
url: pathname
|
|
131
|
+
});
|
|
132
|
+
return offlineErrorResponse(pathname);
|
|
133
|
+
}
|
|
131
134
|
}
|
|
132
135
|
async function staleWhileRevalidate(event, request, pathname, cacheName) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
136
|
+
const cache = await caches.open(cacheName);
|
|
137
|
+
const cached = await cache.match(request);
|
|
138
|
+
const revalidatePromise = fetch(request).then(async (response) => {
|
|
139
|
+
if (response.ok) {
|
|
140
|
+
await cache.put(request, response.clone());
|
|
141
|
+
notifyClients({
|
|
142
|
+
type: "EIDOS_CACHE_UPDATED",
|
|
143
|
+
url: pathname,
|
|
144
|
+
strategy: "stale-while-revalidate"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return response;
|
|
148
|
+
}).catch(() => {
|
|
149
|
+
notifyClients({
|
|
150
|
+
type: "EIDOS_NETWORK_ERROR",
|
|
151
|
+
url: pathname,
|
|
152
|
+
strategy: "stale-while-revalidate"
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
if (cached) {
|
|
156
|
+
event?.waitUntil(revalidatePromise);
|
|
157
|
+
notifyClients({
|
|
158
|
+
type: "EIDOS_CACHE_HIT",
|
|
159
|
+
url: pathname,
|
|
160
|
+
strategy: "stale-while-revalidate"
|
|
161
|
+
});
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
|
164
|
+
return await revalidatePromise ?? offlineErrorResponse(pathname);
|
|
159
165
|
}
|
|
160
166
|
async function networkFirst(request, pathname, cacheName) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
const cache = await caches.open(cacheName);
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
|
|
170
|
+
if (response.ok) {
|
|
171
|
+
await cache.put(request, response.clone());
|
|
172
|
+
notifyClients({
|
|
173
|
+
type: "EIDOS_CACHE_UPDATED",
|
|
174
|
+
url: pathname,
|
|
175
|
+
strategy: "network-first"
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return response;
|
|
179
|
+
} catch {
|
|
180
|
+
const cached = await cache.match(request);
|
|
181
|
+
if (cached) {
|
|
182
|
+
notifyClients({
|
|
183
|
+
type: "EIDOS_CACHE_HIT",
|
|
184
|
+
url: pathname,
|
|
185
|
+
strategy: "network-first"
|
|
186
|
+
});
|
|
187
|
+
return cached;
|
|
188
|
+
}
|
|
189
|
+
notifyClients({
|
|
190
|
+
type: "EIDOS_NETWORK_ERROR",
|
|
191
|
+
url: pathname
|
|
192
|
+
});
|
|
193
|
+
return offlineErrorResponse(pathname);
|
|
194
|
+
}
|
|
178
195
|
}
|
|
179
196
|
async function serveOffline(request, pathname, cacheName) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
197
|
+
const cached = await (await caches.open(cacheName)).match(request);
|
|
198
|
+
if (cached) {
|
|
199
|
+
notifyClients({
|
|
200
|
+
type: "EIDOS_CACHE_HIT",
|
|
201
|
+
url: pathname,
|
|
202
|
+
strategy: "offline-simulation",
|
|
203
|
+
simulated: true
|
|
204
|
+
});
|
|
205
|
+
return cached;
|
|
206
|
+
}
|
|
207
|
+
return offlineErrorResponse(pathname);
|
|
187
208
|
}
|
|
188
209
|
function offlineErrorResponse(pathname) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
);
|
|
210
|
+
return new Response(JSON.stringify({
|
|
211
|
+
error: "offline",
|
|
212
|
+
message: `No cached response available for ${pathname}`,
|
|
213
|
+
eidos: true
|
|
214
|
+
}), {
|
|
215
|
+
status: 503,
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json",
|
|
218
|
+
"X-Eidos-Offline": "true"
|
|
219
|
+
}
|
|
220
|
+
});
|
|
203
221
|
}
|
|
204
222
|
async function notifyClients(message) {
|
|
205
|
-
|
|
206
|
-
clients.forEach((client) => client.postMessage(message));
|
|
223
|
+
(await self.clients.matchAll({ includeUncontrolled: true })).forEach((client) => client.postMessage(message));
|
|
207
224
|
}
|
|
208
225
|
self.addEventListener("sync", (event) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
const syncEvent = event;
|
|
227
|
+
if (syncEvent.tag === "eidos-queue-replay") syncEvent.waitUntil(notifyClients({ type: "EIDOS_BACKGROUND_SYNC" }));
|
|
228
|
+
});
|
|
229
|
+
self.addEventListener("push", (event) => {
|
|
230
|
+
let payload = null;
|
|
231
|
+
try {
|
|
232
|
+
payload = event.data?.json();
|
|
233
|
+
} catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!payload?.title) return;
|
|
237
|
+
event.waitUntil(self.registration.showNotification(payload.title, {
|
|
238
|
+
body: payload.body,
|
|
239
|
+
icon: payload.icon,
|
|
240
|
+
badge: payload.badge,
|
|
241
|
+
tag: payload.tag,
|
|
242
|
+
data: payload.data
|
|
243
|
+
}));
|
|
244
|
+
});
|
|
245
|
+
self.addEventListener("notificationclick", (event) => {
|
|
246
|
+
event.notification.close();
|
|
247
|
+
const data = event.notification.data ?? {};
|
|
248
|
+
event.waitUntil(self.clients.matchAll({
|
|
249
|
+
type: "window",
|
|
250
|
+
includeUncontrolled: true
|
|
251
|
+
}).then((clients) => {
|
|
252
|
+
const client = clients[0];
|
|
253
|
+
if (client) {
|
|
254
|
+
client.focus();
|
|
255
|
+
client.postMessage({
|
|
256
|
+
type: "EIDOS_NOTIFICATION_CLICK",
|
|
257
|
+
data
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (data.url) return self.clients.openWindow(data.url);
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
264
|
+
self.addEventListener("pushsubscriptionchange", (event) => {
|
|
265
|
+
const psEvent = event;
|
|
266
|
+
psEvent.waitUntil((async () => {
|
|
267
|
+
const vapidPublicKey = await idbGet("vapidPublicKey");
|
|
268
|
+
if (!vapidPublicKey) return;
|
|
269
|
+
await notifyClients({
|
|
270
|
+
type: "EIDOS_SUBSCRIPTION_EXPIRED",
|
|
271
|
+
subscription: (psEvent.newSubscription ?? await self.registration.pushManager.subscribe({
|
|
272
|
+
userVisibleOnly: true,
|
|
273
|
+
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
|
|
274
|
+
})).toJSON()
|
|
275
|
+
});
|
|
276
|
+
})());
|
|
213
277
|
});
|
|
278
|
+
var META_DB = "eidos-sw-meta";
|
|
279
|
+
var META_STORE = "kv";
|
|
280
|
+
function openMetaDb() {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const req = indexedDB.open(META_DB, 1);
|
|
283
|
+
req.onupgradeneeded = () => req.result.createObjectStore(META_STORE);
|
|
284
|
+
req.onsuccess = () => resolve(req.result);
|
|
285
|
+
req.onerror = () => reject(req.error);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function idbSet(key, value) {
|
|
289
|
+
const db = await openMetaDb();
|
|
290
|
+
await new Promise((resolve, reject) => {
|
|
291
|
+
const tx = db.transaction(META_STORE, "readwrite");
|
|
292
|
+
tx.objectStore(META_STORE).put(value, key);
|
|
293
|
+
tx.oncomplete = () => resolve();
|
|
294
|
+
tx.onerror = () => reject(tx.error);
|
|
295
|
+
});
|
|
296
|
+
db.close();
|
|
297
|
+
}
|
|
298
|
+
async function idbGet(key) {
|
|
299
|
+
const db = await openMetaDb();
|
|
300
|
+
const value = await new Promise((resolve, reject) => {
|
|
301
|
+
const req = db.transaction(META_STORE, "readonly").objectStore(META_STORE).get(key);
|
|
302
|
+
req.onsuccess = () => resolve(req.result);
|
|
303
|
+
req.onerror = () => reject(req.error);
|
|
304
|
+
});
|
|
305
|
+
db.close();
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
package/dist/eidos.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let S=require("react"),H=require("react/jsx-runtime");function pe(e){return{registerResource:(t,r)=>e(n=>({resources:{...n.resources,[t]:r}})),updateResource:(t,r)=>e(n=>({resources:{...n.resources,[t]:n.resources[t]?{...n.resources[t],...r}:n.resources[t]}})),unregisterResource:t=>e(r=>({resources:Object.fromEntries(Object.entries(r.resources).filter(([n])=>n!==t))}))}}function he(e){return{addQueueItem:t=>e(r=>({queue:[...r.queue,t]})),updateQueueItem:(t,r)=>e(n=>({queue:n.queue.map(a=>a.id===t?{...a,...r}:a)})),batchUpdateQueueItems:t=>e(r=>{const n=new Map(t.map(a=>[a.id,a.update]));return{queue:r.queue.map(a=>{const s=n.get(a.id);return s?{...a,...s}:a})}}),removeQueueItem:t=>e(r=>({queue:r.queue.filter(n=>n.id!==t)})),hydrateQueue:t=>e(()=>({queue:t}))}}var w,D=new Set;function G(){D.forEach(e=>e())}function k(e){w={...w,...e(w)},G()}w={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>k(()=>({isOnline:e})),setSwStatus:(e,t)=>k(()=>({swStatus:e,swError:t})),...pe(k),...he(k)};function ye(){return w}function ge(e){return D.add(e),()=>{D.delete(e)}}var o={getState:ye,subscribe:ge,setState:e=>{const t=typeof e=="function"?e(w):e;w={...w,...t},G()}},m=null,T=[];function Y(){return m}async function we(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){o.getState().setSwStatus("unsupported");return}const t=o.getState();t.setSwStatus("registering");try{m=await navigator.serviceWorker.register(e,{scope:"/"}),await ve(m),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",Ee),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),_e()}catch(r){t.setSwStatus("error",String(r))}}function ve(e){return new Promise(t=>{if(e.active){t();return}const r=e.installing??e.waiting;if(!r){t();return}const n=setTimeout(t,1e4);r.addEventListener("statechange",function a(){r.state==="activated"&&(clearTimeout(n),r.removeEventListener("statechange",a),t())})})}function _(e){const t=m?.active;t?t.postMessage(e):T.push(e)}var J=null;function me(e){J=e}function Se(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&m!==null&&"sync"in m}catch{return!1}}var M={};function be(e){M=e}function Ee(e){const t=e.data;if(!t?.type)return;const r=o.getState(),{type:n,url:a}=t;if(n==="EIDOS_BACKGROUND_SYNC"){J?.();return}if(n==="EIDOS_NOTIFICATION_CLICK"){M.onNotificationClick?.(t.data);return}if(n==="EIDOS_SUBSCRIPTION_EXPIRED"){M.onSubscriptionExpired?.(t.subscription);return}if(a)switch(n){case"EIDOS_CACHE_HIT":{const s=r.resources[a];r.updateResource(a,{status:"fresh",lastEvent:"cache-hit",cacheHits:(s?.cacheHits??0)+1});break}case"EIDOS_CACHE_UPDATED":r.updateResource(a,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break;case"EIDOS_NETWORK_ERROR":r.updateResource(a,{status:"error",lastEvent:"network-error"});break}}function Re(e){_({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),o.getState().setOnline(!e)}function _e(){const e=m?.active;if(e){for(const t of T)e.postMessage(t);T=[]}}var v=new Map,C=new Map,X=null;function ke(e){X=e}function q(e){return e.includes("*")||/:[^/]+/.test(e)}function Ie(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function z(e,t){const r=qe(t),n=q(e)?Ie(e):void 0,a={url:e,config:t,strategy:r,status:"idle",cacheHits:0,cacheMisses:0};return o.getState().registerResource(e,a),_({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:r.swStrategy,cacheName:r.cacheName,...n!==void 0&&{pattern:n}}),{strategy:r,regexStr:n}}function Z(e,t,r){return async()=>{_({type:"EIDOS_CLEAR_CACHE",url:e});const n=await caches.open(t.cacheName).catch(()=>null);if(n){const a=await n.keys(),s=r?new RegExp(r):null,i=e.startsWith("http");await Promise.all(a.filter(c=>{const u=c.url,d=new URL(u).pathname;return s?s.test(i?u:d):i?u===e:u===e||d===e}).map(c=>n.delete(c)))}q(e)||o.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),X?.(["eidos",e])}}function ee(e){return()=>{v.delete(e),_({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),o.getState().unregisterResource(e)}}function Oe(e,t){if(q(e))throw new Error(`[eidos] resource('${e}') is a URL pattern — use resourcePattern('${e}', config) instead. Pattern handles only support invalidate()/unregister(); the SW intercepts matching requests automatically.`);if(v.has(e))return v.get(e);const{strategy:r}=z(e,t),n={url:e,config:t,strategy:r,fetch:async()=>{const a=C.get(e);if(a)return a.then(i=>i.clone());const s=Qe(e,t,r);return C.set(e,s),s.finally(()=>C.delete(e)).catch(()=>{}),s.then(i=>i.clone())},json:async()=>(await n.fetch()).json(),query:()=>({queryKey:["eidos",e],queryFn:()=>n.json()}),prefetch:async()=>{await n.fetch()},invalidate:Z(e,r,void 0),unregister:ee(e)};return v.set(e,n),n}function Ae(e,t){if(!q(e))throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);if(v.has(e))return v.get(e);const{strategy:r,regexStr:n}=z(e,t),a={url:e,config:t,strategy:r,invalidate:Z(e,r,n),unregister:ee(e)};return v.set(e,a),a}async function Qe(e,t,r){const n=o.getState();n.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const a=await caches.open(r.cacheName).catch(()=>null);try{if(r.swStrategy!=="network-first"){const c=a?await a.match(e).catch(()=>null):null,u=o.getState().resources[e],d=t.maxAge!==void 0&&u?.cachedAt!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(c&&!d)return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(u?.cacheHits??0)+1}),r.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async l=>{l.ok&&a&&(await a.put(e,l.clone()),o.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),c;const g=o.getState().resources[e];n.updateResource(e,{cacheMisses:(g?.cacheMisses??0)+1})}const s=await fetch(e);if(s.ok)return a&&await a.put(e,s.clone()),n.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),s;n.updateResource(e,{status:s.status===503?"offline":"error"});const i=s.headers.get("X-Eidos-Offline")==="true";throw new Error(i?`offline: no cached response for ${e}`:`${s.status} ${s.statusText}`)}catch(s){const i=a?await a.match(e).catch(()=>null):null;if(i){const c=o.getState().resources[e];return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(c?.cacheHits??0)+1}),i}throw n.updateResource(e,{status:"error"}),s}}function qe(e){const t=e.strategy;return e.offline?B(t??"stale-while-revalidate",e.cacheName):B(t??"network-first",e.cacheName)}var Ce={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},xe={"stale-while-revalidate":{reasoning:"offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.",behavior:["Cache hit → return immediately, kick off background revalidation","Cache miss → fetch from network, cache the response, return it","Offline → return cached version if available, 503 if not","Reconnect → next request triggers a background refresh"],equivalentCode:`// Workbox equivalent
|
|
2
2
|
new StaleWhileRevalidate({
|
|
3
3
|
cacheName: 'eidos-resources-v1',
|
|
4
4
|
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
@@ -10,6 +10,6 @@ new CacheFirst({
|
|
|
10
10
|
new NetworkFirst({
|
|
11
11
|
cacheName: 'eidos-resources-v1',
|
|
12
12
|
networkTimeoutSeconds: 3,
|
|
13
|
-
})`}};function M(e,t){const n=we[e];return{name:ge[e],swStrategy:e,cacheName:t??"eidos-resources-v1",reasoning:n.reasoning,behavior:n.behavior,equivalentCode:""}}async function ye(e){const t=await Promise.allSettled(e.map(r=>r.prefetch())),n=t.filter(r=>r.status==="rejected").map(r=>r.reason);return{warmed:t.filter(r=>r.status==="fulfilled").length,failed:n.length,errors:n}}var Se="eidos",ve=1,d="action-queue",A=null;function m(){return A?Promise.resolve(A):new Promise((e,t)=>{const n=indexedDB.open(Se,ve);n.onupgradeneeded=r=>{const s=r.target.result;if(!s.objectStoreNames.contains(d)){const a=s.createObjectStore(d,{keyPath:"id"});a.createIndex("status","status",{unique:!1}),a.createIndex("actionId","actionId",{unique:!1})}},n.onsuccess=()=>{A=n.result,e(n.result)},n.onerror=()=>t(n.error)})}async function me(e){const t=await m();return new Promise((n,r)=>{const s=t.transaction(d,"readwrite");s.objectStore(d).add(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function L(){const e=await m();return new Promise((t,n)=>{const r=e.transaction(d,"readonly").objectStore(d).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function Ee(e,t){const n=await m();return new Promise((r,s)=>{const a=n.transaction(d,"readwrite"),i=a.objectStore(d),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},a.oncomplete=()=>r(),a.onerror=()=>s(a.error)})}async function be(e){const t=await m();return new Promise((n,r)=>{const s=t.transaction(d,"readwrite");s.objectStore(d).delete(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function Re(){const e=await m();function t(s){return new Promise((a,i)=>{const c=e.transaction(d,"readonly").objectStore(d).index("status"),u=[],p=c.openCursor(IDBKeyRange.only(s));p.onsuccess=w=>{const l=w.target.result;l?(u.push(l.value),l.continue()):a(u)},p.onerror=()=>i(p.error)})}const[n,r]=await Promise.all([t("pending"),t("failed")]);return[...n,...r]}async function Oe(){const e=await m();return new Promise((t,n)=>{const r=e.transaction(d,"readwrite");r.objectStore(d).clear(),r.oncomplete=()=>t(),r.onerror=()=>n(r.error)})}var ke={add:me,getAll:L,getPending:Re,update:Ee,remove:be,clear:Oe},B=null;function qe(e){B=e}function F(){return B}var N=new Map,G=new Map,K=new Map;function h(){return F()??ke}function V(){return crypto.randomUUID()}function _e(e,t){const n=t.name||e.name||V();N.set(n,e),t.onRollback&&G.set(n,t.onRollback),t.onConflict&&K.set(n,t.onConflict);const r=async(...s)=>{const{isOnline:a}=o.getState();if(t.onOptimistic?.(...s),t.reliability==="neverLose"){if(!a)return U(n,n,s,t);try{return await e(...s)}catch{return U(n,n,s,t)}}try{return await e(...s)}catch(i){throw t.onRollback?.(...s),i}};return Object.defineProperty(r,"id",{value:n,writable:!1}),Object.defineProperty(r,"config",{value:t,writable:!1}),r}async function U(e,t,n,r){const s=V(),a={id:s,actionId:e,actionName:t,args:n,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending",priority:r.priority??"normal"};await h().add(a),o.getState().addQueueItem(a);try{const i=ne();i&&"sync"in i&&await i.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:s,message:`"${t}" queued — will execute when online`}}function Ae(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function Ie(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}var I=!1;async function q(){const e=o.getState();if(!e.isOnline||I)return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};I=!0;try{return await Ce(e)}finally{I=!1}}async function Qe(e,t){const n=N.get(e.actionId);if(!n)return"skipped";try{await n(...e.args);const r=Date.now();return t.updateQueueItem(e.id,{status:"succeeded",completedAt:r}),await h().update(e.id,{status:"succeeded",completedAt:r}),setTimeout(()=>{t.removeQueueItem(e.id),h().remove(e.id)},3e3),"succeeded"}catch(r){if(Ae(r)){const a=K.get(e.actionId);if(a&&a(r,e.args)==="skip")return t.removeQueueItem(e.id),await h().remove(e.id),"conflicted"}const s=e.retryCount+1;if(s>=e.maxRetries)return t.updateQueueItem(e.id,{status:"failed",error:String(r),retryCount:s}),await h().update(e.id,{status:"failed",error:String(r),retryCount:s}),G.get(e.actionId)?.(...e.args),"failed";{const a=Date.now()+Ie(s);return t.updateQueueItem(e.id,{status:"pending",retryCount:s,nextRetryAt:a}),await h().update(e.id,{status:"pending",retryCount:s,nextRetryAt:a}),"retrying"}}}async function xe(e,t,n){if(e.length===0)return;const r=e.filter(a=>N.has(a.actionId));if(n.skipped+=e.length-r.length,r.length>0){t.batchUpdateQueueItems(r.map(a=>({id:a.id,update:{status:"replaying"}})));for(const a of r)h().update(a.id,{status:"replaying"})}const s=await Promise.allSettled(r.map(a=>Qe(a,t)));for(const a of s){const i=a.status==="fulfilled"?a.value:"failed";i==="skipped"?n.skipped++:i==="conflicted"?n.conflicted++:(n.attempted++,n[i]++)}}async function Ce(e){const t=await h().getPending(),n=Date.now(),r=t.filter(a=>a.retryCount<a.maxRetries&&(!a.nextRetryAt||a.nextRetryAt<=n)),s={attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};for(const a of["high","normal","low"])await xe(r.filter(i=>(i.priority??"normal")===a),e,s);return s}async function Pe(){await h().clear(),o.getState().hydrateQueue([])}function Y(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:s}=o.getState(),a=s&&!e;e=s,a&&setTimeout(q,600)}),n=o.getState(),r=n.queue.some(s=>s.status==="pending");return n.isOnline&&r&&setTimeout(q,1200),t}var P=!1,D=null;async function J(e={}){if(typeof window>"u"||P)return;P=!0;const t=e.swPath??"/eidos-sw.js",n=e.autoReplay??!0;try{const r=await L();r.length>0&&o.getState().hydrateQueue(r)}catch{}try{await re(t)}catch{}ae(()=>{o.getState().isOnline&&setTimeout(q,200)}),n&&(D=Y())}function De(){D?.(),D=null,P=!1}var Q="@eidos:queue",Ne=class{constructor(e){this.storage=e}async readAll(){try{const e=await this.storage.getItem(Q);return e?JSON.parse(e):[]}catch{return[]}}async writeAll(e){await this.storage.setItem(Q,JSON.stringify(e))}async add(e){const t=await this.readAll();t.push(e),await this.writeAll(t)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(e=>e.status==="pending"||e.status==="failed")}async update(e,t){const n=await this.readAll(),r=n.findIndex(s=>s.id===e);r!==-1&&(n[r]={...n[r],...t}),await this.writeAll(n)}async remove(e){const t=await this.readAll();await this.writeAll(t.filter(n=>n.id!==e))}async clear(){await this.storage.removeItem(Q)}};function Te({children:e,swPath:t,autoReplay:n}){return(0,v.useEffect)(()=>{J({swPath:t,autoReplay:n})},[]),(0,j.jsx)(j.Fragment,{children:e})}function f(e){const t=e??(n=>n);return(0,v.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function je(){return f()}function Me(){return f(e=>e.resources)}function Ue(e){return f(t=>t.resources[e])}function We(){return f(e=>e.queue)}function He(e){return f(t=>t.queue.find(n=>n.id===e))}function $e(){return{isOnline:f(e=>e.isOnline),swStatus:f(e=>e.swStatus),swError:f(e=>e.swError)}}function Le(){const[e,t,n,r]=f(s=>{let a=0,i=0,c=0;for(const u of s.queue)u.status==="pending"?a++:u.status==="failed"?i++:u.status==="replaying"&&c++;return`${a},${i},${c},${s.queue.length}`}).split(",");return{pending:+e,failed:+t,replaying:+n,total:+r}}function Be(e){const t=f(s=>s.queue.length),n=(0,v.useRef)(0),r=(0,v.useRef)(e);(0,v.useEffect)(()=>{r.current=e}),(0,v.useEffect)(()=>{n.current>0&&t===0&&r.current(),n.current=t},[t])}var Fe="1.1.0";function Ge(e,t){const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(e[r]!==t[r])return!1;return!0}function z(e,t){return Ge(e,t)}function E(e,t=Object.is){return{subscribe(n){let r=e(o.getState());return n(r),o.subscribe(()=>{const s=e(o.getState());t(r,s)||(r=s,n(s))})},getState(){return e(o.getState())}}}var Ke=E(e=>e),Ve=E(e=>e.queue),Ye=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),z),Je=E(e=>{let t=0,n=0,r=0;for(const s of e.queue)s.status==="pending"?t++:s.status==="failed"?n++:s.status==="replaying"&&r++;return{pending:t,failed:n,replaying:r,total:e.queue.length}},z);function ze(e){return E(t=>t.resources[e])}function Xe(e){return E(t=>t.queue.find(n=>n.id===e))}exports.AsyncStorageQueueStorage=Ne;exports.EidosProvider=Te;exports.VERSION=Fe;exports._getQueueStorage=F;exports._resetEidos=De;exports.action=_e;exports.clearQueue=Pe;exports.eidosAction=Xe;exports.eidosQueue=Ve;exports.eidosQueueStats=Je;exports.eidosResource=ze;exports.eidosStatus=Ye;exports.eidosStore=Ke;exports.initEidos=J;exports.isBgSyncSupported=ie;exports.replayQueue=q;exports.resource=fe;exports.setOfflineSimulation=ce;exports.setQueryInvalidator=de;exports.setQueueStorage=qe;exports.subscribeReplayOnReconnect=Y;exports.useEidos=je;exports.useEidosAction=He;exports.useEidosOnDrain=Be;exports.useEidosQueue=We;exports.useEidosQueueStats=Le;exports.useEidosResource=Ue;exports.useEidosResources=Me;exports.useEidosStatus=$e;exports.useEidosStore=o;exports.warmCache=ye;
|
|
13
|
+
})`}};function B(e,t){const r=xe[e];return{name:Ce[e],swStrategy:e,cacheName:t??"eidos-resources-v1",reasoning:r.reasoning,behavior:r.behavior,equivalentCode:""}}async function Pe(e){const t=await Promise.allSettled(e.map(n=>n.prefetch())),r=t.filter(n=>n.status==="rejected").map(n=>n.reason);return{warmed:t.filter(n=>n.status==="fulfilled").length,failed:r.length,errors:r}}var Ne="eidos",De=1,f="action-queue",x=null;function b(){return x?Promise.resolve(x):new Promise((e,t)=>{const r=indexedDB.open(Ne,De);r.onupgradeneeded=n=>{const a=n.target.result;if(!a.objectStoreNames.contains(f)){const s=a.createObjectStore(f,{keyPath:"id"});s.createIndex("status","status",{unique:!1}),s.createIndex("actionId","actionId",{unique:!1})}},r.onsuccess=()=>{x=r.result,e(r.result)},r.onerror=()=>t(r.error)})}async function Te(e){const t=await b();return new Promise((r,n)=>{const a=t.transaction(f,"readwrite");a.objectStore(f).add(e),a.oncomplete=()=>r(),a.onerror=()=>n(a.error)})}async function te(){const e=await b();return new Promise((t,r)=>{const n=e.transaction(f,"readonly").objectStore(f).getAll();n.onsuccess=()=>t(n.result),n.onerror=()=>r(n.error)})}async function Me(e,t){const r=await b();return new Promise((n,a)=>{const s=r.transaction(f,"readwrite"),i=s.objectStore(f),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},s.oncomplete=()=>n(),s.onerror=()=>a(s.error)})}async function je(e){const t=await b();return new Promise((r,n)=>{const a=t.transaction(f,"readwrite");a.objectStore(f).delete(e),a.oncomplete=()=>r(),a.onerror=()=>n(a.error)})}async function Ue(){const e=await b();function t(a){return new Promise((s,i)=>{const c=e.transaction(f,"readonly").objectStore(f).index("status"),u=[],d=c.openCursor(IDBKeyRange.only(a));d.onsuccess=g=>{const l=g.target.result;l?(u.push(l.value),l.continue()):s(u)},d.onerror=()=>i(d.error)})}const[r,n]=await Promise.all([t("pending"),t("failed")]);return[...r,...n]}async function Ke(){const e=await b();return new Promise((t,r)=>{const n=e.transaction(f,"readwrite");n.objectStore(f).clear(),n.oncomplete=()=>t(),n.onerror=()=>r(n.error)})}var ne={add:Te,getAll:te,getPending:Ue,update:Me,remove:je,clear:Ke},re=null;function Le(e){re=e}function $(){return re}var We="eidos-queue-sync",I;function ae(){return I!==void 0||(I=typeof BroadcastChannel>"u"?null:new BroadcastChannel(We)),I}function y(e){ae()?.postMessage(e)}function $e(){const e=ae();if(!e)return()=>{};const t=r=>{const n=o.getState(),a=r.data;switch(a.type){case"update":n.updateQueueItem(a.id,a.update);break;case"batchUpdate":n.batchUpdateQueueItems(a.updates);break;case"remove":n.removeQueueItem(a.id);break}};return e.addEventListener("message",t),()=>e.removeEventListener("message",t)}function se(e){let t=0,r=0,n=0;for(const a of e)a.status==="pending"?t++:a.status==="failed"?r++:a.status==="replaying"&&n++;return{pending:t,failed:r,replaying:n,total:e.length}}var A=new Map,ie=new Map,oe=new Map,ce=new Map,R=new Map;function p(){return $()??ne}function j(){return crypto.randomUUID()}function U(e,t,r){return e(...t,r)}function He(e,t){const r=t.name||e.name||j(),n=t.namespace?`${t.namespace}::${r}`:r;if(A.has(n))throw new Error(`[eidos] duplicate action id "${n}" — an action with this id is already registered. Pass a unique config.name or config.namespace.`);A.set(n,e),ce.set(n,t),t.onRollback&&ie.set(n,t.onRollback),t.conflict&&oe.set(n,t.conflict);const a=async(...i)=>{const{isOnline:c}=o.getState(),u=j();let d;if(t.cancellable){const l=new AbortController;R.set(u,l),d=l.signal}const g={idempotencyKey:u,attempt:0,signal:d};t.onOptimistic?.(...i,g);try{if(t.reliability==="neverLose"){if(!c)return F(n,n,i,t,u);try{return await U(e,i,g)}catch(l){if(ue(l))throw l;return F(n,n,i,t,u)}}try{return await U(e,i,g)}catch(l){throw t.onRollback?.(...i,g),l}}finally{t.cancellable&&R.delete(u)}},s=async i=>{const c=R.get(i);if(c)return c.abort(),!0;const u=(await p().getAll()).find(d=>d.idempotencyKey===i&&d.status==="pending");return u?(o.getState().removeQueueItem(u.id),y({type:"remove",id:u.id}),await p().remove(u.id),!0):!1};return Object.defineProperty(a,"id",{value:n,writable:!1}),Object.defineProperty(a,"config",{value:t,writable:!1}),Object.defineProperty(a,"cancel",{value:s,writable:!1}),a}async function F(e,t,r,n,a){const s=j(),i={schemaVersion:2,id:s,actionId:e,actionName:t,idempotencyKey:a,args:r,queuedAt:Date.now(),retryCount:0,maxRetries:n.maxRetries??3,status:"pending",priority:n.priority??"normal"};await p().add(i),o.getState().addQueueItem(i);try{const c=Y();c&&"sync"in c&&await c.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:s,message:`"${t}" queued — will execute when online`}}function ue(e){return e instanceof DOMException&&e.name==="AbortError"}function Be(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function Fe(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}function O(){return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0,cancelled:0}}var P=!1,Ve="eidos-queue-replay";async function Q(){const e=o.getState();if(!e.isOnline)return O();if(typeof navigator<"u"&&navigator.locks)return navigator.locks.request(Ve,{ifAvailable:!0},async t=>t?V(e):O());if(P)return O();P=!0;try{return await V(e)}finally{P=!1}}async function Ge(e,t){const r=Date.now();t.updateQueueItem(e.id,{status:"succeeded",completedAt:r}),y({type:"update",id:e.id,update:{status:"succeeded",completedAt:r}}),await p().update(e.id,{status:"succeeded",completedAt:r}),setTimeout(()=>{t.removeQueueItem(e.id),y({type:"remove",id:e.id}),p().remove(e.id)},3e3)}async function Ye(e,t,r){const n=oe.get(e.actionId);let a;if(n)switch(n.strategy){case"serverWins":a="skip";break;case"clientWins":a="retry";break;case"merge":case"custom":{const s={error:r,args:e.args,attempt:e.retryCount,idempotencyKey:e.idempotencyKey};a=n.resolve?.(s)??"retry";break}}if(a==="skip")return t.removeQueueItem(e.id),y({type:"remove",id:e.id}),await p().remove(e.id),"conflicted";a&&typeof a=="object"&&(e.args=a.resolved,t.updateQueueItem(e.id,{args:a.resolved}),y({type:"update",id:e.id,update:{args:a.resolved}}),await p().update(e.id,{args:a.resolved}))}async function Je(e,t,r){const n=e.retryCount+1;if(n>=e.maxRetries){const s={status:"failed",error:String(r),retryCount:n};t.updateQueueItem(e.id,s),y({type:"update",id:e.id,update:s}),await p().update(e.id,s);const i={idempotencyKey:e.idempotencyKey,attempt:n};return ie.get(e.actionId)?.(...e.args,i),"failed"}const a={status:"pending",retryCount:n,nextRetryAt:Date.now()+Fe(n)};return t.updateQueueItem(e.id,a),y({type:"update",id:e.id,update:a}),await p().update(e.id,a),"retrying"}async function Xe(e,t){const r=A.get(e.actionId);if(!r)return"skipped";const n=ce.get(e.actionId)?.cancellable;let a;if(n){const i=new AbortController;R.set(e.idempotencyKey,i),a=i.signal}const s={idempotencyKey:e.idempotencyKey,attempt:e.retryCount,signal:a};try{return await U(r,e.args,s),await Ge(e,t),"succeeded"}catch(i){if(ue(i))return t.removeQueueItem(e.id),y({type:"remove",id:e.id}),await p().remove(e.id),"cancelled";if(Be(i)){const c=await Ye(e,t,i);if(c)return c}return Je(e,t,i)}finally{n&&R.delete(e.idempotencyKey)}}async function ze(e,t,r){if(e.length===0)return;const n=e.filter(s=>A.has(s.actionId));if(r.skipped+=e.length-n.length,n.length>0){const s=n.map(i=>({id:i.id,update:{status:"replaying"}}));t.batchUpdateQueueItems(s),y({type:"batchUpdate",updates:s});for(const i of n)p().update(i.id,{status:"replaying"})}const a=await Promise.allSettled(n.map(s=>Xe(s,t)));for(const s of a){const i=s.status==="fulfilled"?s.value:"failed";i==="skipped"?r.skipped++:i==="conflicted"?r.conflicted++:i==="cancelled"?r.cancelled++:(r.attempted++,r[i]++)}}async function V(e){const t=await p().getPending(),r=Date.now(),n=t.filter(s=>s.retryCount<s.maxRetries&&(!s.nextRetryAt||s.nextRetryAt<=r)),a=O();for(const s of["high","normal","low"])await ze(n.filter(i=>(i.priority??"normal")===s),e,a);return a}async function Ze(){await p().clear(),o.getState().hydrateQueue([])}function de(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:a}=o.getState(),s=a&&!e;e=a,s&&setTimeout(Q,600)}),r=o.getState(),n=r.queue.some(a=>a.status==="pending");return r.isOnline&&n&&setTimeout(Q,1200),t}async function et(e){if(e.schemaVersion===2&&e.idempotencyKey)return e;const t={...e,schemaVersion:2,idempotencyKey:e.idempotencyKey??crypto.randomUUID()};return await($()??ne).update(t.id,{schemaVersion:t.schemaVersion,idempotencyKey:t.idempotencyKey}).catch(()=>{}),t}var K=!1,L=null,W=null;async function le(e={}){if(typeof window>"u"||K)return;K=!0;const t=e.swPath??"/eidos-sw.js",r=e.autoReplay??!0;try{const n=await te();if(n.length>0){const a=await Promise.all(n.map(et));o.getState().hydrateQueue(a)}}catch{}try{await we(t)}catch{}me(()=>{o.getState().isOnline&&setTimeout(Q,200)}),r&&(L=de()),W=$e()}function tt(){L?.(),L=null,W?.(),W=null,K=!1}var N="@eidos:queue",nt=class{constructor(e){this.storage=e}async readAll(){try{const e=await this.storage.getItem(N);return e?JSON.parse(e):[]}catch{return[]}}async writeAll(e){await this.storage.setItem(N,JSON.stringify(e))}async add(e){const t=await this.readAll();t.push(e),await this.writeAll(t)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(e=>e.status==="pending"||e.status==="failed")}async update(e,t){const r=await this.readAll(),n=r.findIndex(a=>a.id===e);n!==-1&&(r[n]={...r[n],...t}),await this.writeAll(r)}async remove(e){const t=await this.readAll();await this.writeAll(t.filter(r=>r.id!==e))}async clear(){await this.storage.removeItem(N)}};function rt({children:e,swPath:t,autoReplay:r}){return(0,S.useEffect)(()=>{le({swPath:t,autoReplay:r})},[]),(0,H.jsx)(H.Fragment,{children:e})}function h(e){const t=e??(r=>r);return(0,S.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function at(){return h()}function st(){return h(e=>e.resources)}function it(e){return h(t=>t.resources[e])}function ot(){return h(e=>e.queue)}function ct(e){return h(t=>t.queue.find(r=>r.id===e))}function ut(){return{isOnline:h(e=>e.isOnline),swStatus:h(e=>e.swStatus),swError:h(e=>e.swError)}}function dt(){const[e,t,r,n]=h(a=>{const{pending:s,failed:i,replaying:c,total:u}=se(a.queue);return`${s},${i},${c},${u}`}).split(",");return{pending:+e,failed:+t,replaying:+r,total:+n}}function lt(e){const t=h(a=>a.queue.length),r=(0,S.useRef)(0),n=(0,S.useRef)(e);(0,S.useEffect)(()=>{n.current=e}),(0,S.useEffect)(()=>{r.current>0&&t===0&&n.current(),r.current=t},[t])}var ft="2.0.0";function pt(e,t){const r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(const n of r)if(e[n]!==t[n])return!1;return!0}function fe(e,t){return pt(e,t)}function E(e,t=Object.is){return{subscribe(r){let n=e(o.getState());return r(n),o.subscribe(()=>{const a=e(o.getState());t(n,a)||(n=a,r(a))})},getState(){return e(o.getState())}}}var ht=E(e=>e),yt=E(e=>e.queue),gt=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),fe),wt=E(e=>se(e.queue),fe);function vt(e){return E(t=>t.resources[e])}function mt(e){return E(t=>t.queue.find(r=>r.id===e))}exports.AsyncStorageQueueStorage=nt;exports.EidosProvider=rt;exports.VERSION=ft;exports._getQueueStorage=$;exports._resetEidos=tt;exports.action=He;exports.clearQueue=Ze;exports.eidosAction=mt;exports.eidosQueue=yt;exports.eidosQueueStats=wt;exports.eidosResource=vt;exports.eidosStatus=gt;exports.eidosStore=ht;exports.getSwRegistration=Y;exports.initEidos=le;exports.isBgSyncSupported=Se;exports.registerPushCallbacks=be;exports.replayQueue=Q;exports.resource=Oe;exports.resourcePattern=Ae;exports.sendToWorker=_;exports.setOfflineSimulation=Re;exports.setQueryInvalidator=ke;exports.setQueueStorage=Le;exports.subscribeReplayOnReconnect=de;exports.useEidos=at;exports.useEidosAction=ct;exports.useEidosOnDrain=lt;exports.useEidosQueue=ot;exports.useEidosQueueStats=dt;exports.useEidosResource=it;exports.useEidosResources=st;exports.useEidosStatus=ut;exports.useEidosStore=o;exports.warmCache=Pe;
|
|
14
14
|
|
|
15
15
|
//# sourceMappingURL=eidos.cjs.map
|