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