@johnhenry/wsh 0.0.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/LICENSE +21 -0
- package/README.md +150 -0
- package/package.json +42 -0
- package/src/auth.mjs +314 -0
- package/src/cbor.mjs +389 -0
- package/src/client.mjs +1842 -0
- package/src/file-transfer.mjs +413 -0
- package/src/index.d.ts +1454 -0
- package/src/index.mjs +70 -0
- package/src/keystore.mjs +473 -0
- package/src/mcp-bridge.mjs +228 -0
- package/src/messages.gen.mjs +928 -0
- package/src/messages.mjs +3 -0
- package/src/recording.mjs +402 -0
- package/src/session.mjs +557 -0
- package/src/transport-ws.mjs +462 -0
- package/src/transport.mjs +343 -0
- package/src/virtual-session.mjs +160 -0
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,1454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wsh -- Web Shell client library
|
|
3
|
+
*
|
|
4
|
+
* Browser-native remote command execution over WebTransport/WebSocket
|
|
5
|
+
* with Ed25519 authentication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// cbor.mjs -- CBOR codec + length-prefixed framing
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Encode a JS value into CBOR bytes.
|
|
14
|
+
* Supports: maps, arrays, strings, integers, bytes (Uint8Array),
|
|
15
|
+
* booleans, null, and floats.
|
|
16
|
+
*/
|
|
17
|
+
export function cborEncode(value: unknown): Uint8Array;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decode CBOR bytes into a JS value.
|
|
21
|
+
*/
|
|
22
|
+
export function cborDecode(data: Uint8Array): unknown;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Frame a CBOR-encoded message with a 4-byte big-endian length prefix.
|
|
26
|
+
* @returns [4-byte length][CBOR payload]
|
|
27
|
+
*/
|
|
28
|
+
export function frameEncode(value: unknown): Uint8Array;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Streaming frame decoder. Feed it chunks and it yields complete messages.
|
|
32
|
+
*/
|
|
33
|
+
export class FrameDecoder {
|
|
34
|
+
/**
|
|
35
|
+
* Feed bytes and return decoded messages.
|
|
36
|
+
*/
|
|
37
|
+
feed(chunk: Uint8Array): unknown[];
|
|
38
|
+
|
|
39
|
+
/** Reset internal buffer. */
|
|
40
|
+
reset(): void;
|
|
41
|
+
|
|
42
|
+
/** Bytes remaining in buffer. */
|
|
43
|
+
readonly pending: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// messages.gen.mjs / messages.mjs -- Protocol messages
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/** Wire message type constants (frozen object of hex opcodes). */
|
|
51
|
+
export const MSG: {
|
|
52
|
+
// Handshake
|
|
53
|
+
readonly HELLO: 0x01;
|
|
54
|
+
readonly SERVER_HELLO: 0x02;
|
|
55
|
+
readonly CHALLENGE: 0x03;
|
|
56
|
+
readonly AUTH_METHODS: 0x04;
|
|
57
|
+
readonly AUTH: 0x05;
|
|
58
|
+
readonly AUTH_OK: 0x06;
|
|
59
|
+
readonly AUTH_FAIL: 0x07;
|
|
60
|
+
|
|
61
|
+
// Channel
|
|
62
|
+
readonly OPEN: 0x10;
|
|
63
|
+
readonly OPEN_OK: 0x11;
|
|
64
|
+
readonly OPEN_FAIL: 0x12;
|
|
65
|
+
readonly RESIZE: 0x13;
|
|
66
|
+
readonly SIGNAL: 0x14;
|
|
67
|
+
readonly EXIT: 0x15;
|
|
68
|
+
readonly CLOSE: 0x16;
|
|
69
|
+
readonly SESSION_DATA: 0x17;
|
|
70
|
+
|
|
71
|
+
// Transport
|
|
72
|
+
readonly ERROR: 0x20;
|
|
73
|
+
readonly PING: 0x21;
|
|
74
|
+
readonly PONG: 0x22;
|
|
75
|
+
|
|
76
|
+
// Session
|
|
77
|
+
readonly ATTACH: 0x30;
|
|
78
|
+
readonly RESUME: 0x31;
|
|
79
|
+
readonly RENAME: 0x32;
|
|
80
|
+
readonly IDLE_WARNING: 0x33;
|
|
81
|
+
readonly SHUTDOWN: 0x34;
|
|
82
|
+
readonly SNAPSHOT: 0x35;
|
|
83
|
+
readonly PRESENCE: 0x36;
|
|
84
|
+
readonly CONTROL_CHANGED: 0x37;
|
|
85
|
+
readonly METRICS: 0x38;
|
|
86
|
+
readonly CLIPBOARD: 0x39;
|
|
87
|
+
readonly RECORDING_EXPORT: 0x3a;
|
|
88
|
+
readonly COMMAND_JOURNAL: 0x3b;
|
|
89
|
+
readonly METRICS_REQUEST: 0x3c;
|
|
90
|
+
readonly SUSPEND_SESSION: 0x3d;
|
|
91
|
+
readonly RESTART_PTY: 0x3e;
|
|
92
|
+
|
|
93
|
+
// Mcp
|
|
94
|
+
readonly MCP_DISCOVER: 0x40;
|
|
95
|
+
readonly MCP_TOOLS: 0x41;
|
|
96
|
+
readonly MCP_CALL: 0x42;
|
|
97
|
+
readonly MCP_RESULT: 0x43;
|
|
98
|
+
|
|
99
|
+
// Reverse
|
|
100
|
+
readonly REVERSE_REGISTER: 0x50;
|
|
101
|
+
readonly REVERSE_LIST: 0x51;
|
|
102
|
+
readonly REVERSE_PEERS: 0x52;
|
|
103
|
+
readonly REVERSE_CONNECT: 0x53;
|
|
104
|
+
readonly REVERSE_ACCEPT: 0x54;
|
|
105
|
+
readonly REVERSE_REJECT: 0x55;
|
|
106
|
+
|
|
107
|
+
// Framing
|
|
108
|
+
readonly WS_DATA: 0x60;
|
|
109
|
+
|
|
110
|
+
// Gateway
|
|
111
|
+
readonly OPEN_TCP: 0x70;
|
|
112
|
+
readonly OPEN_UDP: 0x71;
|
|
113
|
+
readonly RESOLVE_DNS: 0x72;
|
|
114
|
+
readonly GATEWAY_OK: 0x73;
|
|
115
|
+
readonly GATEWAY_FAIL: 0x74;
|
|
116
|
+
readonly GATEWAY_CLOSE: 0x75;
|
|
117
|
+
readonly INBOUND_OPEN: 0x76;
|
|
118
|
+
readonly INBOUND_ACCEPT: 0x77;
|
|
119
|
+
readonly INBOUND_REJECT: 0x78;
|
|
120
|
+
readonly DNS_RESULT: 0x79;
|
|
121
|
+
readonly LISTEN_REQUEST: 0x7a;
|
|
122
|
+
readonly LISTEN_OK: 0x7b;
|
|
123
|
+
readonly LISTEN_FAIL: 0x7c;
|
|
124
|
+
readonly LISTEN_CLOSE: 0x7d;
|
|
125
|
+
readonly GATEWAY_DATA: 0x7e;
|
|
126
|
+
|
|
127
|
+
// Guest
|
|
128
|
+
readonly GUEST_INVITE: 0x80;
|
|
129
|
+
readonly GUEST_JOIN: 0x81;
|
|
130
|
+
readonly GUEST_REVOKE: 0x82;
|
|
131
|
+
|
|
132
|
+
// Sharing
|
|
133
|
+
readonly SHARE_SESSION: 0x83;
|
|
134
|
+
readonly SHARE_REVOKE: 0x84;
|
|
135
|
+
|
|
136
|
+
// Compression
|
|
137
|
+
readonly COMPRESS_BEGIN: 0x85;
|
|
138
|
+
readonly COMPRESS_ACK: 0x86;
|
|
139
|
+
|
|
140
|
+
// Ratecontrol
|
|
141
|
+
readonly RATE_CONTROL: 0x87;
|
|
142
|
+
readonly RATE_WARNING: 0x88;
|
|
143
|
+
|
|
144
|
+
// Linking
|
|
145
|
+
readonly SESSION_LINK: 0x89;
|
|
146
|
+
readonly SESSION_UNLINK: 0x8a;
|
|
147
|
+
|
|
148
|
+
// Copilot
|
|
149
|
+
readonly COPILOT_ATTACH: 0x8b;
|
|
150
|
+
readonly COPILOT_SUGGEST: 0x8c;
|
|
151
|
+
readonly COPILOT_DETACH: 0x8d;
|
|
152
|
+
|
|
153
|
+
// E2e
|
|
154
|
+
readonly KEY_EXCHANGE: 0x8e;
|
|
155
|
+
readonly ENCRYPTED_FRAME: 0x8f;
|
|
156
|
+
|
|
157
|
+
// Echo
|
|
158
|
+
readonly ECHO_ACK: 0x90;
|
|
159
|
+
readonly ECHO_STATE: 0x91;
|
|
160
|
+
|
|
161
|
+
// Termsync
|
|
162
|
+
readonly TERM_SYNC: 0x92;
|
|
163
|
+
readonly TERM_DIFF: 0x93;
|
|
164
|
+
|
|
165
|
+
// Scaling
|
|
166
|
+
readonly NODE_ANNOUNCE: 0x94;
|
|
167
|
+
readonly NODE_REDIRECT: 0x95;
|
|
168
|
+
|
|
169
|
+
// Principals
|
|
170
|
+
readonly SESSION_GRANT: 0x96;
|
|
171
|
+
readonly SESSION_REVOKE: 0x97;
|
|
172
|
+
|
|
173
|
+
// Filechannel
|
|
174
|
+
readonly FILE_OP: 0x98;
|
|
175
|
+
readonly FILE_RESULT: 0x99;
|
|
176
|
+
readonly FILE_CHUNK: 0x9a;
|
|
177
|
+
|
|
178
|
+
// Policy
|
|
179
|
+
readonly POLICY_EVAL: 0x9b;
|
|
180
|
+
readonly POLICY_RESULT: 0x9c;
|
|
181
|
+
readonly POLICY_UPDATE: 0x9d;
|
|
182
|
+
|
|
183
|
+
// Terminal
|
|
184
|
+
readonly TERMINAL_CONFIG: 0x9e;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** Reverse lookup: message type number to name string. */
|
|
188
|
+
export const MSG_NAMES: Readonly<Record<number, string>>;
|
|
189
|
+
|
|
190
|
+
/** Channel kind constants. */
|
|
191
|
+
export const CHANNEL_KIND: {
|
|
192
|
+
readonly PTY: 'pty';
|
|
193
|
+
readonly EXEC: 'exec';
|
|
194
|
+
readonly META: 'meta';
|
|
195
|
+
readonly FILE: 'file';
|
|
196
|
+
readonly TCP: 'tcp';
|
|
197
|
+
readonly UDP: 'udp';
|
|
198
|
+
readonly JOB: 'job';
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/** Authentication method constants. */
|
|
202
|
+
export const AUTH_METHOD: {
|
|
203
|
+
readonly PUBKEY: 'pubkey';
|
|
204
|
+
readonly PASSWORD: 'password';
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** Protocol version string. */
|
|
208
|
+
export const PROTOCOL_VERSION: 'wsh-v1';
|
|
209
|
+
|
|
210
|
+
/** A wsh protocol control message (all messages have a numeric `type`). */
|
|
211
|
+
export interface WshMessage {
|
|
212
|
+
type: number;
|
|
213
|
+
[key: string]: unknown;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// -- Message constructors (handshake) --
|
|
217
|
+
|
|
218
|
+
export function hello(opts?: {
|
|
219
|
+
username?: string;
|
|
220
|
+
features?: string[];
|
|
221
|
+
authMethod?: string;
|
|
222
|
+
}): WshMessage;
|
|
223
|
+
|
|
224
|
+
export function serverHello(opts?: {
|
|
225
|
+
sessionId?: string;
|
|
226
|
+
features?: string[];
|
|
227
|
+
fingerprints?: string[];
|
|
228
|
+
}): WshMessage;
|
|
229
|
+
|
|
230
|
+
export function challenge(opts?: {
|
|
231
|
+
nonce?: Uint8Array;
|
|
232
|
+
}): WshMessage;
|
|
233
|
+
|
|
234
|
+
export function authMethods(opts?: {
|
|
235
|
+
methods?: string[];
|
|
236
|
+
}): WshMessage;
|
|
237
|
+
|
|
238
|
+
export function auth(opts?: {
|
|
239
|
+
method?: string;
|
|
240
|
+
signature?: Uint8Array;
|
|
241
|
+
publicKey?: Uint8Array;
|
|
242
|
+
password?: string;
|
|
243
|
+
}): WshMessage;
|
|
244
|
+
|
|
245
|
+
export function authOk(opts?: {
|
|
246
|
+
sessionId?: string;
|
|
247
|
+
token?: string;
|
|
248
|
+
ttl?: number;
|
|
249
|
+
}): WshMessage;
|
|
250
|
+
|
|
251
|
+
export function authFail(opts?: {
|
|
252
|
+
reason?: string;
|
|
253
|
+
}): WshMessage;
|
|
254
|
+
|
|
255
|
+
// -- Message constructors (channel) --
|
|
256
|
+
|
|
257
|
+
export function open(opts?: {
|
|
258
|
+
kind?: string;
|
|
259
|
+
command?: string;
|
|
260
|
+
cols?: number;
|
|
261
|
+
rows?: number;
|
|
262
|
+
env?: Record<string, string>;
|
|
263
|
+
}): WshMessage;
|
|
264
|
+
|
|
265
|
+
export function openOk(opts?: {
|
|
266
|
+
channelId?: number;
|
|
267
|
+
streamIds?: number[];
|
|
268
|
+
dataMode?: string;
|
|
269
|
+
capabilities?: string[];
|
|
270
|
+
}): WshMessage;
|
|
271
|
+
|
|
272
|
+
export function openFail(opts?: {
|
|
273
|
+
reason?: string;
|
|
274
|
+
}): WshMessage;
|
|
275
|
+
|
|
276
|
+
export function resize(opts?: {
|
|
277
|
+
channelId?: number;
|
|
278
|
+
cols?: number;
|
|
279
|
+
rows?: number;
|
|
280
|
+
}): WshMessage;
|
|
281
|
+
|
|
282
|
+
export function signal(opts?: {
|
|
283
|
+
channelId?: number;
|
|
284
|
+
signal?: string;
|
|
285
|
+
}): WshMessage;
|
|
286
|
+
|
|
287
|
+
export function exit(opts?: {
|
|
288
|
+
channelId?: number;
|
|
289
|
+
code?: number;
|
|
290
|
+
}): WshMessage;
|
|
291
|
+
|
|
292
|
+
export function close(opts?: {
|
|
293
|
+
channelId?: number;
|
|
294
|
+
}): WshMessage;
|
|
295
|
+
|
|
296
|
+
export function sessionData(opts?: {
|
|
297
|
+
channelId?: number;
|
|
298
|
+
data?: Uint8Array;
|
|
299
|
+
}): WshMessage;
|
|
300
|
+
|
|
301
|
+
// -- Message constructors (transport) --
|
|
302
|
+
|
|
303
|
+
export function error(opts?: {
|
|
304
|
+
code?: number | string;
|
|
305
|
+
message?: string;
|
|
306
|
+
}): WshMessage;
|
|
307
|
+
|
|
308
|
+
export function ping(opts?: {
|
|
309
|
+
id?: number;
|
|
310
|
+
}): WshMessage;
|
|
311
|
+
|
|
312
|
+
export function pong(opts?: {
|
|
313
|
+
id?: number;
|
|
314
|
+
}): WshMessage;
|
|
315
|
+
|
|
316
|
+
// -- Message constructors (session) --
|
|
317
|
+
|
|
318
|
+
export function attach(opts?: {
|
|
319
|
+
sessionId?: string;
|
|
320
|
+
token?: string;
|
|
321
|
+
mode?: string;
|
|
322
|
+
deviceLabel?: string;
|
|
323
|
+
}): WshMessage;
|
|
324
|
+
|
|
325
|
+
export function resume(opts?: {
|
|
326
|
+
sessionId?: string;
|
|
327
|
+
token?: string;
|
|
328
|
+
lastSeq?: number;
|
|
329
|
+
}): WshMessage;
|
|
330
|
+
|
|
331
|
+
export function rename(opts?: {
|
|
332
|
+
sessionId?: string;
|
|
333
|
+
name?: string;
|
|
334
|
+
}): WshMessage;
|
|
335
|
+
|
|
336
|
+
export function idleWarning(opts?: {
|
|
337
|
+
expiresIn?: number;
|
|
338
|
+
}): WshMessage;
|
|
339
|
+
|
|
340
|
+
export function shutdown(opts?: {
|
|
341
|
+
reason?: string;
|
|
342
|
+
retryAfter?: number;
|
|
343
|
+
}): WshMessage;
|
|
344
|
+
|
|
345
|
+
export function snapshot(opts?: {
|
|
346
|
+
label?: string;
|
|
347
|
+
}): WshMessage;
|
|
348
|
+
|
|
349
|
+
export function presence(opts?: {
|
|
350
|
+
attachments?: unknown;
|
|
351
|
+
}): WshMessage;
|
|
352
|
+
|
|
353
|
+
export function controlChanged(opts?: {
|
|
354
|
+
newController?: string;
|
|
355
|
+
}): WshMessage;
|
|
356
|
+
|
|
357
|
+
export function metrics(opts?: {
|
|
358
|
+
cpu?: number;
|
|
359
|
+
memory?: number;
|
|
360
|
+
sessions?: number;
|
|
361
|
+
rtt?: number;
|
|
362
|
+
}): WshMessage;
|
|
363
|
+
|
|
364
|
+
// -- Message constructors (MCP) --
|
|
365
|
+
|
|
366
|
+
export function mcpDiscover(): WshMessage;
|
|
367
|
+
|
|
368
|
+
export function mcpTools(opts?: {
|
|
369
|
+
tools?: unknown[];
|
|
370
|
+
}): WshMessage;
|
|
371
|
+
|
|
372
|
+
export function mcpCall(opts?: {
|
|
373
|
+
tool?: string;
|
|
374
|
+
arguments?: Record<string, unknown>;
|
|
375
|
+
}): WshMessage;
|
|
376
|
+
|
|
377
|
+
export function mcpResult(opts?: {
|
|
378
|
+
result?: unknown;
|
|
379
|
+
}): WshMessage;
|
|
380
|
+
|
|
381
|
+
// -- Message constructors (reverse) --
|
|
382
|
+
|
|
383
|
+
export function reverseRegister(opts?: {
|
|
384
|
+
username?: string;
|
|
385
|
+
capabilities?: string[];
|
|
386
|
+
peerType?: string;
|
|
387
|
+
shellBackend?: string;
|
|
388
|
+
supportsAttach?: boolean;
|
|
389
|
+
supportsReplay?: boolean;
|
|
390
|
+
supportsEcho?: boolean;
|
|
391
|
+
supportsTermSync?: boolean;
|
|
392
|
+
publicKey?: Uint8Array | null;
|
|
393
|
+
}): WshMessage;
|
|
394
|
+
|
|
395
|
+
export function reverseList(): WshMessage;
|
|
396
|
+
|
|
397
|
+
export function reversePeers(opts?: {
|
|
398
|
+
peers?: unknown[];
|
|
399
|
+
}): WshMessage;
|
|
400
|
+
|
|
401
|
+
export function reverseConnect(opts?: {
|
|
402
|
+
targetFingerprint?: string;
|
|
403
|
+
username?: string;
|
|
404
|
+
}): WshMessage;
|
|
405
|
+
|
|
406
|
+
export function reverseAccept(opts?: {
|
|
407
|
+
targetFingerprint?: string;
|
|
408
|
+
username?: string;
|
|
409
|
+
capabilities?: string[];
|
|
410
|
+
peerType?: string;
|
|
411
|
+
shellBackend?: string;
|
|
412
|
+
supportsAttach?: boolean;
|
|
413
|
+
supportsReplay?: boolean;
|
|
414
|
+
supportsEcho?: boolean;
|
|
415
|
+
supportsTermSync?: boolean;
|
|
416
|
+
}): WshMessage;
|
|
417
|
+
|
|
418
|
+
export function reverseReject(opts?: {
|
|
419
|
+
targetFingerprint?: string;
|
|
420
|
+
username?: string;
|
|
421
|
+
reason?: string;
|
|
422
|
+
}): WshMessage;
|
|
423
|
+
|
|
424
|
+
// -- Message constructors (gateway) --
|
|
425
|
+
|
|
426
|
+
export function openTcp(opts?: {
|
|
427
|
+
gatewayId?: number;
|
|
428
|
+
host?: string;
|
|
429
|
+
port?: number;
|
|
430
|
+
}): WshMessage;
|
|
431
|
+
|
|
432
|
+
export function openUdp(opts?: {
|
|
433
|
+
gatewayId?: number;
|
|
434
|
+
host?: string;
|
|
435
|
+
port?: number;
|
|
436
|
+
}): WshMessage;
|
|
437
|
+
|
|
438
|
+
export function resolveDns(opts?: {
|
|
439
|
+
gatewayId?: number;
|
|
440
|
+
name?: string;
|
|
441
|
+
recordType?: string;
|
|
442
|
+
}): WshMessage;
|
|
443
|
+
|
|
444
|
+
export function gatewayOk(opts?: {
|
|
445
|
+
gatewayId?: number;
|
|
446
|
+
resolvedAddr?: string;
|
|
447
|
+
}): WshMessage;
|
|
448
|
+
|
|
449
|
+
export function gatewayFail(opts?: {
|
|
450
|
+
gatewayId?: number;
|
|
451
|
+
code?: number | string;
|
|
452
|
+
message?: string;
|
|
453
|
+
}): WshMessage;
|
|
454
|
+
|
|
455
|
+
export function gatewayClose(opts?: {
|
|
456
|
+
gatewayId?: number;
|
|
457
|
+
reason?: string;
|
|
458
|
+
}): WshMessage;
|
|
459
|
+
|
|
460
|
+
export function inboundOpen(opts?: {
|
|
461
|
+
listenerId?: number;
|
|
462
|
+
channelId?: number;
|
|
463
|
+
peerAddr?: string;
|
|
464
|
+
peerPort?: number;
|
|
465
|
+
}): WshMessage;
|
|
466
|
+
|
|
467
|
+
export function inboundAccept(opts?: {
|
|
468
|
+
channelId?: number;
|
|
469
|
+
gatewayId?: number;
|
|
470
|
+
}): WshMessage;
|
|
471
|
+
|
|
472
|
+
export function inboundReject(opts?: {
|
|
473
|
+
channelId?: number;
|
|
474
|
+
reason?: string;
|
|
475
|
+
}): WshMessage;
|
|
476
|
+
|
|
477
|
+
export function dnsResult(opts?: {
|
|
478
|
+
gatewayId?: number;
|
|
479
|
+
addresses?: string[];
|
|
480
|
+
ttl?: number;
|
|
481
|
+
}): WshMessage;
|
|
482
|
+
|
|
483
|
+
export function listenRequest(opts?: {
|
|
484
|
+
listenerId?: number;
|
|
485
|
+
port?: number;
|
|
486
|
+
bindAddr?: string;
|
|
487
|
+
}): WshMessage;
|
|
488
|
+
|
|
489
|
+
export function listenOk(opts?: {
|
|
490
|
+
listenerId?: number;
|
|
491
|
+
actualPort?: number;
|
|
492
|
+
}): WshMessage;
|
|
493
|
+
|
|
494
|
+
export function listenFail(opts?: {
|
|
495
|
+
listenerId?: number;
|
|
496
|
+
reason?: string;
|
|
497
|
+
}): WshMessage;
|
|
498
|
+
|
|
499
|
+
export function listenClose(opts?: {
|
|
500
|
+
listenerId?: number;
|
|
501
|
+
}): WshMessage;
|
|
502
|
+
|
|
503
|
+
export function gatewayData(opts?: {
|
|
504
|
+
gatewayId?: number;
|
|
505
|
+
data?: Uint8Array;
|
|
506
|
+
}): WshMessage;
|
|
507
|
+
|
|
508
|
+
// -- Utility --
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Get the human-readable name for a message type number.
|
|
512
|
+
*/
|
|
513
|
+
export function msgName(typeNum: number): string;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Validate that a message has a recognized type field.
|
|
517
|
+
*/
|
|
518
|
+
export function isValidMessage(msg: unknown): boolean;
|
|
519
|
+
|
|
520
|
+
// ============================================================================
|
|
521
|
+
// auth.mjs -- Ed25519 crypto
|
|
522
|
+
// ============================================================================
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Generate a new Ed25519 key pair.
|
|
526
|
+
* @param extractable - Whether private key can be exported (default false)
|
|
527
|
+
*/
|
|
528
|
+
export function generateKeyPair(extractable?: boolean): Promise<CryptoKeyPair>;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Export public key as raw 32-byte Ed25519 point.
|
|
532
|
+
*/
|
|
533
|
+
export function exportPublicKeyRaw(publicKey: CryptoKey): Promise<Uint8Array>;
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Export public key in SSH wire format: ssh-ed25519 AAAA...
|
|
537
|
+
*/
|
|
538
|
+
export function exportPublicKeySSH(publicKey: CryptoKey): Promise<string>;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Import a raw 32-byte Ed25519 public key.
|
|
542
|
+
*/
|
|
543
|
+
export function importPublicKeyRaw(raw: Uint8Array): Promise<CryptoKey>;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Export private key as PKCS8 bytes.
|
|
547
|
+
* @param privateKey - Must have been created with extractable=true
|
|
548
|
+
*/
|
|
549
|
+
export function exportPrivateKeyPKCS8(privateKey: CryptoKey): Promise<Uint8Array>;
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Import a PKCS8-encoded Ed25519 private key.
|
|
553
|
+
*/
|
|
554
|
+
export function importPrivateKeyPKCS8(pkcs8: Uint8Array, extractable?: boolean): Promise<CryptoKey>;
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Sign data with an Ed25519 private key.
|
|
558
|
+
* @returns 64-byte signature
|
|
559
|
+
*/
|
|
560
|
+
export function sign(privateKey: CryptoKey, data: Uint8Array): Promise<Uint8Array>;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Verify an Ed25519 signature.
|
|
564
|
+
*/
|
|
565
|
+
export function verify(publicKey: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Build the authentication transcript hash for challenge-response signing.
|
|
569
|
+
*
|
|
570
|
+
* transcript = SHA-256("wsh-v1\0" || session_id || nonce || channel_binding)
|
|
571
|
+
*
|
|
572
|
+
* @returns 32-byte SHA-256 hash
|
|
573
|
+
*/
|
|
574
|
+
export function buildTranscript(
|
|
575
|
+
sessionId: string,
|
|
576
|
+
nonce: Uint8Array,
|
|
577
|
+
channelBinding?: Uint8Array,
|
|
578
|
+
): Promise<Uint8Array>;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Perform the full client-side auth signing:
|
|
582
|
+
* 1. Build transcript hash
|
|
583
|
+
* 2. Sign with private key
|
|
584
|
+
* 3. Export public key for sending to server
|
|
585
|
+
*/
|
|
586
|
+
export function signChallenge(
|
|
587
|
+
privateKey: CryptoKey,
|
|
588
|
+
publicKey: CryptoKey,
|
|
589
|
+
sessionId: string,
|
|
590
|
+
nonce: Uint8Array,
|
|
591
|
+
channelBinding?: Uint8Array,
|
|
592
|
+
): Promise<{ signature: Uint8Array; publicKeyRaw: Uint8Array }>;
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Server-side: verify a client's challenge response.
|
|
596
|
+
*/
|
|
597
|
+
export function verifyChallenge(
|
|
598
|
+
publicKey: CryptoKey,
|
|
599
|
+
signature: Uint8Array,
|
|
600
|
+
sessionId: string,
|
|
601
|
+
nonce: Uint8Array,
|
|
602
|
+
channelBinding?: Uint8Array,
|
|
603
|
+
): Promise<boolean>;
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Compute the SHA-256 fingerprint of a raw public key.
|
|
607
|
+
* @returns hex-encoded fingerprint
|
|
608
|
+
*/
|
|
609
|
+
export function fingerprint(publicKeyRaw: Uint8Array): Promise<string>;
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get the shortest unique prefix of a fingerprint within a set.
|
|
613
|
+
*/
|
|
614
|
+
export function shortFingerprint(
|
|
615
|
+
fp: string,
|
|
616
|
+
allFingerprints?: string[],
|
|
617
|
+
minLen?: number,
|
|
618
|
+
): string;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Generate a random 32-byte nonce.
|
|
622
|
+
*/
|
|
623
|
+
export function generateNonce(): Uint8Array;
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Parse an SSH public key string ("ssh-ed25519 AAAA... comment").
|
|
627
|
+
*/
|
|
628
|
+
export function parseSSHPublicKey(line: string): {
|
|
629
|
+
type: string;
|
|
630
|
+
data: Uint8Array;
|
|
631
|
+
comment: string;
|
|
632
|
+
} | null;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Extract the raw 32-byte Ed25519 public key from SSH wire format.
|
|
636
|
+
*/
|
|
637
|
+
export function extractRawFromSSHWire(wireData: Uint8Array): Uint8Array;
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Decode a base64-encoded string into bytes.
|
|
641
|
+
*/
|
|
642
|
+
export function base64Decode(str: string): Uint8Array;
|
|
643
|
+
|
|
644
|
+
// ============================================================================
|
|
645
|
+
// transport.mjs -- Abstract transport + WebTransport implementation
|
|
646
|
+
// ============================================================================
|
|
647
|
+
|
|
648
|
+
/** A bidirectional data stream handle. */
|
|
649
|
+
export interface WshStream {
|
|
650
|
+
readable: ReadableStream<Uint8Array>;
|
|
651
|
+
writable: WritableStream<Uint8Array>;
|
|
652
|
+
id: number;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Abstract transport for the wsh protocol.
|
|
657
|
+
*
|
|
658
|
+
* Subclasses must implement:
|
|
659
|
+
* - _doConnect(url)
|
|
660
|
+
* - _doClose()
|
|
661
|
+
* - _doSendControl(msg)
|
|
662
|
+
* - _doOpenStream()
|
|
663
|
+
*/
|
|
664
|
+
export class WshTransport {
|
|
665
|
+
/** Callback for incoming control messages. */
|
|
666
|
+
onControl: ((msg: WshMessage) => void) | null;
|
|
667
|
+
|
|
668
|
+
/** Callback for server-initiated streams. */
|
|
669
|
+
onStreamOpen: ((stream: WshStream) => void) | null;
|
|
670
|
+
|
|
671
|
+
/** Callback when transport closes. */
|
|
672
|
+
onClose: (() => void) | null;
|
|
673
|
+
|
|
674
|
+
/** Callback on transport error. */
|
|
675
|
+
onError: ((err: Error) => void) | null;
|
|
676
|
+
|
|
677
|
+
/** Current transport state. */
|
|
678
|
+
readonly state: 'disconnected' | 'connecting' | 'connected' | 'closed';
|
|
679
|
+
|
|
680
|
+
/** Connect to a wsh server. */
|
|
681
|
+
connect(url: string): Promise<void>;
|
|
682
|
+
|
|
683
|
+
/** Gracefully close the transport. */
|
|
684
|
+
close(): Promise<void>;
|
|
685
|
+
|
|
686
|
+
/** Send a control message (CBOR-framed). */
|
|
687
|
+
sendControl(msg: object): Promise<void>;
|
|
688
|
+
|
|
689
|
+
/** Open a new bidirectional data stream. */
|
|
690
|
+
openStream(): Promise<WshStream>;
|
|
691
|
+
|
|
692
|
+
/** @protected Update internal state. */
|
|
693
|
+
protected _setState(s: string): void;
|
|
694
|
+
|
|
695
|
+
/** @protected Emit a control message to the callback. */
|
|
696
|
+
protected _emitControl(msg: WshMessage): void;
|
|
697
|
+
|
|
698
|
+
/** @protected Emit a new server-initiated stream. */
|
|
699
|
+
protected _emitStreamOpen(stream: WshStream): void;
|
|
700
|
+
|
|
701
|
+
/** @protected Emit close event. */
|
|
702
|
+
protected _emitClose(): void;
|
|
703
|
+
|
|
704
|
+
/** @protected Emit error event. */
|
|
705
|
+
protected _emitError(err: Error): void;
|
|
706
|
+
|
|
707
|
+
/** @protected Must be overridden by subclasses. */
|
|
708
|
+
protected _doConnect(url: string): Promise<void>;
|
|
709
|
+
|
|
710
|
+
/** @protected Must be overridden by subclasses. */
|
|
711
|
+
protected _doClose(): Promise<void>;
|
|
712
|
+
|
|
713
|
+
/** @protected Must be overridden by subclasses. */
|
|
714
|
+
protected _doSendControl(msg: object): Promise<void>;
|
|
715
|
+
|
|
716
|
+
/** @protected Must be overridden by subclasses. */
|
|
717
|
+
protected _doOpenStream(): Promise<WshStream>;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* wsh transport over the WebTransport API.
|
|
722
|
+
*
|
|
723
|
+
* - The first bidirectional stream becomes the control stream.
|
|
724
|
+
* - Control messages use length-prefixed CBOR (frameEncode / FrameDecoder).
|
|
725
|
+
* - Subsequent streams carry raw byte data (no framing).
|
|
726
|
+
* - Server-initiated streams are surfaced via onStreamOpen.
|
|
727
|
+
*/
|
|
728
|
+
export class WebTransportTransport extends WshTransport {}
|
|
729
|
+
|
|
730
|
+
// ============================================================================
|
|
731
|
+
// transport-ws.mjs -- WebSocket transport
|
|
732
|
+
// ============================================================================
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* wsh transport over a single WebSocket with multiplexed virtual streams.
|
|
736
|
+
*
|
|
737
|
+
* Provides the same interface as WebTransportTransport so that upper
|
|
738
|
+
* layers (session, client) work identically over either transport.
|
|
739
|
+
*/
|
|
740
|
+
export class WebSocketTransport extends WshTransport {}
|
|
741
|
+
|
|
742
|
+
// ============================================================================
|
|
743
|
+
// session.mjs -- WshSession
|
|
744
|
+
// ============================================================================
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Manages a single PTY or exec channel over a wsh transport.
|
|
748
|
+
*
|
|
749
|
+
* Each session owns a pair of data streams (stdin/stdout) and receives
|
|
750
|
+
* control messages (EXIT, CLOSE, RESIZE) dispatched by the parent WshClient.
|
|
751
|
+
*/
|
|
752
|
+
export class WshSession {
|
|
753
|
+
/** Unique session identifier (typically matches channelId). */
|
|
754
|
+
id: string;
|
|
755
|
+
|
|
756
|
+
/** Channel kind. */
|
|
757
|
+
kind: 'pty' | 'exec';
|
|
758
|
+
|
|
759
|
+
/** Channel identifier assigned by the server. */
|
|
760
|
+
channelId: number;
|
|
761
|
+
|
|
762
|
+
/** Current session state. */
|
|
763
|
+
readonly state: 'opening' | 'active' | 'closed';
|
|
764
|
+
|
|
765
|
+
/** Exit code, if the process has exited. */
|
|
766
|
+
readonly exitCode: number | null;
|
|
767
|
+
|
|
768
|
+
/** Session data plane. */
|
|
769
|
+
readonly dataMode: 'stream' | 'virtual';
|
|
770
|
+
|
|
771
|
+
/** Session capabilities advertised by OPEN_OK. */
|
|
772
|
+
readonly capabilities: string[];
|
|
773
|
+
|
|
774
|
+
/** Called when stdout/stderr data arrives. */
|
|
775
|
+
onData: ((data: Uint8Array) => void) | null;
|
|
776
|
+
|
|
777
|
+
/** Called when the remote process exits. */
|
|
778
|
+
onExit: ((code: number) => void) | null;
|
|
779
|
+
|
|
780
|
+
/** Called when the session is fully closed. */
|
|
781
|
+
onClose: (() => void) | null;
|
|
782
|
+
|
|
783
|
+
/** Called when the remote peer acknowledges speculative local echo. */
|
|
784
|
+
onEchoAck: ((payload: Record<string, unknown>) => void) | null;
|
|
785
|
+
|
|
786
|
+
/** Called when the remote peer reports current echo/cursor state. */
|
|
787
|
+
onEchoState: ((payload: Record<string, unknown>) => void) | null;
|
|
788
|
+
|
|
789
|
+
/** Called when the remote peer emits a terminal sync hash. */
|
|
790
|
+
onTermSync: ((payload: Record<string, unknown>) => void) | null;
|
|
791
|
+
|
|
792
|
+
/** Called when the remote peer emits an incremental terminal diff. */
|
|
793
|
+
onTermDiff: ((payload: Record<string, unknown>) => void) | null;
|
|
794
|
+
|
|
795
|
+
/** Last echo acknowledgement received for the session. */
|
|
796
|
+
readonly lastEchoAck: Record<string, unknown> | null;
|
|
797
|
+
|
|
798
|
+
/** Last echo state received for the session. */
|
|
799
|
+
readonly lastEchoState: Record<string, unknown> | null;
|
|
800
|
+
|
|
801
|
+
/** Last terminal sync received for the session. */
|
|
802
|
+
readonly lastTermSync: Record<string, unknown> | null;
|
|
803
|
+
|
|
804
|
+
/** Last terminal diff received for the session. */
|
|
805
|
+
readonly lastTermDiff: Record<string, unknown> | null;
|
|
806
|
+
|
|
807
|
+
constructor(
|
|
808
|
+
transport: WshTransport,
|
|
809
|
+
channelId: number,
|
|
810
|
+
streamIds: object,
|
|
811
|
+
kind: 'pty' | 'exec',
|
|
812
|
+
opts?: {
|
|
813
|
+
dataMode?: 'stream' | 'virtual';
|
|
814
|
+
capabilities?: string[];
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Write data to the session's stdin stream.
|
|
820
|
+
* Accepts a Uint8Array for raw bytes or a string (UTF-8 encoded).
|
|
821
|
+
*/
|
|
822
|
+
write(data: Uint8Array | string): Promise<void>;
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Request the remote PTY to resize.
|
|
826
|
+
*/
|
|
827
|
+
resize(cols: number, rows: number): Promise<void>;
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Send a signal to the remote process (e.g. 'SIGINT', 'SIGTERM').
|
|
831
|
+
*/
|
|
832
|
+
signal(sig: string): Promise<void>;
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Close this session. Sends a CLOSE control message and tears down
|
|
836
|
+
* the data streams. Safe to call multiple times.
|
|
837
|
+
*/
|
|
838
|
+
close(): Promise<void>;
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Bind the raw data streams to this session and start the read pump.
|
|
842
|
+
* Called by WshClient after stream setup.
|
|
843
|
+
* @internal
|
|
844
|
+
*/
|
|
845
|
+
_bind(readable: ReadableStream<Uint8Array>, writable: WritableStream<Uint8Array>): void;
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Activate the message-backed data plane for a virtual session.
|
|
849
|
+
* @internal
|
|
850
|
+
*/
|
|
851
|
+
_activateVirtual(sendControl: (msg: WshMessage) => Promise<void>): void;
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Handle a control message dispatched by WshClient for this channel.
|
|
855
|
+
* @internal
|
|
856
|
+
*/
|
|
857
|
+
_handleControlMessage(msg: WshMessage): void;
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Continuously read from the stdout data stream and invoke onData.
|
|
861
|
+
* @internal
|
|
862
|
+
*/
|
|
863
|
+
_pumpDataStream(): Promise<void>;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export function normalizeSessionData(data: Uint8Array | string): Uint8Array;
|
|
867
|
+
|
|
868
|
+
export class WshVirtualSessionBackend {
|
|
869
|
+
constructor(sendControl: (msg: WshMessage) => Promise<void>, channelId: number);
|
|
870
|
+
write(data: Uint8Array | string): Promise<void>;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ============================================================================
|
|
874
|
+
// client.mjs -- WshClient
|
|
875
|
+
// ============================================================================
|
|
876
|
+
|
|
877
|
+
/** Options for WshClient.connect(). */
|
|
878
|
+
export interface WshConnectOptions {
|
|
879
|
+
username: string;
|
|
880
|
+
keyPair?: CryptoKeyPair;
|
|
881
|
+
password?: string;
|
|
882
|
+
transport?: 'wt' | 'ws';
|
|
883
|
+
timeout?: number;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** Options for WshClient.openSession(). */
|
|
887
|
+
export interface WshOpenSessionOptions {
|
|
888
|
+
type?: 'pty' | 'exec';
|
|
889
|
+
command?: string;
|
|
890
|
+
cols?: number;
|
|
891
|
+
rows?: number;
|
|
892
|
+
env?: Record<string, string>;
|
|
893
|
+
timeout?: number;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/** Options for WshClient.attachSession(). */
|
|
897
|
+
export interface WshAttachSessionOptions {
|
|
898
|
+
readOnly?: boolean;
|
|
899
|
+
timeout?: number;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/** Options for WshClient.connectReverse(). */
|
|
903
|
+
export interface WshConnectReverseOptions {
|
|
904
|
+
username: string;
|
|
905
|
+
keyPair?: CryptoKeyPair;
|
|
906
|
+
password?: string;
|
|
907
|
+
expose?: {
|
|
908
|
+
shell?: boolean;
|
|
909
|
+
exec?: boolean;
|
|
910
|
+
fs?: boolean;
|
|
911
|
+
tools?: boolean;
|
|
912
|
+
};
|
|
913
|
+
peerType?: string;
|
|
914
|
+
shellBackend?: string;
|
|
915
|
+
supportsAttach?: boolean;
|
|
916
|
+
supportsReplay?: boolean;
|
|
917
|
+
supportsEcho?: boolean;
|
|
918
|
+
supportsTermSync?: boolean;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/** Options for WshClient.exec(). */
|
|
922
|
+
export interface WshExecOptions {
|
|
923
|
+
username: string;
|
|
924
|
+
keyPair?: CryptoKeyPair;
|
|
925
|
+
password?: string;
|
|
926
|
+
timeout?: number;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Result from WshClient.exec(). */
|
|
930
|
+
export interface WshExecResult {
|
|
931
|
+
stdout: Uint8Array;
|
|
932
|
+
exitCode: number;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** Session list entry. */
|
|
936
|
+
export interface WshSessionInfo {
|
|
937
|
+
channelId: number;
|
|
938
|
+
kind: string;
|
|
939
|
+
state: string;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/** Peer information from reverse list. */
|
|
943
|
+
export interface WshPeerInfo {
|
|
944
|
+
fingerprint: string;
|
|
945
|
+
fingerprint_short: string;
|
|
946
|
+
username: string;
|
|
947
|
+
capabilities: string[];
|
|
948
|
+
peer_type: string;
|
|
949
|
+
shell_backend: string;
|
|
950
|
+
source: string;
|
|
951
|
+
supports_attach: boolean;
|
|
952
|
+
supports_replay: boolean;
|
|
953
|
+
supports_echo: boolean;
|
|
954
|
+
supports_term_sync: boolean;
|
|
955
|
+
last_seen: number | null;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* WshClient -- manages a wsh connection, authentication, and multiple sessions.
|
|
960
|
+
*
|
|
961
|
+
* Handles the full lifecycle: transport selection, handshake, challenge-response
|
|
962
|
+
* or password auth, channel multiplexing, ping/pong keepalive, and teardown.
|
|
963
|
+
*
|
|
964
|
+
* Supports forward connections (client opens sessions on a remote server) and
|
|
965
|
+
* reverse mode (client registers as a peer for incoming connections).
|
|
966
|
+
*/
|
|
967
|
+
export class WshClient {
|
|
968
|
+
/** Current client state. */
|
|
969
|
+
readonly state: 'disconnected' | 'connecting' | 'connected' | 'authenticated' | 'closed';
|
|
970
|
+
|
|
971
|
+
/** Server-assigned session ID. */
|
|
972
|
+
readonly sessionId: string | null;
|
|
973
|
+
|
|
974
|
+
/** Read-only view of active sessions. */
|
|
975
|
+
readonly sessions: Map<number, WshSession>;
|
|
976
|
+
|
|
977
|
+
/** Server-advertised features from SERVER_HELLO. */
|
|
978
|
+
readonly features: string[];
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Low-level transport reference.
|
|
982
|
+
* Prefer higher-level methods for normal use.
|
|
983
|
+
*/
|
|
984
|
+
readonly _transport: WshTransport | null;
|
|
985
|
+
|
|
986
|
+
/** Called when the connection is closed (intentionally or otherwise). */
|
|
987
|
+
onClose: (() => void) | null;
|
|
988
|
+
|
|
989
|
+
/** Called on connection-level errors. */
|
|
990
|
+
onError: ((err: Error) => void) | null;
|
|
991
|
+
|
|
992
|
+
/** Called when a reverse-connect request arrives (reverse mode only). */
|
|
993
|
+
onReverseConnect: ((msg: WshMessage) => void) | null;
|
|
994
|
+
|
|
995
|
+
/** Called when a clipboard sync message arrives (OSC 52). */
|
|
996
|
+
onClipboard: ((msg: WshMessage) => void) | null;
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Called when a relay-forwarded message arrives from a remote peer.
|
|
1000
|
+
* In reverse mode, the relay bridge forwards messages from the CLI peer
|
|
1001
|
+
* to this browser client.
|
|
1002
|
+
*/
|
|
1003
|
+
onRelayMessage: ((msg: WshMessage) => void) | null;
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Called when a gateway-subsystem control message arrives (opcodes 0x70-0x7f).
|
|
1007
|
+
*/
|
|
1008
|
+
onGatewayMessage: ((msg: WshMessage) => void) | null;
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Check if the server advertised a specific feature.
|
|
1012
|
+
*/
|
|
1013
|
+
hasFeature(name: string): boolean;
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Connect to a wsh server, authenticate, and return the session ID.
|
|
1017
|
+
*/
|
|
1018
|
+
connect(url: string, opts?: WshConnectOptions): Promise<string>;
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Open a new PTY or exec session on the remote server.
|
|
1022
|
+
*/
|
|
1023
|
+
openSession(opts?: WshOpenSessionOptions): Promise<WshSession>;
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* List locally tracked sessions with their current state.
|
|
1027
|
+
*/
|
|
1028
|
+
listSessions(): WshSessionInfo[];
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Attach to an existing remote session (collaborative or read-only).
|
|
1032
|
+
*/
|
|
1033
|
+
attachSession(targetSessionId: string, opts?: WshAttachSessionOptions): Promise<WshMessage>;
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Resume a previously disconnected session.
|
|
1037
|
+
*/
|
|
1038
|
+
resumeSession(targetSessionId: string, token: string, opts?: { timeout?: number }): Promise<WshMessage>;
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Gracefully disconnect: close all sessions and the transport.
|
|
1042
|
+
*/
|
|
1043
|
+
disconnect(): Promise<void>;
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* One-shot command execution: connect, authenticate, run a command,
|
|
1047
|
+
* collect all output, disconnect, and return the result.
|
|
1048
|
+
*/
|
|
1049
|
+
static exec(url: string, command: string, opts?: WshExecOptions): Promise<WshExecResult>;
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Create a client with a pre-configured transport instance.
|
|
1053
|
+
* Useful for WebSocket transport or custom transports.
|
|
1054
|
+
*/
|
|
1055
|
+
static withTransport(transport: WshTransport): WshClient;
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Connect in reverse mode: register as a peer that can accept incoming
|
|
1059
|
+
* connections from other clients.
|
|
1060
|
+
*/
|
|
1061
|
+
connectReverse(url: string, opts?: WshConnectReverseOptions): Promise<string>;
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* List peers registered on the relay server.
|
|
1065
|
+
*/
|
|
1066
|
+
listPeers(timeout?: number): Promise<WshPeerInfo[]>;
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Initiate a reverse connection to a registered peer.
|
|
1070
|
+
*/
|
|
1071
|
+
reverseConnectTo(targetFingerprint: string, timeout?: number): Promise<void>;
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Send a control message over the authenticated relay connection.
|
|
1075
|
+
*/
|
|
1076
|
+
sendRelayControl(msg: WshMessage): Promise<void>;
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Upload a blob to a remote path.
|
|
1080
|
+
*/
|
|
1081
|
+
upload(blob: Blob | Uint8Array, remotePath: string, opts?: { onProgress?: (bytesSent: number) => void }): Promise<void>;
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Download a file from a remote path.
|
|
1085
|
+
*/
|
|
1086
|
+
download(remotePath: string): Promise<Uint8Array>;
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Discover MCP tools available on the remote server.
|
|
1090
|
+
*/
|
|
1091
|
+
discoverTools(timeout?: number): Promise<unknown[]>;
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Call an MCP tool on the remote server.
|
|
1095
|
+
*/
|
|
1096
|
+
callTool(name: string, args: Record<string, unknown>, timeout?: number): Promise<unknown>;
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Connect using an externally created transport.
|
|
1100
|
+
* Use this when you need WebSocket or a custom transport.
|
|
1101
|
+
*/
|
|
1102
|
+
connectWithTransport(transport: WshTransport, url: string, opts: WshConnectOptions): Promise<string>;
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Check whether a message type is relay-forwardable.
|
|
1106
|
+
* @internal
|
|
1107
|
+
*/
|
|
1108
|
+
_isRelayForwardable(type: number): boolean;
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Get the next channel ID.
|
|
1112
|
+
* @internal
|
|
1113
|
+
*/
|
|
1114
|
+
_nextChannelId(): number;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// ============================================================================
|
|
1118
|
+
// keystore.mjs -- WshKeyStore
|
|
1119
|
+
// ============================================================================
|
|
1120
|
+
|
|
1121
|
+
/** Key entry from the key store. */
|
|
1122
|
+
export interface WshKeyEntry {
|
|
1123
|
+
name: string;
|
|
1124
|
+
publicKey: CryptoKey;
|
|
1125
|
+
privateKey: CryptoKey;
|
|
1126
|
+
createdAt: number;
|
|
1127
|
+
fingerprint: string;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/** Key listing entry (without CryptoKey objects). */
|
|
1131
|
+
export interface WshKeyListEntry {
|
|
1132
|
+
name: string;
|
|
1133
|
+
fingerprint: string;
|
|
1134
|
+
createdAt: number;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/** Result of key generation. */
|
|
1138
|
+
export interface WshKeyGenerateResult {
|
|
1139
|
+
name: string;
|
|
1140
|
+
fingerprint: string;
|
|
1141
|
+
publicKeySSH: string;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/** Result of backup operation. */
|
|
1145
|
+
export interface WshBackupResult {
|
|
1146
|
+
backedUp: number;
|
|
1147
|
+
skipped: number;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/** Result of restore operation. */
|
|
1151
|
+
export interface WshRestoreResult {
|
|
1152
|
+
restored: number;
|
|
1153
|
+
skipped: number;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* WshKeyStore -- Ed25519 key management via IndexedDB with OPFS encrypted backup.
|
|
1158
|
+
*
|
|
1159
|
+
* Keys are stored as non-extractable CryptoKey objects in IndexedDB for
|
|
1160
|
+
* day-to-day use. An optional passphrase-encrypted backup can be written
|
|
1161
|
+
* to OPFS (requires extractable keys at generation time).
|
|
1162
|
+
*/
|
|
1163
|
+
export class WshKeyStore {
|
|
1164
|
+
/** @internal */
|
|
1165
|
+
_db: IDBDatabase | null;
|
|
1166
|
+
|
|
1167
|
+
/** Open or create the IndexedDB database. */
|
|
1168
|
+
open(): Promise<void>;
|
|
1169
|
+
|
|
1170
|
+
/** Close the database connection. */
|
|
1171
|
+
close(): void;
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Generate a new Ed25519 key pair and store it in IndexedDB.
|
|
1175
|
+
* @param name - Key name / identifier (default 'default')
|
|
1176
|
+
* @param opts.extractable - Whether the private key can be exported (default false)
|
|
1177
|
+
*/
|
|
1178
|
+
generateKey(name?: string, opts?: { extractable?: boolean }): Promise<WshKeyGenerateResult>;
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Get a stored key entry by name.
|
|
1182
|
+
*/
|
|
1183
|
+
getKey(name: string): Promise<WshKeyEntry | null>;
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* List all stored key names and fingerprints.
|
|
1187
|
+
*/
|
|
1188
|
+
listKeys(): Promise<WshKeyListEntry[]>;
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Delete a key by name.
|
|
1192
|
+
* @returns true if the key existed and was deleted
|
|
1193
|
+
*/
|
|
1194
|
+
deleteKey(name: string): Promise<boolean>;
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Export a public key as an SSH-formatted string.
|
|
1198
|
+
* @returns ssh-ed25519 AAAA... format
|
|
1199
|
+
*/
|
|
1200
|
+
exportPublicKey(name: string): Promise<string>;
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Get the CryptoKey pair for a named key.
|
|
1204
|
+
*/
|
|
1205
|
+
getKeyPair(name: string): Promise<{ publicKey: CryptoKey; privateKey: CryptoKey }>;
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Export all extractable keys, encrypt with a passphrase, and store in OPFS.
|
|
1209
|
+
*/
|
|
1210
|
+
backup(passphrase: string): Promise<WshBackupResult>;
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Restore keys from an OPFS encrypted backup.
|
|
1214
|
+
*/
|
|
1215
|
+
restore(passphrase: string): Promise<WshRestoreResult>;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// ============================================================================
|
|
1219
|
+
// file-transfer.mjs -- WshFileTransfer
|
|
1220
|
+
// ============================================================================
|
|
1221
|
+
|
|
1222
|
+
/** Upload progress callback payload. */
|
|
1223
|
+
export interface WshFileUploadProgress {
|
|
1224
|
+
sent: number;
|
|
1225
|
+
total: number;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/** Download progress callback payload. */
|
|
1229
|
+
export interface WshFileDownloadProgress {
|
|
1230
|
+
received: number;
|
|
1231
|
+
total?: number;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/** Upload result. */
|
|
1235
|
+
export interface WshFileUploadResult {
|
|
1236
|
+
success: boolean;
|
|
1237
|
+
bytesTransferred: number;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/** File listing entry. */
|
|
1241
|
+
export interface WshFileListEntry {
|
|
1242
|
+
name: string;
|
|
1243
|
+
size: number;
|
|
1244
|
+
modified: string;
|
|
1245
|
+
type: 'file' | 'directory' | 'symlink' | 'device' | 'pipe' | 'socket';
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* WshFileTransfer -- dedicated stream file transfer (scp-like) over a wsh connection.
|
|
1250
|
+
*
|
|
1251
|
+
* Uploads and downloads files using the wsh channel protocol with 'file' kind
|
|
1252
|
+
* channels. Data flows over a dedicated bidirectional stream in 64KB chunks.
|
|
1253
|
+
*/
|
|
1254
|
+
export class WshFileTransfer {
|
|
1255
|
+
constructor(client: object);
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Upload data to a remote path.
|
|
1259
|
+
*/
|
|
1260
|
+
upload(
|
|
1261
|
+
data: Uint8Array | ArrayBuffer,
|
|
1262
|
+
remotePath: string,
|
|
1263
|
+
opts?: {
|
|
1264
|
+
onProgress?: (progress: WshFileUploadProgress) => void;
|
|
1265
|
+
timeout?: number;
|
|
1266
|
+
},
|
|
1267
|
+
): Promise<WshFileUploadResult>;
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Download a file from a remote path.
|
|
1271
|
+
*/
|
|
1272
|
+
download(
|
|
1273
|
+
remotePath: string,
|
|
1274
|
+
opts?: {
|
|
1275
|
+
onProgress?: (progress: WshFileDownloadProgress) => void;
|
|
1276
|
+
timeout?: number;
|
|
1277
|
+
},
|
|
1278
|
+
): Promise<Uint8Array>;
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* List files at a remote path.
|
|
1282
|
+
*/
|
|
1283
|
+
list(remotePath: string): Promise<WshFileListEntry[]>;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ============================================================================
|
|
1287
|
+
// recording.mjs -- SessionRecorder + SessionPlayer
|
|
1288
|
+
// ============================================================================
|
|
1289
|
+
|
|
1290
|
+
/** Event type for session recording. */
|
|
1291
|
+
export type SessionRecordingEventType = 'input' | 'output' | 'resize' | 'open' | 'exit';
|
|
1292
|
+
|
|
1293
|
+
/** A single recorded event entry. */
|
|
1294
|
+
export interface SessionRecordingEntry {
|
|
1295
|
+
t: number;
|
|
1296
|
+
type: string;
|
|
1297
|
+
data: unknown;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/** Asciicast v2 export format. */
|
|
1301
|
+
export interface SessionRecordingJSON {
|
|
1302
|
+
version: 2;
|
|
1303
|
+
width: number;
|
|
1304
|
+
height: number;
|
|
1305
|
+
timestamp: number;
|
|
1306
|
+
env: Record<string, string>;
|
|
1307
|
+
sessionId: string;
|
|
1308
|
+
events: Array<[number, string, string]>;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Records PTY I/O events with relative timestamps for session replay.
|
|
1313
|
+
*/
|
|
1314
|
+
export class SessionRecorder {
|
|
1315
|
+
/** Unique session identifier. */
|
|
1316
|
+
sessionId: string;
|
|
1317
|
+
|
|
1318
|
+
/** Unix epoch ms when recording started. */
|
|
1319
|
+
startTime: number;
|
|
1320
|
+
|
|
1321
|
+
/** Recorded event entries. */
|
|
1322
|
+
entries: SessionRecordingEntry[];
|
|
1323
|
+
|
|
1324
|
+
/** Total duration in milliseconds. */
|
|
1325
|
+
readonly duration: number;
|
|
1326
|
+
|
|
1327
|
+
/** Number of recorded events. */
|
|
1328
|
+
readonly length: number;
|
|
1329
|
+
|
|
1330
|
+
constructor(sessionId: string, opts?: { width?: number; height?: number });
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Record an event.
|
|
1334
|
+
*/
|
|
1335
|
+
record(type: SessionRecordingEventType | string, data: unknown): void;
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Export the recording as a JSON-serializable object (asciicast v2 format).
|
|
1339
|
+
*/
|
|
1340
|
+
toJSON(): SessionRecordingJSON;
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Import a recording from JSON.
|
|
1344
|
+
*/
|
|
1345
|
+
static fromJSON(json: SessionRecordingJSON | string): SessionRecorder;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/** Playback controller interface. */
|
|
1349
|
+
export interface SessionPlaybackController {
|
|
1350
|
+
/** Pause playback. */
|
|
1351
|
+
pause(): void;
|
|
1352
|
+
|
|
1353
|
+
/** Resume playback after pause. */
|
|
1354
|
+
resume(): void;
|
|
1355
|
+
|
|
1356
|
+
/** Stop playback entirely. */
|
|
1357
|
+
stop(): void;
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* Seek to a specific time in the recording.
|
|
1361
|
+
* Immediately replays all output events up to the target time,
|
|
1362
|
+
* then resumes normal timed playback from that point.
|
|
1363
|
+
* @param timeMs - Target time in milliseconds from start
|
|
1364
|
+
*/
|
|
1365
|
+
seek(timeMs: number): void;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** Recording metadata. */
|
|
1369
|
+
export interface SessionPlayerMetadata {
|
|
1370
|
+
width: number;
|
|
1371
|
+
height: number;
|
|
1372
|
+
duration: number;
|
|
1373
|
+
eventCount: number;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Replays a recorded session with original timing.
|
|
1378
|
+
*/
|
|
1379
|
+
export class SessionPlayer {
|
|
1380
|
+
constructor(recording: SessionRecorder | SessionRecordingJSON);
|
|
1381
|
+
|
|
1382
|
+
/** Get the recording metadata. */
|
|
1383
|
+
readonly metadata: SessionPlayerMetadata;
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Replay the recording with original timing.
|
|
1387
|
+
*/
|
|
1388
|
+
play(
|
|
1389
|
+
onData: (data: string) => void,
|
|
1390
|
+
opts?: {
|
|
1391
|
+
speed?: number;
|
|
1392
|
+
onEvent?: (type: string, data: unknown) => void;
|
|
1393
|
+
},
|
|
1394
|
+
): SessionPlaybackController;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// ============================================================================
|
|
1398
|
+
// mcp-bridge.mjs -- WshMcpBridge
|
|
1399
|
+
// ============================================================================
|
|
1400
|
+
|
|
1401
|
+
/** MCP tool specification. */
|
|
1402
|
+
export interface WshMcpToolSpec {
|
|
1403
|
+
name: string;
|
|
1404
|
+
description: string;
|
|
1405
|
+
parameters: Record<string, unknown>;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/** MCP call result. */
|
|
1409
|
+
export interface WshMcpCallResult {
|
|
1410
|
+
success: boolean;
|
|
1411
|
+
output: unknown;
|
|
1412
|
+
error?: string;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* WshMcpBridge -- bridges remote MCP tools over the wsh meta channel.
|
|
1417
|
+
*
|
|
1418
|
+
* Sends MCP_DISCOVER and MCP_CALL messages through the wsh control channel,
|
|
1419
|
+
* enabling a wsh client to discover and invoke MCP tools hosted on the
|
|
1420
|
+
* remote server.
|
|
1421
|
+
*/
|
|
1422
|
+
export class WshMcpBridge {
|
|
1423
|
+
constructor(client: object);
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* Discover available MCP tools on the remote server.
|
|
1427
|
+
*/
|
|
1428
|
+
discover(opts?: { timeout?: number }): Promise<WshMcpToolSpec[]>;
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Call a remote MCP tool by name.
|
|
1432
|
+
*/
|
|
1433
|
+
call(
|
|
1434
|
+
toolName: string,
|
|
1435
|
+
args?: Record<string, unknown>,
|
|
1436
|
+
opts?: { timeout?: number },
|
|
1437
|
+
): Promise<WshMcpCallResult>;
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Get cached tool specs (after discover() has been called).
|
|
1441
|
+
*/
|
|
1442
|
+
getToolSpecs(): WshMcpToolSpec[];
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Check whether a specific tool is available (based on cached discovery).
|
|
1446
|
+
*/
|
|
1447
|
+
hasTool(toolName: string): boolean;
|
|
1448
|
+
|
|
1449
|
+
/** Get the number of cached tools. */
|
|
1450
|
+
readonly toolCount: number;
|
|
1451
|
+
|
|
1452
|
+
/** Clear the cached tool specs. Call discover() again to refresh. */
|
|
1453
|
+
clearCache(): void;
|
|
1454
|
+
}
|