@statewalker/webrun-http-browser 0.1.0 → 0.2.1

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/index-sw.js CHANGED
@@ -1,18 +1,4 @@
1
- // @statewalker/webrun-http-browser v0.1.0 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
2
- function serializeError(error) {
3
- return Object.assign({
4
- message: error,
5
- stack: error.stack,
6
- }, error);
7
- }
8
-
9
- function deserializeError(error) {
10
- if (typeof error === "string") {
11
- error = { message: error };
12
- }
13
- return Object.assign(new Error(error.message), error);
14
- }
15
-
1
+ // @statewalker/webrun-http-browser v0.2.1 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
16
2
  // See https://github.com/agenjs/agen-utils/blob/main/src/iterator.js
17
3
  async function* iterate(init, queue = []) {
18
4
  let promise, notify, push = async (error, value, done) => {
@@ -51,6 +37,126 @@ async function* iterate(init, queue = []) {
51
37
  }
52
38
  }
53
39
 
40
+ function newRegistry(onError = console.error) {
41
+ let counter = 0;
42
+ const registrations = {};
43
+ const register = (action) => {
44
+ const id = counter++;
45
+ return (registrations[id] = Object.assign(
46
+ function (skip) {
47
+ try {
48
+ delete registrations[id];
49
+ return !skip && action && action();
50
+ } catch (error) {
51
+ onError(error);
52
+ }
53
+ },
54
+ { action }
55
+ ));
56
+ };
57
+ const unregister = (action) =>
58
+ Object.values(registrations).forEach((r) => r.action === action && r(true));
59
+ const clear = () => Object.values(registrations).forEach((r) => r());
60
+ return Object.assign([register, clear, unregister], {
61
+ register,
62
+ clear,
63
+ unregister
64
+ });
65
+ }
66
+
67
+ function promisifyRequest(request) {
68
+ return new Promise((resolve, reject) => {
69
+ // @ts-ignore - file size hacks
70
+ request.oncomplete = request.onsuccess = () => resolve(request.result);
71
+ // @ts-ignore - file size hacks
72
+ request.onabort = request.onerror = () => reject(request.error);
73
+ });
74
+ }
75
+ function createStore(dbName, storeName) {
76
+ const request = indexedDB.open(dbName);
77
+ request.onupgradeneeded = () => request.result.createObjectStore(storeName);
78
+ const dbp = promisifyRequest(request);
79
+ return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
80
+ }
81
+ let defaultGetStoreFunc;
82
+ function defaultGetStore() {
83
+ if (!defaultGetStoreFunc) {
84
+ defaultGetStoreFunc = createStore('keyval-store', 'keyval');
85
+ }
86
+ return defaultGetStoreFunc;
87
+ }
88
+ /**
89
+ * Get a value by its key.
90
+ *
91
+ * @param key
92
+ * @param customStore Method to get a custom store. Use with caution (see the docs).
93
+ */
94
+ function get(key, customStore = defaultGetStore()) {
95
+ return customStore('readonly', (store) => promisifyRequest(store.get(key)));
96
+ }
97
+ /**
98
+ * Set a value with a key.
99
+ *
100
+ * @param key
101
+ * @param value
102
+ * @param customStore Method to get a custom store. Use with caution (see the docs).
103
+ */
104
+ function set(key, value, customStore = defaultGetStore()) {
105
+ return customStore('readwrite', (store) => {
106
+ store.put(value, key);
107
+ return promisifyRequest(store.transaction);
108
+ });
109
+ }
110
+
111
+ function serializeError(error) {
112
+ return Object.assign({
113
+ message: error,
114
+ stack: error.stack,
115
+ }, error);
116
+ }
117
+
118
+ function deserializeError(error) {
119
+ if (typeof error === "string") {
120
+ error = { message: error };
121
+ }
122
+ return Object.assign(new Error(error.message), error);
123
+ }
124
+
125
+ async function callChannel(mainChannel, callType, params, ...transfers) {
126
+ return new Promise((resolve, reject) => {
127
+ const channel = new MessageChannel();
128
+ channel.port1.onmessage = (ev) => {
129
+ const { result, error } = ev.data;
130
+ if (error) reject(deserializeError(error));
131
+ else resolve(result);
132
+ };
133
+ mainChannel.postMessage({ type: callType, params }, [
134
+ channel.port2,
135
+ ...transfers,
136
+ ]);
137
+ });
138
+ }
139
+
140
+ function handleChannelCalls(mainChannel, callType, handler) {
141
+ const messageListener = async (event) => {
142
+ if (!event.data || event.data.type !== callType) return;
143
+ const [port, ...transfers] = event.ports;
144
+ let response = {};
145
+ try {
146
+ const params = event.data.params;
147
+ response.result = await handler(event, params, ...transfers);
148
+ } catch (error) {
149
+ response.error = serializeError(error);
150
+ }
151
+ port.postMessage(response);
152
+ };
153
+ mainChannel.addEventListener("message", messageListener);
154
+ try {
155
+ mainChannel.start && mainChannel.start();
156
+ } catch (e) {}
157
+ return () => mainChannel.removeEventListener("message", messageListener);
158
+ }
159
+
54
160
  async function* recieveData(onMessage) {
55
161
  yield* iterate((iterator) => {
56
162
  return onMessage(async ({ done = true, value, error } = {}) => {
@@ -115,7 +221,10 @@ function newChannel(port) {
115
221
  await listener(data);
116
222
  }
117
223
  };
118
- const channel = newInvokationChannel(port, notifyAll);
224
+ const channel = newInvokationChannel({
225
+ port,
226
+ handler: notifyAll,
227
+ });
119
228
 
120
229
  const start = () => channel.start();
121
230
 
@@ -153,15 +262,20 @@ function newChannel(port) {
153
262
  };
154
263
  }
155
264
 
156
- function newInvokationChannel(port, handler = () => {
157
- throw new Error("Handler not implemented");
265
+ function newInvokationChannel({
266
+ port,
267
+ handler = () => {
268
+ throw new Error("Handler not implemented");
269
+ },
270
+ onError = console.error,
271
+ newCallId = () =>
272
+ newInvokationChannel.__counter = (newInvokationChannel.__counter || 0) + 1,
158
273
  }) {
159
274
  const MESSAGE_TYPE_REQUEST = "REQUEST";
160
275
  const MESSAGE_TYPE_RESPONSE = "RESPONSE";
161
276
  const EVENT_MESSAGE = "message";
162
277
 
163
278
  const requests = {};
164
- let requestCounter = 0;
165
279
  const listener = async (event) => {
166
280
  const data = event.data || {};
167
281
  const { type, callId } = data;
@@ -198,17 +312,25 @@ function newInvokationChannel(port, handler = () => {
198
312
  };
199
313
 
200
314
  const start = async () => {
201
- port.addEventListener(EVENT_MESSAGE, listener);
202
- await port.start();
315
+ try {
316
+ port.addEventListener(EVENT_MESSAGE, listener);
317
+ port.start && await port.start();
318
+ } catch (e) {
319
+ onError(e);
320
+ }
203
321
  };
204
322
 
205
323
  const close = async () => {
206
- port.removeEventListener(EVENT_MESSAGE, listener);
207
- await port.close();
324
+ try {
325
+ port.removeEventListener(EVENT_MESSAGE, listener);
326
+ port.close && await port.close();
327
+ } catch (e) {
328
+ onError(e);
329
+ }
208
330
  };
209
331
 
210
332
  const invoke = async function (request = {}, ...transfers) {
211
- const callId = ++requestCounter;
333
+ const callId = newCallId();
212
334
  return new Promise((resolve, reject) => {
213
335
  try {
214
336
  requests[callId] = { resolve, reject };
@@ -366,278 +488,300 @@ async function sendHttpRequest(communicationPort, request) {
366
488
  return await clientStub(request);
367
489
  }
368
490
 
369
- async function callChannel(mainChannel, callType, params, ...transfers) {
370
- return new Promise((resolve, reject) => {
371
- const channel = new MessageChannel();
372
- channel.port1.onmessage = (ev) => {
373
- const { result, error } = ev.data;
374
- if (error) reject(deserializeError(error));
375
- else resolve(result);
376
- };
377
- mainChannel.postMessage({ type: callType, params }, [
378
- channel.port2,
379
- ...transfers,
380
- ]);
381
- });
382
- }
383
-
384
- function handleChannelCalls(mainChannel, callType, handler) {
385
- const messageListener = async (event) => {
386
- if (!event.data || event.data.type !== callType) return;
387
- const [port, ...transfers] = event.ports;
388
- let response = {};
389
- try {
390
- const params = event.data.params;
391
- response.result = await handler(event, params, ...transfers);
392
- } catch (error) {
393
- response.error = serializeError(error);
394
- }
395
- port.postMessage(response);
491
+ function splitServiceUrl(url, separator = "~") {
492
+ let key = "";
493
+ let path = "";
494
+ let str = url + "";
495
+ const idx = str.indexOf(separator);
496
+ let baseUrl = "";
497
+ if (idx >= 0) {
498
+ baseUrl = str.substring(0, idx + separator.length);
499
+ str.substring(idx + separator.length)
500
+ .replace(/^([^\/]+)/, (match, $1) => {
501
+ baseUrl += match;
502
+ if (baseUrl.length < str.length) baseUrl += "/";
503
+ key = $1;
504
+ path = str.substring(baseUrl.length);
505
+ return "";
506
+ });
507
+ }
508
+ return {
509
+ url,
510
+ key,
511
+ baseUrl,
512
+ path,
396
513
  };
397
- mainChannel.addEventListener("message", messageListener);
398
- try {
399
- mainChannel.start && mainChannel.start();
400
- } catch (e) {}
401
- return () => mainChannel.removeEventListener("message", messageListener);
402
- }
403
-
404
- function promisifyRequest(request) {
405
- return new Promise((resolve, reject) => {
406
- // @ts-ignore - file size hacks
407
- request.oncomplete = request.onsuccess = () => resolve(request.result);
408
- // @ts-ignore - file size hacks
409
- request.onabort = request.onerror = () => reject(request.error);
410
- });
411
- }
412
- function createStore(dbName, storeName) {
413
- const request = indexedDB.open(dbName);
414
- request.onupgradeneeded = () => request.result.createObjectStore(storeName);
415
- const dbp = promisifyRequest(request);
416
- return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
417
- }
418
- let defaultGetStoreFunc;
419
- function defaultGetStore() {
420
- if (!defaultGetStoreFunc) {
421
- defaultGetStoreFunc = createStore('keyval-store', 'keyval');
422
- }
423
- return defaultGetStoreFunc;
424
- }
425
- /**
426
- * Get a value by its key.
427
- *
428
- * @param key
429
- * @param customStore Method to get a custom store. Use with caution (see the docs).
430
- */
431
- function get(key, customStore = defaultGetStore()) {
432
- return customStore('readonly', (store) => promisifyRequest(store.get(key)));
433
- }
434
- /**
435
- * Set a value with a key.
436
- *
437
- * @param key
438
- * @param value
439
- * @param customStore Method to get a custom store. Use with caution (see the docs).
440
- */
441
- function set(key, value, customStore = defaultGetStore()) {
442
- return customStore('readwrite', (store) => {
443
- store.put(value, key);
444
- return promisifyRequest(store.transaction);
445
- });
446
514
  }
447
515
 
448
- class SwPortDispatcher {
449
- constructor({ self, log }) {
450
- this.self = self;
451
- this.log = log;
452
- // Contains mapping between path prefixes and clientIds
453
- this.handlersIndex = new Map();
516
+ class HttpError extends Error {
517
+ constructor(options) {
518
+ super(options.message);
519
+ this.status = options.status;
520
+ this.statusText = options.statusText;
454
521
  }
455
-
456
- get scope() {
457
- return new URL("./", import.meta.url) + "";
522
+ getResponseOptions(options = {}) {
523
+ return Object.assign(this.toJson(), options);
524
+ }
525
+ toJson() {
526
+ return {
527
+ status: this.status,
528
+ statusText: this.statusText,
529
+ message: this.message,
530
+ };
458
531
  }
459
532
 
460
- async loadChannelInfo(key) {
461
- await this._checkActivation();
462
- let result;
463
- for (let channelInfo of this.handlersIndex.values()) {
464
- if (channelInfo.key !== key) continue;
465
- const clientId = channelInfo.clientId;
466
- const client = await self.clients.get(clientId);
467
- if (!client) {
468
- // Client gone. Remove the corresponding handlers from the index.
469
- this.handlersIndex.delete(clientId);
470
- throw Object.assign(new Error("Error 410: Resource Gone"), {
471
- status: 410,
472
- });
473
- }
474
- result = channelInfo;
475
- break;
533
+ static fromError(error) {
534
+ if (!(error instanceof HttpError)) {
535
+ error = new HttpError(Object.assign({
536
+ status: 500,
537
+ statusText: "Bad Request",
538
+ reason: error.message,
539
+ }));
476
540
  }
477
- return result;
541
+ return error;
478
542
  }
479
543
 
480
- start() {
481
- this._cleanup = handleChannelCalls(
482
- this.self,
483
- "UPDATE_COMMUNICATION_PORT",
484
- async (event, channelInfo, port) => {
485
- const clientId = event.source.id;
486
- this.log("[UPDATE_COMMUNICATION_PORT]", clientId, channelInfo);
487
- await this._updateChannelInfo({ ...channelInfo, port, clientId });
488
- return { ...channelInfo };
489
- },
490
- );
491
- this.self.onmessage = () => {};
492
-
493
- this.self.addEventListener("install", (event) => {
494
- this.log("Skip waiting on install.", event);
495
- this.self.skipWaiting();
544
+ static errorResourceNotFound(options = {}) {
545
+ return new HttpError({
546
+ status: 404,
547
+ statusText: "Error 404: Resource not found",
548
+ ...options,
496
549
  });
497
-
498
- this.self.addEventListener("activate", async (event) => {
499
- this.log(
500
- "Claim control over all clients.",
501
- event,
502
- clients,
503
- this.self.clients,
504
- );
505
- event.waitUntil((async () => {
506
- await this.self.clients.claim();
507
- await this._checkActivation();
508
- })());
550
+ }
551
+ static errorForbidden(options = {}) {
552
+ return new HttpError({
553
+ status: 403,
554
+ statusText: "Error 403: Forbidden",
555
+ ...options,
509
556
  });
510
557
  }
511
558
 
512
- async stop() {
513
- if (this._cleanup) {
514
- await this._cleanup();
515
- this._cleanup = undefined;
516
- }
559
+ static errorResourceGone(options = {}) {
560
+ return new HttpError({
561
+ status: 410,
562
+ statusText: "Error 410: Resource Gone",
563
+ ...options,
564
+ });
517
565
  }
518
566
 
519
- async _checkActivation() {
520
- return this._activationPromise = this._activationPromise ||
521
- this._activate();
567
+ static errorInternalError(options) {
568
+ return new HttpError({
569
+ ...options,
570
+ status: 500,
571
+ statusText: "Error 500: Internal error",
572
+ });
522
573
  }
574
+ }
523
575
 
524
- async _activate() {
525
- this.log("[checkActivation]");
526
- let clientsIndex = await this._loadClientIds();
527
- for (let [clientId, client] of clientsIndex.entries()) {
528
- const messageChannel = new MessageChannel();
529
- const port = messageChannel.port1;
530
- const channelInfo = await callChannel(
531
- client,
532
- "UPDATE_COMMUNICATION_PORT",
533
- {},
534
- messageChannel.port2,
535
- );
536
- await this._updateChannelInfo({ ...channelInfo, port, clientId });
537
- }
538
- }
576
+ function startRelayServiceWorker(self) {
577
+ const [register, clear] = newRegistry();
539
578
 
540
- async _updateChannelInfo(channelInfo) {
541
- this.log("[updateClientInfo]", channelInfo);
542
- this.handlersIndex.set(channelInfo.clientId, channelInfo);
543
- await this._updateClientIds([]);
579
+ if (typeof self.skipWaiting === "function") {
580
+ console.log("self.skipWaiting() is supported.");
581
+ self.addEventListener("install", (e) => {
582
+ // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-skipwaiting
583
+ e.waitUntil(self.skipWaiting());
584
+ });
585
+ } else {
586
+ console.log("self.skipWaiting() is not supported.");
544
587
  }
545
588
 
546
- async _loadClientIds() {
547
- const clientIds = new Set((await get("clientIds")) || []);
548
- return await this._updateClientIds(clientIds);
589
+ if (self.clients && (typeof self.clients.claim === "function")) {
590
+ console.log("self.clients.claim() is supported.");
591
+ self.addEventListener("activate", (e) => {
592
+ // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#clients-claim-method
593
+ e.waitUntil(self.clients.claim());
594
+ });
595
+ } else {
596
+ console.log("self.clients.claim() is not supported.");
549
597
  }
550
598
 
551
- async _updateClientIds(clientIds) {
552
- clientIds = new Set(clientIds);
553
- for (let clientId of this.handlersIndex.keys()) {
554
- clientIds.add(clientId);
599
+ const clientsRegistry = newClientsRegistry({
600
+ self,
601
+ get,
602
+ set,
603
+ });
604
+
605
+ // This listener handles the following message types:
606
+ // * registration: registers new clients as service providers
607
+ // * handles service calls rounting:
608
+ // - it gets the clientKey defined in the call
609
+ // - it uses the serviceKey to get the service-specifc channel
610
+ // - it creates a new message channel and sent its ports to peers
611
+
612
+ register(handleChannelCalls(self, "REGISTER", async (event, data) => {
613
+ return await clientsRegistry.addClient(data.key, event.source);
614
+ }));
615
+ register(handleChannelCalls(self, "UNREGISTER", async (event, data) => {
616
+ return await clientsRegistry.removeClient(data.key);
617
+ }));
618
+
619
+ // Transfers all parameters (and ports) to the target client.
620
+ register(handleChannelCalls(self, "CONNECT", async (event, data, port) => {
621
+ const { key } = data;
622
+ const client = await clientsRegistry.getClient(key);
623
+ if (!client) {
624
+ throw new Error(`Target client was not found. Target key: "${key}".`);
625
+ }
626
+ return await callChannel(client, "CONNECT", data, port);
627
+ }));
628
+
629
+ const fetchListener = (event) => {
630
+ const request = event.request;
631
+ const params = splitServiceUrl(request.url);
632
+ const { key } = params;
633
+ if (key) {
634
+ const handler = async () => {
635
+ try {
636
+ const channel = new MessageChannel();
637
+ const client = await clientsRegistry.getClient(key);
638
+ if (!client) {
639
+ throw HttpError.errorResourceGone(params);
640
+ }
641
+ const data = {
642
+ type: "http",
643
+ key,
644
+ };
645
+ if (!await callChannel(client, "CONNECT", data, channel.port2)) {
646
+ throw HttpError.errorForbidden(params);
647
+ }
648
+ return await sendHttpRequest(channel.port1, request);
649
+ } catch (error) {
650
+ error = HttpError.fromError(error);
651
+ const options = error.getResponseOptions(params);
652
+ return new Response(JSON.stringify(options), {
653
+ ...options,
654
+ headers: {
655
+ "Content-Type": "text/javascript",
656
+ },
657
+ });
658
+ }
659
+ };
660
+ event.respondWith(handler());
555
661
  }
556
- const index = new Map();
557
- for (let clientId of clientIds) {
558
- const client = await this.self.clients.get(clientId);
559
- if (client) index.set(clientId, client);
560
- else this.handlersIndex.delete(clientId);
662
+ };
663
+ self.addEventListener("fetch", fetchListener);
664
+ register(() => self.removeEventListener("fetch", fetchListener));
665
+
666
+ return clear;
667
+ }
668
+
669
+ // --------------------------------------------
670
+ // errors.js
671
+
672
+ // --------------------------------------------
673
+
674
+ function newClientsRegistry({
675
+ self,
676
+ key = "clientsIds",
677
+ get,
678
+ set,
679
+ }) {
680
+ let _index;
681
+ async function loadClientsIndex() {
682
+ if (!_index) {
683
+ const entries = (await get(key)) || [];
684
+ _index = Object.fromEntries(entries);
561
685
  }
562
- await set("clientIds", [...index.keys()].sort());
686
+ return _index;
687
+ }
688
+ async function storeClientsIndex() {
689
+ const index = await loadClientsIndex();
690
+ const entries = Object.entries(index);
691
+ await set(key, entries);
563
692
  return index;
564
693
  }
565
- }
566
694
 
567
- class SwHttpDispatcher extends SwPortDispatcher {
568
- constructor(options) {
569
- super(options);
695
+ async function addClient(clientKey, client) {
696
+ const index = await loadClientsIndex();
697
+ const clientId = client.id;
698
+ let result = false;
699
+ if (!(clientId in index)) {
700
+ index[clientKey] = clientId;
701
+ await storeClientsIndex();
702
+ result = true;
703
+ }
704
+ return result;
570
705
  }
571
706
 
572
- start() {
573
- super.start();
574
- this.self.addEventListener("fetch", this._handleFetchEvent.bind(this));
707
+ async function removeClient(clientKey) {
708
+ const index = await loadClientsIndex();
709
+ let result = false;
710
+ if (clientKey in index) {
711
+ delete index[clientKey];
712
+ await storeClientsIndex();
713
+ result = true;
714
+ }
715
+ return result;
575
716
  }
576
717
 
577
- _handleFetchEvent(event) {
578
- event.respondWith((async () => {
579
- const request = event.request;
580
- try {
581
- const requestUrl = request.url;
582
- const rootUrl = this.scope;
583
- let channelInfo;
584
- if (requestUrl.indexOf(rootUrl) === 0) {
585
- const key = requestUrl
586
- .substring(rootUrl.length)
587
- .replace(/^\/?([^\/]+).*$/, "$1");
588
- // console.log(`* rootUrl="${rootUrl}" requestUrl="${requestUrl}" key="${key}"`);
589
- channelInfo = await this.loadChannelInfo(key);
590
- }
591
- if (channelInfo && channelInfo.port) {
592
- return await sendHttpRequest(channelInfo.port, request);
593
- } else {
594
- const newRequest = new Request(requestUrl, {
595
- method: request.method,
596
- headers: request.headers,
597
- body: request.body,
598
- referrer: request.referrer,
599
- referrerPolicy: request.referrerPolicy,
600
- // mode: request.mode,
601
- credentials: request.credentials,
602
- cache: request.cache,
603
- redirect: request.redirect,
604
- integrity: request.integrity,
605
- });
606
- return await fetch(newRequest);
607
- }
608
-
609
- // const requestUrl = request.url;
610
- // let url = requestUrl.replace(
611
- // /^.*\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}(.*)$/,
612
- // "$1",
613
- // );
614
- // if (url !== requestUrl) {
615
- // url = new URL(
616
- // "/packages/webrun-http-browser/src" + url,
617
- // self.location.href,
618
- // );
619
- // }
620
- } catch (error) {
621
- console.error(error);
622
- return new Response(null, {
623
- status: 500,
624
- statusText: "Error 500: Internal error",
625
- });
626
- }
627
- })());
718
+ async function getClient(clientKey) {
719
+ const index = await loadClientsIndex();
720
+ const clientId = index[clientKey];
721
+ const client = await self.clients.get(clientId);
722
+ if (!client) {
723
+ delete index[clientKey];
724
+ await storeClientsIndex();
725
+ }
726
+ return client;
628
727
  }
629
- }
630
- function startHttpDispatcher({ self, log }) {
631
- const dispatcher = new SwHttpDispatcher({ self, log });
632
- dispatcher.start();
633
- return () => {
634
- dispatcher.stop();
728
+
729
+ return {
730
+ getClient,
731
+ addClient,
732
+ removeClient,
635
733
  };
636
734
  }
637
-
638
- const result = startHttpDispatcher({
639
- self,
640
- log: console.log.bind(console, "[sw]"),
641
- });
642
-
643
- export { result };
735
+ // ----------------------------------------------------
736
+ // Content of the "idb-keyval" package
737
+ // import { get, set } from "idb-keyval";
738
+
739
+ // function promisifyRequest(request) {
740
+ // return new Promise((resolve, reject) => {
741
+ // // @ts-ignore - file size hacks
742
+ // request.oncomplete = request.onsuccess = () => resolve(request.result);
743
+ // // @ts-ignore - file size hacks
744
+ // request.onabort = request.onerror = () => reject(request.error);
745
+ // });
746
+ // }
747
+ // function createStore(dbName, storeName) {
748
+ // const request = indexedDB.open(dbName);
749
+ // request.onupgradeneeded = () => request.result.createObjectStore(storeName);
750
+ // const dbp = promisifyRequest(request);
751
+ // return (txMode, callback) =>
752
+ // dbp.then((db) =>
753
+ // callback(db.transaction(storeName, txMode).objectStore(storeName))
754
+ // );
755
+ // }
756
+ // let defaultGetStoreFunc;
757
+ // function defaultGetStore() {
758
+ // if (!defaultGetStoreFunc) {
759
+ // defaultGetStoreFunc = createStore("keyval-store", "keyval");
760
+ // }
761
+ // return defaultGetStoreFunc;
762
+ // }
763
+ // /**
764
+ // * Get a value by its key.
765
+ // *
766
+ // * @param key
767
+ // * @param customStore Method to get a custom store. Use with caution (see the docs).
768
+ // */
769
+ // function get(key, customStore = defaultGetStore()) {
770
+ // return customStore("readonly", (store) => promisifyRequest(store.get(key)));
771
+ // }
772
+ // /**
773
+ // * Set a value with a key.
774
+ // *
775
+ // * @param key
776
+ // * @param value
777
+ // * @param customStore Method to get a custom store. Use with caution (see the docs).
778
+ // */
779
+ // function set(key, value, customStore = defaultGetStore()) {
780
+ // return customStore("readwrite", (store) => {
781
+ // store.put(value, key);
782
+ // return promisifyRequest(store.transaction);
783
+ // });
784
+ // }
785
+
786
+ startRelayServiceWorker(self);
787
+ //