@volter/tabnode 0.4.0 → 0.5.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/dist/__sw__.js +259 -5
- package/dist/assets/{runtime-worker-DoV8lqBn.js → runtime-worker-B9buriZs.js} +4021 -2961
- package/dist/fetch-transport.d.ts +20 -0
- package/dist/guest-timers.d.ts +15 -0
- package/dist/http-client-transport.d.ts +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +5204 -3057
- package/dist/native-stream-binding.d.ts +57 -0
- package/dist/native-stream-owner.d.ts +121 -0
- package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
- package/dist/node-lib/binding/fds.d.ts +8 -0
- package/dist/node-lib/binding/fs.d.ts +311 -78
- package/dist/node-lib/binding/misc.d.ts +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
- package/dist/node-lib/binding/process_wrap.d.ts +13 -2
- package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
- package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
- package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
- package/dist/node-lib/binding/util.d.ts +83 -0
- package/dist/node-lib/binding/uv.d.ts +4 -0
- package/dist/node-lib/fs-module.d.ts +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
- package/dist/node-lib/internals/index.d.ts +1 -1
- package/dist/node-lib/load.d.ts +17 -3
- package/dist/node-lib/module-hooks.d.ts +1 -0
- package/dist/node-lib/public-modules.d.ts +1 -1
- package/dist/node-lib/timers.d.ts +38 -0
- package/dist/node-process-host.d.ts +41 -0
- package/dist/process-registry.d.ts +39 -0
- package/dist/process-tokens.d.ts +16 -0
- package/dist/promise-ownership.d.ts +3 -0
- package/dist/runtime.d.ts +10 -9
- package/dist/shims/child_process.d.ts +2 -0
- package/dist/shims/crypto.d.ts +660 -119
- package/dist/shims/dns.d.ts +76 -2
- package/dist/shims/process.d.ts +1 -0
- package/dist/shims/string_decoder.d.ts +19 -3
- package/dist/shims/tls.d.ts +853 -31
- package/dist/source-edits.d.ts +7 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +2 -3
- package/src/fetch-transport.ts +98 -0
- package/src/guest-timers.ts +89 -0
- package/src/http-client-transport.ts +169 -0
- package/src/index.ts +12 -0
- package/src/native-stream-binding.ts +393 -0
- package/src/native-stream-owner.ts +317 -0
- package/src/node-lib/binding/duplex_stream.ts +95 -0
- package/src/node-lib/binding/fds.ts +18 -1
- package/src/node-lib/binding/fs.ts +45 -17
- package/src/node-lib/binding/misc.ts +2 -0
- package/src/node-lib/binding/pipe_wrap.ts +35 -12
- package/src/node-lib/binding/process_wrap.ts +72 -18
- package/src/node-lib/binding/stream_wrap.ts +196 -72
- package/src/node-lib/binding/tcp_wrap.ts +43 -8
- package/src/node-lib/binding/tty_wrap.ts +135 -8
- package/src/node-lib/binding/util.ts +24 -15
- package/src/node-lib/binding/uv.ts +4 -0
- package/src/node-lib/fs-module.ts +2 -18
- package/src/node-lib/internal/navigator.js +149 -0
- package/src/node-lib/internals/buffer-and-streams.ts +50 -11
- package/src/node-lib/internals/events-util.ts +5 -0
- package/src/node-lib/internals/index.ts +12 -2
- package/src/node-lib/load.ts +170 -62
- package/src/node-lib/module-hooks.ts +5 -2
- package/src/node-lib/public-modules.ts +12 -48
- package/src/node-lib/sources.ts +4 -0
- package/src/node-lib/timers.ts +93 -0
- package/src/node-process-host.ts +124 -0
- package/src/process-registry.ts +144 -0
- package/src/process-tokens.ts +60 -6
- package/src/promise-ownership.ts +28 -0
- package/src/runtime.ts +90 -214
- package/src/shims/child_process.ts +313 -80
- package/src/shims/crypto.ts +115 -100
- package/src/shims/dns.ts +17 -1
- package/src/shims/process.ts +33 -11
- package/src/shims/string_decoder.ts +10 -6
- package/src/shims/tls.ts +49 -19
- package/src/source-edits.ts +20 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Native process identity shared by the container's worker realms. */
|
|
2
|
+
export interface ProcessIdentity {
|
|
3
|
+
readonly pid: number;
|
|
4
|
+
readonly ppid: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** A run already admitted by the container owner into this realm's scope. */
|
|
8
|
+
export interface InitialProcessRegistration {
|
|
9
|
+
readonly token: string;
|
|
10
|
+
readonly identity: ProcessIdentity;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Installed by the trusted embedding host before this realm starts runs. */
|
|
14
|
+
export interface ProcessRegistry {
|
|
15
|
+
allocate(): number;
|
|
16
|
+
publish(token: string, identity: ProcessIdentity): void;
|
|
17
|
+
forget(token: string): void;
|
|
18
|
+
lookup(pid: number): ProcessIdentity | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* `kill(pid, signal)` for a live process of another realm in this container:
|
|
21
|
+
* whether a process there took the signal. A machine lets a process signal
|
|
22
|
+
* any process of its user, including one its parent left behind.
|
|
23
|
+
*/
|
|
24
|
+
signal(pid: number, signal: string): boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProcessRegistryScope extends ProcessRegistry {
|
|
28
|
+
/** Trusted owner only: how this realm answers a signal sent to one of its processes. */
|
|
29
|
+
receiveSignals(handler: (pid: number, signal: string) => boolean): void;
|
|
30
|
+
/** Trusted owner handoff before the destination worker starts; not a guest operation. */
|
|
31
|
+
adoptRun(source: ProcessRegistryScope, sourceToken: string, token: string, parentPid?: number): ProcessIdentity;
|
|
32
|
+
/** Ends this realm's registrations, including after abrupt worker death. */
|
|
33
|
+
dispose(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One container owns the counter and live table. Each realm receives a scope,
|
|
38
|
+
* so identical local run tokens cannot overwrite or release another realm's
|
|
39
|
+
* process. Allocation alone does not announce a live process.
|
|
40
|
+
*/
|
|
41
|
+
export function createProcessRegistryOwner(): { createScope(): ProcessRegistryScope } {
|
|
42
|
+
let nextPid = 1000 + Math.floor(Math.random() * 30000);
|
|
43
|
+
const live = new Map<number, ProcessIdentity>();
|
|
44
|
+
// Which realm's scope answers for each live pid, including after handoff.
|
|
45
|
+
const receivers = new Map<number, { handler?: (pid: number, signal: string) => boolean }>();
|
|
46
|
+
const scopes = new WeakMap<ProcessRegistryScope, {
|
|
47
|
+
allocated: Set<number>;
|
|
48
|
+
runs: Map<string, ProcessIdentity>;
|
|
49
|
+
active(): void;
|
|
50
|
+
}>();
|
|
51
|
+
return {
|
|
52
|
+
createScope() {
|
|
53
|
+
const allocated = new Set<number>();
|
|
54
|
+
const runs = new Map<string, ProcessIdentity>();
|
|
55
|
+
let disposed = false;
|
|
56
|
+
const receiver: { handler?: (pid: number, signal: string) => boolean } = {};
|
|
57
|
+
const active = (): void => {
|
|
58
|
+
if (disposed) throw new Error('Process registry scope is closed.');
|
|
59
|
+
};
|
|
60
|
+
const scope: ProcessRegistryScope = {
|
|
61
|
+
adoptRun(source, sourceToken, token, parentPid) {
|
|
62
|
+
active();
|
|
63
|
+
const parent = scopes.get(source);
|
|
64
|
+
if (!parent) throw new Error('Process scopes belong to different container owners.');
|
|
65
|
+
parent.active();
|
|
66
|
+
if (allocated.size || runs.size) throw new Error('Process handoff requires an unused destination scope.');
|
|
67
|
+
if (typeof sourceToken !== 'string' || typeof token !== 'string' || !token.length) throw new Error('Invalid process handoff token.');
|
|
68
|
+
const identity = parent.runs.get(sourceToken);
|
|
69
|
+
if (!identity || live.get(identity.pid) !== identity) throw new Error('Source scope does not own that live process.');
|
|
70
|
+
if (parentPid !== undefined) {
|
|
71
|
+
// The trusted holder retains its admitted parent PID. The parent
|
|
72
|
+
// may already have exited; publication established this ancestry
|
|
73
|
+
// while it still belonged to the source scope.
|
|
74
|
+
if (!Number.isSafeInteger(parentPid) || parentPid < 1 || identity.pid === parentPid || identity.ppid !== parentPid) {
|
|
75
|
+
throw new Error('Process admission does not name a child of the source realm.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// The PID stays live throughout the synchronous owner operation.
|
|
79
|
+
// Old-scope disposal/forget cannot remove the child's new ownership.
|
|
80
|
+
parent.runs.delete(sourceToken);
|
|
81
|
+
parent.allocated.delete(identity.pid);
|
|
82
|
+
allocated.add(identity.pid);
|
|
83
|
+
runs.set(token, identity);
|
|
84
|
+
receivers.set(identity.pid, receiver);
|
|
85
|
+
return identity;
|
|
86
|
+
},
|
|
87
|
+
allocate() {
|
|
88
|
+
active();
|
|
89
|
+
if (nextPid > 0x7fffffff) throw new Error('Process identifier space exhausted.');
|
|
90
|
+
const pid = nextPid++;
|
|
91
|
+
allocated.add(pid);
|
|
92
|
+
return pid;
|
|
93
|
+
},
|
|
94
|
+
publish(token, identity) {
|
|
95
|
+
active();
|
|
96
|
+
const previous = runs.get(token);
|
|
97
|
+
if (previous?.pid === identity.pid && previous.ppid === identity.ppid) return;
|
|
98
|
+
if (previous || !allocated.has(identity.pid) || live.has(identity.pid)) {
|
|
99
|
+
throw new Error('Process identity is not owned by this run.');
|
|
100
|
+
}
|
|
101
|
+
if (!Number.isInteger(identity.ppid) || identity.ppid < 0 || identity.ppid > 0x7fffffff) {
|
|
102
|
+
throw new Error('Invalid parent process identifier.');
|
|
103
|
+
}
|
|
104
|
+
if (identity.ppid !== 0 && !Array.from(runs.values()).some(parent => parent.pid === identity.ppid)) {
|
|
105
|
+
throw new Error('Parent process is not owned by this realm.');
|
|
106
|
+
}
|
|
107
|
+
const entry = Object.freeze({ pid: identity.pid, ppid: identity.ppid });
|
|
108
|
+
runs.set(token, entry);
|
|
109
|
+
live.set(entry.pid, entry);
|
|
110
|
+
receivers.set(entry.pid, receiver);
|
|
111
|
+
},
|
|
112
|
+
forget(token) {
|
|
113
|
+
active();
|
|
114
|
+
const entry = runs.get(token);
|
|
115
|
+
if (!entry) return;
|
|
116
|
+
runs.delete(token);
|
|
117
|
+
live.delete(entry.pid);
|
|
118
|
+
receivers.delete(entry.pid);
|
|
119
|
+
allocated.delete(entry.pid);
|
|
120
|
+
},
|
|
121
|
+
lookup(pid) { active(); return live.get(pid); },
|
|
122
|
+
signal(pid, signal) {
|
|
123
|
+
active();
|
|
124
|
+
if (!live.has(pid) || typeof signal !== 'string') return false;
|
|
125
|
+
return receivers.get(pid)?.handler?.(pid, signal) === true;
|
|
126
|
+
},
|
|
127
|
+
receiveSignals(handler) {
|
|
128
|
+
active();
|
|
129
|
+
receiver.handler = handler;
|
|
130
|
+
},
|
|
131
|
+
dispose() {
|
|
132
|
+
if (disposed) return;
|
|
133
|
+
disposed = true;
|
|
134
|
+
for (const entry of runs.values()) { live.delete(entry.pid); receivers.delete(entry.pid); }
|
|
135
|
+
receiver.handler = undefined;
|
|
136
|
+
runs.clear();
|
|
137
|
+
allocated.clear();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
scopes.set(scope, { allocated, runs, active });
|
|
141
|
+
return scope;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
package/src/process-tokens.ts
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* command gives its process has it removed.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import type { Process } from './shims/process';
|
|
18
19
|
import { AsyncLocalStorage } from './shims/async_hooks';
|
|
20
|
+
import { createProcessRegistryOwner, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
19
21
|
|
|
20
22
|
/** Whatever the embedding runtime uses to name one guest process. */
|
|
21
23
|
export type ProcessToken = string;
|
|
@@ -25,6 +27,10 @@ export const PROCESS_TOKEN_ENV = '__TABNODE_PROCESS_TOKEN';
|
|
|
25
27
|
|
|
26
28
|
/** What the engine can answer about one named run. */
|
|
27
29
|
export interface OwnedRun {
|
|
30
|
+
/** The guest whose cwd and inherited streams belong to this run. */
|
|
31
|
+
process: Process;
|
|
32
|
+
stdout(text: string): void;
|
|
33
|
+
stderr(text: string): void;
|
|
28
34
|
/** The timers the run's guest still holds, as Node's loop counts them. */
|
|
29
35
|
pendingTimers(): number;
|
|
30
36
|
/** Clear those timers, as ending the process clears them. */
|
|
@@ -38,6 +44,7 @@ export interface OwnedRun {
|
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
const runs = new Map<ProcessToken, OwnedRun>();
|
|
47
|
+
const tokensByProcess = new WeakMap<Process, ProcessToken>();
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* The run whose guest code is executing, as an async-local value rather than a
|
|
@@ -60,14 +67,22 @@ export function enterRun<T>(token: ProcessToken, fn: () => T): T {
|
|
|
60
67
|
*/
|
|
61
68
|
export function __recordRun(token: ProcessToken, run: OwnedRun): () => void {
|
|
62
69
|
runs.set(token, run);
|
|
70
|
+
tokensByProcess.set(run.process, token);
|
|
63
71
|
let released = false;
|
|
64
72
|
return () => {
|
|
65
73
|
if (released) return;
|
|
66
74
|
released = true;
|
|
67
75
|
if (runs.get(token) === run) runs.delete(token);
|
|
76
|
+
// Retain the weak association so delayed callbacks cannot turn an ended
|
|
77
|
+
// process into an unowned host request. The Process itself remains weak.
|
|
68
78
|
};
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
/** Bound guest globals retain their actual process across native await turns. */
|
|
82
|
+
export function __tokenForProcess(process: Process): ProcessToken | null {
|
|
83
|
+
return tokensByProcess.get(process) ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
/** The run recorded under this name, while it lasts. */
|
|
72
87
|
export function __runFor(token: ProcessToken): OwnedRun | undefined {
|
|
73
88
|
return runs.get(token);
|
|
@@ -120,16 +135,51 @@ export function __stopOwnedProcess(token: ProcessToken): void {
|
|
|
120
135
|
* every extension in the tab sat at "Activating..." and `vscode.git` never
|
|
121
136
|
* started at all.
|
|
122
137
|
*/
|
|
123
|
-
|
|
138
|
+
const processRegistryOwner = createProcessRegistryOwner();
|
|
139
|
+
let processRegistry: ProcessRegistry = processRegistryOwner.createScope();
|
|
140
|
+
let registryInstalled = false;
|
|
141
|
+
let registryUsed = false;
|
|
124
142
|
const pidsOfRuns = new Map<ProcessToken, { pid: number; ppid: number }>();
|
|
125
143
|
|
|
144
|
+
/** Each child realm gets its own registration authority in this container. */
|
|
145
|
+
export function createProcessRegistryScope(): ProcessRegistryScope {
|
|
146
|
+
if (registryInstalled) throw new Error('Only the container owner can create process registry scopes.');
|
|
147
|
+
return processRegistryOwner.createScope();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Trusted container owner only; worker clients cannot expose their authority. */
|
|
151
|
+
export function ownerProcessRegistryScope(): ProcessRegistryScope {
|
|
152
|
+
if (registryInstalled) throw new Error('This realm is a process registry client, not the container owner.');
|
|
153
|
+
return processRegistry as ProcessRegistryScope;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Startup-only host seam; never swap identity authorities under live runs. */
|
|
157
|
+
export function installProcessRegistry(registry: ProcessRegistry, initial?: InitialProcessRegistration): void {
|
|
158
|
+
if (registryInstalled || registryUsed) throw new Error('Process registry must be installed once before creating processes.');
|
|
159
|
+
if (initial) {
|
|
160
|
+
if (typeof initial.token !== 'string' || initial.token.length === 0) throw new Error('Invalid initial process token.');
|
|
161
|
+
// Validate with the owner before installing any local state. For an
|
|
162
|
+
// adopted child this is an idempotent publication, not a new PID or a
|
|
163
|
+
// guest claim to another realm's parent. Container construction can
|
|
164
|
+
// allocate an anonymous Runtime first; that must not consume this PID.
|
|
165
|
+
const identity = Object.freeze({ pid: initial.identity.pid, ppid: initial.identity.ppid });
|
|
166
|
+
registry.publish(initial.token, identity);
|
|
167
|
+
pidsOfRuns.set(initial.token, identity);
|
|
168
|
+
}
|
|
169
|
+
processRegistry = registry;
|
|
170
|
+
registryInstalled = true;
|
|
171
|
+
}
|
|
172
|
+
|
|
126
173
|
/** The next process number this engine hands out. */
|
|
127
174
|
export function mintPid(): number {
|
|
128
|
-
|
|
175
|
+
registryUsed = true;
|
|
176
|
+
return processRegistry.allocate();
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
/** Record the numbers a named run was started with, for the run to read back. */
|
|
132
180
|
export function setRunPid(token: ProcessToken, pid: number, ppid: number): void {
|
|
181
|
+
registryUsed = true;
|
|
182
|
+
processRegistry.publish(token, { pid, ppid });
|
|
133
183
|
pidsOfRuns.set(token, { pid, ppid });
|
|
134
184
|
}
|
|
135
185
|
|
|
@@ -140,17 +190,21 @@ export function runPid(token: ProcessToken | null | undefined): { pid: number; p
|
|
|
140
190
|
|
|
141
191
|
/** A run that has ended is no longer a process; its number is nobody's. */
|
|
142
192
|
export function forgetRunPid(token: ProcessToken): void {
|
|
193
|
+
processRegistry.forget(token);
|
|
143
194
|
pidsOfRuns.delete(token);
|
|
144
195
|
}
|
|
145
196
|
|
|
146
197
|
/** Whether a live run carries this number, which is what `kill(pid, 0)` asks. */
|
|
147
198
|
export function pidIsLive(pid: number): boolean {
|
|
148
|
-
|
|
149
|
-
|
|
199
|
+
return processRegistry.lookup(pid) !== undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** `kill(pid, signal)` to another realm's live process; whether one took it. */
|
|
203
|
+
export function signalPid(pid: number, signal: string): boolean {
|
|
204
|
+
return processRegistry.signal(pid, signal);
|
|
150
205
|
}
|
|
151
206
|
|
|
152
207
|
/** The numbers a live process carries, looked up by its own pid. */
|
|
153
208
|
export function processByPid(pid: number): { pid: number; ppid: number } | undefined {
|
|
154
|
-
|
|
155
|
-
return undefined;
|
|
209
|
+
return processRegistry.lookup(pid);
|
|
156
210
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// A browser rejection event belongs to one guest, not every run sharing the
|
|
2
|
+
// worker. Keep provenance on the promise itself; the active/last process at
|
|
3
|
+
// event-delivery time is not necessarily the process which rejected it.
|
|
4
|
+
const owners = new WeakMap<object, object>();
|
|
5
|
+
const constructors = new WeakMap<object, PromiseConstructor>();
|
|
6
|
+
export const intrinsicPromise = Promise;
|
|
7
|
+
|
|
8
|
+
export function promiseOwner(promise: unknown): object | undefined {
|
|
9
|
+
return promise !== null && typeof promise === 'object' ? owners.get(promise) : undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function guestPromise(owner: object): PromiseConstructor {
|
|
13
|
+
let constructor = constructors.get(owner);
|
|
14
|
+
if (!constructor) {
|
|
15
|
+
// Used only by the runtime's existing `new Identifier(...)` seam. The
|
|
16
|
+
// visible global stays native, including prototype/resolve identity.
|
|
17
|
+
// Unobserved async/static/species construction retains unknown ownership.
|
|
18
|
+
constructor = new Proxy(intrinsicPromise, {
|
|
19
|
+
construct(target, args) {
|
|
20
|
+
const promise = Reflect.construct(target, args, target);
|
|
21
|
+
owners.set(promise, owner);
|
|
22
|
+
return promise;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
constructors.set(owner, constructor);
|
|
26
|
+
}
|
|
27
|
+
return constructor;
|
|
28
|
+
}
|