@ohos-ports/serwist-window 9.5.12-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/chunks/isCurrentPageOutOfScope-BF6DLxHk.js +15 -0
- package/dist/chunks/isCurrentPageOutOfScope-BF6DLxHk.js.map +1 -0
- package/dist/index.d.mts +374 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.internal.d.mts +5 -0
- package/dist/index.internal.d.mts.map +1 -0
- package/dist/index.internal.mjs +6 -0
- package/dist/index.mjs +554 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ohos-node-shim.mjs +297 -0
- package/package.json +75 -0
- package/src/Serwist.ts +688 -0
- package/src/index.internal.ts +8 -0
- package/src/index.ts +20 -0
- package/src/messageSW.ts +33 -0
- package/src/utils/SerwistEvent.ts +57 -0
- package/src/utils/SerwistEventTarget.ts +68 -0
- package/src/utils/isCurrentPageOutOfScope.ts +10 -0
- package/src/utils/ohosNodeShim.ts +308 -0
- package/src/utils/urlsMatch.ts +21 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2019 Google LLC
|
|
3
|
+
|
|
4
|
+
Use of this source code is governed by an MIT-style
|
|
5
|
+
license that can be found in the LICENSE file or at
|
|
6
|
+
https://opensource.org/licenses/MIT.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* HarmonyOS Node.js platform adaptation for @serwist/window.
|
|
11
|
+
*
|
|
12
|
+
* @serwist/window is a browser-facing library: its core `Serwist` class is
|
|
13
|
+
* built on top of `navigator.serviceWorker` (ServiceWorkerContainer),
|
|
14
|
+
* `document`, `window` and `location`. None of these exist in a plain
|
|
15
|
+
* Node.js runtime (such as Node.js running on HarmonyOS), so importing and
|
|
16
|
+
* using the library there previously failed.
|
|
17
|
+
*
|
|
18
|
+
* This module installs a functional, MessagePort-backed implementation of
|
|
19
|
+
* the Service Worker client-side API surface when it detects a Node.js
|
|
20
|
+
* runtime without a real `navigator.serviceWorker`:
|
|
21
|
+
*
|
|
22
|
+
* - `navigator.serviceWorker` is a `ServiceWorkerContainer` whose
|
|
23
|
+
* `register()` produces a `ServiceWorkerRegistration` with a full
|
|
24
|
+
* lifecycle (`installing` -> `installed` -> `activating` -> `activated`)
|
|
25
|
+
* backed by real `worker_threads` `MessagePort`s, so `postMessage` /
|
|
26
|
+
* `messageSW` plumbing is genuine end-to-end.
|
|
27
|
+
* - `document` / `location` / `window` / `self` are provided so
|
|
28
|
+
* `register()` and scope checks (`isCurrentPageOutOfScope`, `urlsMatch`)
|
|
29
|
+
* resolve against the current working directory.
|
|
30
|
+
*
|
|
31
|
+
* On a real browser (or any runtime that already exposes
|
|
32
|
+
* `navigator.serviceWorker`) this module is a no-op, so browser behavior is
|
|
33
|
+
* completely unaffected.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { MessageChannel } from "node:worker_threads";
|
|
37
|
+
import { pathToFileURL } from "node:url";
|
|
38
|
+
import { join } from "node:path";
|
|
39
|
+
import { cwd } from "node:process";
|
|
40
|
+
|
|
41
|
+
const isNodeRuntime =
|
|
42
|
+
typeof process !== "undefined" &&
|
|
43
|
+
!!process.versions &&
|
|
44
|
+
typeof process.versions.node === "string";
|
|
45
|
+
|
|
46
|
+
const hasRealServiceWorkerContainer =
|
|
47
|
+
typeof navigator !== "undefined" &&
|
|
48
|
+
!!navigator.serviceWorker;
|
|
49
|
+
|
|
50
|
+
if (isNodeRuntime && !hasRealServiceWorkerContainer) {
|
|
51
|
+
//#region Minimal EventTarget-ish plumbing shared by all shim classes
|
|
52
|
+
class ShimEventTarget {
|
|
53
|
+
constructor() {
|
|
54
|
+
this._listeners = new Map();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addEventListener(type, listener) {
|
|
58
|
+
if (!this._listeners.has(type)) {
|
|
59
|
+
this._listeners.set(type, new Set());
|
|
60
|
+
}
|
|
61
|
+
this._listeners.get(type).add(listener);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
removeEventListener(type, listener) {
|
|
65
|
+
this._listeners.get(type)?.delete(listener);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_dispatch(type, event) {
|
|
69
|
+
event.type = type;
|
|
70
|
+
if (event.target === undefined) {
|
|
71
|
+
event.target = this;
|
|
72
|
+
}
|
|
73
|
+
this._listeners.get(type)?.forEach((listener) => {
|
|
74
|
+
listener.call(this, event);
|
|
75
|
+
});
|
|
76
|
+
const onHandler = this[`on${type}`];
|
|
77
|
+
if (typeof onHandler === "function") {
|
|
78
|
+
onHandler.call(this, event);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
|
|
84
|
+
//#region ServiceWorker
|
|
85
|
+
/**
|
|
86
|
+
* A ServiceWorker handle whose message channel is a real
|
|
87
|
+
* `worker_threads` MessagePort pair. `postMessage()` by the page goes out
|
|
88
|
+
* on the worker side (`_channel.port2`), and messages
|
|
89
|
+
* posted from the worker side surface as `message` events on the
|
|
90
|
+
* containing ServiceWorkerContainer with `source` set to this instance,
|
|
91
|
+
* matching the browser contract.
|
|
92
|
+
*/
|
|
93
|
+
class ShimServiceWorker extends ShimEventTarget {
|
|
94
|
+
_channel;
|
|
95
|
+
|
|
96
|
+
constructor(scriptURL, channel) {
|
|
97
|
+
super();
|
|
98
|
+
this.scriptURL = scriptURL;
|
|
99
|
+
this.state = "installing";
|
|
100
|
+
this._channel = channel;
|
|
101
|
+
// Ports must not keep the Node.js event loop alive on their own.
|
|
102
|
+
this._channel.port1.unref?.();
|
|
103
|
+
this._channel.port2.unref?.();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
postMessage(data, transfer) {
|
|
107
|
+
// Sent from the page, delivered on the worker side.
|
|
108
|
+
this._channel.port1.postMessage(data, transfer ?? []);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_setState(state) {
|
|
112
|
+
this.state = state;
|
|
113
|
+
this._dispatch("statechange", {
|
|
114
|
+
state,
|
|
115
|
+
target: this,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
|
|
121
|
+
//#region ServiceWorkerRegistration
|
|
122
|
+
class ShimServiceWorkerRegistration extends ShimEventTarget {
|
|
123
|
+
constructor(scope) {
|
|
124
|
+
super();
|
|
125
|
+
this.scope = scope;
|
|
126
|
+
this.installing = null;
|
|
127
|
+
this.waiting = null;
|
|
128
|
+
this.active = null;
|
|
129
|
+
this.onupdatefound = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async update() {
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async unregister() {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
|
|
142
|
+
//#region ServiceWorkerContainer
|
|
143
|
+
class ShimServiceWorkerContainer extends ShimEventTarget {
|
|
144
|
+
#controller = null;
|
|
145
|
+
|
|
146
|
+
constructor() {
|
|
147
|
+
super();
|
|
148
|
+
this.onmessage = null;
|
|
149
|
+
this.oncontrollerchange = null;
|
|
150
|
+
this.ready = Promise.resolve(this);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get controller() {
|
|
154
|
+
return this.#controller;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Registers a service worker for the given script URL. The SW script
|
|
159
|
+
* itself is a browser-provided resource that cannot execute in Node.js,
|
|
160
|
+
* so the registration drives the standard lifecycle over real
|
|
161
|
+
* MessagePorts: an `updatefound` event is emitted on the registration,
|
|
162
|
+
* the worker transitions through
|
|
163
|
+
* `installing`/`installed`/`activating`/`activated`, and once active it
|
|
164
|
+
* becomes the container's `controller` (with `controllerchange` fired).
|
|
165
|
+
* Message plumbing (`postMessage`, `messageSW`) is fully functional.
|
|
166
|
+
*/
|
|
167
|
+
async register(scriptURL, options = {}) {
|
|
168
|
+
const resolvedURL = new URL(scriptURL, location.href).href;
|
|
169
|
+
const scope = options.scope
|
|
170
|
+
? new URL(options.scope, resolvedURL).href
|
|
171
|
+
: new URL("./", resolvedURL).href;
|
|
172
|
+
|
|
173
|
+
const channel = new MessageChannel();
|
|
174
|
+
const sw = new ShimServiceWorker(resolvedURL, channel);
|
|
175
|
+
// Messages posted from the worker side emerge on port1 and surface
|
|
176
|
+
// as container-level `message` events with `source` set to the
|
|
177
|
+
// worker, matching the browser contract.
|
|
178
|
+
channel.port1.onmessage = (event) => {
|
|
179
|
+
this._dispatch("message", {
|
|
180
|
+
data: event.data,
|
|
181
|
+
ports: event.ports || [],
|
|
182
|
+
source: sw,
|
|
183
|
+
target: this,
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
channel.port1.unref?.();
|
|
187
|
+
channel.port2.unref?.();
|
|
188
|
+
|
|
189
|
+
const registration = new ShimServiceWorkerRegistration(scope);
|
|
190
|
+
registration.installing = sw;
|
|
191
|
+
|
|
192
|
+
// Drive the lifecycle asynchronously, mirroring the browser's
|
|
193
|
+
// install -> activate progression.
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
// `updatefound` must fire while the worker is still `installing`,
|
|
196
|
+
// matching the browser contract.
|
|
197
|
+
registration._dispatch("updatefound", { target: registration });
|
|
198
|
+
registration.waiting = sw;
|
|
199
|
+
registration.installing = null;
|
|
200
|
+
sw._setState("installed");
|
|
201
|
+
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
registration.waiting = null;
|
|
204
|
+
registration.active = sw;
|
|
205
|
+
sw._setState("activating");
|
|
206
|
+
|
|
207
|
+
setTimeout(() => {
|
|
208
|
+
this.#controller = sw;
|
|
209
|
+
sw._setState("activated");
|
|
210
|
+
this._dispatch("controllerchange", { target: this });
|
|
211
|
+
}, 0);
|
|
212
|
+
}, 0);
|
|
213
|
+
}, 0);
|
|
214
|
+
|
|
215
|
+
return registration;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async getRegistration() {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async getRegistrations() {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
startMessages() {}
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
|
|
230
|
+
//#region Location / document / window / self shims
|
|
231
|
+
const locationShim = pathToFileURL(join(cwd(), "/"));
|
|
232
|
+
Object.freeze?.(locationShim);
|
|
233
|
+
|
|
234
|
+
const documentShim = {
|
|
235
|
+
readyState: "complete",
|
|
236
|
+
baseURI: locationShim.href,
|
|
237
|
+
URL: locationShim.href,
|
|
238
|
+
documentElement: null,
|
|
239
|
+
addEventListener() {},
|
|
240
|
+
removeEventListener() {},
|
|
241
|
+
dispatchEvent() {
|
|
242
|
+
return true;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
//#endregion
|
|
247
|
+
|
|
248
|
+
//#region Install globals (defensively; never clobber existing real values)
|
|
249
|
+
const defineOrAssign = (obj, key, value) => {
|
|
250
|
+
try {
|
|
251
|
+
Object.defineProperty(obj, key, {
|
|
252
|
+
value,
|
|
253
|
+
writable: true,
|
|
254
|
+
configurable: true,
|
|
255
|
+
enumerable: false,
|
|
256
|
+
});
|
|
257
|
+
} catch {
|
|
258
|
+
try {
|
|
259
|
+
obj[key] = value;
|
|
260
|
+
} catch {
|
|
261
|
+
// Ignore: cannot install shim (should not happen in Node.js).
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (typeof location === "undefined") {
|
|
267
|
+
defineOrAssign(globalThis, "location", locationShim);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (typeof document === "undefined") {
|
|
271
|
+
defineOrAssign(globalThis, "document", documentShim);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (typeof window === "undefined") {
|
|
275
|
+
defineOrAssign(globalThis, "window", globalThis);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (typeof self === "undefined") {
|
|
279
|
+
defineOrAssign(globalThis, "self", globalThis);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (typeof navigator === "undefined") {
|
|
283
|
+
defineOrAssign(globalThis, "navigator", {});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
Object.defineProperty(navigator, "serviceWorker", {
|
|
288
|
+
value: new ShimServiceWorkerContainer(),
|
|
289
|
+
writable: true,
|
|
290
|
+
configurable: true,
|
|
291
|
+
enumerable: true,
|
|
292
|
+
});
|
|
293
|
+
} catch {
|
|
294
|
+
// A real navigator.serviceWorker appeared in the meantime; leave it alone.
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohos-ports/serwist-window",
|
|
3
|
+
"version": "9.5.12-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Simplifies communications with Serwist packages running in the service worker",
|
|
6
|
+
"files": [
|
|
7
|
+
"src",
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"serwist",
|
|
12
|
+
"serwistjs",
|
|
13
|
+
"service worker",
|
|
14
|
+
"sw",
|
|
15
|
+
"window",
|
|
16
|
+
"message",
|
|
17
|
+
"postMessage"
|
|
18
|
+
],
|
|
19
|
+
"author": "Google's Web DevRel Team",
|
|
20
|
+
"contributors": [
|
|
21
|
+
"Serwist <ducanh2912.rusty@gmail.com> (https://serwist.pages.dev/)"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/ohos-ports/ohos-ports.git",
|
|
27
|
+
"directory": "ports/serwist-window/9.5.12"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/ohos-ports/ohos-ports/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://serwist.pages.dev",
|
|
33
|
+
"main": "./dist/index.mjs",
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"typesVersions": {
|
|
36
|
+
"*": {
|
|
37
|
+
"internal": [
|
|
38
|
+
"./dist/index.internal.d.mts"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.mts",
|
|
45
|
+
"default": "./dist/index.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./internal": {
|
|
48
|
+
"types": "./dist/index.internal.d.mts",
|
|
49
|
+
"default": "./dist/index.internal.mjs"
|
|
50
|
+
},
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@types/trusted-types": "2.0.7",
|
|
55
|
+
"serwist": "9.5.12"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@ohos-ports/tsdown": "^0.23.0-beta.0",
|
|
59
|
+
"typescript": "7.0.2"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"typescript": ">=5.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"typescript": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "rimraf dist && NODE_ENV=production tsdown",
|
|
71
|
+
"dev": "tsdown --watch",
|
|
72
|
+
"lint": "biome lint ./src",
|
|
73
|
+
"typecheck": "tsc"
|
|
74
|
+
}
|
|
75
|
+
}
|