@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/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # @statewalker/webrun-http-browser
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 statewalker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @statewalker/webrun-http-browser
2
+
3
+ This module simulates HTTP server using Service Workers.
4
+ It allows to develop, test, run and debug server-side code directly in the browser.
5
+ After that the same code can be deployed in Deno / Deno Deploy / Cloudflare / Node JS environments (with adapters).
@@ -0,0 +1,651 @@
1
+ // @statewalker/webrun-http-browser v0.1.0 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
2
+ (function (global, factory) {
3
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
5
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.statewalker = global.statewalker || {}, global.statewalker["webrun-http-browser"] = global.statewalker["webrun-http-browser"] || {})));
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
+ }
21
+
22
+ // See https://github.com/agenjs/agen-utils/blob/main/src/iterator.js
23
+ async function* iterate(init, queue = []) {
24
+ let promise, notify, push = async (error, value, done) => {
25
+ const slot = { error, value, done };
26
+ slot.promise = new Promise(n => slot.notify = n);
27
+ await queue.push(slot);
28
+ notify && notify();
29
+ notify = null;
30
+ return slot.promise;
31
+ };
32
+ const next = (value) => push(undefined, value, false);
33
+ const complete = () => push(undefined, undefined, true);
34
+ const error = err => push(err, undefined, true);
35
+ const unsubscribe = init(Object.assign([next, complete, error], { next, complete, error }));
36
+ let slot;
37
+ try {
38
+ while (true) {
39
+ slot = await queue.shift();
40
+ if (slot) {
41
+ try {
42
+ if (slot.error) { throw slot.error; }
43
+ else if (slot.done) { break; }
44
+ else { yield slot.value; }
45
+ } finally {
46
+ slot.notify(true);
47
+ }
48
+ } else {
49
+ await (promise = notify ? promise : new Promise(n => notify = n));
50
+ }
51
+ }
52
+ } finally {
53
+ notify && notify();
54
+ push = () => false; // Stop pushing in the queue...
55
+ ((typeof unsubscribe === 'function') && (await unsubscribe()));
56
+ while (slot = await queue.shift()) { slot.notify(false); }
57
+ }
58
+ }
59
+
60
+ async function* recieveData(onMessage) {
61
+ yield* iterate((iterator) => {
62
+ return onMessage(async ({ done = true, value, error } = {}) => {
63
+ if (error) {
64
+ await iterator.error(error);
65
+ } else if (done) {
66
+ await iterator.complete();
67
+ } else {
68
+ await iterator.next(value);
69
+ }
70
+ });
71
+ });
72
+ }
73
+
74
+ async function sendData(send, it) {
75
+ let error;
76
+ try {
77
+ for await (let value of it) {
78
+ await send({ done: false, value });
79
+ }
80
+ } catch (err) {
81
+ error = err;
82
+ } finally {
83
+ await send({ done: true, error });
84
+ }
85
+ }
86
+
87
+ async function* sendStream(communicationPort, input, params = {}) {
88
+ const messageChannel = new MessageChannel();
89
+ communicationPort.postMessage({ type: "START_CALL", params }, [
90
+ messageChannel.port2,
91
+ ]);
92
+
93
+ const channel = newChannel(messageChannel.port1);
94
+ try {
95
+ await channel.start();
96
+ channel.sendAll(input);
97
+ yield* channel.recieveAll();
98
+ } finally {
99
+ await channel.close();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * This method creates and returns methods allowing to send and recieve data over the specified port.
105
+ * The main characteristics of the returned methods is that they properly manage backpressure - sender
106
+ * iterators are waiting to send the next value until the previous one is consumed on the other side.
107
+ * @param {*} port
108
+ * @returns an object with the following methods:
109
+ * * `start` - starts the connection over the specified port
110
+ * * `close`- stops all iterators and closes the port
111
+ * * `recieveAll() : AsyncIterator` - returns an AsyncIterator over all values send by the peer
112
+ * * `sendAll(it : AsyncIterator) : Promise<any>` - sends all values defined by the given
113
+ * AsyncIterator to the peer
114
+ */
115
+ function newChannel(port) {
116
+ let listeners = [];
117
+ let iterators = [];
118
+
119
+ const notifyAll = async (data) => {
120
+ for (let listener of listeners) {
121
+ await listener(data);
122
+ }
123
+ };
124
+ const channel = newInvokationChannel(port, notifyAll);
125
+
126
+ const start = () => channel.start();
127
+
128
+ const close = async () => {
129
+ await notifyAll({ done: true });
130
+ [...iterators].forEach((it) => it.return && it.return());
131
+ await channel.close();
132
+ };
133
+
134
+ async function* recieveAll() {
135
+ yield* recieveData((listener) => {
136
+ listeners.push(listener);
137
+ return () => listeners = listeners.filter((l) => l !== listener);
138
+ });
139
+ }
140
+
141
+ async function sendAll(it) {
142
+ await sendData(
143
+ channel.invoke,
144
+ (async function* () {
145
+ try {
146
+ iterators.push(it);
147
+ yield* it;
148
+ } finally {
149
+ iterators = iterators.filter((i) => i !== it);
150
+ }
151
+ })(),
152
+ );
153
+ }
154
+ return {
155
+ start,
156
+ close,
157
+ recieveAll,
158
+ sendAll,
159
+ };
160
+ }
161
+
162
+ function newInvokationChannel(port, handler = () => {
163
+ throw new Error("Handler not implemented");
164
+ }) {
165
+ const MESSAGE_TYPE_REQUEST = "REQUEST";
166
+ const MESSAGE_TYPE_RESPONSE = "RESPONSE";
167
+ const EVENT_MESSAGE = "message";
168
+
169
+ const requests = {};
170
+ let requestCounter = 0;
171
+ const listener = async (event) => {
172
+ const data = event.data || {};
173
+ const { type, callId } = data;
174
+ if (type === MESSAGE_TYPE_REQUEST) {
175
+ try {
176
+ const { request } = data;
177
+ let result = await handler(request, ...(event.ports || []));
178
+ const [response, ...transfers] = Array.isArray(result)
179
+ ? result
180
+ : result !== undefined
181
+ ? [result]
182
+ : [];
183
+ port.postMessage(
184
+ { type: MESSAGE_TYPE_RESPONSE, callId, response },
185
+ transfers,
186
+ );
187
+ } catch (error) {
188
+ port.postMessage({
189
+ type: MESSAGE_TYPE_RESPONSE,
190
+ callId,
191
+ error: serializeError(error),
192
+ });
193
+ }
194
+ } else if (type === MESSAGE_TYPE_RESPONSE) {
195
+ const { callId, response, error } = data;
196
+ const request = requests[callId];
197
+ delete requests[callId];
198
+ if (request) {
199
+ error
200
+ ? request.reject(deserializeError(error))
201
+ : request.resolve(response);
202
+ }
203
+ }
204
+ };
205
+
206
+ const start = async () => {
207
+ port.addEventListener(EVENT_MESSAGE, listener);
208
+ await port.start();
209
+ };
210
+
211
+ const close = async () => {
212
+ port.removeEventListener(EVENT_MESSAGE, listener);
213
+ await port.close();
214
+ };
215
+
216
+ const invoke = async function (request = {}, ...transfers) {
217
+ const callId = ++requestCounter;
218
+ return new Promise((resolve, reject) => {
219
+ try {
220
+ requests[callId] = { resolve, reject };
221
+ port.postMessage(
222
+ { type: MESSAGE_TYPE_REQUEST, callId, request },
223
+ transfers,
224
+ );
225
+ } catch (error) {
226
+ reject(error);
227
+ delete request[callId];
228
+ }
229
+ });
230
+ };
231
+
232
+ return {
233
+ start,
234
+ close,
235
+ invoke,
236
+ };
237
+ }
238
+
239
+ function toReadableStream(it) {
240
+ return new ReadableStream({
241
+ type: "bytes",
242
+ async pull(controller) {
243
+ let handled = false;
244
+ try {
245
+ while (true) {
246
+ const slot = await it.next();
247
+ if (!slot || slot.done) {
248
+ break;
249
+ }
250
+ controller.enqueue(await slot.value);
251
+ }
252
+ } catch (error) {
253
+ handled = true;
254
+ controller.error(error);
255
+ } finally {
256
+ !handled && controller.close();
257
+ }
258
+ },
259
+ });
260
+ }
261
+
262
+ async function* fromReadableStream(stream) {
263
+ const reader = await stream.getReader();
264
+ let done, value;
265
+ while (({ done, value } = await reader.read())) {
266
+ if (done) {
267
+ break;
268
+ }
269
+ yield await value;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * This method returns an HTTP handlers transforming
275
+ * HTTP Request instances to binary representations and
276
+ * sending them to a transport layer.
277
+ * Results of these transport ivokations are deserialized
278
+ * as meaningful Response instances and returned back to the caller.
279
+ *
280
+ * The specified `send` method is used to dispatch serialized
281
+ * requests to return the serialized remote results.
282
+ * This transport method accepts and returns objects with
283
+ * the same structure:
284
+ * * `options` - contains serialized request or response parameters
285
+ * * `content` - an async iterator over binary blocks for
286
+ * the request or response
287
+ *
288
+ * @param {*} send transport function accepting serialized requests
289
+ * and returning back serialized responses
290
+ * @returns an async handler accepting standard
291
+ * HTTP `Request` instances and returning `Response` instances
292
+ */
293
+ function newHttpClientStub(send) {
294
+ return async (request) => {
295
+ request = await request;
296
+ const fields = [
297
+ "url",
298
+ "method",
299
+ "mode",
300
+ "credentials",
301
+ "cache",
302
+ "redirect",
303
+ "referrer",
304
+ "referrerPolicy",
305
+ "integrity",
306
+ "keepalive",
307
+ ];
308
+ // request = await request.clone();
309
+ const requestOptions = fields.reduce((options, key) => {
310
+ const val = request[key];
311
+ if (val !== undefined) options[key] = val;
312
+ return options;
313
+ }, {});
314
+ const requestContent = (request.body)
315
+ ? fromReadableStream(request.body)
316
+ : [];
317
+ const result = await send({
318
+ options: requestOptions,
319
+ content: requestContent,
320
+ });
321
+ let responseBody = null;
322
+ let responseOptions = {};
323
+ if (!result) {
324
+ responseOptions = {
325
+ status: 404,
326
+ statusText: "Error 404: Not Found",
327
+ };
328
+ } else {
329
+ responseOptions = result.options;
330
+ const responseContent = result.content;
331
+ if (
332
+ requestOptions.method !== "HEAD" &&
333
+ requestOptions.method !== "OPTIONS"
334
+ ) {
335
+ responseBody = toReadableStream(responseContent);
336
+ } else if (responseContent && responseContent.return) {
337
+ responseContent.return();
338
+ }
339
+ }
340
+ return new Response(responseBody, responseOptions);
341
+ };
342
+ }
343
+
344
+ async function* httpToIterator(params) {
345
+ const { options, content } = await params;
346
+ const encoder = new TextEncoder();
347
+ yield encoder.encode(JSON.stringify(options));
348
+ yield* content;
349
+ }
350
+
351
+ async function httpFromIterator(it) {
352
+ it = await it;
353
+ const { done, value } = await it.next();
354
+ let options = {};
355
+ if (!done && value) {
356
+ const decoder = new TextDecoder();
357
+ const str = decoder.decode(value);
358
+ options = JSON.parse(str);
359
+ }
360
+ return {
361
+ options,
362
+ content: it,
363
+ };
364
+ }
365
+
366
+ async function sendHttpRequest(communicationPort, request) {
367
+ const clientStub = newHttpClientStub(async (req) => {
368
+ return await httpFromIterator(
369
+ sendStream(communicationPort, httpToIterator(req)),
370
+ );
371
+ });
372
+ return await clientStub(request);
373
+ }
374
+
375
+ async function callChannel(mainChannel, callType, params, ...transfers) {
376
+ return new Promise((resolve, reject) => {
377
+ const channel = new MessageChannel();
378
+ channel.port1.onmessage = (ev) => {
379
+ const { result, error } = ev.data;
380
+ if (error) reject(deserializeError(error));
381
+ else resolve(result);
382
+ };
383
+ mainChannel.postMessage({ type: callType, params }, [
384
+ channel.port2,
385
+ ...transfers,
386
+ ]);
387
+ });
388
+ }
389
+
390
+ function handleChannelCalls(mainChannel, callType, handler) {
391
+ const messageListener = async (event) => {
392
+ if (!event.data || event.data.type !== callType) return;
393
+ const [port, ...transfers] = event.ports;
394
+ let response = {};
395
+ try {
396
+ const params = event.data.params;
397
+ response.result = await handler(event, params, ...transfers);
398
+ } catch (error) {
399
+ response.error = serializeError(error);
400
+ }
401
+ port.postMessage(response);
402
+ };
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
+ }
453
+
454
+ class SwPortDispatcher {
455
+ constructor({ self, log }) {
456
+ this.self = self;
457
+ this.log = log;
458
+ // Contains mapping between path prefixes and clientIds
459
+ this.handlersIndex = new Map();
460
+ }
461
+
462
+ get scope() {
463
+ return new URL("./", (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('index-sw-umd.js', document.baseURI).href))) + "";
464
+ }
465
+
466
+ async loadChannelInfo(key) {
467
+ await this._checkActivation();
468
+ let result;
469
+ for (let channelInfo of this.handlersIndex.values()) {
470
+ if (channelInfo.key !== key) continue;
471
+ const clientId = channelInfo.clientId;
472
+ const client = await self.clients.get(clientId);
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;
482
+ }
483
+ return result;
484
+ }
485
+
486
+ start() {
487
+ this._cleanup = handleChannelCalls(
488
+ this.self,
489
+ "UPDATE_COMMUNICATION_PORT",
490
+ async (event, channelInfo, port) => {
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();
502
+ });
503
+
504
+ this.self.addEventListener("activate", async (event) => {
505
+ this.log(
506
+ "Claim control over all clients.",
507
+ event,
508
+ clients,
509
+ this.self.clients,
510
+ );
511
+ event.waitUntil((async () => {
512
+ await this.self.clients.claim();
513
+ await this._checkActivation();
514
+ })());
515
+ });
516
+ }
517
+
518
+ async stop() {
519
+ if (this._cleanup) {
520
+ await this._cleanup();
521
+ this._cleanup = undefined;
522
+ }
523
+ }
524
+
525
+ async _checkActivation() {
526
+ return this._activationPromise = this._activationPromise ||
527
+ this._activate();
528
+ }
529
+
530
+ async _activate() {
531
+ this.log("[checkActivation]");
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
+ }
545
+
546
+ async _updateChannelInfo(channelInfo) {
547
+ this.log("[updateClientInfo]", channelInfo);
548
+ this.handlersIndex.set(channelInfo.clientId, channelInfo);
549
+ await this._updateClientIds([]);
550
+ }
551
+
552
+ async _loadClientIds() {
553
+ const clientIds = new Set((await get("clientIds")) || []);
554
+ return await this._updateClientIds(clientIds);
555
+ }
556
+
557
+ async _updateClientIds(clientIds) {
558
+ clientIds = new Set(clientIds);
559
+ for (let clientId of this.handlersIndex.keys()) {
560
+ clientIds.add(clientId);
561
+ }
562
+ const index = new Map();
563
+ for (let clientId of clientIds) {
564
+ const client = await this.self.clients.get(clientId);
565
+ if (client) index.set(clientId, client);
566
+ else this.handlersIndex.delete(clientId);
567
+ }
568
+ await set("clientIds", [...index.keys()].sort());
569
+ return index;
570
+ }
571
+ }
572
+
573
+ class SwHttpDispatcher extends SwPortDispatcher {
574
+ constructor(options) {
575
+ super(options);
576
+ }
577
+
578
+ start() {
579
+ super.start();
580
+ this.self.addEventListener("fetch", this._handleFetchEvent.bind(this));
581
+ }
582
+
583
+ _handleFetchEvent(event) {
584
+ event.respondWith((async () => {
585
+ const request = event.request;
586
+ try {
587
+ const requestUrl = request.url;
588
+ const rootUrl = this.scope;
589
+ let channelInfo;
590
+ if (requestUrl.indexOf(rootUrl) === 0) {
591
+ const key = requestUrl
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
+ })());
634
+ }
635
+ }
636
+ function startHttpDispatcher({ self, log }) {
637
+ const dispatcher = new SwHttpDispatcher({ self, log });
638
+ dispatcher.start();
639
+ return () => {
640
+ dispatcher.stop();
641
+ };
642
+ }
643
+
644
+ const result = startHttpDispatcher({
645
+ self,
646
+ log: console.log.bind(console, "[sw]"),
647
+ });
648
+
649
+ exports.result = result;
650
+
651
+ }));
@@ -0,0 +1,2 @@
1
+ // @statewalker/webrun-http-browser v0.1.0 https://github.com/statewalker/statewalker-webrun-http-browser Copyright 2023 Mikhail Kotelnikov
2
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).statewalker=e.statewalker||{},e.statewalker["webrun-http-browser"]=e.statewalker["webrun-http-browser"]||{}))}(this,(function(e){"use strict";function t(e){return Object.assign({message:e,stack:e.stack},e)}function n(e){return"string"==typeof e&&(e={message:e}),Object.assign(new Error(e.message),e)}async function*s(e){yield*async function*(e,t=[]){let n,s,r=async(e,n,r)=>{const a={error:e,value:n,done:r};return a.promise=new Promise((e=>a.notify=e)),await t.push(a),s&&s(),s=null,a.promise};const a=e=>r(void 0,e,!1),o=()=>r(void 0,void 0,!0),i=e=>r(e,void 0,!0),c=e(Object.assign([a,o,i],{next:a,complete:o,error:i}));let l;try{for(;;)if(l=await t.shift(),l)try{if(l.error)throw l.error;if(l.done)break;yield l.value}finally{l.notify(!0)}else await(n=s?n:new Promise((e=>s=e)))}finally{for(s&&s(),r=()=>!1,"function"==typeof c&&await c();l=await t.shift();)l.notify(!1)}}((t=>e((async({done:e=!0,value:n,error:s}={})=>{s?await t.error(s):e?await t.complete():await t.next(n)}))))}async function*r(e,r,a={}){const o=new MessageChannel;e.postMessage({type:"START_CALL",params:a},[o.port2]);const i=function(e){let r=[],a=[];const o=async e=>{for(let t of r)await t(e)},i=function(e,s=(()=>{throw new Error("Handler not implemented")})){const r="REQUEST",a="RESPONSE",o="message",i={};let c=0;const l=async o=>{const c=o.data||{},{type:l,callId:d}=c;if(l===r)try{const{request:t}=c;let n=await s(t,...o.ports||[]);const[r,...i]=Array.isArray(n)?n:void 0!==n?[n]:[];e.postMessage({type:a,callId:d,response:r},i)}catch(n){e.postMessage({type:a,callId:d,error:t(n)})}else if(l===a){const{callId:e,response:t,error:s}=c,r=i[e];delete i[e],r&&(s?r.reject(n(s)):r.resolve(t))}},d=async()=>{e.addEventListener(o,l),await e.start()},u=async()=>{e.removeEventListener(o,l),await e.close()},f=async function(t={},...n){const s=++c;return new Promise(((a,o)=>{try{i[s]={resolve:a,reject:o},e.postMessage({type:r,callId:s,request:t},n)}catch(e){o(e),delete t[s]}}))};return{start:d,close:u,invoke:f}}(e,o),c=()=>i.start(),l=async()=>{await o({done:!0}),[...a].forEach((e=>e.return&&e.return())),await i.close()};async function*d(){yield*s((e=>(r.push(e),()=>r=r.filter((t=>t!==e)))))}async function u(e){await async function(e,t){let n;try{for await(let n of t)await e({done:!1,value:n})}catch(e){n=e}finally{await e({done:!0,error:n})}}(i.invoke,async function*(){try{a.push(e),yield*e}finally{a=a.filter((t=>t!==e))}}())}return{start:c,close:l,recieveAll:d,sendAll:u}}(o.port1);try{await i.start(),i.sendAll(r),yield*i.recieveAll()}finally{await i.close()}}function a(e){return async t=>{t=await t;const n=["url","method","mode","credentials","cache","redirect","referrer","referrerPolicy","integrity","keepalive"].reduce(((e,n)=>{const s=t[n];return void 0!==s&&(e[n]=s),e}),{}),s=t.body?async function*(e){const t=await e.getReader();let n,s;for(;({done:n,value:s}=await t.read())&&!n;)yield await s}(t.body):[],r=await e({options:n,content:s});let a=null,o={};if(r){o=r.options;const e=r.content;"HEAD"!==n.method&&"OPTIONS"!==n.method?(i=e,a=new ReadableStream({type:"bytes",async pull(e){let t=!1;try{for(;;){const t=await i.next();if(!t||t.done)break;e.enqueue(await t.value)}}catch(n){t=!0,e.error(n)}finally{!t&&e.close()}}})):e&&e.return&&e.return()}else o={status:404,statusText:"Error 404: Not Found"};var i;return new Response(a,o)}}async function o(e,t){const n=a((async t=>await async function(e){e=await e;const{done:t,value:n}=await e.next();let s={};if(!t&&n){const e=(new TextDecoder).decode(n);s=JSON.parse(e)}return{options:s,content:e}}(r(e,async function*(e){const{options:t,content:n}=await e,s=new TextEncoder;yield s.encode(JSON.stringify(t)),yield*n}(t)))));return await n(t)}async function i(e,t,s,...r){return new Promise(((a,o)=>{const i=new MessageChannel;i.port1.onmessage=e=>{const{result:t,error:s}=e.data;s?o(n(s)):a(t)},e.postMessage({type:t,params:s},[i.port2,...r])}))}function c(e){return new Promise(((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)}))}let l;function d(){return l||(l=function(e,t){const n=indexedDB.open(e);n.onupgradeneeded=()=>n.result.createObjectStore(t);const s=c(n);return(e,n)=>s.then((s=>n(s.transaction(t,e).objectStore(t))))}("keyval-store","keyval")),l}class u{constructor({self:e,log:t}){this.self=e,this.log=t,this.handlersIndex=new Map}get scope(){return new URL("./","undefined"==typeof document&&"undefined"==typeof location?require("url").pathToFileURL(__filename).href:"undefined"==typeof document?location.href:document.currentScript&&document.currentScript.src||new URL("index-sw-umd.min.js",document.baseURI).href)+""}async loadChannelInfo(e){let t;await this._checkActivation();for(let n of this.handlersIndex.values()){if(n.key!==e)continue;const s=n.clientId;if(!await self.clients.get(s))throw this.handlersIndex.delete(s),Object.assign(new Error("Error 410: Resource Gone"),{status:410});t=n;break}return t}start(){this._cleanup=function(e,n,s){const r=async e=>{if(!e.data||e.data.type!==n)return;const[r,...a]=e.ports;let o={};try{const t=e.data.params;o.result=await s(e,t,...a)}catch(e){o.error=t(e)}r.postMessage(o)};e.addEventListener("message",r);try{e.start&&e.start()}catch(e){}return()=>e.removeEventListener("message",r)}(this.self,"UPDATE_COMMUNICATION_PORT",(async(e,t,n)=>{const s=e.source.id;return this.log("[UPDATE_COMMUNICATION_PORT]",s,t),await this._updateChannelInfo({...t,port:n,clientId:s}),{...t}})),this.self.onmessage=()=>{},this.self.addEventListener("install",(e=>{this.log("Skip waiting on install.",e),this.self.skipWaiting()})),this.self.addEventListener("activate",(async e=>{this.log("Claim control over all clients.",e,clients,this.self.clients),e.waitUntil((async()=>{await this.self.clients.claim(),await this._checkActivation()})())}))}async stop(){this._cleanup&&(await this._cleanup(),this._cleanup=void 0)}async _checkActivation(){return this._activationPromise=this._activationPromise||this._activate()}async _activate(){this.log("[checkActivation]");let e=await this._loadClientIds();for(let[t,n]of e.entries()){const e=new MessageChannel,s=e.port1,r=await i(n,"UPDATE_COMMUNICATION_PORT",{},e.port2);await this._updateChannelInfo({...r,port:s,clientId:t})}}async _updateChannelInfo(e){this.log("[updateClientInfo]",e),this.handlersIndex.set(e.clientId,e),await this._updateClientIds([])}async _loadClientIds(){const e=new Set(await function(e,t=d()){return t("readonly",(t=>c(t.get(e))))}("clientIds")||[]);return await this._updateClientIds(e)}async _updateClientIds(e){e=new Set(e);for(let t of this.handlersIndex.keys())e.add(t);const t=new Map;for(let n of e){const e=await this.self.clients.get(n);e?t.set(n,e):this.handlersIndex.delete(n)}return await function(e,t,n=d()){return n("readwrite",(n=>(n.put(t,e),c(n.transaction))))}("clientIds",[...t.keys()].sort()),t}}class f extends u{constructor(e){super(e)}start(){super.start(),this.self.addEventListener("fetch",this._handleFetchEvent.bind(this))}_handleFetchEvent(e){e.respondWith((async()=>{const t=e.request;try{const e=t.url,n=this.scope;let s;if(0===e.indexOf(n)){const t=e.substring(n.length).replace(/^\/?([^\/]+).*$/,"$1");s=await this.loadChannelInfo(t)}if(s&&s.port)return await o(s.port,t);{const n=new Request(e,{method:t.method,headers:t.headers,body:t.body,referrer:t.referrer,referrerPolicy:t.referrerPolicy,credentials:t.credentials,cache:t.cache,redirect:t.redirect,integrity:t.integrity});return await fetch(n)}}catch(e){return console.error(e),new Response(null,{status:500,statusText:"Error 500: Internal error"})}})())}}const h=function({self:e,log:t}){const n=new f({self:e,log:t});return n.start(),()=>{n.stop()}}({self:self,log:console.log.bind(console,"[sw]")});e.result=h}));