@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 +1 -0
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/index-sw-umd.js +651 -0
- package/dist/index-sw-umd.min.js +2 -0
- package/dist/index-sw.js +643 -0
- package/dist/index-sw.min.js +2 -0
- package/dist/index-umd.js +974 -0
- package/dist/index-umd.min.js +2 -0
- package/dist/index.js +945 -0
- package/dist/index.min.js +2 -0
- package/index.js +1 -0
- package/package.json +55 -0
- package/public/abc.txt +1 -0
- package/public/index-service-worker.js +1 -0
- package/public/index.html +132 -0
- package/public/index.js +9 -0
- package/public-relay/heartbeat.js +30 -0
- package/public-relay/relay-lib.js +188 -0
- package/public-relay/relay-sw.js +343 -0
- package/public-relay/relay.html +33 -0
- package/src/data-calls.js +36 -0
- package/src/data-channels.js +176 -0
- package/src/data-send-recieve.js +28 -0
- package/src/errors.js +13 -0
- package/src/http-send-recieve.js +40 -0
- package/src/http-stubs.js +126 -0
- package/src/http-sw-dispatcher.js +128 -0
- package/src/index.js +12 -0
- package/src/readable-streams.js +33 -0
- package/src/sw-dispatcher.js +264 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fromReadableStream, toReadableStream } from "./readable-streams.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This method returns an HTTP handlers transforming
|
|
5
|
+
* HTTP Request instances to binary representations and
|
|
6
|
+
* sending them to a transport layer.
|
|
7
|
+
* Results of these transport ivokations are deserialized
|
|
8
|
+
* as meaningful Response instances and returned back to the caller.
|
|
9
|
+
*
|
|
10
|
+
* The specified `send` method is used to dispatch serialized
|
|
11
|
+
* requests to return the serialized remote results.
|
|
12
|
+
* This transport method accepts and returns objects with
|
|
13
|
+
* the same structure:
|
|
14
|
+
* * `options` - contains serialized request or response parameters
|
|
15
|
+
* * `content` - an async iterator over binary blocks for
|
|
16
|
+
* the request or response
|
|
17
|
+
*
|
|
18
|
+
* @param {*} send transport function accepting serialized requests
|
|
19
|
+
* and returning back serialized responses
|
|
20
|
+
* @returns an async handler accepting standard
|
|
21
|
+
* HTTP `Request` instances and returning `Response` instances
|
|
22
|
+
*/
|
|
23
|
+
export function newHttpClientStub(send) {
|
|
24
|
+
return async (request) => {
|
|
25
|
+
request = await request;
|
|
26
|
+
const fields = [
|
|
27
|
+
"url",
|
|
28
|
+
"method",
|
|
29
|
+
"mode",
|
|
30
|
+
"credentials",
|
|
31
|
+
"cache",
|
|
32
|
+
"redirect",
|
|
33
|
+
"referrer",
|
|
34
|
+
"referrerPolicy",
|
|
35
|
+
"integrity",
|
|
36
|
+
"keepalive",
|
|
37
|
+
];
|
|
38
|
+
// request = await request.clone();
|
|
39
|
+
const requestOptions = fields.reduce((options, key) => {
|
|
40
|
+
const val = request[key];
|
|
41
|
+
if (val !== undefined) options[key] = val;
|
|
42
|
+
return options;
|
|
43
|
+
}, {});
|
|
44
|
+
const requestContent = (request.body)
|
|
45
|
+
? fromReadableStream(request.body)
|
|
46
|
+
: [];
|
|
47
|
+
const result = await send({
|
|
48
|
+
options: requestOptions,
|
|
49
|
+
content: requestContent,
|
|
50
|
+
});
|
|
51
|
+
let responseBody = null;
|
|
52
|
+
let responseOptions = {};
|
|
53
|
+
if (!result) {
|
|
54
|
+
responseOptions = {
|
|
55
|
+
status: 404,
|
|
56
|
+
statusText: "Error 404: Not Found",
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
responseOptions = result.options;
|
|
60
|
+
const responseContent = result.content;
|
|
61
|
+
if (
|
|
62
|
+
requestOptions.method !== "HEAD" &&
|
|
63
|
+
requestOptions.method !== "OPTIONS"
|
|
64
|
+
) {
|
|
65
|
+
responseBody = toReadableStream(responseContent);
|
|
66
|
+
} else if (responseContent && responseContent.return) {
|
|
67
|
+
responseContent.return();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return new Response(responseBody, responseOptions);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* This method returns the server-side call handler transforming
|
|
76
|
+
* serialized requests to HTTP Request instances, delegating
|
|
77
|
+
* their handing to the given `handler` method. This handler
|
|
78
|
+
* should handle the HTTP request and return a valid Response instance.
|
|
79
|
+
* The resulting instance is serialized as an object with the same
|
|
80
|
+
* structure as the initial serialized request:
|
|
81
|
+
* - `options` - contains serialized request or response parameters
|
|
82
|
+
* - `content` - an async iterator over binary blocks for
|
|
83
|
+
* the request or response
|
|
84
|
+
* @param {*} handler this handler method accepts standard HTTP Request
|
|
85
|
+
* and returns back an HTTP Response instance
|
|
86
|
+
* @returns a method deserializing HTTP requests and returning back
|
|
87
|
+
* serialized HTTP responses
|
|
88
|
+
*/
|
|
89
|
+
export function newHttpServerStub(handler) {
|
|
90
|
+
return async (params) => {
|
|
91
|
+
const {
|
|
92
|
+
options: requestOptions,
|
|
93
|
+
content: requestContent,
|
|
94
|
+
} = await params;
|
|
95
|
+
let requestBody = undefined;
|
|
96
|
+
const { url, mode } = requestOptions;
|
|
97
|
+
delete requestOptions.mode;
|
|
98
|
+
delete requestOptions.url;
|
|
99
|
+
if (
|
|
100
|
+
requestOptions.method !== "GET" &&
|
|
101
|
+
requestOptions.method !== "HEAD" &&
|
|
102
|
+
requestOptions.method !== "OPTIONS"
|
|
103
|
+
) {
|
|
104
|
+
requestBody = toReadableStream(requestContent);
|
|
105
|
+
requestOptions.duplex = "half";
|
|
106
|
+
} else if (requestContent && requestContent.return) {
|
|
107
|
+
requestContent.return();
|
|
108
|
+
}
|
|
109
|
+
const request = new Request(url, {
|
|
110
|
+
...requestOptions,
|
|
111
|
+
// duplex: 'full',
|
|
112
|
+
body: requestBody,
|
|
113
|
+
});
|
|
114
|
+
const response = await handler(request);
|
|
115
|
+
const responseOptions = {
|
|
116
|
+
status: response.status,
|
|
117
|
+
statusText: response.statusText,
|
|
118
|
+
headers: Object.fromEntries([...response.headers]),
|
|
119
|
+
};
|
|
120
|
+
const responseContent = await fromReadableStream(await response.body);
|
|
121
|
+
return {
|
|
122
|
+
options: responseOptions,
|
|
123
|
+
content: responseContent,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { handleHttpRequests, sendHttpRequest } from "./http-send-recieve.js";
|
|
2
|
+
import { SwPortDispatcher, SwPortHandler } from "./sw-dispatcher.js";
|
|
3
|
+
|
|
4
|
+
export class SwHttpAdapter extends SwPortHandler {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
super(options);
|
|
7
|
+
this._handlers = new Map();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async _setCommunicationPort(port) {
|
|
11
|
+
if (this._cleanupRequestChannel) {
|
|
12
|
+
this._cleanupRequestChannel();
|
|
13
|
+
}
|
|
14
|
+
this._cleanupRequestChannel = handleHttpRequests(
|
|
15
|
+
port,
|
|
16
|
+
this._handleHttpRequest.bind(this),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async _handleHttpRequest(request) {
|
|
21
|
+
const requestUrl = request.url;
|
|
22
|
+
let handler;
|
|
23
|
+
for (let entry of this._handlers.entries()) {
|
|
24
|
+
const [urlPrefix] = entry;
|
|
25
|
+
if (requestUrl.indexOf(urlPrefix) === 0) {
|
|
26
|
+
handler = entry[1];
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return !handler
|
|
31
|
+
? new Response(null, {
|
|
32
|
+
status: 404,
|
|
33
|
+
statusMessage: "Error 404: Not found",
|
|
34
|
+
})
|
|
35
|
+
: handler(request);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Registers the given request handler.
|
|
40
|
+
* @param {string} prefix unique base URL prefix used to register the specified handler
|
|
41
|
+
* @param {*} handler the request handler
|
|
42
|
+
* @return {string} full URL of the resulting endpoint
|
|
43
|
+
*/
|
|
44
|
+
async register(prefix, handler) {
|
|
45
|
+
prefix = "./" + (prefix || "").replace(/^[\.\/]+/, "");
|
|
46
|
+
const rootUrl = this.rootUrl;
|
|
47
|
+
const baseUrl = new URL(prefix, rootUrl) + "";
|
|
48
|
+
this._handlers.set(baseUrl, handler);
|
|
49
|
+
return {
|
|
50
|
+
baseUrl,
|
|
51
|
+
prefix,
|
|
52
|
+
async remove() {
|
|
53
|
+
this._handlers.delete(baseUrl);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class SwHttpDispatcher extends SwPortDispatcher {
|
|
60
|
+
constructor(options) {
|
|
61
|
+
super(options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
start() {
|
|
65
|
+
super.start();
|
|
66
|
+
this.self.addEventListener("fetch", this._handleFetchEvent.bind(this));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_handleFetchEvent(event) {
|
|
70
|
+
event.respondWith((async () => {
|
|
71
|
+
const request = event.request;
|
|
72
|
+
try {
|
|
73
|
+
const requestUrl = request.url;
|
|
74
|
+
const rootUrl = this.scope;
|
|
75
|
+
let channelInfo;
|
|
76
|
+
if (requestUrl.indexOf(rootUrl) === 0) {
|
|
77
|
+
const key = requestUrl
|
|
78
|
+
.substring(rootUrl.length)
|
|
79
|
+
.replace(/^\/?([^\/]+).*$/, "$1");
|
|
80
|
+
// console.log(`* rootUrl="${rootUrl}" requestUrl="${requestUrl}" key="${key}"`);
|
|
81
|
+
channelInfo = await this.loadChannelInfo(key);
|
|
82
|
+
}
|
|
83
|
+
if (channelInfo && channelInfo.port) {
|
|
84
|
+
return await sendHttpRequest(channelInfo.port, request);
|
|
85
|
+
} else {
|
|
86
|
+
const newRequest = new Request(requestUrl, {
|
|
87
|
+
method: request.method,
|
|
88
|
+
headers: request.headers,
|
|
89
|
+
body: request.body,
|
|
90
|
+
referrer: request.referrer,
|
|
91
|
+
referrerPolicy: request.referrerPolicy,
|
|
92
|
+
// mode: request.mode,
|
|
93
|
+
credentials: request.credentials,
|
|
94
|
+
cache: request.cache,
|
|
95
|
+
redirect: request.redirect,
|
|
96
|
+
integrity: request.integrity,
|
|
97
|
+
});
|
|
98
|
+
return await fetch(newRequest);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// const requestUrl = request.url;
|
|
102
|
+
// let url = requestUrl.replace(
|
|
103
|
+
// /^.*\/[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}(.*)$/,
|
|
104
|
+
// "$1",
|
|
105
|
+
// );
|
|
106
|
+
// if (url !== requestUrl) {
|
|
107
|
+
// url = new URL(
|
|
108
|
+
// "/packages/webrun-http-browser/src" + url,
|
|
109
|
+
// self.location.href,
|
|
110
|
+
// );
|
|
111
|
+
// }
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(error);
|
|
114
|
+
return new Response(null, {
|
|
115
|
+
status: 500,
|
|
116
|
+
statusText: "Error 500: Internal error",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
})());
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function startHttpDispatcher({ self, log }) {
|
|
123
|
+
const dispatcher = new SwHttpDispatcher({ self, log });
|
|
124
|
+
dispatcher.start();
|
|
125
|
+
return () => {
|
|
126
|
+
dispatcher.stop();
|
|
127
|
+
};
|
|
128
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./data-calls.js";
|
|
2
|
+
export * from "./data-channels.js";
|
|
3
|
+
export * from "./data-send-recieve.js";
|
|
4
|
+
export * from "./errors.js";
|
|
5
|
+
export * from "./http-send-recieve.js";
|
|
6
|
+
export * from "./http-stubs.js";
|
|
7
|
+
export * from "./http-sw-dispatcher.js";
|
|
8
|
+
export * from "./readable-streams.js";
|
|
9
|
+
export * from "./sw-dispatcher.js";
|
|
10
|
+
|
|
11
|
+
import { newRegistry } from "@statewalker/utils";
|
|
12
|
+
export { newRegistry };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function toReadableStream(it) {
|
|
2
|
+
return new ReadableStream({
|
|
3
|
+
type: "bytes",
|
|
4
|
+
async pull(controller) {
|
|
5
|
+
let handled = false;
|
|
6
|
+
try {
|
|
7
|
+
while (true) {
|
|
8
|
+
const slot = await it.next();
|
|
9
|
+
if (!slot || slot.done) {
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
controller.enqueue(await slot.value);
|
|
13
|
+
}
|
|
14
|
+
} catch (error) {
|
|
15
|
+
handled = true;
|
|
16
|
+
controller.error(error);
|
|
17
|
+
} finally {
|
|
18
|
+
!handled && controller.close();
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function* fromReadableStream(stream) {
|
|
25
|
+
const reader = await stream.getReader();
|
|
26
|
+
let done, value;
|
|
27
|
+
while (({ done, value } = await reader.read())) {
|
|
28
|
+
if (done) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
yield await value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { newRegistry } from "@statewalker/utils";
|
|
2
|
+
import { callChannel, handleChannelCalls } from "./data-calls.js";
|
|
3
|
+
import { get, set } from "idb-keyval";
|
|
4
|
+
|
|
5
|
+
export class SwPortHandler {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
if (!this.key) throw new Error("Key is not defined.");
|
|
9
|
+
if (!this.scope) throw new Error("Scope is not defined");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get key() {
|
|
13
|
+
return this.options.key;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get scope() {
|
|
17
|
+
return this.options.scope || new URL("./", this.serviceWorkerUrl).pathname;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get rootUrl() {
|
|
21
|
+
return new URL(this.scope, import.meta.url);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get serviceWorkerUrl() {
|
|
25
|
+
if (!this._serviceWorkerUrl) {
|
|
26
|
+
let url = this.options.serviceWorkerUrl
|
|
27
|
+
? new URL(this.options.serviceWorkerUrl)
|
|
28
|
+
: new URL("./index-sw.js", this.rootUrl);
|
|
29
|
+
this._serviceWorkerUrl = url + "";
|
|
30
|
+
}
|
|
31
|
+
return this._serviceWorkerUrl;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async _newCommunicationChannel() {
|
|
35
|
+
const messageChannel = new MessageChannel();
|
|
36
|
+
return [messageChannel.port1, messageChannel.port2];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async _setCommunicationPort(port) {
|
|
40
|
+
await this.options.bindPort(port);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async _updateCommunicationChannel() {
|
|
44
|
+
// console.log("[_updateCommunicationChannel]", this._serviceWorker);
|
|
45
|
+
if (!this._serviceWorker) return;
|
|
46
|
+
const [port1, port2] = await this._newCommunicationChannel();
|
|
47
|
+
await this._setCommunicationPort(port1);
|
|
48
|
+
await callChannel(
|
|
49
|
+
this._serviceWorker,
|
|
50
|
+
"UPDATE_COMMUNICATION_PORT",
|
|
51
|
+
this._getRegistrationInfo(),
|
|
52
|
+
port2,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_getRegistrationInfo() {
|
|
57
|
+
return { key: this.key };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Starts the server.
|
|
62
|
+
*/
|
|
63
|
+
async start() {
|
|
64
|
+
return this._registrationPromise = this._registrationPromise ||
|
|
65
|
+
(async () => {
|
|
66
|
+
const [register, cleanup] = newRegistry();
|
|
67
|
+
this._cleanupRegistrations = cleanup;
|
|
68
|
+
register(() => this._cleanupRegistrations = null);
|
|
69
|
+
const registration = await navigator.serviceWorker.register(
|
|
70
|
+
this.serviceWorkerUrl,
|
|
71
|
+
{
|
|
72
|
+
// type: "module",
|
|
73
|
+
scope: this.scope,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
register(() => registration.unregister());
|
|
77
|
+
|
|
78
|
+
// (example: when the service worker is the same but it is waked up/activated after an idle period)
|
|
79
|
+
register(
|
|
80
|
+
handleChannelCalls(
|
|
81
|
+
navigator.serviceWorker,
|
|
82
|
+
"UPDATE_COMMUNICATION_PORT",
|
|
83
|
+
async (event, params, port) => {
|
|
84
|
+
console.log("[UPDATE_COMMUNICATION_PORT]", port);
|
|
85
|
+
this._setCommunicationPort(port);
|
|
86
|
+
return this._getRegistrationInfo();
|
|
87
|
+
},
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
this._serviceWorker = await getServiceWorkerController();
|
|
92
|
+
await awaitServiceWorkerActivation(this._serviceWorker);
|
|
93
|
+
await this._updateCommunicationChannel();
|
|
94
|
+
|
|
95
|
+
async function getServiceWorkerController() {
|
|
96
|
+
return await new Promise((resolve) => {
|
|
97
|
+
// Resolve right away if this page is already controlled.
|
|
98
|
+
const serviceWorker = navigator.serviceWorker;
|
|
99
|
+
if (serviceWorker.controller) {
|
|
100
|
+
resolve(serviceWorker.controller);
|
|
101
|
+
} else {
|
|
102
|
+
const onChange = () => {
|
|
103
|
+
resolve(serviceWorker.controller);
|
|
104
|
+
serviceWorker.removeEventListener("controllerchange", onChange);
|
|
105
|
+
serviceWorker.oncontrollerchange = null;
|
|
106
|
+
};
|
|
107
|
+
serviceWorker.addEventListener("controllerchange", onChange);
|
|
108
|
+
serviceWorker.oncontrollerchange = onChange;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ------------------------------
|
|
114
|
+
// Await until the service is ready
|
|
115
|
+
async function awaitServiceWorkerActivation(worker) {
|
|
116
|
+
const isActivated = () => worker.state === "activated";
|
|
117
|
+
await new Promise(async (resolve) => {
|
|
118
|
+
if (isActivated()) resolve();
|
|
119
|
+
else {
|
|
120
|
+
worker.onstatechange = () => {
|
|
121
|
+
if (!isActivated()) return;
|
|
122
|
+
worker.onstatechange = null;
|
|
123
|
+
resolve();
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Stops the server and removes associated handlers.
|
|
133
|
+
*/
|
|
134
|
+
async stop() {
|
|
135
|
+
if (this._cleanupRegistrations) this._cleanupRegistrations();
|
|
136
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
137
|
+
for (let registration of registrations) {
|
|
138
|
+
try {
|
|
139
|
+
await registration.unregister();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log("Service Worker registration failed: ", error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class SwPortDispatcher {
|
|
148
|
+
constructor({ self, log }) {
|
|
149
|
+
this.self = self;
|
|
150
|
+
this.log = log;
|
|
151
|
+
// Contains mapping between path prefixes and clientIds
|
|
152
|
+
this.handlersIndex = new Map();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get scope() {
|
|
156
|
+
return new URL("./", import.meta.url) + "";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async loadChannelInfo(key) {
|
|
160
|
+
await this._checkActivation();
|
|
161
|
+
let result;
|
|
162
|
+
for (let channelInfo of this.handlersIndex.values()) {
|
|
163
|
+
if (channelInfo.key !== key) continue;
|
|
164
|
+
const clientId = channelInfo.clientId;
|
|
165
|
+
const client = await self.clients.get(clientId);
|
|
166
|
+
if (!client) {
|
|
167
|
+
// Client gone. Remove the corresponding handlers from the index.
|
|
168
|
+
this.handlersIndex.delete(clientId);
|
|
169
|
+
throw Object.assign(new Error("Error 410: Resource Gone"), {
|
|
170
|
+
status: 410,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
result = channelInfo;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
start() {
|
|
180
|
+
this._cleanup = handleChannelCalls(
|
|
181
|
+
this.self,
|
|
182
|
+
"UPDATE_COMMUNICATION_PORT",
|
|
183
|
+
async (event, channelInfo, port) => {
|
|
184
|
+
const clientId = event.source.id;
|
|
185
|
+
this.log("[UPDATE_COMMUNICATION_PORT]", clientId, channelInfo);
|
|
186
|
+
await this._updateChannelInfo({ ...channelInfo, port, clientId });
|
|
187
|
+
return { ...channelInfo };
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
this.self.onmessage = () => {};
|
|
191
|
+
|
|
192
|
+
this.self.addEventListener("install", (event) => {
|
|
193
|
+
this.log("Skip waiting on install.", event);
|
|
194
|
+
this.self.skipWaiting();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
this.self.addEventListener("activate", async (event) => {
|
|
198
|
+
this.log(
|
|
199
|
+
"Claim control over all clients.",
|
|
200
|
+
event,
|
|
201
|
+
clients,
|
|
202
|
+
this.self.clients,
|
|
203
|
+
);
|
|
204
|
+
event.waitUntil((async () => {
|
|
205
|
+
await this.self.clients.claim();
|
|
206
|
+
await this._checkActivation();
|
|
207
|
+
})());
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async stop() {
|
|
212
|
+
if (this._cleanup) {
|
|
213
|
+
await this._cleanup();
|
|
214
|
+
this._cleanup = undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async _checkActivation() {
|
|
219
|
+
return this._activationPromise = this._activationPromise ||
|
|
220
|
+
this._activate();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async _activate() {
|
|
224
|
+
this.log("[checkActivation]");
|
|
225
|
+
let clientsIndex = await this._loadClientIds();
|
|
226
|
+
for (let [clientId, client] of clientsIndex.entries()) {
|
|
227
|
+
const messageChannel = new MessageChannel();
|
|
228
|
+
const port = messageChannel.port1;
|
|
229
|
+
const channelInfo = await callChannel(
|
|
230
|
+
client,
|
|
231
|
+
"UPDATE_COMMUNICATION_PORT",
|
|
232
|
+
{},
|
|
233
|
+
messageChannel.port2,
|
|
234
|
+
);
|
|
235
|
+
await this._updateChannelInfo({ ...channelInfo, port, clientId });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async _updateChannelInfo(channelInfo) {
|
|
240
|
+
this.log("[updateClientInfo]", channelInfo);
|
|
241
|
+
this.handlersIndex.set(channelInfo.clientId, channelInfo);
|
|
242
|
+
await this._updateClientIds([]);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async _loadClientIds() {
|
|
246
|
+
const clientIds = new Set((await get("clientIds")) || []);
|
|
247
|
+
return await this._updateClientIds(clientIds);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async _updateClientIds(clientIds) {
|
|
251
|
+
clientIds = new Set(clientIds);
|
|
252
|
+
for (let clientId of this.handlersIndex.keys()) {
|
|
253
|
+
clientIds.add(clientId);
|
|
254
|
+
}
|
|
255
|
+
const index = new Map();
|
|
256
|
+
for (let clientId of clientIds) {
|
|
257
|
+
const client = await this.self.clients.get(clientId);
|
|
258
|
+
if (client) index.set(clientId, client);
|
|
259
|
+
else this.handlersIndex.delete(clientId);
|
|
260
|
+
}
|
|
261
|
+
await set("clientIds", [...index.keys()].sort());
|
|
262
|
+
return index;
|
|
263
|
+
}
|
|
264
|
+
}
|