@volter/tabnode 0.5.25 → 0.5.27

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,175 @@
1
+ /**
2
+ * The page's side of the virtual-port service worker (public/__sw__.js): the
3
+ * servers a host registered, by port, and the protocol the worker speaks to
4
+ * reach them, flow control and streaming included. It carries none of Node's
5
+ * library, so a page whose engine runs in a worker loads no engine to serve
6
+ * that worker's ports (browser-substrate ADR-0042). An engine's own bridge,
7
+ * `ServerBridge`, extends it with the servers its guests listen on.
8
+ */
9
+ import type { ResponseData, LoopbackStreamFlow } from './node-lib/http-bridge';
10
+ /** The bytes this view names, in a buffer of their own. A pooled Buffer is a window on 8 KB. */
11
+ export declare function ownedBytes(view: Uint8Array): Uint8Array;
12
+ export type { ResponseData, LoopbackStreamFlow };
13
+ /**
14
+ * Interface for virtual servers that can be registered with the bridge
15
+ */
16
+ export interface IVirtualServer {
17
+ listening: boolean;
18
+ /** What `net.Server.address()` answers: an address and a port, or the PATH of a unix-domain socket. */
19
+ address(): {
20
+ port: number;
21
+ address: string;
22
+ family: string;
23
+ } | string | null;
24
+ handleRequest(method: string, url: string, headers: Record<string, string>, body?: Uint8Array | string): Promise<ResponseData>;
25
+ }
26
+ export interface VirtualServer {
27
+ /**
28
+ * A server the host registered and answers itself, or null for a guest's
29
+ * own: a guest's server is a port this engine is listening on, and it is
30
+ * reached by connecting to it like any other client.
31
+ */
32
+ server: IVirtualServer | null;
33
+ port: number;
34
+ hostname: string;
35
+ }
36
+ export interface BridgeOptions {
37
+ baseUrl?: string;
38
+ onServerReady?: (port: number, url: string) => void;
39
+ }
40
+ export interface InitServiceWorkerOptions {
41
+ /**
42
+ * The URL path to the service worker file
43
+ * @default '/__sw__.js'
44
+ */
45
+ swUrl?: string;
46
+ /**
47
+ * The page's own documents that are frames of it (a sandbox, a worker
48
+ * host), by path, so the worker does not take one for the preview's.
49
+ */
50
+ ownDocuments?: string[];
51
+ }
52
+ type Listener = (...args: any[]) => void;
53
+ /** The events a bridge announces, `server-ready` and `sw-ready`, without Node's EventEmitter. */
54
+ declare class BridgeEvents {
55
+ private readonly listenersByEvent;
56
+ on(event: string | symbol, listener: Listener): this;
57
+ addListener(event: string | symbol, listener: Listener): this;
58
+ once(event: string | symbol, listener: Listener): this;
59
+ off(event: string | symbol, listener: Listener): this;
60
+ removeListener(event: string | symbol, listener: Listener): this;
61
+ removeAllListeners(event?: string | symbol): this;
62
+ listenerCount(event: string | symbol): number;
63
+ emit(event: string | symbol, ...args: unknown[]): boolean;
64
+ }
65
+ /**
66
+ * Server Bridge manages virtual HTTP servers and routes requests
67
+ */
68
+ export declare class PortBridge extends BridgeEvents {
69
+ static DEBUG: boolean;
70
+ servers: Map<number, VirtualServer>;
71
+ private baseUrl;
72
+ private options;
73
+ private messageChannel;
74
+ private serviceWorkerReady;
75
+ private keepaliveInterval;
76
+ constructor(options?: BridgeOptions);
77
+ /**
78
+ * Give back everything this bridge opened: the service worker keepalive,
79
+ * and in an engine's bridge its upgrade channel. A host that is done with a
80
+ * container calls it and has its process back; calling it twice is nothing.
81
+ */
82
+ close(): void;
83
+ /**
84
+ * Register a server on a port
85
+ */
86
+ registerServer(server: IVirtualServer | null, port: number, hostname?: string): void;
87
+ /**
88
+ * Unregister a server
89
+ */
90
+ unregisterServer(port: number): void;
91
+ /** The server the service worker answers at the origin's root, the preview's; null for none. */
92
+ private primaryPort;
93
+ /**
94
+ * Names the server the service worker serves at the origin's root, as an
95
+ * app is served at its own origin's root: its documents read their routes
96
+ * from `location.pathname`, which under /__virtual__/<port>/ is not the
97
+ * path they expect. Told to the worker now and again whenever the worker
98
+ * is (re)initialized, since a worker that restarted remembers nothing.
99
+ */
100
+ setPrimaryPort(port: number | null): void;
101
+ /** Every registration the worker should hold, sent again to a worker that was (re)initialized. */
102
+ private announceServers;
103
+ private ownDocuments;
104
+ /**
105
+ * Get server URL for a port
106
+ */
107
+ getServerUrl(port: number): string;
108
+ /**
109
+ * Get all registered server ports
110
+ */
111
+ getServerPorts(): number[];
112
+ /**
113
+ * Handle an incoming request from Service Worker
114
+ */
115
+ handleRequest(port: number, method: string, url: string, headers: Record<string, string>, body?: ArrayBuffer): Promise<ResponseData>;
116
+ /**
117
+ * Initialize Service Worker communication
118
+ * @param options - Configuration options for the service worker
119
+ * @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
120
+ */
121
+ initServiceWorker(options?: InitServiceWorkerOptions): Promise<void>;
122
+ /**
123
+ * Handle messages from Service Worker
124
+ */
125
+ private handleServiceWorkerMessage;
126
+ /**
127
+ * Handle a streaming request - sends chunks as they arrive
128
+ */
129
+ /**
130
+ * A page-side request streamed to the caller as it arrives, the door a host
131
+ * reads a guest's server through when it wants chunks rather than a body:
132
+ * a guest's own server is reached over the loopback as any client reaches
133
+ * it; a server the host registered answers through its own streaming
134
+ * method where it has one, else its buffered answer is delivered whole.
135
+ */
136
+ handleStreamingRequest(port: number, method: string, url: string, headers: Record<string, string>, body: ArrayBuffer | undefined, callbacks: {
137
+ start(statusCode: number, statusMessage: string, headers: Record<string, string>): void;
138
+ chunk(chunk: Uint8Array): void;
139
+ end(): void;
140
+ }, flow?: LoopbackStreamFlow): Promise<boolean>;
141
+ private streamToServiceWorker;
142
+ /**
143
+ * Send message to Service Worker
144
+ */
145
+ private notifyServiceWorker;
146
+ /** Flow-controlled requests in flight: a pull is one chunk's credit, a cancel ends the upstream. */
147
+ private readonly controlledStreams;
148
+ /**
149
+ * A request the worker sent under flow control: the response starts with
150
+ * its head, then goes one chunk (at most FLOW_MAX_CHUNK_BYTES) for each
151
+ * `stream-pull`, and ends with `stream-end` once the server finished and
152
+ * every chunk went out. The upstream connection is paused while chunks wait
153
+ * for credit, so a slow reader holds the server back; `stream-cancel` ends it.
154
+ */
155
+ private controlledStream;
156
+ /**
157
+ * Create a mock request handler for testing without Service Worker
158
+ */
159
+ createFetchHandler(): (request: Request) => Promise<Response>;
160
+ /**
161
+ * Whether a registration is a guest's own listener, answered over the
162
+ * engine's loopback rather than by a server object the host registered. A
163
+ * bridge with no engine in its realm has none: every server it holds was
164
+ * registered by the host, a worker's proxies among them.
165
+ */
166
+ protected isGuest(entry: VirtualServer): boolean;
167
+ /** A request to a guest's listener, over the engine's loopback. */
168
+ protected requestGuest(port: number, _method: string, _url: string, _headers: Record<string, string>, _body: Uint8Array | undefined): Promise<ResponseData>;
169
+ /** A guest's answer streamed off its connection as it arrives. */
170
+ protected streamGuest(port: number, _method: string, _url: string, _headers: Record<string, string>, _body: Uint8Array | undefined, _onStart: (statusCode: number, statusMessage: string, headers: Record<string, string>) => void, _onChunk: (chunk: Uint8Array) => void, _onEnd: () => void, _flow?: LoopbackStreamFlow): Promise<void>;
171
+ /** A request's body as the servers of this realm read it. */
172
+ protected bodyOf(bytes: Uint8Array): Uint8Array;
173
+ }
174
+ /** Get or create this realm's port bridge, for a page whose engine, if any, runs elsewhere. */
175
+ export declare function getPortBridge(options?: BridgeOptions): PortBridge;
@@ -0,0 +1,6 @@
1
+ import { P, g, o } from "./port-bridge-CixAESwA.js";
2
+ export {
3
+ P as PortBridge,
4
+ g as getPortBridge,
5
+ o as ownedBytes
6
+ };
package/dist/runtime.d.ts CHANGED
@@ -8,6 +8,19 @@ import { VirtualFS } from './virtual-fs';
8
8
  import type { IExecuteResult } from './runtime-interface';
