@statewalker/webrun-http-browser 0.3.3 → 0.4.2
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/README.md +406 -51
- package/demo/demo-1.html +109 -194
- package/demo/demo-2.html +118 -299
- package/dist/core/data-calls.d.ts +5 -0
- package/dist/core/data-calls.d.ts.map +1 -0
- package/dist/core/data-channels.d.ts +17 -0
- package/dist/core/data-channels.d.ts.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/message-target.d.ts +2 -0
- package/dist/core/message-target.d.ts.map +1 -0
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/http/http-send-recieve.d.ts +36 -0
- package/dist/http/http-send-recieve.d.ts.map +1 -0
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2670 -807
- package/dist/relay/index-sw.d.ts +7 -0
- package/dist/relay/index-sw.d.ts.map +1 -0
- package/dist/relay/index.d.ts +69 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/split-service-url.d.ts +13 -0
- package/dist/relay/split-service-url.d.ts.map +1 -0
- package/dist/relay-sw.d.ts +2 -0
- package/dist/relay-sw.d.ts.map +1 -0
- package/dist/relay-sw.js +1660 -0
- package/dist/sw/http-sw-dispatcher.d.ts +28 -0
- package/dist/sw/http-sw-dispatcher.d.ts.map +1 -0
- package/dist/sw/index.d.ts +3 -0
- package/dist/sw/index.d.ts.map +1 -0
- package/dist/sw/sw-dispatcher.d.ts +74 -0
- package/dist/sw/sw-dispatcher.d.ts.map +1 -0
- package/dist/sw-worker.d.ts +2 -0
- package/dist/sw-worker.d.ts.map +1 -0
- package/dist/sw-worker.js +1597 -0
- package/dist/sw.d.ts +2 -0
- package/dist/sw.d.ts.map +1 -0
- package/dist/sw.js +1883 -0
- package/package.json +51 -37
- package/public/index.html +61 -97
- package/public/index.js +7 -4
- package/public/sw-worker.js +4 -0
- package/public-relay/heartbeat.js +4 -3
- package/public-relay/relay-sw.js +4 -6
- package/public-relay/relay.html +16 -11
- package/src/core/data-calls.ts +46 -0
- package/src/core/data-channels.ts +262 -0
- package/src/core/index.ts +9 -0
- package/src/core/message-target.ts +8 -0
- package/src/core/registry.ts +54 -0
- package/src/http/http-send-recieve.ts +94 -0
- package/src/http/index.ts +7 -0
- package/src/index.ts +3 -0
- package/src/relay/index-sw.ts +145 -0
- package/src/relay/index.ts +261 -0
- package/src/relay/split-service-url.ts +30 -0
- package/src/relay-sw.ts +7 -0
- package/src/sw/http-sw-dispatcher.ts +122 -0
- package/src/sw/index.ts +2 -0
- package/src/sw/sw-dispatcher.ts +332 -0
- package/src/sw-worker.ts +7 -0
- package/src/sw.ts +1 -0
- package/CHANGELOG.md +0 -1
- package/LICENSE +0 -21
- package/demo/demo-2 copy.html +0 -598
- package/demo/demo-3.html +0 -167
- package/dist/index-sw-umd.js +0 -798
- package/dist/index-sw-umd.min.js +0 -2
- package/dist/index-sw.js +0 -791
- package/dist/index-sw.min.js +0 -2
- package/dist/index-umd.js +0 -853
- package/dist/index-umd.min.js +0 -2
- package/dist/index.min.js +0 -2
- package/index.js +0 -1
- package/src/core/data-calls.js +0 -36
- package/src/core/data-channels.js +0 -192
- package/src/core/data-send-recieve.js +0 -28
- package/src/core/errors.js +0 -13
- package/src/core/index.js +0 -4
- package/src/http/HttpError.js +0 -59
- package/src/http/http-send-recieve.js +0 -40
- package/src/http/http-stubs.js +0 -135
- package/src/http/index.js +0 -3
- package/src/http/readable-streams.js +0 -33
- package/src/index.js +0 -7
- package/src/relay/index-sw.js +0 -216
- package/src/relay/index.js +0 -263
- package/src/relay/splitServiceUrl.js +0 -24
- package/src/sw/http-sw-dispatcher.js +0 -128
- package/src/sw/sw-dispatcher.js +0 -264
package/dist/index-sw-umd.js
DELETED
|
@@ -1,798 +0,0 @@
|
|
|
1
|
-
// @statewalker/webrun-http-browser v0.3.3 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';
|
|
6
|
-
|
|
7
|
-
// See https://github.com/agenjs/agen-utils/blob/main/src/iterator.js
|
|
8
|
-
async function* iterate(init, queue = []) {
|
|
9
|
-
let promise, notify, push = async (error, value, done) => {
|
|
10
|
-
const slot = { error, value, done };
|
|
11
|
-
slot.promise = new Promise(n => slot.notify = n);
|
|
12
|
-
await queue.push(slot);
|
|
13
|
-
notify && notify();
|
|
14
|
-
notify = null;
|
|
15
|
-
return slot.promise;
|
|
16
|
-
};
|
|
17
|
-
const next = (value) => push(undefined, value, false);
|
|
18
|
-
const complete = () => push(undefined, undefined, true);
|
|
19
|
-
const error = err => push(err, undefined, true);
|
|
20
|
-
const unsubscribe = init(Object.assign([next, complete, error], { next, complete, error }));
|
|
21
|
-
let slot;
|
|
22
|
-
try {
|
|
23
|
-
while (true) {
|
|
24
|
-
slot = await queue.shift();
|
|
25
|
-
if (slot) {
|
|
26
|
-
try {
|
|
27
|
-
if (slot.error) { throw slot.error; }
|
|
28
|
-
else if (slot.done) { break; }
|
|
29
|
-
else { yield slot.value; }
|
|
30
|
-
} finally {
|
|
31
|
-
slot.notify(true);
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
await (promise = notify ? promise : new Promise(n => notify = n));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
} finally {
|
|
38
|
-
notify && notify();
|
|
39
|
-
push = () => false; // Stop pushing in the queue...
|
|
40
|
-
((typeof unsubscribe === 'function') && (await unsubscribe()));
|
|
41
|
-
while (slot = await queue.shift()) { slot.notify(false); }
|
|
42
|
-
}
|
|
43
|
-
}
|
|
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
|
-
|
|
165
|
-
async function* recieveData(onMessage) {
|
|
166
|
-
yield* iterate((iterator) => {
|
|
167
|
-
return onMessage(async ({ done = true, value, error } = {}) => {
|
|
168
|
-
if (error) {
|
|
169
|
-
await iterator.error(error);
|
|
170
|
-
} else if (done) {
|
|
171
|
-
await iterator.complete();
|
|
172
|
-
} else {
|
|
173
|
-
await iterator.next(value);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function sendData(send, it) {
|
|
180
|
-
let error;
|
|
181
|
-
try {
|
|
182
|
-
for await (let value of it) {
|
|
183
|
-
await send({ done: false, value });
|
|
184
|
-
}
|
|
185
|
-
} catch (err) {
|
|
186
|
-
error = err;
|
|
187
|
-
} finally {
|
|
188
|
-
await send({ done: true, error });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function* sendStream(communicationPort, input, params = {}) {
|
|
193
|
-
const messageChannel = new MessageChannel();
|
|
194
|
-
communicationPort.postMessage({ type: "START_CALL", params }, [
|
|
195
|
-
messageChannel.port2,
|
|
196
|
-
]);
|
|
197
|
-
|
|
198
|
-
const channel = newChannel(messageChannel.port1);
|
|
199
|
-
try {
|
|
200
|
-
await channel.start();
|
|
201
|
-
channel.sendAll(input);
|
|
202
|
-
yield* channel.recieveAll();
|
|
203
|
-
} finally {
|
|
204
|
-
await channel.close();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* This method creates and returns methods allowing to send and recieve data over the specified port.
|
|
210
|
-
* The main characteristics of the returned methods is that they properly manage backpressure - sender
|
|
211
|
-
* iterators are waiting to send the next value until the previous one is consumed on the other side.
|
|
212
|
-
* @param {*} port
|
|
213
|
-
* @returns an object with the following methods:
|
|
214
|
-
* * `start` - starts the connection over the specified port
|
|
215
|
-
* * `close`- stops all iterators and closes the port
|
|
216
|
-
* * `recieveAll() : AsyncIterator` - returns an AsyncIterator over all values send by the peer
|
|
217
|
-
* * `sendAll(it : AsyncIterator) : Promise<any>` - sends all values defined by the given
|
|
218
|
-
* AsyncIterator to the peer
|
|
219
|
-
*/
|
|
220
|
-
function newChannel(port) {
|
|
221
|
-
let listeners = [];
|
|
222
|
-
let iterators = [];
|
|
223
|
-
|
|
224
|
-
const notifyAll = async (data) => {
|
|
225
|
-
for (let listener of listeners) {
|
|
226
|
-
await listener(data);
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
const channel = newInvokationChannel({
|
|
230
|
-
port,
|
|
231
|
-
handler: notifyAll,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const start = () => channel.start();
|
|
235
|
-
|
|
236
|
-
const close = async () => {
|
|
237
|
-
await notifyAll({ done: true });
|
|
238
|
-
[...iterators].forEach((it) => it.return && it.return());
|
|
239
|
-
await channel.close();
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
async function* recieveAll() {
|
|
243
|
-
yield* recieveData((listener) => {
|
|
244
|
-
listeners.push(listener);
|
|
245
|
-
return () => listeners = listeners.filter((l) => l !== listener);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function sendAll(it) {
|
|
250
|
-
await sendData(
|
|
251
|
-
channel.invoke,
|
|
252
|
-
(async function* () {
|
|
253
|
-
try {
|
|
254
|
-
iterators.push(it);
|
|
255
|
-
yield* it;
|
|
256
|
-
} finally {
|
|
257
|
-
iterators = iterators.filter((i) => i !== it);
|
|
258
|
-
}
|
|
259
|
-
})(),
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
start,
|
|
264
|
-
close,
|
|
265
|
-
recieveAll,
|
|
266
|
-
sendAll,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
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,
|
|
278
|
-
}) {
|
|
279
|
-
const MESSAGE_TYPE_REQUEST = "REQUEST";
|
|
280
|
-
const MESSAGE_TYPE_RESPONSE = "RESPONSE";
|
|
281
|
-
const EVENT_MESSAGE = "message";
|
|
282
|
-
|
|
283
|
-
const requests = {};
|
|
284
|
-
const listener = async (event) => {
|
|
285
|
-
const data = event.data || {};
|
|
286
|
-
const { type, callId } = data;
|
|
287
|
-
if (type === MESSAGE_TYPE_REQUEST) {
|
|
288
|
-
try {
|
|
289
|
-
const { request } = data;
|
|
290
|
-
let result = await handler(request, ...(event.ports || []));
|
|
291
|
-
const [response, ...transfers] = Array.isArray(result)
|
|
292
|
-
? result
|
|
293
|
-
: result !== undefined
|
|
294
|
-
? [result]
|
|
295
|
-
: [];
|
|
296
|
-
port.postMessage(
|
|
297
|
-
{ type: MESSAGE_TYPE_RESPONSE, callId, response },
|
|
298
|
-
transfers,
|
|
299
|
-
);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
port.postMessage({
|
|
302
|
-
type: MESSAGE_TYPE_RESPONSE,
|
|
303
|
-
callId,
|
|
304
|
-
error: serializeError(error),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
} else if (type === MESSAGE_TYPE_RESPONSE) {
|
|
308
|
-
const { callId, response, error } = data;
|
|
309
|
-
const request = requests[callId];
|
|
310
|
-
delete requests[callId];
|
|
311
|
-
if (request) {
|
|
312
|
-
error
|
|
313
|
-
? request.reject(deserializeError(error))
|
|
314
|
-
: request.resolve(response);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const start = async () => {
|
|
320
|
-
try {
|
|
321
|
-
port.addEventListener(EVENT_MESSAGE, listener);
|
|
322
|
-
port.start && await port.start();
|
|
323
|
-
} catch (e) {
|
|
324
|
-
onError(e);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const close = async () => {
|
|
329
|
-
try {
|
|
330
|
-
port.removeEventListener(EVENT_MESSAGE, listener);
|
|
331
|
-
port.close && await port.close();
|
|
332
|
-
} catch (e) {
|
|
333
|
-
onError(e);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const invoke = async function (request = {}, ...transfers) {
|
|
338
|
-
const callId = newCallId();
|
|
339
|
-
return new Promise((resolve, reject) => {
|
|
340
|
-
try {
|
|
341
|
-
requests[callId] = { resolve, reject };
|
|
342
|
-
port.postMessage(
|
|
343
|
-
{ type: MESSAGE_TYPE_REQUEST, callId, request },
|
|
344
|
-
transfers,
|
|
345
|
-
);
|
|
346
|
-
} catch (error) {
|
|
347
|
-
reject(error);
|
|
348
|
-
delete request[callId];
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
start,
|
|
355
|
-
close,
|
|
356
|
-
invoke,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function toReadableStream(it) {
|
|
361
|
-
return new ReadableStream({
|
|
362
|
-
type: "bytes",
|
|
363
|
-
async pull(controller) {
|
|
364
|
-
let handled = false;
|
|
365
|
-
try {
|
|
366
|
-
while (true) {
|
|
367
|
-
const slot = await it.next();
|
|
368
|
-
if (!slot || slot.done) {
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
controller.enqueue(await slot.value);
|
|
372
|
-
}
|
|
373
|
-
} catch (error) {
|
|
374
|
-
handled = true;
|
|
375
|
-
controller.error(error);
|
|
376
|
-
} finally {
|
|
377
|
-
!handled && controller.close();
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async function* fromReadableStream(stream) {
|
|
384
|
-
const reader = await stream.getReader();
|
|
385
|
-
let done, value;
|
|
386
|
-
while (({ done, value } = await reader.read())) {
|
|
387
|
-
if (done) {
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
yield await value;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* This method returns an HTTP handlers transforming
|
|
396
|
-
* HTTP Request instances to binary representations and
|
|
397
|
-
* sending them to a transport layer.
|
|
398
|
-
* Results of these transport ivokations are deserialized
|
|
399
|
-
* as meaningful Response instances and returned back to the caller.
|
|
400
|
-
*
|
|
401
|
-
* The specified `send` method is used to dispatch serialized
|
|
402
|
-
* requests to return the serialized remote results.
|
|
403
|
-
* This transport method accepts and returns objects with
|
|
404
|
-
* the same structure:
|
|
405
|
-
* * `options` - contains serialized request or response parameters
|
|
406
|
-
* * `content` - an async iterator over binary blocks for
|
|
407
|
-
* the request or response
|
|
408
|
-
*
|
|
409
|
-
* @param {*} send transport function accepting serialized requests
|
|
410
|
-
* and returning back serialized responses
|
|
411
|
-
* @returns an async handler accepting standard
|
|
412
|
-
* HTTP `Request` instances and returning `Response` instances
|
|
413
|
-
*/
|
|
414
|
-
function newHttpClientStub(send) {
|
|
415
|
-
return async (request) => {
|
|
416
|
-
request = await request;
|
|
417
|
-
const fields = [
|
|
418
|
-
"url",
|
|
419
|
-
"method",
|
|
420
|
-
"mode",
|
|
421
|
-
"credentials",
|
|
422
|
-
"cache",
|
|
423
|
-
"redirect",
|
|
424
|
-
"referrer",
|
|
425
|
-
"referrerPolicy",
|
|
426
|
-
"integrity",
|
|
427
|
-
"keepalive"
|
|
428
|
-
];
|
|
429
|
-
const headers = [...request.headers].map(([key, value]) => {
|
|
430
|
-
return [key, value]
|
|
431
|
-
});
|
|
432
|
-
const requestOptions = fields.reduce((options, key) => {
|
|
433
|
-
const val = request[key];
|
|
434
|
-
if (val !== undefined) options[key] = val;
|
|
435
|
-
return options;
|
|
436
|
-
}, {
|
|
437
|
-
headers
|
|
438
|
-
});
|
|
439
|
-
const requestContent = (request.body)
|
|
440
|
-
? fromReadableStream(request.body)
|
|
441
|
-
: [];
|
|
442
|
-
const result = await send({
|
|
443
|
-
options: requestOptions,
|
|
444
|
-
content: requestContent,
|
|
445
|
-
});
|
|
446
|
-
let responseBody = null;
|
|
447
|
-
let responseOptions = {};
|
|
448
|
-
if (!result) {
|
|
449
|
-
responseOptions = {
|
|
450
|
-
status: 404,
|
|
451
|
-
statusText: "Error 404: Not Found",
|
|
452
|
-
};
|
|
453
|
-
} else {
|
|
454
|
-
responseOptions = result.options;
|
|
455
|
-
const responseContent = result.content;
|
|
456
|
-
if (
|
|
457
|
-
requestOptions.method !== "HEAD" &&
|
|
458
|
-
requestOptions.method !== "OPTIONS"
|
|
459
|
-
) {
|
|
460
|
-
responseBody = toReadableStream(responseContent);
|
|
461
|
-
} else if (responseContent && responseContent.return) {
|
|
462
|
-
responseContent.return();
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return new Response(responseBody, responseOptions);
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async function* httpToIterator(params) {
|
|
470
|
-
const { options, content } = await params;
|
|
471
|
-
const encoder = new TextEncoder();
|
|
472
|
-
yield encoder.encode(JSON.stringify(options));
|
|
473
|
-
yield* content;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async function httpFromIterator(it) {
|
|
477
|
-
it = await it;
|
|
478
|
-
const { done, value } = await it.next();
|
|
479
|
-
let options = {};
|
|
480
|
-
if (!done && value) {
|
|
481
|
-
const decoder = new TextDecoder();
|
|
482
|
-
const str = decoder.decode(value);
|
|
483
|
-
options = JSON.parse(str);
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
options,
|
|
487
|
-
content: it,
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function sendHttpRequest(communicationPort, request) {
|
|
492
|
-
const clientStub = newHttpClientStub(async (req) => {
|
|
493
|
-
return await httpFromIterator(
|
|
494
|
-
sendStream(communicationPort, httpToIterator(req)),
|
|
495
|
-
);
|
|
496
|
-
});
|
|
497
|
-
return await clientStub(request);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function splitServiceUrl(url, separator = "~") {
|
|
501
|
-
let key = "";
|
|
502
|
-
let path = "";
|
|
503
|
-
let str = url + "";
|
|
504
|
-
const idx = str.indexOf(separator);
|
|
505
|
-
let baseUrl = "";
|
|
506
|
-
if (idx >= 0) {
|
|
507
|
-
baseUrl = str.substring(0, idx + separator.length);
|
|
508
|
-
str.substring(idx + separator.length)
|
|
509
|
-
.replace(/^([^\/]+)/, (match, $1) => {
|
|
510
|
-
baseUrl += match;
|
|
511
|
-
if (baseUrl.length < str.length) baseUrl += "/";
|
|
512
|
-
key = $1;
|
|
513
|
-
path = str.substring(baseUrl.length);
|
|
514
|
-
return "";
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
return {
|
|
518
|
-
url,
|
|
519
|
-
key,
|
|
520
|
-
baseUrl,
|
|
521
|
-
path,
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
class HttpError extends Error {
|
|
526
|
-
constructor(options) {
|
|
527
|
-
super(options.message);
|
|
528
|
-
this.status = options.status;
|
|
529
|
-
this.statusText = options.statusText;
|
|
530
|
-
}
|
|
531
|
-
getResponseOptions(options = {}) {
|
|
532
|
-
return Object.assign(this.toJson(), options);
|
|
533
|
-
}
|
|
534
|
-
toJson() {
|
|
535
|
-
return {
|
|
536
|
-
status: this.status,
|
|
537
|
-
statusText: this.statusText,
|
|
538
|
-
message: this.message,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
static fromError(error) {
|
|
543
|
-
if (!(error instanceof HttpError)) {
|
|
544
|
-
error = new HttpError(Object.assign({
|
|
545
|
-
status: 500,
|
|
546
|
-
statusText: "Bad Request",
|
|
547
|
-
reason: error.message,
|
|
548
|
-
}));
|
|
549
|
-
}
|
|
550
|
-
return error;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
static errorResourceNotFound(options = {}) {
|
|
554
|
-
return new HttpError({
|
|
555
|
-
status: 404,
|
|
556
|
-
statusText: "Error 404: Resource not found",
|
|
557
|
-
...options,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
static errorForbidden(options = {}) {
|
|
561
|
-
return new HttpError({
|
|
562
|
-
status: 403,
|
|
563
|
-
statusText: "Error 403: Forbidden",
|
|
564
|
-
...options,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
static errorResourceGone(options = {}) {
|
|
569
|
-
return new HttpError({
|
|
570
|
-
status: 410,
|
|
571
|
-
statusText: "Error 410: Resource Gone",
|
|
572
|
-
...options,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
static errorInternalError(options) {
|
|
577
|
-
return new HttpError({
|
|
578
|
-
...options,
|
|
579
|
-
status: 500,
|
|
580
|
-
statusText: "Error 500: Internal error",
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function startRelayServiceWorker(self) {
|
|
586
|
-
const [register, clear] = newRegistry();
|
|
587
|
-
|
|
588
|
-
if (typeof self.skipWaiting === "function") {
|
|
589
|
-
console.log("self.skipWaiting() is supported.");
|
|
590
|
-
self.addEventListener("install", (e) => {
|
|
591
|
-
// See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-skipwaiting
|
|
592
|
-
e.waitUntil(self.skipWaiting());
|
|
593
|
-
});
|
|
594
|
-
} else {
|
|
595
|
-
console.log("self.skipWaiting() is not supported.");
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (self.clients && (typeof self.clients.claim === "function")) {
|
|
599
|
-
console.log("self.clients.claim() is supported.");
|
|
600
|
-
self.addEventListener("activate", (e) => {
|
|
601
|
-
// See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#clients-claim-method
|
|
602
|
-
e.waitUntil(self.clients.claim());
|
|
603
|
-
});
|
|
604
|
-
} else {
|
|
605
|
-
console.log("self.clients.claim() is not supported.");
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const clientsRegistry = newClientsRegistry({
|
|
609
|
-
self,
|
|
610
|
-
get,
|
|
611
|
-
set,
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
// This listener handles the following message types:
|
|
615
|
-
// * registration: registers new clients as service providers
|
|
616
|
-
// * handles service calls rounting:
|
|
617
|
-
// - it gets the clientKey defined in the call
|
|
618
|
-
// - it uses the serviceKey to get the service-specifc channel
|
|
619
|
-
// - it creates a new message channel and sent its ports to peers
|
|
620
|
-
|
|
621
|
-
register(handleChannelCalls(self, "REGISTER", async (event, data) => {
|
|
622
|
-
return await clientsRegistry.addClient(data.key, event.source);
|
|
623
|
-
}));
|
|
624
|
-
register(handleChannelCalls(self, "UNREGISTER", async (event, data) => {
|
|
625
|
-
return await clientsRegistry.removeClient(data.key);
|
|
626
|
-
}));
|
|
627
|
-
|
|
628
|
-
// Transfers all parameters (and ports) to the target client.
|
|
629
|
-
register(handleChannelCalls(self, "CONNECT", async (event, data, port) => {
|
|
630
|
-
const { key } = data;
|
|
631
|
-
const client = await clientsRegistry.getClient(key);
|
|
632
|
-
if (!client) {
|
|
633
|
-
throw new Error(`Target client was not found. Target key: "${key}".`);
|
|
634
|
-
}
|
|
635
|
-
return await callChannel(client, "CONNECT", data, port);
|
|
636
|
-
}));
|
|
637
|
-
|
|
638
|
-
const fetchListener = (event) => {
|
|
639
|
-
const request = event.request;
|
|
640
|
-
const params = splitServiceUrl(request.url);
|
|
641
|
-
const { key } = params;
|
|
642
|
-
if (key) {
|
|
643
|
-
const handler = async () => {
|
|
644
|
-
try {
|
|
645
|
-
const channel = new MessageChannel();
|
|
646
|
-
const client = await clientsRegistry.getClient(key);
|
|
647
|
-
if (!client) {
|
|
648
|
-
throw HttpError.errorResourceGone(params);
|
|
649
|
-
}
|
|
650
|
-
const data = {
|
|
651
|
-
type: "http",
|
|
652
|
-
key,
|
|
653
|
-
};
|
|
654
|
-
if (!await callChannel(client, "CONNECT", data, channel.port2)) {
|
|
655
|
-
throw HttpError.errorForbidden(params);
|
|
656
|
-
}
|
|
657
|
-
return await sendHttpRequest(channel.port1, request);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
error = HttpError.fromError(error);
|
|
660
|
-
const options = error.getResponseOptions(params);
|
|
661
|
-
return new Response(JSON.stringify(options), {
|
|
662
|
-
...options,
|
|
663
|
-
headers: {
|
|
664
|
-
"Content-Type": "text/javascript",
|
|
665
|
-
},
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
event.respondWith(handler());
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
self.addEventListener("fetch", fetchListener);
|
|
673
|
-
register(() => self.removeEventListener("fetch", fetchListener));
|
|
674
|
-
|
|
675
|
-
return clear;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// --------------------------------------------
|
|
679
|
-
// errors.js
|
|
680
|
-
|
|
681
|
-
// --------------------------------------------
|
|
682
|
-
|
|
683
|
-
function newClientsRegistry({
|
|
684
|
-
self,
|
|
685
|
-
key = "clientsIds",
|
|
686
|
-
get,
|
|
687
|
-
set,
|
|
688
|
-
}) {
|
|
689
|
-
let _index;
|
|
690
|
-
async function loadClientsIndex() {
|
|
691
|
-
if (!_index) {
|
|
692
|
-
const entries = (await get(key)) || [];
|
|
693
|
-
_index = Object.fromEntries(entries);
|
|
694
|
-
}
|
|
695
|
-
return _index;
|
|
696
|
-
}
|
|
697
|
-
async function storeClientsIndex() {
|
|
698
|
-
const index = await loadClientsIndex();
|
|
699
|
-
const entries = Object.entries(index);
|
|
700
|
-
await set(key, entries);
|
|
701
|
-
return index;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
async function addClient(clientKey, client) {
|
|
705
|
-
const index = await loadClientsIndex();
|
|
706
|
-
const clientId = client.id;
|
|
707
|
-
let result = false;
|
|
708
|
-
if (!(clientId in index)) {
|
|
709
|
-
index[clientKey] = clientId;
|
|
710
|
-
await storeClientsIndex();
|
|
711
|
-
result = true;
|
|
712
|
-
}
|
|
713
|
-
return result;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
async function removeClient(clientKey) {
|
|
717
|
-
const index = await loadClientsIndex();
|
|
718
|
-
let result = false;
|
|
719
|
-
if (clientKey in index) {
|
|
720
|
-
delete index[clientKey];
|
|
721
|
-
await storeClientsIndex();
|
|
722
|
-
result = true;
|
|
723
|
-
}
|
|
724
|
-
return result;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
async function getClient(clientKey) {
|
|
728
|
-
const index = await loadClientsIndex();
|
|
729
|
-
const clientId = index[clientKey];
|
|
730
|
-
const client = await self.clients.get(clientId);
|
|
731
|
-
if (!client) {
|
|
732
|
-
delete index[clientKey];
|
|
733
|
-
await storeClientsIndex();
|
|
734
|
-
}
|
|
735
|
-
return client;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return {
|
|
739
|
-
getClient,
|
|
740
|
-
addClient,
|
|
741
|
-
removeClient,
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
// ----------------------------------------------------
|
|
745
|
-
// Content of the "idb-keyval" package
|
|
746
|
-
// import { get, set } from "idb-keyval";
|
|
747
|
-
|
|
748
|
-
// function promisifyRequest(request) {
|
|
749
|
-
// return new Promise((resolve, reject) => {
|
|
750
|
-
// // @ts-ignore - file size hacks
|
|
751
|
-
// request.oncomplete = request.onsuccess = () => resolve(request.result);
|
|
752
|
-
// // @ts-ignore - file size hacks
|
|
753
|
-
// request.onabort = request.onerror = () => reject(request.error);
|
|
754
|
-
// });
|
|
755
|
-
// }
|
|
756
|
-
// function createStore(dbName, storeName) {
|
|
757
|
-
// const request = indexedDB.open(dbName);
|
|
758
|
-
// request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
|
759
|
-
// const dbp = promisifyRequest(request);
|
|
760
|
-
// return (txMode, callback) =>
|
|
761
|
-
// dbp.then((db) =>
|
|
762
|
-
// callback(db.transaction(storeName, txMode).objectStore(storeName))
|
|
763
|
-
// );
|
|
764
|
-
// }
|
|
765
|
-
// let defaultGetStoreFunc;
|
|
766
|
-
// function defaultGetStore() {
|
|
767
|
-
// if (!defaultGetStoreFunc) {
|
|
768
|
-
// defaultGetStoreFunc = createStore("keyval-store", "keyval");
|
|
769
|
-
// }
|
|
770
|
-
// return defaultGetStoreFunc;
|
|
771
|
-
// }
|
|
772
|
-
// /**
|
|
773
|
-
// * Get a value by its key.
|
|
774
|
-
// *
|
|
775
|
-
// * @param key
|
|
776
|
-
// * @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
777
|
-
// */
|
|
778
|
-
// function get(key, customStore = defaultGetStore()) {
|
|
779
|
-
// return customStore("readonly", (store) => promisifyRequest(store.get(key)));
|
|
780
|
-
// }
|
|
781
|
-
// /**
|
|
782
|
-
// * Set a value with a key.
|
|
783
|
-
// *
|
|
784
|
-
// * @param key
|
|
785
|
-
// * @param value
|
|
786
|
-
// * @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
787
|
-
// */
|
|
788
|
-
// function set(key, value, customStore = defaultGetStore()) {
|
|
789
|
-
// return customStore("readwrite", (store) => {
|
|
790
|
-
// store.put(value, key);
|
|
791
|
-
// return promisifyRequest(store.transaction);
|
|
792
|
-
// });
|
|
793
|
-
// }
|
|
794
|
-
|
|
795
|
-
startRelayServiceWorker(self);
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
}));
|