@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.
@@ -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
+ }