9
9
  import { Process } from './shims/process';
10
10
  export { pendingGuestTimers, stopGuestTimers } from './guest-timers';
11
+ /**
12
+ * Module bodies prepared where the image is built. Parsing a package's files
13
+ * for the passes above is most of a `require` in a tab: 36 of 45 s for
14
+ * playwright-core's, measured in a tab, against 0.9 s under Node on the same
15
+ * machine. The image carries each body under the hash of the file it was made
16
+ * from, in this directory of the tab's filesystem, and the loader takes it in
17
+ * place of the passes when the file it read hashes to one.
18
+ */
19
+ export declare const PREPARED_MODULES_DIR = "/.tabnode/prepared";
20
+ /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
21
+ export declare function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined;
22
+ /** The body the loader would compile for this file, with no load hooks and no type stripping: what the image carries. */
23
+ export declare function prepareModuleForImage(rawCode: string, resolvedPath: string): string;
11
24
  export declare function __substratePendingOf(value: unknown): Promise<void> | undefined;
12
25
  export interface Module {
13
26
  id: string;
@@ -3,62 +3,12 @@
3
3
  * Connects Service Worker requests to virtual HTTP servers
4
4
  */
5
5
  import { type ResponseData, type LoopbackStreamFlow } from './node-lib/http-bridge';
6
- import { EventEmitter } from './node-lib/events-module';
7
- /**
8
- * Interface for virtual servers that can be registered with the bridge
9
- */
10
- export interface IVirtualServer {
11
- listening: boolean;
12
- /** What `net.Server.address()` answers: an address and a port, or the PATH of a unix-domain socket. */
13
- address(): {
14
- port: number;
15
- address: string;
16
- family: string;
17
- } | string | null;
18
- handleRequest(method: string, url: string, headers: Record<string, string>, body?: Buffer | string): Promise<ResponseData>;
19
- }
20
- export interface VirtualServer {
21
- /**
22
- * A server the host registered and answers itself, or null for a guest's
23
- * own: a guest's server is a port this engine is listening on, and it is
24
- * reached by connecting to it like any other client.
25
- */
26
- server: IVirtualServer | null;
27
- port: number;
28
- hostname: string;
29
- }
30
- export interface BridgeOptions {
31
- baseUrl?: string;
32
- onServerReady?: (port: number, url: string) => void;
33
- }
34
- export interface InitServiceWorkerOptions {
35
- /**
36
- * The URL path to the service worker file
37
- * @default '/__sw__.js'
38
- */
39
- swUrl?: string;
40
- /**
41
- * The page's own documents that are frames of it (a sandbox, a worker
42
- * host), by path, so the worker does not take one for the preview's.
43
- */
44
- ownDocuments?: string[];
45
- }
46
- export declare class ServerBridge extends EventEmitter {
47
- static DEBUG: boolean;
48
- servers: Map<number, VirtualServer>;
49
- private baseUrl;
50
- private options;
51
- private messageChannel;
6
+ import { PortBridge, type VirtualServer, type BridgeOptions } from './port-bridge';
7
+ export type { IVirtualServer, VirtualServer, BridgeOptions, InitServiceWorkerOptions } from './port-bridge';
8
+ export declare class ServerBridge extends PortBridge {
52
9
  /** The upgrade channel this bridge opened, held so it can be given back. */
53
10
  private upgradeChannel;
54
- private serviceWorkerReady;
55
- private keepaliveInterval;
56
11
  constructor(options?: BridgeOptions);
57
- /**
58
- * Give back everything this bridge opened: the upgrade channel and the
59
- * service worker keepalive. A host that is done with a container calls it
60
- * and has its process back; calling it twice is nothing.
61
- */
62
12
  /**
63
13
  * Every port a guest starts listening on becomes a server this bridge can
64
14
  * answer for, and every port it stops listening on stops being one. The
@@ -68,83 +18,12 @@ export declare class ServerBridge extends EventEmitter {
68
18
  private readonly guestServers;
69
19
  private watchGuestPorts;
70
20
  close(): void;
71
- /**
72
- * Register a server on a port
73
- */
74
- registerServer(server: IVirtualServer | null, port: number, hostname?: string): void;
75
- /**
76
- * Unregister a server
77
- */
78
- unregisterServer(port: number): void;
79
- /** The server the service worker answers at the origin's root, the preview's; null for none. */
80
- private primaryPort;
81
- /**
82
- * Names the server the service worker serves at the origin's root, as an
83
- * app is served at its own origin's root: its documents read their routes
84
- * from `location.pathname`, which under /__virtual__/<port>/ is not the
85
- * path they expect. Told to the worker now and again whenever the worker
86
- * is (re)initialized, since a worker that restarted remembers nothing.
87
- */
88
- setPrimaryPort(port: number | null): void;
89
- /** Every registration the worker should hold, sent again to a worker that was (re)initialized. */
90
- private announceServers;
91
- private ownDocuments;
92
- /**
93
- * Get server URL for a port
94
- */
95
- getServerUrl(port: number): string;
96
- /**
97
- * Get all registered server ports
98
- */
99
- getServerPorts(): number[];
100
- /**
101
- * Handle an incoming request from Service Worker
102
- */
103
- handleRequest(port: number, method: string, url: string, headers: Record<string, string>, body?: ArrayBuffer): Promise<ResponseData>;
104
- /**
105
- * Initialize Service Worker communication
106
- * @param options - Configuration options for the service worker
107
- * @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
108
- */
109
- initServiceWorker(options?: InitServiceWorkerOptions): Promise<void>;
110
- /**
111
- * Handle messages from Service Worker
112
- */
113
- private handleServiceWorkerMessage;
114
- /**
115
- * Handle a streaming request - sends chunks as they arrive
116
- */
117
- /**
118
- * A page-side request streamed to the caller as it arrives, the door a host
119
- * reads a guest's server through when it wants chunks rather than a body:
120
- * a guest's own server is reached over the loopback as any client reaches
121
- * it; a server the host registered answers through its own streaming
122
- * method where it has one, else its buffered answer is delivered whole.
123
- */
124
- handleStreamingRequest(port: number, method: string, url: string, headers: Record<string, string>, body: ArrayBuffer | undefined, callbacks: {
125
- start(statusCode: number, statusMessage: string, headers: Record<string, string>): void;
126
- chunk(chunk: Uint8Array): void;
127
- end(): void;
128
- }, flow?: LoopbackStreamFlow): Promise<boolean>;
129
- private streamToServiceWorker;
130
- /**
131
- * Send message to Service Worker
132
- */
133
- private notifyServiceWorker;
134
- /** Flow-controlled requests in flight: a pull is one chunk's credit, a cancel ends the upstream. */
135
- private readonly controlledStreams;
136
- /**
137
- * A request the worker sent under flow control: the response starts with
138
- * its head, then goes one chunk (at most FLOW_MAX_CHUNK_BYTES) for each
139
- * `stream-pull`, and ends with `stream-end` once the server finished and
140
- * every chunk went out. The upstream connection is paused while chunks wait
141
- * for credit, so a slow reader holds the server back; `stream-cancel` ends it.
142
- */
143
- private controlledStream;
144
- /**
145
- * Create a mock request handler for testing without Service Worker
146
- */
147
- createFetchHandler(): (request: Request) => Promise<Response>;
21
+ /** A guest's listener (the old null sentinel, now a request adapter) is answered over the loopback. */
22
+ protected isGuest(entry: VirtualServer): boolean;
23
+ protected requestGuest(port: number, method: string, url: string, headers: Record<string, string>, body: Uint8Array | undefined): Promise<ResponseData>;
24
+ protected streamGuest(port: number, method: string, url: string, headers: Record<string, string>, body: Uint8Array | undefined, onStart: (statusCode: number, statusMessage: string, headers: Record<string, string>) => void, onChunk: (chunk: Uint8Array) => void, onEnd: () => void, flow?: LoopbackStreamFlow): Promise<void>;
25
+ /** A server of this engine reads a request's body as Node's Buffer. */
26
+ protected bodyOf(bytes: Uint8Array): Uint8Array;
148
27
  }
149
28
  /**
150
29
  * Get or create the global server bridge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.25",
3
+ "version": "0.5.27",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,6 +33,10 @@
33
33
  "./vite": {
34
34
  "types": "./dist/vite-plugin.d.ts",
35
35
  "default": "./dist/vite-plugin.mjs"
36
+ },
37
+ "./port-bridge": {
38
+ "types": "./dist/port-bridge.d.ts",
39
+ "default": "./dist/port-bridge.mjs"
36
40
  }
37
41
  },
38
42
  "files": [
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  export { restoreHostGlobals, guestRealmInstalled } from './host-globals';
15
15
  export { VirtualFS } from './virtual-fs';
16
16
  export type { FSNode, MountedTree, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
17
- export { Runtime, execute } from './runtime';
17
+ export { Runtime, execute, prepareModuleForImage, preparedModuleKey, PREPARED_MODULES_DIR } from './runtime';
18
18
  export type { Module, RuntimeOptions, RequireFunction } from './runtime';
19
19
  export { createRuntime, WorkerRuntime } from './create-runtime';
20
20
  export type { IRuntime, IExecuteResult, CreateRuntimeOptions, IRuntimeOptions, VFSSnapshot } from './runtime-interface';
@@ -36,6 +36,7 @@ export { utilModule as util } from './node-lib/util-module';
36
36
  export * as npm from './npm';
37
37
  export { PackageManager, install } from './npm';
38
38
  export { ServerBridge, getServerBridge, resetServerBridge } from './server-bridge';
39
+ export { PortBridge, getPortBridge } from './port-bridge';
39
40
  export type { InitServiceWorkerOptions } from './server-bridge';
40
41
  /** What a page's request is answered with, the shape the bridge answers. */
41
42
  export type { ResponseData, LoopbackStreamFlow } from './node-lib/http-bridge';