@slicervm/sdk 0.1.5 → 0.1.7
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 +313 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -1
- package/dist/index.d.ts +257 -1
- package/dist/index.js +302 -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.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";
|
|
@@ -255,6 +255,7 @@ function createVMReqToWire(r) {
|
|
|
255
255
|
if (r.ip !== void 0) o.ip = r.ip;
|
|
256
256
|
if (r.tags !== void 0) o.tags = r.tags;
|
|
257
257
|
if (r.secrets !== void 0) o.secrets = r.secrets;
|
|
258
|
+
if (r.network !== void 0) o.network = r.network;
|
|
258
259
|
return o;
|
|
259
260
|
}
|
|
260
261
|
function createVMResFromWire(w) {
|
|
@@ -526,7 +527,7 @@ function openWebSocket(init, mapping) {
|
|
|
526
527
|
if (init.transport.kind === "socket") {
|
|
527
528
|
opts.agent = unixAgent(init.transport.socketPath);
|
|
528
529
|
}
|
|
529
|
-
return new WebSocket(url, opts);
|
|
530
|
+
return new WebSocket$1(url, opts);
|
|
530
531
|
}
|
|
531
532
|
function wsURLForVM(transport, hostname) {
|
|
532
533
|
if (transport.kind === "socket") {
|
|
@@ -965,8 +966,8 @@ var VMBg = class {
|
|
|
965
966
|
q.set("cmd", req.command);
|
|
966
967
|
for (const a of req.args ?? []) q.append("args", a);
|
|
967
968
|
for (const e of req.env ?? []) q.append("env", e);
|
|
968
|
-
if (req.uid !== void 0
|
|
969
|
-
if (req.gid !== void 0
|
|
969
|
+
if (req.uid !== void 0) q.set("uid", String(req.uid));
|
|
970
|
+
if (req.gid !== void 0) q.set("gid", String(req.gid));
|
|
970
971
|
if (req.shell) q.set("shell", req.shell);
|
|
971
972
|
if (req.cwd) q.set("cwd", req.cwd);
|
|
972
973
|
if (req.ringBytes !== void 0 && req.ringBytes > 0) {
|
|
@@ -1251,17 +1252,173 @@ function buildListQuery(opts) {
|
|
|
1251
1252
|
return s ? `?${s}` : "";
|
|
1252
1253
|
}
|
|
1253
1254
|
|
|
1255
|
+
// src/proxy.ts
|
|
1256
|
+
var ProxySecretBearer = "bearer";
|
|
1257
|
+
var ProxySecretBasic = "basic";
|
|
1258
|
+
function clientFromWire(w) {
|
|
1259
|
+
return { name: w.name, createdAt: w.created_at };
|
|
1260
|
+
}
|
|
1261
|
+
function clientCreatedFromWire(w) {
|
|
1262
|
+
return { name: w.name, token: w.token, createdAt: w.created_at };
|
|
1263
|
+
}
|
|
1264
|
+
function secretFromWire2(w) {
|
|
1265
|
+
const out = { name: w.name, host: w.host, createdAt: w.created_at };
|
|
1266
|
+
if (w.type) out.type = w.type;
|
|
1267
|
+
return out;
|
|
1268
|
+
}
|
|
1269
|
+
function ruleFromWire(w) {
|
|
1270
|
+
const out = { host: w.host };
|
|
1271
|
+
if (w.secret) out.secret = w.secret;
|
|
1272
|
+
if (w.methods && w.methods.length > 0) out.methods = w.methods;
|
|
1273
|
+
if (w.paths && w.paths.length > 0) out.paths = w.paths;
|
|
1274
|
+
if (w.expires && w.expires !== "0001-01-01T00:00:00Z") out.expires = w.expires;
|
|
1275
|
+
if (w.passthrough) out.passthrough = w.passthrough;
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
var ProxyAPI = class {
|
|
1279
|
+
constructor(transport) {
|
|
1280
|
+
this.transport = transport;
|
|
1281
|
+
this.clients = new ProxyClientsAPI(transport);
|
|
1282
|
+
this.secrets = new ProxySecretsAPI(transport);
|
|
1283
|
+
this.allows = new ProxyAllowsAPI(transport);
|
|
1284
|
+
}
|
|
1285
|
+
transport;
|
|
1286
|
+
clients;
|
|
1287
|
+
secrets;
|
|
1288
|
+
allows;
|
|
1289
|
+
};
|
|
1290
|
+
var ProxyClientsAPI = class {
|
|
1291
|
+
constructor(transport) {
|
|
1292
|
+
this.transport = transport;
|
|
1293
|
+
}
|
|
1294
|
+
transport;
|
|
1295
|
+
/** Mint a new proxy client. The returned token is shown once. */
|
|
1296
|
+
async create(name, opts = {}) {
|
|
1297
|
+
const body = { name };
|
|
1298
|
+
if (opts.token) body.token = opts.token;
|
|
1299
|
+
const wire = await this.transport.request(
|
|
1300
|
+
"POST",
|
|
1301
|
+
"/proxy/v1/clients",
|
|
1302
|
+
body
|
|
1303
|
+
);
|
|
1304
|
+
return clientCreatedFromWire(wire);
|
|
1305
|
+
}
|
|
1306
|
+
async list() {
|
|
1307
|
+
const wire = await this.transport.request("GET", "/proxy/v1/clients");
|
|
1308
|
+
return (wire ?? []).map(clientFromWire);
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Revoke the token, drop every allow rule the client owned, and
|
|
1312
|
+
* remove the client.
|
|
1313
|
+
*/
|
|
1314
|
+
async delete(name) {
|
|
1315
|
+
await this.transport.request("DELETE", `/proxy/v1/clients/${encodeURIComponent(name)}`);
|
|
1316
|
+
}
|
|
1317
|
+
/** List a client's allow rules in declaration order (first-match-wins). */
|
|
1318
|
+
async rules(name) {
|
|
1319
|
+
const wire = await this.transport.request(
|
|
1320
|
+
"GET",
|
|
1321
|
+
`/proxy/v1/clients/${encodeURIComponent(name)}`
|
|
1322
|
+
);
|
|
1323
|
+
return (wire ?? []).map(ruleFromWire);
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
var ProxySecretsAPI = class {
|
|
1327
|
+
constructor(transport) {
|
|
1328
|
+
this.transport = transport;
|
|
1329
|
+
}
|
|
1330
|
+
transport;
|
|
1331
|
+
async create(req) {
|
|
1332
|
+
const body = {
|
|
1333
|
+
name: req.name,
|
|
1334
|
+
host: req.host,
|
|
1335
|
+
value: req.value
|
|
1336
|
+
};
|
|
1337
|
+
if (req.type) body.type = req.type;
|
|
1338
|
+
await this.transport.request("POST", "/proxy/v1/secrets", body);
|
|
1339
|
+
}
|
|
1340
|
+
async list() {
|
|
1341
|
+
const wire = await this.transport.request("GET", "/proxy/v1/secrets");
|
|
1342
|
+
return (wire ?? []).map(secretFromWire2);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Remove a secret. Allow rules that reference it stop matching until
|
|
1346
|
+
* the secret is recreated or the rule is rewritten.
|
|
1347
|
+
*/
|
|
1348
|
+
async delete(name) {
|
|
1349
|
+
await this.transport.request("DELETE", `/proxy/v1/secrets/${encodeURIComponent(name)}`);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
var ProxyAllowsAPI = class {
|
|
1353
|
+
constructor(transport) {
|
|
1354
|
+
this.transport = transport;
|
|
1355
|
+
}
|
|
1356
|
+
transport;
|
|
1357
|
+
/** Add an allow rule. Returns the resolved rule with absolute `expires`. */
|
|
1358
|
+
async add(req) {
|
|
1359
|
+
const body = {
|
|
1360
|
+
client: req.client,
|
|
1361
|
+
host: req.host
|
|
1362
|
+
};
|
|
1363
|
+
if (req.secret) body.secret = req.secret;
|
|
1364
|
+
if (req.methods && req.methods.length > 0) body.methods = req.methods;
|
|
1365
|
+
if (req.paths && req.paths.length > 0) body.paths = req.paths;
|
|
1366
|
+
if (req.ttlSeconds && req.ttlSeconds > 0) body.ttl_seconds = req.ttlSeconds;
|
|
1367
|
+
if (req.passthrough) body.passthrough = true;
|
|
1368
|
+
const wire = await this.transport.request(
|
|
1369
|
+
"POST",
|
|
1370
|
+
"/proxy/v1/allows",
|
|
1371
|
+
body
|
|
1372
|
+
);
|
|
1373
|
+
return ruleFromWire(wire);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Host-bulk revoke: removes **every** rule on the client whose host
|
|
1377
|
+
* matches. For surgical removal of one rule among siblings on the
|
|
1378
|
+
* same host (e.g. several path-scoped rules on `github.com`), use
|
|
1379
|
+
* `removeByTuple` instead.
|
|
1380
|
+
*/
|
|
1381
|
+
async remove(client, host) {
|
|
1382
|
+
await this.transport.request(
|
|
1383
|
+
"DELETE",
|
|
1384
|
+
`/proxy/v1/allows/${encodeURIComponent(client)}/${encodeURIComponent(host)}`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Surgical revoke: removes the single rule whose
|
|
1389
|
+
* (host, methods, paths, passthrough) tuple matches the request.
|
|
1390
|
+
* Pass exactly the same fields you used at create time. Method and
|
|
1391
|
+
* host casing are normalised server-side, so `"GET"` / `"get"` and
|
|
1392
|
+
* `"github.com"` / `"GITHUB.COM"` all match the same stored rule.
|
|
1393
|
+
*
|
|
1394
|
+
* Returns 404 (surfaced as a SlicerAPIError) when no rule matches.
|
|
1395
|
+
*/
|
|
1396
|
+
async removeByTuple(req) {
|
|
1397
|
+
const body = {
|
|
1398
|
+
client: req.client,
|
|
1399
|
+
host: req.host
|
|
1400
|
+
};
|
|
1401
|
+
if (req.secret) body.secret = req.secret;
|
|
1402
|
+
if (req.methods && req.methods.length > 0) body.methods = req.methods;
|
|
1403
|
+
if (req.paths && req.paths.length > 0) body.paths = req.paths;
|
|
1404
|
+
if (req.passthrough) body.passthrough = true;
|
|
1405
|
+
await this.transport.request("POST", "/proxy/v1/allows/revoke", body);
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1254
1409
|
// src/client.ts
|
|
1255
1410
|
var SlicerClient = class _SlicerClient {
|
|
1256
1411
|
transport;
|
|
1257
1412
|
hostGroups;
|
|
1258
1413
|
vms;
|
|
1259
1414
|
secrets;
|
|
1415
|
+
proxy;
|
|
1260
1416
|
constructor(opts) {
|
|
1261
1417
|
this.transport = new TransportClient(opts);
|
|
1262
1418
|
this.hostGroups = new HostGroupsAPI(this.transport);
|
|
1263
1419
|
this.vms = new VMsAPI(this.transport);
|
|
1264
1420
|
this.secrets = new SecretsAPI(this.transport);
|
|
1421
|
+
this.proxy = new ProxyAPI(this.transport);
|
|
1265
1422
|
}
|
|
1266
1423
|
static fromEnv(overrides = {}) {
|
|
1267
1424
|
const baseURL = overrides.baseURL ?? process.env.SLICER_URL;
|
|
@@ -1278,6 +1435,146 @@ var SlicerClient = class _SlicerClient {
|
|
|
1278
1435
|
}
|
|
1279
1436
|
};
|
|
1280
1437
|
|
|
1281
|
-
|
|
1438
|
+
// src/shell.ts
|
|
1439
|
+
var FRAME_TYPE_DATA = 1;
|
|
1440
|
+
var FRAME_TYPE_WINDOW_SIZE = 2;
|
|
1441
|
+
var FRAME_TYPE_SHUTDOWN = 3;
|
|
1442
|
+
var FRAME_TYPE_HEARTBEAT = 4;
|
|
1443
|
+
var FRAME_TYPE_SESSION_CLOSE = 5;
|
|
1444
|
+
var HEADER_SIZE = 5;
|
|
1445
|
+
function encodeFrame(frameType, payload) {
|
|
1446
|
+
const payloadLen = payload ? payload.byteLength : 0;
|
|
1447
|
+
const buf = new Uint8Array(HEADER_SIZE + payloadLen);
|
|
1448
|
+
const view = new DataView(buf.buffer);
|
|
1449
|
+
view.setUint8(0, frameType);
|
|
1450
|
+
view.setUint32(1, payloadLen, false);
|
|
1451
|
+
if (payload) buf.set(payload, HEADER_SIZE);
|
|
1452
|
+
return buf;
|
|
1453
|
+
}
|
|
1454
|
+
function parseFrame(data) {
|
|
1455
|
+
if (data.byteLength < HEADER_SIZE) return null;
|
|
1456
|
+
const view = new DataView(data);
|
|
1457
|
+
const frameType = view.getUint8(0);
|
|
1458
|
+
const payloadLen = view.getUint32(1, false);
|
|
1459
|
+
if (data.byteLength < HEADER_SIZE + payloadLen) return null;
|
|
1460
|
+
const payload = new Uint8Array(data, HEADER_SIZE, payloadLen);
|
|
1461
|
+
return { frameType, payload };
|
|
1462
|
+
}
|
|
1463
|
+
var encoder = new TextEncoder();
|
|
1464
|
+
var decoder = new TextDecoder();
|
|
1465
|
+
var SlicerShellSession = class {
|
|
1466
|
+
constructor(terminal, options) {
|
|
1467
|
+
this.terminal = terminal;
|
|
1468
|
+
this.options = options;
|
|
1469
|
+
}
|
|
1470
|
+
terminal;
|
|
1471
|
+
options;
|
|
1472
|
+
ws = null;
|
|
1473
|
+
heartbeatTimer = null;
|
|
1474
|
+
dataDisposable = null;
|
|
1475
|
+
/** True when the WebSocket is open and relaying. */
|
|
1476
|
+
get connected() {
|
|
1477
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
1478
|
+
}
|
|
1479
|
+
/** Open the WebSocket and begin relaying. */
|
|
1480
|
+
connect() {
|
|
1481
|
+
if (this.ws) return;
|
|
1482
|
+
this.options.onStateChange?.("connecting");
|
|
1483
|
+
const ws = new WebSocket(this.options.url);
|
|
1484
|
+
ws.binaryType = "arraybuffer";
|
|
1485
|
+
this.ws = ws;
|
|
1486
|
+
ws.onopen = () => {
|
|
1487
|
+
this.options.onStateChange?.("connected");
|
|
1488
|
+
this.terminal.reset();
|
|
1489
|
+
this.sendResize(this.terminal.cols, this.terminal.rows);
|
|
1490
|
+
this.startHeartbeat();
|
|
1491
|
+
};
|
|
1492
|
+
ws.onmessage = (ev) => {
|
|
1493
|
+
if (!(ev.data instanceof ArrayBuffer)) return;
|
|
1494
|
+
const frame = parseFrame(ev.data);
|
|
1495
|
+
if (!frame) return;
|
|
1496
|
+
switch (frame.frameType) {
|
|
1497
|
+
case FRAME_TYPE_DATA:
|
|
1498
|
+
this.terminal.write(decoder.decode(frame.payload));
|
|
1499
|
+
break;
|
|
1500
|
+
case FRAME_TYPE_SHUTDOWN:
|
|
1501
|
+
case FRAME_TYPE_SESSION_CLOSE:
|
|
1502
|
+
this.teardown();
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
ws.onclose = () => {
|
|
1507
|
+
this.teardown();
|
|
1508
|
+
};
|
|
1509
|
+
ws.onerror = () => {
|
|
1510
|
+
this.options.onError?.("WebSocket error");
|
|
1511
|
+
this.teardown();
|
|
1512
|
+
};
|
|
1513
|
+
this.dataDisposable = this.terminal.onData((data) => {
|
|
1514
|
+
if (!this.connected) return;
|
|
1515
|
+
const payload = encoder.encode(data);
|
|
1516
|
+
this.ws.send(encodeFrame(FRAME_TYPE_DATA, payload));
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
/** Send a graceful shutdown frame and close. */
|
|
1520
|
+
disconnect() {
|
|
1521
|
+
if (this.ws) {
|
|
1522
|
+
try {
|
|
1523
|
+
this.ws.send(encodeFrame(FRAME_TYPE_SHUTDOWN));
|
|
1524
|
+
} catch {
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
this.teardown();
|
|
1528
|
+
}
|
|
1529
|
+
/** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
|
|
1530
|
+
resize(cols, rows) {
|
|
1531
|
+
if (!this.connected) return;
|
|
1532
|
+
this.sendResize(cols, rows);
|
|
1533
|
+
}
|
|
1534
|
+
// --- internals -------------------------------------------------------------
|
|
1535
|
+
sendResize(cols, rows) {
|
|
1536
|
+
const payload = new Uint8Array(8);
|
|
1537
|
+
const view = new DataView(payload.buffer);
|
|
1538
|
+
view.setUint32(0, cols, false);
|
|
1539
|
+
view.setUint32(4, rows, false);
|
|
1540
|
+
this.ws.send(encodeFrame(FRAME_TYPE_WINDOW_SIZE, payload));
|
|
1541
|
+
}
|
|
1542
|
+
startHeartbeat() {
|
|
1543
|
+
this.stopHeartbeat();
|
|
1544
|
+
const intervalMs = this.options.heartbeatIntervalMs ?? 3e4;
|
|
1545
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1546
|
+
if (!this.connected) return;
|
|
1547
|
+
this.ws.send(encodeFrame(FRAME_TYPE_HEARTBEAT));
|
|
1548
|
+
}, intervalMs);
|
|
1549
|
+
}
|
|
1550
|
+
stopHeartbeat() {
|
|
1551
|
+
if (this.heartbeatTimer !== null) {
|
|
1552
|
+
clearInterval(this.heartbeatTimer);
|
|
1553
|
+
this.heartbeatTimer = null;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
teardown() {
|
|
1557
|
+
this.stopHeartbeat();
|
|
1558
|
+
if (this.dataDisposable) {
|
|
1559
|
+
this.dataDisposable.dispose();
|
|
1560
|
+
this.dataDisposable = null;
|
|
1561
|
+
}
|
|
1562
|
+
if (this.ws) {
|
|
1563
|
+
const ws = this.ws;
|
|
1564
|
+
this.ws = null;
|
|
1565
|
+
ws.onopen = null;
|
|
1566
|
+
ws.onmessage = null;
|
|
1567
|
+
ws.onclose = null;
|
|
1568
|
+
ws.onerror = null;
|
|
1569
|
+
try {
|
|
1570
|
+
ws.close();
|
|
1571
|
+
} catch {
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
this.options.onStateChange?.("disconnected");
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
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, ProxyAPI, ProxyAllowsAPI, ProxyClientsAPI, ProxySecretBasic, ProxySecretBearer, ProxySecretsAPI, SecretExistsError, SecretsAPI, SlicerAPIError, SlicerClient, SlicerShellSession, VM, VMBg, VMFileSystem, VMsAPI, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
|
|
1282
1579
|
//# sourceMappingURL=index.js.map
|
|
1283
1580
|
//# sourceMappingURL=index.js.map
|