@volter/tabnode 0.5.26 → 0.5.28
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/assets/{runtime-worker-VIeZbbDr.js → runtime-worker-C6ulimZ4.js} +75 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +109 -597
- package/dist/port-bridge-CixAESwA.js +655 -0
- package/dist/port-bridge.d.ts +175 -0
- package/dist/port-bridge.mjs +6 -0
- package/dist/server-bridge.d.ts +9 -130
- package/package.json +5 -1
- package/src/index.ts +1 -0
- package/src/port-bridge.ts +791 -0
- package/src/runtime.ts +63 -6
- package/src/server-bridge.ts +22 -664
- package/src/shims/child_process.ts +31 -6
|
@@ -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;
|
package/dist/server-bridge.d.ts
CHANGED
|
@@ -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 {
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
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.
|
|
3
|
+
"version": "0.5.28",
|
|
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
|
@@ -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';
|