@statewalker/webrun-http-browser 0.1.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/index.js ADDED
@@ -0,0 +1,945 @@
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
+
16
+ async function callChannel(mainChannel, callType, params, ...transfers) {
17
+ return new Promise((resolve, reject) => {
18
+ const channel = new MessageChannel();
19
+ channel.port1.onmessage = (ev) => {
20
+ const { result, error } = ev.data;
21
+ if (error) reject(deserializeError(error));
22
+ else resolve(result);
23
+ };
24
+ mainChannel.postMessage({ type: callType, params }, [
25
+ channel.port2,
26
+ ...transfers,
27
+ ]);
28
+ });
29
+ }
30
+
31
+ function handleChannelCalls(mainChannel, callType, handler) {
32
+ const messageListener = async (event) => {
33
+ if (!event.data || event.data.type !== callType) return;
34
+ const [port, ...transfers] = event.ports;
35
+ let response = {};
36
+ try {
37
+ const params = event.data.params;
38
+ response.result = await handler(event, params, ...transfers);
39
+ } catch (error) {
40
+ response.error = serializeError(error);
41
+ }
42
+ port.postMessage(response);
43
+ };
44
+ mainChannel.addEventListener("message", messageListener);
45
+ try {
46
+ mainChannel.start && mainChannel.start();
47
+ } catch (e) {}
48
+ return () => mainChannel.removeEventListener("message", messageListener);
49
+ }
50
+
51
+ // See https://github.com/agenjs/agen-utils/blob/main/src/iterator.js
52
+ async function* iterate(init, queue = []) {
53
+ let promise, notify, push = async (error, value, done) => {
54
+ const slot = { error, value, done };
55
+ slot.promise = new Promise(n => slot.notify = n);
56
+ await queue.push(slot);
57
+ notify && notify();
58
+ notify = null;
59
+ return slot.promise;
60
+ };
61
+ const next = (value) => push(undefined, value, false);
62
+ const complete = () => push(undefined, undefined, true);
63
+ const error = err => push(err, undefined, true);
64
+ const unsubscribe = init(Object.assign([next, complete, error], { next, complete, error }));
65
+ let slot;
66
+ try {
67
+ while (true) {
68
+ slot = await queue.shift();
69
+ if (slot) {
70
+ try {
71
+ if (slot.error) { throw slot.error; }
72
+ else if (slot.done) { break; }
73
+ else { yield slot.value; }
74
+ } finally {
75
+ slot.notify(true);
76
+ }
77
+ } else {
78
+ await (promise = notify ? promise : new Promise(n => notify = n));
79
+ }
80
+ }
81
+ } finally {
82
+ notify && notify();
83
+ push = () => false; // Stop pushing in the queue...
84
+ ((typeof unsubscribe === 'function') && (await unsubscribe()));
85
+ while (slot = await queue.shift()) { slot.notify(false); }
86
+ }
87
+ }
88
+
89
+ function newRegistry(onError = console.error) {
90
+ let counter = 0;
91
+ const registrations = {};
92
+ const register = (action) => {
93
+ const id = counter++;
94
+ return (registrations[id] = Object.assign(
95
+ function (skip) {
96
+ try {
97
+ delete registrations[id];
98
+ return !skip && action && action();
99
+ } catch (error) {
100
+ onError(error);
101
+ }
102
+ },
103
+ { action }
104
+ ));
105
+ };
106
+ const unregister = (action) =>
107
+ Object.values(registrations).forEach((r) => r.action === action && r(true));
108
+ const clear = () => Object.values(registrations).forEach((r) => r());
109
+ return Object.assign([register, clear, unregister], {
110
+ register,
111
+ clear,
112
+ unregister
113
+ });
114
+ }
115
+
116
+ async function* recieveData(onMessage) {
117
+ yield* iterate((iterator) => {
118
+ return onMessage(async ({ done = true, value, error } = {}) => {
119
+ if (error) {
120
+ await iterator.error(error);
121
+ } else if (done) {
122
+ await iterator.complete();
123
+ } else {
124
+ await iterator.next(value);
125
+ }
126
+ });
127
+ });
128
+ }
129
+
130
+ async function sendData(send, it) {
131
+ let error;
132
+ try {
133
+ for await (let value of it) {
134
+ await send({ done: false, value });
135
+ }
136
+ } catch (err) {
137
+ error = err;
138
+ } finally {
139
+ await send({ done: true, error });
140
+ }
141
+ }
142
+
143
+ async function* sendStream(communicationPort, input, params = {}) {
144
+ const messageChannel = new MessageChannel();
145
+ communicationPort.postMessage({ type: "START_CALL", params }, [
146
+ messageChannel.port2,
147
+ ]);
148
+
149
+ const channel = newChannel(messageChannel.port1);
150
+ try {
151
+ await channel.start();
152
+ channel.sendAll(input);
153
+ yield* channel.recieveAll();
154
+ } finally {
155
+ await channel.close();
156
+ }
157
+ }
158
+
159
+ function handleStreams(communicationPort, handler) {
160
+ const listener = async (event) => {
161
+ const { type, params } = event.data || {};
162
+ if (type !== "START_CALL") return;
163
+ const port = event.ports[0];
164
+ const channel = newChannel(port);
165
+ try {
166
+ await channel.start();
167
+ const input = channel.recieveAll();
168
+ const response = await handler(input, params);
169
+ await channel.sendAll(response);
170
+ } finally {
171
+ await channel.close();
172
+ }
173
+ };
174
+ communicationPort.addEventListener("message", listener);
175
+ try {
176
+ communicationPort.start && communicationPort.start();
177
+ } catch (e) {}
178
+ return () => communicationPort.removeEventListener("message", listener);
179
+ }
180
+
181
+ /**
182
+ * This method creates and returns methods allowing to send and recieve data over the specified port.
183
+ * The main characteristics of the returned methods is that they properly manage backpressure - sender
184
+ * iterators are waiting to send the next value until the previous one is consumed on the other side.
185
+ * @param {*} port
186
+ * @returns an object with the following methods:
187
+ * * `start` - starts the connection over the specified port
188
+ * * `close`- stops all iterators and closes the port
189
+ * * `recieveAll() : AsyncIterator` - returns an AsyncIterator over all values send by the peer
190
+ * * `sendAll(it : AsyncIterator) : Promise<any>` - sends all values defined by the given
191
+ * AsyncIterator to the peer
192
+ */
193
+ function newChannel(port) {
194
+ let listeners = [];
195
+ let iterators = [];
196
+
197
+ const notifyAll = async (data) => {
198
+ for (let listener of listeners) {
199
+ await listener(data);
200
+ }
201
+ };
202
+ const channel = newInvokationChannel(port, notifyAll);
203
+
204
+ const start = () => channel.start();
205
+
206
+ const close = async () => {
207
+ await notifyAll({ done: true });
208
+ [...iterators].forEach((it) => it.return && it.return());
209
+ await channel.close();
210
+ };
211
+
212
+ async function* recieveAll() {
213
+ yield* recieveData((listener) => {
214
+ listeners.push(listener);
215
+ return () => listeners = listeners.filter((l) => l !== listener);
216
+ });
217
+ }
218
+
219
+ async function sendAll(it) {
220
+ await sendData(
221
+ channel.invoke,
222
+ (async function* () {
223
+ try {
224
+ iterators.push(it);
225
+ yield* it;
226
+ } finally {
227
+ iterators = iterators.filter((i) => i !== it);
228
+ }
229
+ })(),
230
+ );
231
+ }
232
+ return {
233
+ start,
234
+ close,
235
+ recieveAll,
236
+ sendAll,
237
+ };
238
+ }
239
+
240
+ function newInvokationChannel(port, handler = () => {
241
+ throw new Error("Handler not implemented");
242
+ }) {
243
+ const MESSAGE_TYPE_REQUEST = "REQUEST";
244
+ const MESSAGE_TYPE_RESPONSE = "RESPONSE";
245
+ const EVENT_MESSAGE = "message";
246
+
247
+ const requests = {};
248
+ let requestCounter = 0;
249
+ const listener = async (event) => {
250
+ const data = event.data || {};
251
+ const { type, callId } = data;
252
+ if (type === MESSAGE_TYPE_REQUEST) {
253
+ try {
254
+ const { request } = data;
255
+ let result = await handler(request, ...(event.ports || []));
256
+ const [response, ...transfers] = Array.isArray(result)
257
+ ? result
258
+ : result !== undefined
259
+ ? [result]
260
+ : [];
261
+ port.postMessage(
262
+ { type: MESSAGE_TYPE_RESPONSE, callId, response },
263
+ transfers,
264
+ );
265
+ } catch (error) {
266
+ port.postMessage({
267
+ type: MESSAGE_TYPE_RESPONSE,
268
+ callId,
269
+ error: serializeError(error),
270
+ });
271
+ }
272
+ } else if (type === MESSAGE_TYPE_RESPONSE) {
273
+ const { callId, response, error } = data;
274
+ const request = requests[callId];
275
+ delete requests[callId];
276
+ if (request) {
277
+ error
278
+ ? request.reject(deserializeError(error))
279
+ : request.resolve(response);
280
+ }
281
+ }
282
+ };
283
+
284
+ const start = async () => {
285
+ port.addEventListener(EVENT_MESSAGE, listener);
286
+ await port.start();
287
+ };
288
+
289
+ const close = async () => {
290
+ port.removeEventListener(EVENT_MESSAGE, listener);
291
+ await port.close();
292
+ };
293
+
294
+ const invoke = async function (request = {}, ...transfers) {
295
+ const callId = ++requestCounter;
296
+ return new Promise((resolve, reject) => {
297
+ try {
298
+ requests[callId] = { resolve, reject };
299
+ port.postMessage(
300
+ { type: MESSAGE_TYPE_REQUEST, callId, request },
301
+ transfers,
302
+ );
303
+ } catch (error) {
304
+ reject(error);
305
+ delete request[callId];
306
+ }
307
+ });
308
+ };
309
+
310
+ return {
311
+ start,
312
+ close,
313
+ invoke,
314
+ };
315
+ }
316
+
317
+ function toReadableStream(it) {
318
+ return new ReadableStream({
319
+ type: "bytes",
320
+ async pull(controller) {
321
+ let handled = false;
322
+ try {
323
+ while (true) {
324
+ const slot = await it.next();
325
+ if (!slot || slot.done) {
326
+ break;
327
+ }
328
+ controller.enqueue(await slot.value);
329
+ }
330
+ } catch (error) {
331
+ handled = true;
332
+ controller.error(error);
333
+ } finally {
334
+ !handled && controller.close();
335
+ }
336
+ },
337
+ });
338
+ }
339
+
340
+ async function* fromReadableStream(stream) {
341
+ const reader = await stream.getReader();
342
+ let done, value;
343
+ while (({ done, value } = await reader.read())) {
344
+ if (done) {
345
+ break;
346
+ }
347
+ yield await value;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * This method returns an HTTP handlers transforming
353
+ * HTTP Request instances to binary representations and
354
+ * sending them to a transport layer.
355
+ * Results of these transport ivokations are deserialized
356
+ * as meaningful Response instances and returned back to the caller.
357
+ *
358
+ * The specified `send` method is used to dispatch serialized
359
+ * requests to return the serialized remote results.
360
+ * This transport method accepts and returns objects with
361
+ * the same structure:
362
+ * * `options` - contains serialized request or response parameters
363
+ * * `content` - an async iterator over binary blocks for
364
+ * the request or response
365
+ *
366
+ * @param {*} send transport function accepting serialized requests
367
+ * and returning back serialized responses
368
+ * @returns an async handler accepting standard
369
+ * HTTP `Request` instances and returning `Response` instances
370
+ */
371
+ function newHttpClientStub(send) {
372
+ return async (request) => {
373
+ request = await request;
374
+ const fields = [
375
+ "url",
376
+ "method",
377
+ "mode",
378
+ "credentials",
379
+ "cache",
380
+ "redirect",
381
+ "referrer",
382
+ "referrerPolicy",
383
+ "integrity",
384
+ "keepalive",
385
+ ];
386
+ // request = await request.clone();
387
+ const requestOptions = fields.reduce((options, key) => {
388
+ const val = request[key];
389
+ if (val !== undefined) options[key] = val;
390
+ return options;
391
+ }, {});
392
+ const requestContent = (request.body)
393
+ ? fromReadableStream(request.body)
394
+ : [];
395
+ const result = await send({
396
+ options: requestOptions,
397
+ content: requestContent,
398
+ });
399
+ let responseBody = null;
400
+ let responseOptions = {};
401
+ if (!result) {
402
+ responseOptions = {
403
+ status: 404,
404
+ statusText: "Error 404: Not Found",
405
+ };
406
+ } else {
407
+ responseOptions = result.options;
408
+ const responseContent = result.content;
409
+ if (
410
+ requestOptions.method !== "HEAD" &&
411
+ requestOptions.method !== "OPTIONS"
412
+ ) {
413
+ responseBody = toReadableStream(responseContent);
414
+ } else if (responseContent && responseContent.return) {
415
+ responseContent.return();
416
+ }
417
+ }
418
+ return new Response(responseBody, responseOptions);
419
+ };
420
+ }
421
+
422
+ /**
423
+ * This method returns the server-side call handler transforming
424
+ * serialized requests to HTTP Request instances, delegating
425
+ * their handing to the given `handler` method. This handler
426
+ * should handle the HTTP request and return a valid Response instance.
427
+ * The resulting instance is serialized as an object with the same
428
+ * structure as the initial serialized request:
429
+ * - `options` - contains serialized request or response parameters
430
+ * - `content` - an async iterator over binary blocks for
431
+ * the request or response
432
+ * @param {*} handler this handler method accepts standard HTTP Request
433
+ * and returns back an HTTP Response instance
434
+ * @returns a method deserializing HTTP requests and returning back
435
+ * serialized HTTP responses
436
+ */
437
+ function newHttpServerStub(handler) {
438
+ return async (params) => {
439
+ const {
440
+ options: requestOptions,
441
+ content: requestContent,
442
+ } = await params;
443
+ let requestBody = undefined;
444
+ const { url, mode } = requestOptions;
445
+ delete requestOptions.mode;
446
+ delete requestOptions.url;
447
+ if (
448
+ requestOptions.method !== "GET" &&
449
+ requestOptions.method !== "HEAD" &&
450
+ requestOptions.method !== "OPTIONS"
451
+ ) {
452
+ requestBody = toReadableStream(requestContent);
453
+ requestOptions.duplex = "half";
454
+ } else if (requestContent && requestContent.return) {
455
+ requestContent.return();
456
+ }
457
+ const request = new Request(url, {
458
+ ...requestOptions,
459
+ // duplex: 'full',
460
+ body: requestBody,
461
+ });
462
+ const response = await handler(request);
463
+ const responseOptions = {
464
+ status: response.status,
465
+ statusText: response.statusText,
466
+ headers: Object.fromEntries([...response.headers]),
467
+ };
468
+ const responseContent = await fromReadableStream(await response.body);
469
+ return {
470
+ options: responseOptions,
471
+ content: responseContent,
472
+ };
473
+ };
474
+ }
475
+
476
+ async function* httpToIterator(params) {
477
+ const { options, content } = await params;
478
+ const encoder = new TextEncoder();
479
+ yield encoder.encode(JSON.stringify(options));
480
+ yield* content;
481
+ }
482
+
483
+ async function httpFromIterator(it) {
484
+ it = await it;
485
+ const { done, value } = await it.next();
486
+ let options = {};
487
+ if (!done && value) {
488
+ const decoder = new TextDecoder();
489
+ const str = decoder.decode(value);
490
+ options = JSON.parse(str);
491
+ }
492
+ return {
493
+ options,
494
+ content: it,
495
+ };
496
+ }
497
+
498
+ function handleHttpRequests(communicationPort, handler) {
499
+ const serverStub = newHttpServerStub(handler);
500
+ return handleStreams(communicationPort, (it) => {
501
+ return httpToIterator(serverStub(httpFromIterator(it)));
502
+ });
503
+ }
504
+
505
+ async function sendHttpRequest(communicationPort, request) {
506
+ const clientStub = newHttpClientStub(async (req) => {
507
+ return await httpFromIterator(
508
+ sendStream(communicationPort, httpToIterator(req)),
509
+ );
510
+ });
511
+ return await clientStub(request);
512
+ }
513
+
514
+ function promisifyRequest(request) {
515
+ return new Promise((resolve, reject) => {
516
+ // @ts-ignore - file size hacks
517
+ request.oncomplete = request.onsuccess = () => resolve(request.result);
518
+ // @ts-ignore - file size hacks
519
+ request.onabort = request.onerror = () => reject(request.error);
520
+ });
521
+ }
522
+ function createStore(dbName, storeName) {
523
+ const request = indexedDB.open(dbName);
524
+ request.onupgradeneeded = () => request.result.createObjectStore(storeName);
525
+ const dbp = promisifyRequest(request);
526
+ return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
527
+ }
528
+ let defaultGetStoreFunc;
529
+ function defaultGetStore() {
530
+ if (!defaultGetStoreFunc) {
531
+ defaultGetStoreFunc = createStore('keyval-store', 'keyval');
532
+ }
533
+ return defaultGetStoreFunc;
534
+ }
535
+ /**
536
+ * Get a value by its key.
537
+ *
538
+ * @param key
539
+ * @param customStore Method to get a custom store. Use with caution (see the docs).
540
+ */
541
+ function get(key, customStore = defaultGetStore()) {
542
+ return customStore('readonly', (store) => promisifyRequest(store.get(key)));
543
+ }
544
+ /**
545
+ * Set a value with a key.
546
+ *
547
+ * @param key
548
+ * @param value
549
+ * @param customStore Method to get a custom store. Use with caution (see the docs).
550
+ */
551
+ function set(key, value, customStore = defaultGetStore()) {
552
+ return customStore('readwrite', (store) => {
553
+ store.put(value, key);
554
+ return promisifyRequest(store.transaction);
555
+ });
556
+ }
557
+
558
+ class SwPortHandler {
559
+ constructor(options) {
560
+ this.options = options;
561
+ if (!this.key) throw new Error("Key is not defined.");
562
+ if (!this.scope) throw new Error("Scope is not defined");
563
+ }
564
+
565
+ get key() {
566
+ return this.options.key;
567
+ }
568
+
569
+ get scope() {
570
+ return this.options.scope || new URL("./", this.serviceWorkerUrl).pathname;
571
+ }
572
+
573
+ get rootUrl() {
574
+ return new URL(this.scope, import.meta.url);
575
+ }
576
+
577
+ get serviceWorkerUrl() {
578
+ if (!this._serviceWorkerUrl) {
579
+ let url = this.options.serviceWorkerUrl
580
+ ? new URL(this.options.serviceWorkerUrl)
581
+ : new URL("./index-sw.js", this.rootUrl);
582
+ this._serviceWorkerUrl = url + "";
583
+ }
584
+ return this._serviceWorkerUrl;
585
+ }
586
+
587
+ async _newCommunicationChannel() {
588
+ const messageChannel = new MessageChannel();
589
+ return [messageChannel.port1, messageChannel.port2];
590
+ }
591
+
592
+ async _setCommunicationPort(port) {
593
+ await this.options.bindPort(port);
594
+ }
595
+
596
+ async _updateCommunicationChannel() {
597
+ // console.log("[_updateCommunicationChannel]", this._serviceWorker);
598
+ if (!this._serviceWorker) return;
599
+ const [port1, port2] = await this._newCommunicationChannel();
600
+ await this._setCommunicationPort(port1);
601
+ await callChannel(
602
+ this._serviceWorker,
603
+ "UPDATE_COMMUNICATION_PORT",
604
+ this._getRegistrationInfo(),
605
+ port2,
606
+ );
607
+ }
608
+
609
+ _getRegistrationInfo() {
610
+ return { key: this.key };
611
+ }
612
+
613
+ /**
614
+ * Starts the server.
615
+ */
616
+ async start() {
617
+ return this._registrationPromise = this._registrationPromise ||
618
+ (async () => {
619
+ const [register, cleanup] = newRegistry();
620
+ this._cleanupRegistrations = cleanup;
621
+ register(() => this._cleanupRegistrations = null);
622
+ const registration = await navigator.serviceWorker.register(
623
+ this.serviceWorkerUrl,
624
+ {
625
+ // type: "module",
626
+ scope: this.scope,
627
+ },
628
+ );
629
+ register(() => registration.unregister());
630
+
631
+ // (example: when the service worker is the same but it is waked up/activated after an idle period)
632
+ register(
633
+ handleChannelCalls(
634
+ navigator.serviceWorker,
635
+ "UPDATE_COMMUNICATION_PORT",
636
+ async (event, params, port) => {
637
+ console.log("[UPDATE_COMMUNICATION_PORT]", port);
638
+ this._setCommunicationPort(port);
639
+ return this._getRegistrationInfo();
640
+ },
641
+ ),
642
+ );
643
+
644
+ this._serviceWorker = await getServiceWorkerController();
645
+ await awaitServiceWorkerActivation(this._serviceWorker);
646
+ await this._updateCommunicationChannel();
647
+
648
+ async function getServiceWorkerController() {
649
+ return await new Promise((resolve) => {
650
+ // Resolve right away if this page is already controlled.
651
+ const serviceWorker = navigator.serviceWorker;
652
+ if (serviceWorker.controller) {
653
+ resolve(serviceWorker.controller);
654
+ } else {
655
+ const onChange = () => {
656
+ resolve(serviceWorker.controller);
657
+ serviceWorker.removeEventListener("controllerchange", onChange);
658
+ serviceWorker.oncontrollerchange = null;
659
+ };
660
+ serviceWorker.addEventListener("controllerchange", onChange);
661
+ serviceWorker.oncontrollerchange = onChange;
662
+ }
663
+ });
664
+ }
665
+
666
+ // ------------------------------
667
+ // Await until the service is ready
668
+ async function awaitServiceWorkerActivation(worker) {
669
+ const isActivated = () => worker.state === "activated";
670
+ await new Promise(async (resolve) => {
671
+ if (isActivated()) resolve();
672
+ else {
673
+ worker.onstatechange = () => {
674
+ if (!isActivated()) return;
675
+ worker.onstatechange = null;
676
+ resolve();
677
+ };
678
+ }
679
+ });
680
+ }
681
+ })();
682
+ }
683
+
684
+ /**
685
+ * Stops the server and removes associated handlers.
686
+ */
687
+ async stop() {
688
+ if (this._cleanupRegistrations) this._cleanupRegistrations();
689
+ const registrations = await navigator.serviceWorker.getRegistrations();
690
+ for (let registration of registrations) {
691
+ try {
692
+ await registration.unregister();
693
+ } catch (error) {
694
+ console.log("Service Worker registration failed: ", error);
695
+ }
696
+ }
697
+ }
698
+ }
699
+
700
+ class SwPortDispatcher {
701
+ constructor({ self, log }) {
702
+ this.self = self;
703
+ this.log = log;
704
+ // Contains mapping between path prefixes and clientIds
705
+ this.handlersIndex = new Map();
706
+ }
707
+
708
+ get scope() {
709
+ return new URL("./", import.meta.url) + "";
710
+ }
711
+
712
+ async loadChannelInfo(key) {
713
+ await this._checkActivation();
714
+ let result;
715
+ for (let channelInfo of this.handlersIndex.values()) {
716
+ if (channelInfo.key !== key) continue;
717
+ const clientId = channelInfo.clientId;
718
+ const client = await self.clients.get(clientId);
719
+ if (!client) {
720
+ // Client gone. Remove the corresponding handlers from the index.
721
+ this.handlersIndex.delete(clientId);
722
+ throw Object.assign(new Error("Error 410: Resource Gone"), {
723
+ status: 410,
724
+ });
725
+ }
726
+ result = channelInfo;
727
+ break;
728
+ }
729
+ return result;
730
+ }
731
+
732
+ start() {
733
+ this._cleanup = handleChannelCalls(
734
+ this.self,
735
+ "UPDATE_COMMUNICATION_PORT",
736
+ async (event, channelInfo, port) => {
737
+ const clientId = event.source.id;
738
+ this.log("[UPDATE_COMMUNICATION_PORT]", clientId, channelInfo);
739
+ await this._updateChannelInfo({ ...channelInfo, port, clientId });
740
+ return { ...channelInfo };
741
+ },
742
+ );
743
+ this.self.onmessage = () => {};
744
+
745
+ this.self.addEventListener("install", (event) => {
746
+ this.log("Skip waiting on install.", event);
747
+ this.self.skipWaiting();
748
+ });
749
+
750
+ this.self.addEventListener("activate", async (event) => {
751
+ this.log(
752
+ "Claim control over all clients.",
753
+ event,
754
+ clients,
755
+ this.self.clients,
756
+ );
757
+ event.waitUntil((async () => {
758
+ await this.self.clients.claim();
759
+ await this._checkActivation();
760
+ })());
761
+ });
762
+ }
763
+
764
+ async stop() {
765
+ if (this._cleanup) {
766
+ await this._cleanup();
767
+ this._cleanup = undefined;
768
+ }
769
+ }
770
+
771
+ async _checkActivation() {
772
+ return this._activationPromise = this._activationPromise ||
773
+ this._activate();
774
+ }
775
+
776
+ async _activate() {
777
+ this.log("[checkActivation]");
778
+ let clientsIndex = await this._loadClientIds();
779
+ for (let [clientId, client] of clientsIndex.entries()) {
780
+ const messageChannel = new MessageChannel();
781
+ const port = messageChannel.port1;
782
+ const channelInfo = await callChannel(
783
+ client,
784
+ "UPDATE_COMMUNICATION_PORT",
785
+ {},
786
+ messageChannel.port2,
787
+ );
788
+ await this._updateChannelInfo({ ...channelInfo, port, clientId });
789
+ }
790
+ }
791
+
792
+ async _updateChannelInfo(channelInfo) {
793
+ this.log("[updateClientInfo]", channelInfo);
794
+ this.handlersIndex.set(channelInfo.clientId, channelInfo);
795
+ await this._updateClientIds([]);
796
+ }
797
+
798
+ async _loadClientIds() {
799
+ const clientIds = new Set((await get("clientIds")) || []);
800
+ return await this._updateClientIds(clientIds);
801
+ }
802
+
803
+ async _updateClientIds(clientIds) {
804
+ clientIds = new Set(clientIds);
805
+ for (let clientId of this.handlersIndex.keys()) {
806
+ clientIds.add(clientId);
807
+ }
808
+ const index = new Map();
809
+ for (let clientId of clientIds) {
810
+ const client = await this.self.clients.get(clientId);
811
+ if (client) index.set(clientId, client);
812
+ else this.handlersIndex.delete(clientId);
813
+ }
814
+ await set("clientIds", [...index.keys()].sort());
815
+ return index;
816
+ }
817
+ }
818
+
819
+ class SwHttpAdapter extends SwPortHandler {
820
+ constructor(options) {
821
+ super(options);
822
+ this._handlers = new Map();
823
+ }
824
+
825
+ async _setCommunicationPort(port) {
826
+ if (this._cleanupRequestChannel) {
827
+ this._cleanupRequestChannel();
828
+ }
829
+ this._cleanupRequestChannel = handleHttpRequests(
830
+ port,
831
+ this._handleHttpRequest.bind(this),
832
+ );
833
+ }
834
+
835
+ async _handleHttpRequest(request) {
836
+ const requestUrl = request.url;
837
+ let handler;
838
+ for (let entry of this._handlers.entries()) {
839
+ const [urlPrefix] = entry;
840
+ if (requestUrl.indexOf(urlPrefix) === 0) {
841
+ handler = entry[1];
842
+ break;
843
+ }
844
+ }
845
+ return !handler
846
+ ? new Response(null, {
847
+ status: 404,
848
+ statusMessage: "Error 404: Not found",
849
+ })
850
+ : handler(request);
851
+ }
852
+
853
+ /**
854
+ * Registers the given request handler.
855
+ * @param {string} prefix unique base URL prefix used to register the specified handler
856
+ * @param {*} handler the request handler
857
+ * @return {string} full URL of the resulting endpoint
858
+ */
859
+ async register(prefix, handler) {
860
+ prefix = "./" + (prefix || "").replace(/^[\.\/]+/, "");
861
+ const rootUrl = this.rootUrl;
862
+ const baseUrl = new URL(prefix, rootUrl) + "";
863
+ this._handlers.set(baseUrl, handler);
864
+ return {
865
+ baseUrl,
866
+ prefix,
867
+ async remove() {
868
+ this._handlers.delete(baseUrl);
869
+ },
870
+ };
871
+ }
872
+ }
873
+
874
+ class SwHttpDispatcher extends SwPortDispatcher {
875
+ constructor(options) {
876
+ super(options);
877
+ }
878
+
879
+ start() {
880
+ super.start();
881
+ this.self.addEventListener("fetch", this._handleFetchEvent.bind(this));
882
+ }
883
+
884
+ _handleFetchEvent(event) {
885
+ event.respondWith((async () => {
886
+ const request = event.request;
887
+ try {
888
+ const requestUrl = request.url;
889
+ const rootUrl = this.scope;
890
+ let channelInfo;
891
+ if (requestUrl.indexOf(rootUrl) === 0) {
892
+ const key = requestUrl
893
+ .substring(rootUrl.length)
894
+ .replace(/^\/?([^\/]+).*$/, "$1");
895
+ // console.log(`* rootUrl="${rootUrl}" requestUrl="${requestUrl}" key="${key}"`);
896
+ channelInfo = await this.loadChannelInfo(key);
897
+ }
898
+ if (channelInfo && channelInfo.port) {
899
+ return await sendHttpRequest(channelInfo.port, request);
900
+ } else {
901
+ const newRequest = new Request(requestUrl, {
902
+ method: request.method,
903
+ headers: request.headers,
904
+ body: request.body,
905
+ referrer: request.referrer,
906
+ referrerPolicy: request.referrerPolicy,
907
+ // mode: request.mode,
908
+ credentials: request.credentials,
909
+ cache: request.cache,
910
+ redirect: request.redirect,
911
+ integrity: request.integrity,
912
+ });
913
+ return await fetch(newRequest);
914
+ }
915
+
916
+ // const requestUrl = request.url;
917
+ // let url = requestUrl.replace(
918
+ // /^.*\/[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}(.*)$/,
919
+ // "$1",
920
+ // );
921
+ // if (url !== requestUrl) {
922
+ // url = new URL(
923
+ // "/packages/webrun-http-browser/src" + url,
924
+ // self.location.href,
925
+ // );
926
+ // }
927
+ } catch (error) {
928
+ console.error(error);
929
+ return new Response(null, {
930
+ status: 500,
931
+ statusText: "Error 500: Internal error",
932
+ });
933
+ }
934
+ })());
935
+ }
936
+ }
937
+ function startHttpDispatcher({ self, log }) {
938
+ const dispatcher = new SwHttpDispatcher({ self, log });
939
+ dispatcher.start();
940
+ return () => {
941
+ dispatcher.stop();
942
+ };
943
+ }
944
+
945
+ export { SwHttpAdapter, SwHttpDispatcher, SwPortDispatcher, SwPortHandler, callChannel, deserializeError, fromReadableStream, handleChannelCalls, handleHttpRequests, handleStreams, newChannel, newHttpClientStub, newHttpServerStub, newInvokationChannel, newRegistry, recieveData, sendData, sendHttpRequest, sendStream, serializeError, startHttpDispatcher, toReadableStream };