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