@slicervm/sdk 0.1.5 → 0.1.6
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/index.cjs +150 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +145 -5
- package/dist/index.js.map +1 -1
- package/dist/shell.cjs +152 -0
- package/dist/shell.cjs.map +1 -0
- package/dist/shell.d.cts +67 -0
- package/dist/shell.d.ts +67 -0
- package/dist/shell.js +143 -0
- package/dist/shell.js.map +1 -0
- package/package.json +11 -1
package/dist/index.d.cts
CHANGED
|
@@ -712,4 +712,70 @@ declare class SlicerClient {
|
|
|
712
712
|
getInfo(): Promise<SlicerInfo>;
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
-
|
|
715
|
+
/**
|
|
716
|
+
* Browser-compatible shell adapter for Slicer VMs.
|
|
717
|
+
*
|
|
718
|
+
* Provides frame encode/decode helpers and a `SlicerShellSession` class that
|
|
719
|
+
* wires a standard browser WebSocket to an xterm.js Terminal instance using
|
|
720
|
+
* the Slicer binary shell protocol.
|
|
721
|
+
*
|
|
722
|
+
* This module intentionally avoids Node-only imports so it can be used in
|
|
723
|
+
* browser bundles.
|
|
724
|
+
*/
|
|
725
|
+
declare const FRAME_TYPE_DATA = 1;
|
|
726
|
+
declare const FRAME_TYPE_WINDOW_SIZE = 2;
|
|
727
|
+
declare const FRAME_TYPE_SHUTDOWN = 3;
|
|
728
|
+
declare const FRAME_TYPE_HEARTBEAT = 4;
|
|
729
|
+
declare const FRAME_TYPE_SESSION_CLOSE = 5;
|
|
730
|
+
/** Encode a frame into the 5-byte-header binary protocol. */
|
|
731
|
+
declare function encodeFrame(frameType: number, payload?: Uint8Array): Uint8Array;
|
|
732
|
+
/** Parse a binary frame. Returns null if the data is malformed. */
|
|
733
|
+
declare function parseFrame(data: ArrayBuffer): {
|
|
734
|
+
frameType: number;
|
|
735
|
+
payload: Uint8Array;
|
|
736
|
+
} | null;
|
|
737
|
+
interface ShellSessionOptions {
|
|
738
|
+
/** WebSocket URL for the shell endpoint (ws:// or wss://). */
|
|
739
|
+
url: string;
|
|
740
|
+
/** Heartbeat interval in ms. Default 30000. */
|
|
741
|
+
heartbeatIntervalMs?: number;
|
|
742
|
+
/** Called when connection state changes. */
|
|
743
|
+
onStateChange?: (state: 'connecting' | 'connected' | 'disconnected') => void;
|
|
744
|
+
/** Called on error. */
|
|
745
|
+
onError?: (error: string) => void;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Minimal xterm.js Terminal interface — only the methods SlicerShellSession
|
|
749
|
+
* actually calls. Avoids requiring @xterm/xterm as a dependency.
|
|
750
|
+
*/
|
|
751
|
+
interface XTermLike {
|
|
752
|
+
onData: (cb: (data: string) => void) => {
|
|
753
|
+
dispose: () => void;
|
|
754
|
+
};
|
|
755
|
+
write: (data: string) => void;
|
|
756
|
+
reset: () => void;
|
|
757
|
+
cols: number;
|
|
758
|
+
rows: number;
|
|
759
|
+
}
|
|
760
|
+
declare class SlicerShellSession {
|
|
761
|
+
private readonly terminal;
|
|
762
|
+
private readonly options;
|
|
763
|
+
private ws;
|
|
764
|
+
private heartbeatTimer;
|
|
765
|
+
private dataDisposable;
|
|
766
|
+
constructor(terminal: XTermLike, options: ShellSessionOptions);
|
|
767
|
+
/** True when the WebSocket is open and relaying. */
|
|
768
|
+
get connected(): boolean;
|
|
769
|
+
/** Open the WebSocket and begin relaying. */
|
|
770
|
+
connect(): void;
|
|
771
|
+
/** Send a graceful shutdown frame and close. */
|
|
772
|
+
disconnect(): void;
|
|
773
|
+
/** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
|
|
774
|
+
resize(cols: number, rows: number): void;
|
|
775
|
+
private sendResize;
|
|
776
|
+
private startHeartbeat;
|
|
777
|
+
private stopHeartbeat;
|
|
778
|
+
private teardown;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShellSessionOptions, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, SlicerShellSession, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, type XTermLike, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
|
package/dist/index.d.ts
CHANGED
|
@@ -712,4 +712,70 @@ declare class SlicerClient {
|
|
|
712
712
|
getInfo(): Promise<SlicerInfo>;
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
-
|
|
715
|
+
/**
|
|
716
|
+
* Browser-compatible shell adapter for Slicer VMs.
|
|
717
|
+
*
|
|
718
|
+
* Provides frame encode/decode helpers and a `SlicerShellSession` class that
|
|
719
|
+
* wires a standard browser WebSocket to an xterm.js Terminal instance using
|
|
720
|
+
* the Slicer binary shell protocol.
|
|
721
|
+
*
|
|
722
|
+
* This module intentionally avoids Node-only imports so it can be used in
|
|
723
|
+
* browser bundles.
|
|
724
|
+
*/
|
|
725
|
+
declare const FRAME_TYPE_DATA = 1;
|
|
726
|
+
declare const FRAME_TYPE_WINDOW_SIZE = 2;
|
|
727
|
+
declare const FRAME_TYPE_SHUTDOWN = 3;
|
|
728
|
+
declare const FRAME_TYPE_HEARTBEAT = 4;
|
|
729
|
+
declare const FRAME_TYPE_SESSION_CLOSE = 5;
|
|
730
|
+
/** Encode a frame into the 5-byte-header binary protocol. */
|
|
731
|
+
declare function encodeFrame(frameType: number, payload?: Uint8Array): Uint8Array;
|
|
732
|
+
/** Parse a binary frame. Returns null if the data is malformed. */
|
|
733
|
+
declare function parseFrame(data: ArrayBuffer): {
|
|
734
|
+
frameType: number;
|
|
735
|
+
payload: Uint8Array;
|
|
736
|
+
} | null;
|
|
737
|
+
interface ShellSessionOptions {
|
|
738
|
+
/** WebSocket URL for the shell endpoint (ws:// or wss://). */
|
|
739
|
+
url: string;
|
|
740
|
+
/** Heartbeat interval in ms. Default 30000. */
|
|
741
|
+
heartbeatIntervalMs?: number;
|
|
742
|
+
/** Called when connection state changes. */
|
|
743
|
+
onStateChange?: (state: 'connecting' | 'connected' | 'disconnected') => void;
|
|
744
|
+
/** Called on error. */
|
|
745
|
+
onError?: (error: string) => void;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Minimal xterm.js Terminal interface — only the methods SlicerShellSession
|
|
749
|
+
* actually calls. Avoids requiring @xterm/xterm as a dependency.
|
|
750
|
+
*/
|
|
751
|
+
interface XTermLike {
|
|
752
|
+
onData: (cb: (data: string) => void) => {
|
|
753
|
+
dispose: () => void;
|
|
754
|
+
};
|
|
755
|
+
write: (data: string) => void;
|
|
756
|
+
reset: () => void;
|
|
757
|
+
cols: number;
|
|
758
|
+
rows: number;
|
|
759
|
+
}
|
|
760
|
+
declare class SlicerShellSession {
|
|
761
|
+
private readonly terminal;
|
|
762
|
+
private readonly options;
|
|
763
|
+
private ws;
|
|
764
|
+
private heartbeatTimer;
|
|
765
|
+
private dataDisposable;
|
|
766
|
+
constructor(terminal: XTermLike, options: ShellSessionOptions);
|
|
767
|
+
/** True when the WebSocket is open and relaying. */
|
|
768
|
+
get connected(): boolean;
|
|
769
|
+
/** Open the WebSocket and begin relaying. */
|
|
770
|
+
connect(): void;
|
|
771
|
+
/** Send a graceful shutdown frame and close. */
|
|
772
|
+
disconnect(): void;
|
|
773
|
+
/** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
|
|
774
|
+
resize(cols: number, rows: number): void;
|
|
775
|
+
private sendResize;
|
|
776
|
+
private startHeartbeat;
|
|
777
|
+
private stopHeartbeat;
|
|
778
|
+
private teardown;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShellSessionOptions, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, SlicerShellSession, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, type XTermLike, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'os';
|
|
|
5
5
|
import path2 from 'path';
|
|
6
6
|
import net, { createServer } from 'net';
|
|
7
7
|
import fs from 'fs';
|
|
8
|
-
import { createWebSocketStream, WebSocket } from 'ws';
|
|
8
|
+
import { createWebSocketStream, WebSocket as WebSocket$1 } from 'ws';
|
|
9
9
|
|
|
10
10
|
// src/types.ts
|
|
11
11
|
var ExecStdioText = "text";
|
|
@@ -526,7 +526,7 @@ function openWebSocket(init, mapping) {
|
|
|
526
526
|
if (init.transport.kind === "socket") {
|
|
527
527
|
opts.agent = unixAgent(init.transport.socketPath);
|
|
528
528
|
}
|
|
529
|
-
return new WebSocket(url, opts);
|
|
529
|
+
return new WebSocket$1(url, opts);
|
|
530
530
|
}
|
|
531
531
|
function wsURLForVM(transport, hostname) {
|
|
532
532
|
if (transport.kind === "socket") {
|
|
@@ -965,8 +965,8 @@ var VMBg = class {
|
|
|
965
965
|
q.set("cmd", req.command);
|
|
966
966
|
for (const a of req.args ?? []) q.append("args", a);
|
|
967
967
|
for (const e of req.env ?? []) q.append("env", e);
|
|
968
|
-
if (req.uid !== void 0
|
|
969
|
-
if (req.gid !== void 0
|
|
968
|
+
if (req.uid !== void 0) q.set("uid", String(req.uid));
|
|
969
|
+
if (req.gid !== void 0) q.set("gid", String(req.gid));
|
|
970
970
|
if (req.shell) q.set("shell", req.shell);
|
|
971
971
|
if (req.cwd) q.set("cwd", req.cwd);
|
|
972
972
|
if (req.ringBytes !== void 0 && req.ringBytes > 0) {
|
|
@@ -1278,6 +1278,146 @@ var SlicerClient = class _SlicerClient {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
};
|
|
1280
1280
|
|
|
1281
|
-
|
|
1281
|
+
// src/shell.ts
|
|
1282
|
+
var FRAME_TYPE_DATA = 1;
|
|
1283
|
+
var FRAME_TYPE_WINDOW_SIZE = 2;
|
|
1284
|
+
var FRAME_TYPE_SHUTDOWN = 3;
|
|
1285
|
+
var FRAME_TYPE_HEARTBEAT = 4;
|
|
1286
|
+
var FRAME_TYPE_SESSION_CLOSE = 5;
|
|
1287
|
+
var HEADER_SIZE = 5;
|
|
1288
|
+
function encodeFrame(frameType, payload) {
|
|
1289
|
+
const payloadLen = payload ? payload.byteLength : 0;
|
|
1290
|
+
const buf = new Uint8Array(HEADER_SIZE + payloadLen);
|
|
1291
|
+
const view = new DataView(buf.buffer);
|
|
1292
|
+
view.setUint8(0, frameType);
|
|
1293
|
+
view.setUint32(1, payloadLen, false);
|
|
1294
|
+
if (payload) buf.set(payload, HEADER_SIZE);
|
|
1295
|
+
return buf;
|
|
1296
|
+
}
|
|
1297
|
+
function parseFrame(data) {
|
|
1298
|
+
if (data.byteLength < HEADER_SIZE) return null;
|
|
1299
|
+
const view = new DataView(data);
|
|
1300
|
+
const frameType = view.getUint8(0);
|
|
1301
|
+
const payloadLen = view.getUint32(1, false);
|
|
1302
|
+
if (data.byteLength < HEADER_SIZE + payloadLen) return null;
|
|
1303
|
+
const payload = new Uint8Array(data, HEADER_SIZE, payloadLen);
|
|
1304
|
+
return { frameType, payload };
|
|
1305
|
+
}
|
|
1306
|
+
var encoder = new TextEncoder();
|
|
1307
|
+
var decoder = new TextDecoder();
|
|
1308
|
+
var SlicerShellSession = class {
|
|
1309
|
+
constructor(terminal, options) {
|
|
1310
|
+
this.terminal = terminal;
|
|
1311
|
+
this.options = options;
|
|
1312
|
+
}
|
|
1313
|
+
terminal;
|
|
1314
|
+
options;
|
|
1315
|
+
ws = null;
|
|
1316
|
+
heartbeatTimer = null;
|
|
1317
|
+
dataDisposable = null;
|
|
1318
|
+
/** True when the WebSocket is open and relaying. */
|
|
1319
|
+
get connected() {
|
|
1320
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
1321
|
+
}
|
|
1322
|
+
/** Open the WebSocket and begin relaying. */
|
|
1323
|
+
connect() {
|
|
1324
|
+
if (this.ws) return;
|
|
1325
|
+
this.options.onStateChange?.("connecting");
|
|
1326
|
+
const ws = new WebSocket(this.options.url);
|
|
1327
|
+
ws.binaryType = "arraybuffer";
|
|
1328
|
+
this.ws = ws;
|
|
1329
|
+
ws.onopen = () => {
|
|
1330
|
+
this.options.onStateChange?.("connected");
|
|
1331
|
+
this.terminal.reset();
|
|
1332
|
+
this.sendResize(this.terminal.cols, this.terminal.rows);
|
|
1333
|
+
this.startHeartbeat();
|
|
1334
|
+
};
|
|
1335
|
+
ws.onmessage = (ev) => {
|
|
1336
|
+
if (!(ev.data instanceof ArrayBuffer)) return;
|
|
1337
|
+
const frame = parseFrame(ev.data);
|
|
1338
|
+
if (!frame) return;
|
|
1339
|
+
switch (frame.frameType) {
|
|
1340
|
+
case FRAME_TYPE_DATA:
|
|
1341
|
+
this.terminal.write(decoder.decode(frame.payload));
|
|
1342
|
+
break;
|
|
1343
|
+
case FRAME_TYPE_SHUTDOWN:
|
|
1344
|
+
case FRAME_TYPE_SESSION_CLOSE:
|
|
1345
|
+
this.teardown();
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
ws.onclose = () => {
|
|
1350
|
+
this.teardown();
|
|
1351
|
+
};
|
|
1352
|
+
ws.onerror = () => {
|
|
1353
|
+
this.options.onError?.("WebSocket error");
|
|
1354
|
+
this.teardown();
|
|
1355
|
+
};
|
|
1356
|
+
this.dataDisposable = this.terminal.onData((data) => {
|
|
1357
|
+
if (!this.connected) return;
|
|
1358
|
+
const payload = encoder.encode(data);
|
|
1359
|
+
this.ws.send(encodeFrame(FRAME_TYPE_DATA, payload));
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
/** Send a graceful shutdown frame and close. */
|
|
1363
|
+
disconnect() {
|
|
1364
|
+
if (this.ws) {
|
|
1365
|
+
try {
|
|
1366
|
+
this.ws.send(encodeFrame(FRAME_TYPE_SHUTDOWN));
|
|
1367
|
+
} catch {
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
this.teardown();
|
|
1371
|
+
}
|
|
1372
|
+
/** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
|
|
1373
|
+
resize(cols, rows) {
|
|
1374
|
+
if (!this.connected) return;
|
|
1375
|
+
this.sendResize(cols, rows);
|
|
1376
|
+
}
|
|
1377
|
+
// --- internals -------------------------------------------------------------
|
|
1378
|
+
sendResize(cols, rows) {
|
|
1379
|
+
const payload = new Uint8Array(8);
|
|
1380
|
+
const view = new DataView(payload.buffer);
|
|
1381
|
+
view.setUint32(0, cols, false);
|
|
1382
|
+
view.setUint32(4, rows, false);
|
|
1383
|
+
this.ws.send(encodeFrame(FRAME_TYPE_WINDOW_SIZE, payload));
|
|
1384
|
+
}
|
|
1385
|
+
startHeartbeat() {
|
|
1386
|
+
this.stopHeartbeat();
|
|
1387
|
+
const intervalMs = this.options.heartbeatIntervalMs ?? 3e4;
|
|
1388
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1389
|
+
if (!this.connected) return;
|
|
1390
|
+
this.ws.send(encodeFrame(FRAME_TYPE_HEARTBEAT));
|
|
1391
|
+
}, intervalMs);
|
|
1392
|
+
}
|
|
1393
|
+
stopHeartbeat() {
|
|
1394
|
+
if (this.heartbeatTimer !== null) {
|
|
1395
|
+
clearInterval(this.heartbeatTimer);
|
|
1396
|
+
this.heartbeatTimer = null;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
teardown() {
|
|
1400
|
+
this.stopHeartbeat();
|
|
1401
|
+
if (this.dataDisposable) {
|
|
1402
|
+
this.dataDisposable.dispose();
|
|
1403
|
+
this.dataDisposable = null;
|
|
1404
|
+
}
|
|
1405
|
+
if (this.ws) {
|
|
1406
|
+
const ws = this.ws;
|
|
1407
|
+
this.ws = null;
|
|
1408
|
+
ws.onopen = null;
|
|
1409
|
+
ws.onmessage = null;
|
|
1410
|
+
ws.onclose = null;
|
|
1411
|
+
ws.onerror = null;
|
|
1412
|
+
try {
|
|
1413
|
+
ws.close();
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
this.options.onStateChange?.("disconnected");
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
export { ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, Forwarder, GiB, HostGroupsAPI, MiB, NonRootUser, SecretExistsError, SecretsAPI, SlicerAPIError, SlicerClient, SlicerShellSession, VM, VMBg, VMFileSystem, VMsAPI, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
|
|
1282
1422
|
//# sourceMappingURL=index.js.map
|
|
1283
1423
|
//# sourceMappingURL=index.js.map
|