@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.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.1
|
|
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(
|
|
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(
|
|
157
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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 =
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
449
|
-
constructor(
|
|
450
|
-
|
|
451
|
-
this.
|
|
452
|
-
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
|
541
|
+
return error;
|
|
478
542
|
}
|
|
479
543
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"
|
|
484
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
520
|
-
return
|
|
521
|
-
|
|
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
|
-
|
|
525
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
dispatcher.stop();
|
|
728
|
+
|
|
729
|
+
return {
|
|
730
|
+
getClient,
|
|
731
|
+
addClient,
|
|
732
|
+
removeClient,
|
|
635
733
|
};
|
|
636
734
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
+
//
|