@nubjs/nub-linux-arm64 0.2.1 → 0.2.3
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/bin/nub +0 -0
- package/bin/nubx +0 -0
- package/package.json +1 -1
- package/runtime/addons/nub-native.node +0 -0
- package/runtime/navigator-locks.mjs +188 -81
- package/runtime/navigator-shim.mjs +144 -0
- package/runtime/polyfills.cjs +624 -18
- package/runtime/preload-async-hooks.mjs +16 -3
- package/runtime/preload-common.cjs +49 -0
- package/runtime/preload.cjs +15 -0
- package/runtime/preload.mjs +8 -0
- package/runtime/transform-core.mjs +86 -14
- package/runtime/version.mjs +1 -1
package/bin/nub
CHANGED
|
Binary file
|
package/bin/nubx
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
// Web Locks API polyfill for Node
|
|
2
|
-
// Single-process only — locks don't coordinate across
|
|
1
|
+
// Web Locks API polyfill for Node < 24.5 (native on Node 24.5+, #58666).
|
|
2
|
+
// Single-process only — locks don't coordinate across worker threads (a deliberate
|
|
3
|
+
// scope: the common in-process serialization use is covered; cross-thread coordination
|
|
4
|
+
// would need a SharedArrayBuffer waitlist and is out of scope until there's demand).
|
|
5
|
+
//
|
|
6
|
+
// Spec: https://w3c.github.io/web-locks/. Modeled to match Node's own
|
|
7
|
+
// internal/locks.js (the native impl on 24.5+) so behavior is identical across the
|
|
8
|
+
// version boundary — including `steal`, AbortSignal integration, and the option/name
|
|
9
|
+
// validation that the WPT web-locks suite exercises. Requires a `navigator` object to
|
|
10
|
+
// host `navigator.locks`; navigator-shim.mjs backfills that on Node < 21 and MUST run
|
|
11
|
+
// first.
|
|
3
12
|
|
|
4
13
|
if (typeof globalThis.navigator === "object" && typeof globalThis.navigator.locks === "undefined") {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
14
|
+
const AbortSig = globalThis.AbortSignal;
|
|
15
|
+
|
|
16
|
+
const abortError = (msg) => new DOMException(msg || "The operation was aborted", "AbortError");
|
|
17
|
+
const stolenError = () => abortError("The lock was stolen by another request");
|
|
18
|
+
const notSupported = (msg) => new DOMException(msg, "NotSupportedError");
|
|
8
19
|
|
|
9
20
|
class Lock {
|
|
10
21
|
#name;
|
|
@@ -17,113 +28,209 @@ if (typeof globalThis.navigator === "object" && typeof globalThis.navigator.lock
|
|
|
17
28
|
get mode() { return this.#mode; }
|
|
18
29
|
}
|
|
19
30
|
|
|
31
|
+
// held: name → { mode, holders:Set<Holder> }. A shared grant has N holders sharing
|
|
32
|
+
// one record; an exclusive grant has exactly one. queue: name → Array<Waiter> (FIFO).
|
|
33
|
+
const held = new Map();
|
|
34
|
+
const queue = new Map();
|
|
35
|
+
|
|
20
36
|
function canAcquire(name, mode) {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
|
|
24
|
-
return false;
|
|
37
|
+
const rec = held.get(name);
|
|
38
|
+
if (!rec) return true;
|
|
39
|
+
return mode === "shared" && rec.mode === "shared";
|
|
25
40
|
}
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
// A FRESH request may be granted immediately only when no waiters are already
|
|
43
|
+
// queued for the name — otherwise it must queue behind them. This is the
|
|
44
|
+
// reader/writer FAIRNESS rule: a new shared request must NOT barge ahead of a
|
|
45
|
+
// pending exclusive request and join the current shared holders, which would
|
|
46
|
+
// starve the writer (WPT mode-mixed "An exclusive lock between shared locks").
|
|
47
|
+
// drainQueue, which only ever grants from the FRONT of the queue, keeps using
|
|
48
|
+
// canAcquire directly.
|
|
49
|
+
function canGrantNow(name, mode) {
|
|
50
|
+
const q = queue.get(name);
|
|
51
|
+
if (q && q.length > 0) return false;
|
|
52
|
+
return canAcquire(name, mode);
|
|
34
53
|
}
|
|
35
54
|
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
function addHolder(name, mode, holder) {
|
|
56
|
+
const rec = held.get(name);
|
|
57
|
+
if (rec) rec.holders.add(holder);
|
|
58
|
+
else held.set(name, { mode, holders: new Set([holder]) });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function removeHolder(name, holder) {
|
|
62
|
+
const rec = held.get(name);
|
|
63
|
+
if (!rec) return;
|
|
64
|
+
rec.holders.delete(holder);
|
|
65
|
+
if (rec.holders.size === 0) held.delete(name);
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
function enqueue(name, waiter) {
|
|
69
|
+
let q = queue.get(name);
|
|
70
|
+
if (!q) queue.set(name, (q = []));
|
|
71
|
+
q.push(waiter);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Grant as many head-of-queue waiters as the current held state allows: a head
|
|
75
|
+
// exclusive takes the lock alone; a run of head shared waiters is all granted.
|
|
46
76
|
function drainQueue(name) {
|
|
47
77
|
const q = queue.get(name);
|
|
48
78
|
if (!q || q.length === 0) return;
|
|
79
|
+
while (q.length > 0) {
|
|
80
|
+
const first = q[0];
|
|
81
|
+
if (!canAcquire(name, first.mode)) break;
|
|
82
|
+
q.shift();
|
|
83
|
+
first.grant(); // synchronously addHolders, so canAcquire reflects it next iter
|
|
84
|
+
if (first.mode === "exclusive") break;
|
|
85
|
+
}
|
|
86
|
+
if (q.length === 0) queue.delete(name);
|
|
87
|
+
}
|
|
49
88
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
// Run `cb(lock)` as a holder of (name, mode); return the promise that settles when
|
|
90
|
+
// the holder releases (cb's result/throw) — or rejects early if a steal BREAKS it.
|
|
91
|
+
// The callback is invoked one microtask later (matching Node), so a synchronously
|
|
92
|
+
// signaled abort is observed before the callback runs.
|
|
93
|
+
function runHolder(name, mode, cb) {
|
|
94
|
+
const lock = new Lock(name, mode);
|
|
95
|
+
let broken = false;
|
|
96
|
+
let rejectReleased;
|
|
97
|
+
const holder = {
|
|
98
|
+
mode,
|
|
99
|
+
break(reason) {
|
|
100
|
+
if (broken) return;
|
|
101
|
+
broken = true;
|
|
102
|
+
// Remove WITHOUT draining — the stealer takes the lock next; queued waiters
|
|
103
|
+
// stay pending until the steal's own holder releases.
|
|
104
|
+
removeHolder(name, holder);
|
|
105
|
+
rejectReleased(reason);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
addHolder(name, mode, holder);
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
rejectReleased = reject;
|
|
111
|
+
Promise.resolve()
|
|
112
|
+
.then(() => cb(lock))
|
|
113
|
+
.then(
|
|
114
|
+
(value) => { if (!broken) { removeHolder(name, holder); resolve(value); drainQueue(name); } },
|
|
115
|
+
(err) => { if (!broken) { removeHolder(name, holder); reject(err); drainQueue(name); } },
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The lock engine: returns the `released` promise. `cb` receives the granted Lock
|
|
121
|
+
// (or null on an ifAvailable miss). Mirrors the grant/steal/ifAvailable/queue shape
|
|
122
|
+
// of Node's internalBinding('locks').request.
|
|
123
|
+
function engineRequest(name, mode, steal, ifAvailable, cb) {
|
|
124
|
+
if (steal) {
|
|
125
|
+
const rec = held.get(name);
|
|
126
|
+
if (rec) for (const h of [...rec.holders]) h.break(stolenError());
|
|
127
|
+
return runHolder(name, "exclusive", cb);
|
|
128
|
+
}
|
|
129
|
+
if (canGrantNow(name, mode)) {
|
|
130
|
+
return runHolder(name, mode, cb);
|
|
65
131
|
}
|
|
132
|
+
if (ifAvailable) {
|
|
133
|
+
return Promise.resolve().then(() => cb(null));
|
|
134
|
+
}
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const waiter = {
|
|
137
|
+
mode,
|
|
138
|
+
settled: false,
|
|
139
|
+
grant() {
|
|
140
|
+
if (waiter.settled) return;
|
|
141
|
+
waiter.settled = true;
|
|
142
|
+
runHolder(name, mode, cb).then(resolve, reject);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
enqueue(name, waiter);
|
|
146
|
+
});
|
|
66
147
|
}
|
|
67
148
|
|
|
68
149
|
class LockManager {
|
|
69
|
-
async request(name, optionsOrCallback,
|
|
70
|
-
let options =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
options =
|
|
150
|
+
async request(name, optionsOrCallback, maybeCallback) {
|
|
151
|
+
let options = optionsOrCallback;
|
|
152
|
+
let callback = maybeCallback;
|
|
153
|
+
if (callback === undefined) {
|
|
154
|
+
callback = options;
|
|
155
|
+
options = undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// WebIDL DOMString coercion (throws TypeError on a Symbol, like the binding).
|
|
159
|
+
name = `${name}`;
|
|
160
|
+
if (typeof callback !== "function") {
|
|
161
|
+
throw new TypeError("Failed to execute 'request' on 'LockManager': parameter 2 is not a function.");
|
|
75
162
|
}
|
|
163
|
+
if (options === undefined || typeof options === "function") options = {};
|
|
76
164
|
|
|
77
|
-
const mode = options.mode
|
|
78
|
-
|
|
165
|
+
const mode = options.mode === undefined ? "exclusive" : options.mode;
|
|
166
|
+
if (mode !== "exclusive" && mode !== "shared") {
|
|
167
|
+
throw new TypeError(`Failed to execute 'request' on 'LockManager': '${mode}' is not a valid value for enumeration LockMode.`);
|
|
168
|
+
}
|
|
169
|
+
const ifAvailable = !!options.ifAvailable;
|
|
170
|
+
const steal = !!options.steal;
|
|
79
171
|
const signal = options.signal;
|
|
172
|
+
if (signal !== undefined && signal !== null && !(AbortSig && signal instanceof AbortSig)) {
|
|
173
|
+
throw new TypeError("Failed to execute 'request' on 'LockManager': member signal is not of type AbortSignal.");
|
|
174
|
+
}
|
|
80
175
|
|
|
81
|
-
|
|
82
|
-
|
|
176
|
+
// Already-aborted signal rejects with its reason BEFORE the option-combo checks
|
|
177
|
+
// (matching Node's signal.throwIfAborted() ordering).
|
|
178
|
+
if (signal && signal.aborted) {
|
|
179
|
+
throw signal.reason || abortError();
|
|
180
|
+
}
|
|
181
|
+
if (name[0] === "-") {
|
|
182
|
+
throw notSupported("Lock name may not start with hyphen '-'");
|
|
183
|
+
}
|
|
184
|
+
if (ifAvailable && steal) {
|
|
185
|
+
throw notSupported("ifAvailable and steal options cannot be used together");
|
|
186
|
+
}
|
|
187
|
+
if (mode !== "exclusive" && steal) {
|
|
188
|
+
throw notSupported("mode must be 'exclusive' when using the steal option");
|
|
189
|
+
}
|
|
190
|
+
if (signal && (steal || ifAvailable)) {
|
|
191
|
+
throw notSupported("signal cannot be used with the steal or ifAvailable options");
|
|
83
192
|
}
|
|
84
193
|
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
signal.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
194
|
+
// Signal path: the callback is deferred and skipped if the signal aborts before
|
|
195
|
+
// it runs; the abort rejects the OUTER promise iff the callback hasn't entered
|
|
196
|
+
// (lockGranted false). Verbatim shape of Node's internal/locks.js so a queued
|
|
197
|
+
// abort releases the slot (callback skipped) and the next waiter is granted.
|
|
198
|
+
if (signal) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
let lockGranted = false;
|
|
201
|
+
const onAbort = () => {
|
|
202
|
+
if (!lockGranted) reject(signal.reason || abortError());
|
|
203
|
+
};
|
|
204
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
205
|
+
const wrapped = (lock) =>
|
|
206
|
+
Promise.resolve().then(() => {
|
|
207
|
+
if (signal.aborted) return undefined;
|
|
208
|
+
lockGranted = true;
|
|
209
|
+
return callback(lock);
|
|
210
|
+
});
|
|
211
|
+
const released = engineRequest(name, mode, false, false, wrapped);
|
|
212
|
+
released.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
100
213
|
});
|
|
101
214
|
}
|
|
102
215
|
|
|
103
|
-
|
|
104
|
-
const lock = new Lock(name, mode);
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
return await callback(lock);
|
|
108
|
-
} finally {
|
|
109
|
-
release(name);
|
|
110
|
-
}
|
|
216
|
+
return engineRequest(name, mode, steal, ifAvailable, (lock) => callback(lock));
|
|
111
217
|
}
|
|
112
218
|
|
|
113
219
|
async query() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
220
|
+
// clientId is per-realm and opaque: `node-<pid>-0` (single-process; this polyfill
|
|
221
|
+
// does not coordinate across worker threads, so the threadId slot is always 0 —
|
|
222
|
+
// and we avoid importing node:worker_threads to keep this module builtin-free for a
|
|
223
|
+
// cheap bootstrap). The cross-context distinct-id WPT cases are browser-specific.
|
|
224
|
+
const clientId = `node-${process.pid}-0`;
|
|
225
|
+
const heldOut = [];
|
|
226
|
+
for (const [name, rec] of held) {
|
|
227
|
+
for (let i = 0; i < rec.holders.size; i++) heldOut.push({ name, mode: rec.mode, clientId });
|
|
119
228
|
}
|
|
120
229
|
const pending = [];
|
|
121
230
|
for (const [name, q] of queue) {
|
|
122
|
-
for (const
|
|
123
|
-
pending.push({ name, mode: req.mode, clientId: "" });
|
|
124
|
-
}
|
|
231
|
+
for (const w of q) if (!w.settled) pending.push({ name, mode: w.mode, clientId });
|
|
125
232
|
}
|
|
126
|
-
return { held:
|
|
233
|
+
return { held: heldOut, pending };
|
|
127
234
|
}
|
|
128
235
|
}
|
|
129
236
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// `navigator` global backfill for Node < 21 (where the global is wholly absent).
|
|
2
|
+
//
|
|
3
|
+
// Node ships `globalThis.navigator` from 21.0.0. Below that it is `undefined`, so
|
|
4
|
+
// any web-platform API hosted ON navigator — chiefly `navigator.locks` (Web Locks,
|
|
5
|
+
// see navigator-locks.mjs) — has no object to attach to and silently does nothing.
|
|
6
|
+
// nub's floor is 18.19, so to make those APIs reach the floor we synthesize the
|
|
7
|
+
// `navigator` object Node 21+ would provide. Companion to navigator-locks.mjs:
|
|
8
|
+
// this MUST run BEFORE it so locks has a host on 18.19–20.x.
|
|
9
|
+
//
|
|
10
|
+
// Shape mirrors Node's internal/navigator.js: a `Navigator` instance with the
|
|
11
|
+
// enumerable prototype getters `hardwareConcurrency`, `language`, `languages`,
|
|
12
|
+
// `userAgent`, `platform` (locks is added separately by navigator-locks.mjs). The
|
|
13
|
+
// userAgent is `Node.js/<major>` — NEVER `Nub/…`: the user is running Node, nub is
|
|
14
|
+
// the augmenter, and a `Nub/` UA would be a brand-boundary leak.
|
|
15
|
+
//
|
|
16
|
+
// VERSION-GATE, not a global read: installNavigatorShim() returns early on Node >= 21
|
|
17
|
+
// from `process.versions.node` WITHOUT ever touching `globalThis.navigator`. That
|
|
18
|
+
// matters because on Node 24.5+ the native `navigator` is a lazy getter whose first
|
|
19
|
+
// access realizes ~30 internal/stream/worker-io builtins — a cold-start regression
|
|
20
|
+
// (test-bootstrap-modules). The shim must never trigger that; the version check makes
|
|
21
|
+
// the fast tier (>= 22.15, navigator always present) a free no-op.
|
|
22
|
+
//
|
|
23
|
+
// node: builtins (node:os) are fetched via `process.getBuiltinModule` when present,
|
|
24
|
+
// else via a createRequire THREADED IN through `setBootstrapCreateRequire` — the same
|
|
25
|
+
// brand-safe, off-the-user-loader-chain pattern worker-polyfill.mjs uses. The narrow
|
|
26
|
+
// floor (18.19.x, 20.11–20.15) lacks `process.getBuiltinModule`, and the shim only
|
|
27
|
+
// touches os lazily (the `hardwareConcurrency` getter), so the threaded require is
|
|
28
|
+
// needed exactly there. `os` is never loaded on Node >= 21 (the early return).
|
|
29
|
+
|
|
30
|
+
let _bootstrapCreateRequire = null;
|
|
31
|
+
export function setBootstrapCreateRequire(fn) {
|
|
32
|
+
_bootstrapCreateRequire = fn;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function __getBuiltin(id) {
|
|
36
|
+
if (typeof process.getBuiltinModule === "function") return process.getBuiltinModule(id);
|
|
37
|
+
if (_bootstrapCreateRequire) return _bootstrapCreateRequire(import.meta.url)(id);
|
|
38
|
+
// Last-resort: a bare specifier require off this module's own createRequire is
|
|
39
|
+
// unavailable in ESM without node:module, which is exactly what the threading
|
|
40
|
+
// avoids importing statically. If neither path is wired, surface a clear error.
|
|
41
|
+
throw new Error("navigator-shim: no builtin accessor for " + id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function deriveLanguage() {
|
|
45
|
+
// Approximate Node's ICU default locale from the POSIX locale env, falling back
|
|
46
|
+
// to "en-US" (Node's own fallback). `en_US.UTF-8` → `en-US`.
|
|
47
|
+
const raw =
|
|
48
|
+
process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || "";
|
|
49
|
+
const base = raw.split(".")[0].split("@")[0].replace("_", "-");
|
|
50
|
+
return base && base !== "C" && base !== "POSIX" ? base : "en-US";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function navigatorPlatform(platform, arch) {
|
|
54
|
+
// Mirror node/lib/internal/navigator.js getNavigatorPlatform.
|
|
55
|
+
if (platform === "darwin") return "MacIntel";
|
|
56
|
+
if (platform === "win32") return "Win32";
|
|
57
|
+
if (platform === "linux") {
|
|
58
|
+
if (arch === "ia32") return "Linux i686";
|
|
59
|
+
if (arch === "x64") return "Linux x86_64";
|
|
60
|
+
return `Linux ${arch}`;
|
|
61
|
+
}
|
|
62
|
+
if (platform === "freebsd") return arch === "ia32" ? "FreeBSD i386" : arch === "x64" ? "FreeBSD amd64" : `FreeBSD ${arch}`;
|
|
63
|
+
if (platform === "openbsd") return arch === "ia32" ? "OpenBSD i386" : arch === "x64" ? "OpenBSD amd64" : `OpenBSD ${arch}`;
|
|
64
|
+
if (platform === "sunos") return arch === "ia32" ? "SunOS i86pc" : `SunOS ${arch}`;
|
|
65
|
+
if (platform === "aix") return "AIX";
|
|
66
|
+
return `${platform[0].toUpperCase()}${platform.slice(1)} ${arch}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Build the Navigator class lazily (only when actually backfilling) so loading this
|
|
70
|
+
// module on the fast tier costs nothing beyond the early-return check.
|
|
71
|
+
function makeNavigator() {
|
|
72
|
+
let _hw, _lang, _langs, _ua, _plat;
|
|
73
|
+
class Navigator {
|
|
74
|
+
get hardwareConcurrency() {
|
|
75
|
+
// os.availableParallelism honors cgroup CPU limits (Node 18.14+/19.4+; present
|
|
76
|
+
// on the 18.19 floor); fall back to cpus().length on the off chance it's absent.
|
|
77
|
+
if (_hw === undefined) {
|
|
78
|
+
const os = __getBuiltin("node:os");
|
|
79
|
+
_hw = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
|
|
80
|
+
}
|
|
81
|
+
return _hw;
|
|
82
|
+
}
|
|
83
|
+
get language() {
|
|
84
|
+
return (_lang ??= deriveLanguage());
|
|
85
|
+
}
|
|
86
|
+
get languages() {
|
|
87
|
+
return (_langs ??= Object.freeze([this.language]));
|
|
88
|
+
}
|
|
89
|
+
get userAgent() {
|
|
90
|
+
// `Node.js/<major>` — match Node exactly; never a nub-branded UA.
|
|
91
|
+
return (_ua ??= `Node.js/${process.versions.node.split(".")[0]}`);
|
|
92
|
+
}
|
|
93
|
+
get platform() {
|
|
94
|
+
return (_plat ??= navigatorPlatform(process.platform, process.arch));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Match Node's instance shape: the property getters live on the prototype and are
|
|
98
|
+
// ENUMERABLE (Node sets them with kEnumerableProperty), so `for (const k in
|
|
99
|
+
// navigator)` walks them while `Object.keys(navigator)` (own enumerable) is empty.
|
|
100
|
+
// Class-body getters default to enumerable:false, so flip them in place.
|
|
101
|
+
for (const k of ["hardwareConcurrency", "language", "languages", "userAgent", "platform"]) {
|
|
102
|
+
Object.defineProperty(Navigator.prototype, k, { enumerable: true });
|
|
103
|
+
}
|
|
104
|
+
return { Navigator, instance: new Navigator() };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Install the navigator backfill if and only if the running Node lacks the global
|
|
108
|
+
// (i.e. Node < 21). Idempotent and side-effect-free on Node >= 21.
|
|
109
|
+
export function installNavigatorShim() {
|
|
110
|
+
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
111
|
+
// Node 21+ ships navigator natively — never read the (possibly lazy) global here.
|
|
112
|
+
if (major >= 21) return;
|
|
113
|
+
// Defensive idempotency for the < 21 path (navigator is genuinely undefined there,
|
|
114
|
+
// so this read can't trigger a lazy realization).
|
|
115
|
+
if (typeof globalThis.navigator !== "undefined") return;
|
|
116
|
+
|
|
117
|
+
const { Navigator, instance } = makeNavigator();
|
|
118
|
+
|
|
119
|
+
// The binding on globalThis is NON-ENUMERABLE (invisible to Object.keys(globalThis)
|
|
120
|
+
// / for-in) — nub's additive-global contract, matching how reportError and Worker
|
|
121
|
+
// are installed. Configurable + writable so user code may still override it.
|
|
122
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
123
|
+
value: instance,
|
|
124
|
+
enumerable: false,
|
|
125
|
+
writable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
});
|
|
128
|
+
// Node also exposes the `Navigator` constructor globally; mirror it (non-enumerable).
|
|
129
|
+
if (typeof globalThis.Navigator === "undefined") {
|
|
130
|
+
Object.defineProperty(globalThis, "Navigator", {
|
|
131
|
+
value: Navigator,
|
|
132
|
+
enumerable: false,
|
|
133
|
+
writable: true,
|
|
134
|
+
configurable: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Fast tier / modern compat (getBuiltinModule present, Node >= 20.16): install eagerly
|
|
140
|
+
// at module eval — a guaranteed no-op above Node 21 thanks to the version gate, and
|
|
141
|
+
// correct on a 20.16–20.x compat run where navigator is still absent. On the narrow
|
|
142
|
+
// floor below getBuiltinModule the compat entry calls setBootstrapCreateRequire(...)
|
|
143
|
+
// + installNavigatorShim() explicitly (mirroring worker-polyfill's wiring).
|
|
144
|
+
if (typeof process.getBuiltinModule === "function") installNavigatorShim();
|