@sweidos/eidos 1.1.0 → 1.2.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/dist/devtools.js CHANGED
@@ -266,9 +266,18 @@ function _getQueueStorage() {
266
266
  var _actionRegistry = /* @__PURE__ */ new Map();
267
267
  var _rollbackRegistry = /* @__PURE__ */ new Map();
268
268
  var _conflictRegistry = /* @__PURE__ */ new Map();
269
+ var _conflictConfigRegistry = /* @__PURE__ */ new Map();
270
+ var _configRegistry = /* @__PURE__ */ new Map();
271
+ var _inflightControllers = /* @__PURE__ */ new Map();
269
272
  function qs() {
270
273
  return _getQueueStorage() ?? idbQueueStorage;
271
274
  }
275
+ function callWithContext(fn, args, ctx) {
276
+ return fn(...args, ctx);
277
+ }
278
+ function isAbortError(err) {
279
+ return err instanceof DOMException && err.name === "AbortError";
280
+ }
272
281
  function isClientError(err) {
273
282
  if (err instanceof Response) return err.status >= 400 && err.status < 500;
274
283
  if (typeof err === "object" && err !== null) {
@@ -280,17 +289,27 @@ function isClientError(err) {
280
289
  function backoffMs(retryCount) {
281
290
  return Math.min(2e3 * 2 ** retryCount, 3e5) * (.8 + Math.random() * .4);
282
291
  }
283
- var _replaying = false;
284
- async function replayQueue() {
285
- const store = useEidosStore.getState();
286
- if (!store.isOnline || _replaying) return {
292
+ function emptyReplayResult() {
293
+ return {
287
294
  attempted: 0,
288
295
  succeeded: 0,
289
296
  failed: 0,
290
297
  retrying: 0,
291
298
  skipped: 0,
292
- conflicted: 0
299
+ conflicted: 0,
300
+ cancelled: 0
293
301
  };
302
+ }
303
+ var _replaying = false;
304
+ var REPLAY_LOCK_NAME = "eidos-queue-replay";
305
+ async function replayQueue() {
306
+ const store = useEidosStore.getState();
307
+ if (!store.isOnline) return emptyReplayResult();
308
+ if (typeof navigator !== "undefined" && navigator.locks) return navigator.locks.request(REPLAY_LOCK_NAME, { ifAvailable: true }, async (lock) => {
309
+ if (!lock) return emptyReplayResult();
310
+ return _doReplayQueue(store);
311
+ });
312
+ if (_replaying) return emptyReplayResult();
294
313
  _replaying = true;
295
314
  try {
296
315
  return await _doReplayQueue(store);
@@ -301,8 +320,20 @@ async function replayQueue() {
301
320
  async function _replayItem(item, store) {
302
321
  const fn = _actionRegistry.get(item.actionId);
303
322
  if (!fn) return "skipped";
323
+ const cancellable = _configRegistry.get(item.actionId)?.cancellable;
324
+ let signal;
325
+ if (cancellable) {
326
+ const controller = new AbortController();
327
+ _inflightControllers.set(item.idempotencyKey, controller);
328
+ signal = controller.signal;
329
+ }
330
+ const ctx = {
331
+ idempotencyKey: item.idempotencyKey,
332
+ attempt: item.retryCount,
333
+ signal
334
+ };
304
335
  try {
305
- await fn(...item.args);
336
+ await callWithContext(fn, item.args, ctx);
306
337
  const completedAt = Date.now();
307
338
  store.updateQueueItem(item.id, {
308
339
  status: "succeeded",
@@ -318,15 +349,48 @@ async function _replayItem(item, store) {
318
349
  }, 3e3);
319
350
  return "succeeded";
320
351
  } catch (err) {
352
+ if (isAbortError(err)) {
353
+ store.removeQueueItem(item.id);
354
+ await qs().remove(item.id);
355
+ return "cancelled";
356
+ }
321
357
  if (isClientError(err)) {
322
- const onConflict = _conflictRegistry.get(item.actionId);
323
- if (onConflict) {
324
- if (onConflict(err, item.args) === "skip") {
325
- store.removeQueueItem(item.id);
326
- await qs().remove(item.id);
327
- return "conflicted";
358
+ const conflictConfig = _conflictConfigRegistry.get(item.actionId);
359
+ let resolution;
360
+ if (conflictConfig) switch (conflictConfig.strategy) {
361
+ case "serverWins":
362
+ resolution = "skip";
363
+ break;
364
+ case "clientWins":
365
+ case "lastWriteWins":
366
+ resolution = "retry";
367
+ break;
368
+ case "merge":
369
+ case "custom": {
370
+ const ctx = {
371
+ error: err,
372
+ args: item.args,
373
+ attempt: item.retryCount,
374
+ idempotencyKey: item.idempotencyKey
375
+ };
376
+ resolution = conflictConfig.resolve?.(ctx) ?? "retry";
377
+ break;
328
378
  }
329
379
  }
380
+ else {
381
+ const onConflict = _conflictRegistry.get(item.actionId);
382
+ if (onConflict) resolution = onConflict(err, item.args);
383
+ }
384
+ if (resolution === "skip") {
385
+ store.removeQueueItem(item.id);
386
+ await qs().remove(item.id);
387
+ return "conflicted";
388
+ }
389
+ if (resolution && typeof resolution === "object") {
390
+ item.args = resolution.resolved;
391
+ store.updateQueueItem(item.id, { args: resolution.resolved });
392
+ await qs().update(item.id, { args: resolution.resolved });
393
+ }
330
394
  }
331
395
  const retryCount = item.retryCount + 1;
332
396
  if (retryCount >= item.maxRetries) {
@@ -356,6 +420,8 @@ async function _replayItem(item, store) {
356
420
  });
357
421
  return "retrying";
358
422
  }
423
+ } finally {
424
+ if (cancellable) _inflightControllers.delete(item.idempotencyKey);
359
425
  }
360
426
  }
361
427
  async function _replayTier(items, store, result) {
@@ -374,6 +440,7 @@ async function _replayTier(items, store, result) {
374
440
  const outcome = o.status === "fulfilled" ? o.value : "failed";
375
441
  if (outcome === "skipped") result.skipped++;
376
442
  else if (outcome === "conflicted") result.conflicted++;
443
+ else if (outcome === "cancelled") result.cancelled++;
377
444
  else {
378
445
  result.attempted++;
379
446
  result[outcome]++;
@@ -384,14 +451,7 @@ async function _doReplayQueue(store) {
384
451
  const candidates = await qs().getPending();
385
452
  const now = Date.now();
386
453
  const pending = candidates.filter((item) => item.retryCount < item.maxRetries && (!item.nextRetryAt || item.nextRetryAt <= now));
387
- const result = {
388
- attempted: 0,
389
- succeeded: 0,
390
- failed: 0,
391
- retrying: 0,
392
- skipped: 0,
393
- conflicted: 0
394
- };
454
+ const result = emptyReplayResult();
395
455
  for (const tier of [
396
456
  "high",
397
457
  "normal",
package/dist/eidos-sw.js CHANGED
@@ -1,213 +1,305 @@
1
- const CACHE_VERSION = "v1";
2
- const CACHE_PREFIX = "eidos";
3
- const runtimeConfig = {
4
- resources: /* @__PURE__ */ new Map(),
5
- simulateOffline: false
1
+ //#region src/sw.ts
2
+ var CACHE_VERSION = "v1";
3
+ var CACHE_PREFIX = "eidos";
4
+ var runtimeConfig = {
5
+ resources: /* @__PURE__ */ new Map(),
6
+ simulateOffline: false
6
7
  };
7
8
  self.addEventListener("install", (event) => {
8
- event.waitUntil(self.skipWaiting());
9
+ event.waitUntil(self.skipWaiting());
9
10
  });
10
11
  self.addEventListener("activate", (event) => {
11
- event.waitUntil(
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
- );
12
+ 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
13
  });
23
14
  self.addEventListener("message", (event) => {
24
- const data = event.data;
25
- if (!data?.type) return;
26
- switch (data.type) {
27
- case "EIDOS_REGISTER_RESOURCE": {
28
- const url = data.url;
29
- const patternSrc = data.pattern;
30
- runtimeConfig.resources.set(url, {
31
- strategy: data.strategy,
32
- cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
33
- ...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
34
- });
35
- event.source?.postMessage({ type: "EIDOS_RESOURCE_REGISTERED", url });
36
- break;
37
- }
38
- case "EIDOS_UNREGISTER_RESOURCE": {
39
- runtimeConfig.resources.delete(data.url);
40
- break;
41
- }
42
- case "EIDOS_SIMULATE_OFFLINE": {
43
- runtimeConfig.simulateOffline = data.enabled;
44
- break;
45
- }
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(
55
- keys.filter((req) => {
56
- const reqUrl = req.url;
57
- const p = new URL(reqUrl).pathname;
58
- if (reg?.pattern) {
59
- return reg.pattern.test(isCrossOrigin ? reqUrl : p);
60
- }
61
- return isCrossOrigin ? reqUrl === targetUrl : p === targetUrl;
62
- }).map((req) => cache.delete(req))
63
- );
64
- } else {
65
- await cache.keys().then((keys) => Promise.all(keys.map((k) => cache.delete(k))));
66
- }
67
- notifyClients({ type: "EIDOS_CACHE_CLEARED", url: targetUrl });
68
- });
69
- break;
70
- }
71
- case "EIDOS_PING":
72
- event.source?.postMessage({ type: "EIDOS_PONG" });
73
- break;
74
- }
15
+ const data = event.data;
16
+ if (!data?.type) return;
17
+ switch (data.type) {
18
+ case "EIDOS_REGISTER_RESOURCE": {
19
+ const url = data.url;
20
+ const patternSrc = data.pattern;
21
+ runtimeConfig.resources.set(url, {
22
+ strategy: data.strategy,
23
+ cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
24
+ ...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
25
+ });
26
+ event.source?.postMessage({
27
+ type: "EIDOS_RESOURCE_REGISTERED",
28
+ url
29
+ });
30
+ break;
31
+ }
32
+ case "EIDOS_UNREGISTER_RESOURCE":
33
+ runtimeConfig.resources.delete(data.url);
34
+ break;
35
+ case "EIDOS_SIMULATE_OFFLINE":
36
+ runtimeConfig.simulateOffline = data.enabled;
37
+ break;
38
+ case "EIDOS_CLEAR_CACHE": {
39
+ const targetUrl = data.url;
40
+ const reg = targetUrl ? runtimeConfig.resources.get(targetUrl) : void 0;
41
+ const cacheName = reg?.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`;
42
+ caches.open(cacheName).then(async (cache) => {
43
+ if (targetUrl) {
44
+ const keys = await cache.keys();
45
+ const isCrossOrigin = targetUrl.startsWith("http");
46
+ await Promise.all(keys.filter((req) => {
47
+ const reqUrl = req.url;
48
+ const p = new URL(reqUrl).pathname;
49
+ if (reg?.pattern) return reg.pattern.test(isCrossOrigin ? reqUrl : p);
50
+ return isCrossOrigin ? reqUrl === targetUrl : p === targetUrl;
51
+ }).map((req) => cache.delete(req)));
52
+ } else await cache.keys().then((keys) => Promise.all(keys.map((k) => cache.delete(k))));
53
+ notifyClients({
54
+ type: "EIDOS_CACHE_CLEARED",
55
+ url: targetUrl
56
+ });
57
+ });
58
+ break;
59
+ }
60
+ case "EIDOS_PING":
61
+ event.source?.postMessage({ type: "EIDOS_PONG" });
62
+ break;
63
+ case "EIDOS_CACHE_VAPID_KEY":
64
+ idbSet("vapidPublicKey", data.key);
65
+ break;
66
+ }
75
67
  });
76
68
  self.addEventListener("fetch", (event) => {
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) {
82
- for (const [key, registration] of runtimeConfig.resources) {
83
- if (!registration.pattern) continue;
84
- const target = key.startsWith("http") ? requestUrl : pathname;
85
- if (registration.pattern.test(target)) {
86
- reg = registration;
87
- break;
88
- }
89
- }
90
- }
91
- if (!reg) return;
92
- if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
93
- event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg.cacheName));
94
- return;
95
- }
96
- event.respondWith(handleFetch(event.request, pathname, reg));
69
+ if (event.request.method !== "GET") return;
70
+ const requestUrl = event.request.url;
71
+ const pathname = new URL(requestUrl).pathname;
72
+ let reg = runtimeConfig.resources.get(requestUrl) ?? runtimeConfig.resources.get(pathname);
73
+ if (!reg) for (const [key, registration] of runtimeConfig.resources) {
74
+ if (!registration.pattern) continue;
75
+ const target = key.startsWith("http") ? requestUrl : pathname;
76
+ if (registration.pattern.test(target)) {
77
+ reg = registration;
78
+ break;
79
+ }
80
+ }
81
+ if (!reg) return;
82
+ if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
83
+ event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg.cacheName));
84
+ return;
85
+ }
86
+ event.respondWith(handleFetch(event.request, pathname, reg));
97
87
  });
98
88
  async function handleFetch(request, pathname, reg) {
99
- if (runtimeConfig.simulateOffline) {
100
- return serveOffline(request, pathname, reg.cacheName);
101
- }
102
- switch (reg.strategy) {
103
- case "cache-first":
104
- return cacheFirst(request, pathname, reg.cacheName);
105
- case "stale-while-revalidate":
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
- }
89
+ if (runtimeConfig.simulateOffline) return serveOffline(request, pathname, reg.cacheName);
90
+ switch (reg.strategy) {
91
+ case "cache-first": return cacheFirst(request, pathname, reg.cacheName);
92
+ case "stale-while-revalidate": return staleWhileRevalidate(null, request, pathname, reg.cacheName);
93
+ case "network-first": return networkFirst(request, pathname, reg.cacheName);
94
+ default: return fetch(request);
95
+ }
112
96
  }
113
97
  async function cacheFirst(request, pathname, cacheName) {
114
- const cache = await caches.open(cacheName);
115
- const cached = await cache.match(request);
116
- if (cached) {
117
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "cache-first" });
118
- return cached;
119
- }
120
- try {
121
- const response = await fetch(request);
122
- if (response.ok) {
123
- await cache.put(request, response.clone());
124
- notifyClients({ type: "EIDOS_CACHE_UPDATED", url: pathname, strategy: "cache-first" });
125
- }
126
- return response;
127
- } catch {
128
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname });
129
- return offlineErrorResponse(pathname);
130
- }
98
+ const cache = await caches.open(cacheName);
99
+ const cached = await cache.match(request);
100
+ if (cached) {
101
+ notifyClients({
102
+ type: "EIDOS_CACHE_HIT",
103
+ url: pathname,
104
+ strategy: "cache-first"
105
+ });
106
+ return cached;
107
+ }
108
+ try {
109
+ const response = await fetch(request);
110
+ if (response.ok) {
111
+ await cache.put(request, response.clone());
112
+ notifyClients({
113
+ type: "EIDOS_CACHE_UPDATED",
114
+ url: pathname,
115
+ strategy: "cache-first"
116
+ });
117
+ }
118
+ return response;
119
+ } catch {
120
+ notifyClients({
121
+ type: "EIDOS_NETWORK_ERROR",
122
+ url: pathname
123
+ });
124
+ return offlineErrorResponse(pathname);
125
+ }
131
126
  }
132
127
  async function staleWhileRevalidate(event, request, pathname, cacheName) {
133
- const cache = await caches.open(cacheName);
134
- const cached = await cache.match(request);
135
- const revalidatePromise = fetch(request).then(async (response) => {
136
- if (response.ok) {
137
- await cache.put(request, response.clone());
138
- notifyClients({
139
- type: "EIDOS_CACHE_UPDATED",
140
- url: pathname,
141
- strategy: "stale-while-revalidate"
142
- });
143
- }
144
- return response;
145
- }).catch(() => {
146
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname, strategy: "stale-while-revalidate" });
147
- });
148
- if (cached) {
149
- event?.waitUntil(revalidatePromise);
150
- notifyClients({
151
- type: "EIDOS_CACHE_HIT",
152
- url: pathname,
153
- strategy: "stale-while-revalidate"
154
- });
155
- return cached;
156
- }
157
- const fresh = await revalidatePromise;
158
- return fresh ?? offlineErrorResponse(pathname);
128
+ const cache = await caches.open(cacheName);
129
+ const cached = await cache.match(request);
130
+ const revalidatePromise = fetch(request).then(async (response) => {
131
+ if (response.ok) {
132
+ await cache.put(request, response.clone());
133
+ notifyClients({
134
+ type: "EIDOS_CACHE_UPDATED",
135
+ url: pathname,
136
+ strategy: "stale-while-revalidate"
137
+ });
138
+ }
139
+ return response;
140
+ }).catch(() => {
141
+ notifyClients({
142
+ type: "EIDOS_NETWORK_ERROR",
143
+ url: pathname,
144
+ strategy: "stale-while-revalidate"
145
+ });
146
+ });
147
+ if (cached) {
148
+ event?.waitUntil(revalidatePromise);
149
+ notifyClients({
150
+ type: "EIDOS_CACHE_HIT",
151
+ url: pathname,
152
+ strategy: "stale-while-revalidate"
153
+ });
154
+ return cached;
155
+ }
156
+ return await revalidatePromise ?? offlineErrorResponse(pathname);
159
157
  }
160
158
  async function networkFirst(request, pathname, cacheName) {
161
- const cache = await caches.open(cacheName);
162
- try {
163
- const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
164
- if (response.ok) {
165
- await cache.put(request, response.clone());
166
- notifyClients({ type: "EIDOS_CACHE_UPDATED", url: pathname, strategy: "network-first" });
167
- }
168
- return response;
169
- } catch {
170
- const cached = await cache.match(request);
171
- if (cached) {
172
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "network-first" });
173
- return cached;
174
- }
175
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname });
176
- return offlineErrorResponse(pathname);
177
- }
159
+ const cache = await caches.open(cacheName);
160
+ try {
161
+ const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
162
+ if (response.ok) {
163
+ await cache.put(request, response.clone());
164
+ notifyClients({
165
+ type: "EIDOS_CACHE_UPDATED",
166
+ url: pathname,
167
+ strategy: "network-first"
168
+ });
169
+ }
170
+ return response;
171
+ } catch {
172
+ const cached = await cache.match(request);
173
+ if (cached) {
174
+ notifyClients({
175
+ type: "EIDOS_CACHE_HIT",
176
+ url: pathname,
177
+ strategy: "network-first"
178
+ });
179
+ return cached;
180
+ }
181
+ notifyClients({
182
+ type: "EIDOS_NETWORK_ERROR",
183
+ url: pathname
184
+ });
185
+ return offlineErrorResponse(pathname);
186
+ }
178
187
  }
179
188
  async function serveOffline(request, pathname, cacheName) {
180
- const cache = await caches.open(cacheName);
181
- const cached = await cache.match(request);
182
- if (cached) {
183
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "offline-simulation", simulated: true });
184
- return cached;
185
- }
186
- return offlineErrorResponse(pathname);
189
+ const cached = await (await caches.open(cacheName)).match(request);
190
+ if (cached) {
191
+ notifyClients({
192
+ type: "EIDOS_CACHE_HIT",
193
+ url: pathname,
194
+ strategy: "offline-simulation",
195
+ simulated: true
196
+ });
197
+ return cached;
198
+ }
199
+ return offlineErrorResponse(pathname);
187
200
  }
188
201
  function offlineErrorResponse(pathname) {
189
- return new Response(
190
- JSON.stringify({
191
- error: "offline",
192
- message: `No cached response available for ${pathname}`,
193
- eidos: true
194
- }),
195
- {
196
- status: 503,
197
- headers: {
198
- "Content-Type": "application/json",
199
- "X-Eidos-Offline": "true"
200
- }
201
- }
202
- );
202
+ return new Response(JSON.stringify({
203
+ error: "offline",
204
+ message: `No cached response available for ${pathname}`,
205
+ eidos: true
206
+ }), {
207
+ status: 503,
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ "X-Eidos-Offline": "true"
211
+ }
212
+ });
203
213
  }
204
214
  async function notifyClients(message) {
205
- const clients = await self.clients.matchAll({ includeUncontrolled: true });
206
- clients.forEach((client) => client.postMessage(message));
215
+ (await self.clients.matchAll({ includeUncontrolled: true })).forEach((client) => client.postMessage(message));
207
216
  }
208
217
  self.addEventListener("sync", (event) => {
209
- const syncEvent = event;
210
- if (syncEvent.tag === "eidos-queue-replay") {
211
- syncEvent.waitUntil(notifyClients({ type: "EIDOS_BACKGROUND_SYNC" }));
212
- }
218
+ const syncEvent = event;
219
+ if (syncEvent.tag === "eidos-queue-replay") syncEvent.waitUntil(notifyClients({ type: "EIDOS_BACKGROUND_SYNC" }));
213
220
  });
221
+ self.addEventListener("push", (event) => {
222
+ let payload = null;
223
+ try {
224
+ payload = event.data?.json();
225
+ } catch {
226
+ return;
227
+ }
228
+ if (!payload?.title) return;
229
+ event.waitUntil(self.registration.showNotification(payload.title, {
230
+ body: payload.body,
231
+ icon: payload.icon,
232
+ badge: payload.badge,
233
+ tag: payload.tag,
234
+ data: payload.data
235
+ }));
236
+ });
237
+ self.addEventListener("notificationclick", (event) => {
238
+ event.notification.close();
239
+ const data = event.notification.data ?? {};
240
+ event.waitUntil(self.clients.matchAll({
241
+ type: "window",
242
+ includeUncontrolled: true
243
+ }).then((clients) => {
244
+ const client = clients[0];
245
+ if (client) {
246
+ client.focus();
247
+ client.postMessage({
248
+ type: "EIDOS_NOTIFICATION_CLICK",
249
+ data
250
+ });
251
+ return;
252
+ }
253
+ if (data.url) return self.clients.openWindow(data.url);
254
+ }));
255
+ });
256
+ self.addEventListener("pushsubscriptionchange", (event) => {
257
+ const psEvent = event;
258
+ psEvent.waitUntil((async () => {
259
+ const vapidPublicKey = await idbGet("vapidPublicKey");
260
+ if (!vapidPublicKey) return;
261
+ await notifyClients({
262
+ type: "EIDOS_SUBSCRIPTION_EXPIRED",
263
+ subscription: (psEvent.newSubscription ?? await self.registration.pushManager.subscribe({
264
+ userVisibleOnly: true,
265
+ applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
266
+ })).toJSON()
267
+ });
268
+ })());
269
+ });
270
+ function urlBase64ToUint8Array(base64Url) {
271
+ const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
272
+ const raw = atob(base64);
273
+ return Uint8Array.from(raw, (c) => c.charCodeAt(0));
274
+ }
275
+ var META_DB = "eidos-sw-meta";
276
+ var META_STORE = "kv";
277
+ function openMetaDb() {
278
+ return new Promise((resolve, reject) => {
279
+ const req = indexedDB.open(META_DB, 1);
280
+ req.onupgradeneeded = () => req.result.createObjectStore(META_STORE);
281
+ req.onsuccess = () => resolve(req.result);
282
+ req.onerror = () => reject(req.error);
283
+ });
284
+ }
285
+ async function idbSet(key, value) {
286
+ const db = await openMetaDb();
287
+ await new Promise((resolve, reject) => {
288
+ const tx = db.transaction(META_STORE, "readwrite");
289
+ tx.objectStore(META_STORE).put(value, key);
290
+ tx.oncomplete = () => resolve();
291
+ tx.onerror = () => reject(tx.error);
292
+ });
293
+ db.close();
294
+ }
295
+ async function idbGet(key) {
296
+ const db = await openMetaDb();
297
+ const value = await new Promise((resolve, reject) => {
298
+ const req = db.transaction(META_STORE, "readonly").objectStore(META_STORE).get(key);
299
+ req.onsuccess = () => resolve(req.result);
300
+ req.onerror = () => reject(req.error);
301
+ });
302
+ db.close();
303
+ return value;
304
+ }
305
+ //#endregion