instavm 0.11.0 → 0.13.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 +219 -132
- package/dist/index.d.mts +116 -3
- package/dist/index.d.ts +116 -3
- package/dist/index.js +273 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +272 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -7
package/dist/index.mjs
CHANGED
|
@@ -139,7 +139,7 @@ var HTTPClient = class {
|
|
|
139
139
|
timeout: config.timeout,
|
|
140
140
|
headers: {
|
|
141
141
|
"Content-Type": "application/json",
|
|
142
|
-
"User-Agent": "instavm-js-sdk/0.
|
|
142
|
+
"User-Agent": "instavm-js-sdk/0.12.0"
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
this.setupInterceptors();
|
|
@@ -912,10 +912,14 @@ var BrowserManager = class {
|
|
|
912
912
|
|
|
913
913
|
// src/utils/path.ts
|
|
914
914
|
function encodePathSegment(value) {
|
|
915
|
-
|
|
915
|
+
const segment = String(value);
|
|
916
|
+
if (segment === "." || segment === "..") {
|
|
917
|
+
throw new Error("Path traversal not allowed in path segment");
|
|
918
|
+
}
|
|
919
|
+
return encodeURIComponent(segment);
|
|
916
920
|
}
|
|
917
|
-
function encodePathSegments(
|
|
918
|
-
const segments =
|
|
921
|
+
function encodePathSegments(path2) {
|
|
922
|
+
const segments = path2.replace(/^\/+/, "").split("/").filter((segment) => segment.length > 0 && segment !== ".");
|
|
919
923
|
if (segments.some((segment) => segment === "..")) {
|
|
920
924
|
throw new Error("Path traversal not allowed in proxy path");
|
|
921
925
|
}
|
|
@@ -1015,6 +1019,37 @@ var VMsManager = class {
|
|
|
1015
1019
|
headers: { "X-API-Key": this.httpClient.apiKey }
|
|
1016
1020
|
});
|
|
1017
1021
|
}
|
|
1022
|
+
async mountVolume(vmId, payload, wait = true) {
|
|
1023
|
+
this.ensureCloud("VM volume mount");
|
|
1024
|
+
const safeVmId = encodePathSegment(vmId);
|
|
1025
|
+
return this.httpClient.request({
|
|
1026
|
+
method: "POST",
|
|
1027
|
+
url: `/v1/vms/${safeVmId}/volumes`,
|
|
1028
|
+
params: { wait },
|
|
1029
|
+
data: payload,
|
|
1030
|
+
headers: { "X-API-Key": this.httpClient.apiKey }
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
async listVolumes(vmId) {
|
|
1034
|
+
this.ensureCloud("VM volume listing");
|
|
1035
|
+
const safeVmId = encodePathSegment(vmId);
|
|
1036
|
+
return this.httpClient.get(
|
|
1037
|
+
`/v1/vms/${safeVmId}/volumes`,
|
|
1038
|
+
void 0,
|
|
1039
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
async unmountVolume(vmId, volumeId, mountPath, wait = true) {
|
|
1043
|
+
this.ensureCloud("VM volume unmount");
|
|
1044
|
+
const safeVmId = encodePathSegment(vmId);
|
|
1045
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1046
|
+
return this.httpClient.request({
|
|
1047
|
+
method: "DELETE",
|
|
1048
|
+
url: `/v1/vms/${safeVmId}/volumes/${safeVolumeId}`,
|
|
1049
|
+
params: { mount_path: mountPath, wait },
|
|
1050
|
+
headers: { "X-API-Key": this.httpClient.apiKey }
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1018
1053
|
};
|
|
1019
1054
|
|
|
1020
1055
|
// src/client/SnapshotsManager.ts
|
|
@@ -1187,10 +1222,10 @@ var ComputerUseManager = class {
|
|
|
1187
1222
|
{ "X-API-Key": this.httpClient.apiKey }
|
|
1188
1223
|
);
|
|
1189
1224
|
}
|
|
1190
|
-
async proxy(sessionId,
|
|
1225
|
+
async proxy(sessionId, path2, method = "GET", options = {}) {
|
|
1191
1226
|
this.ensureCloud("Computer-use proxy");
|
|
1192
1227
|
const safeSessionId = encodePathSegment(sessionId);
|
|
1193
|
-
const cleanPath = encodePathSegments(
|
|
1228
|
+
const cleanPath = encodePathSegments(path2);
|
|
1194
1229
|
return this.httpClient.request({
|
|
1195
1230
|
method,
|
|
1196
1231
|
url: `/v1/computeruse/${safeSessionId}/${cleanPath}`,
|
|
@@ -1199,23 +1234,39 @@ var ComputerUseManager = class {
|
|
|
1199
1234
|
headers: { "X-API-Key": this.httpClient.apiKey }
|
|
1200
1235
|
});
|
|
1201
1236
|
}
|
|
1202
|
-
async get(sessionId,
|
|
1203
|
-
return this.proxy(sessionId,
|
|
1237
|
+
async get(sessionId, path2, params) {
|
|
1238
|
+
return this.proxy(sessionId, path2, "GET", { params });
|
|
1204
1239
|
}
|
|
1205
|
-
async post(sessionId,
|
|
1206
|
-
return this.proxy(sessionId,
|
|
1240
|
+
async post(sessionId, path2, body) {
|
|
1241
|
+
return this.proxy(sessionId, path2, "POST", { body });
|
|
1207
1242
|
}
|
|
1208
|
-
async put(sessionId,
|
|
1209
|
-
return this.proxy(sessionId,
|
|
1243
|
+
async put(sessionId, path2, body) {
|
|
1244
|
+
return this.proxy(sessionId, path2, "PUT", { body });
|
|
1210
1245
|
}
|
|
1211
|
-
async patch(sessionId,
|
|
1212
|
-
return this.proxy(sessionId,
|
|
1246
|
+
async patch(sessionId, path2, body) {
|
|
1247
|
+
return this.proxy(sessionId, path2, "PATCH", { body });
|
|
1213
1248
|
}
|
|
1214
|
-
async delete(sessionId,
|
|
1215
|
-
return this.proxy(sessionId,
|
|
1249
|
+
async delete(sessionId, path2, params) {
|
|
1250
|
+
return this.proxy(sessionId, path2, "DELETE", { params });
|
|
1216
1251
|
}
|
|
1217
|
-
async options(sessionId,
|
|
1218
|
-
return this.proxy(sessionId,
|
|
1252
|
+
async options(sessionId, path2, params) {
|
|
1253
|
+
return this.proxy(sessionId, path2, "OPTIONS", { params });
|
|
1254
|
+
}
|
|
1255
|
+
async head(sessionId, path2, params) {
|
|
1256
|
+
return this.proxy(sessionId, path2, "HEAD", { params });
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Get the VNC WebSocket URL for a computer-use session.
|
|
1260
|
+
* Returns the URL to connect to; the actual connection requires a WebSocket client.
|
|
1261
|
+
*/
|
|
1262
|
+
async vncWebsockify(sessionId) {
|
|
1263
|
+
this.ensureCloud("Computer-use VNC websockify");
|
|
1264
|
+
const safeSessionId = encodePathSegment(sessionId);
|
|
1265
|
+
return this.httpClient.get(
|
|
1266
|
+
`/v1/computeruse/${safeSessionId}/vnc/websockify`,
|
|
1267
|
+
void 0,
|
|
1268
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1269
|
+
);
|
|
1219
1270
|
}
|
|
1220
1271
|
};
|
|
1221
1272
|
|
|
@@ -1417,8 +1468,164 @@ var WebhooksManager = class {
|
|
|
1417
1468
|
}
|
|
1418
1469
|
};
|
|
1419
1470
|
|
|
1420
|
-
// src/client/
|
|
1471
|
+
// src/client/VolumesManager.ts
|
|
1472
|
+
import fs from "fs";
|
|
1473
|
+
import path from "path";
|
|
1421
1474
|
import FormData from "form-data";
|
|
1475
|
+
var VolumesManager = class {
|
|
1476
|
+
constructor(httpClient, local = false) {
|
|
1477
|
+
this.httpClient = httpClient;
|
|
1478
|
+
this.local = local;
|
|
1479
|
+
}
|
|
1480
|
+
ensureCloud(operation) {
|
|
1481
|
+
if (this.local) {
|
|
1482
|
+
throw new UnsupportedOperationError(
|
|
1483
|
+
`${operation} is not supported in local mode.`
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
async create(payload) {
|
|
1488
|
+
this.ensureCloud("Volume creation");
|
|
1489
|
+
return this.httpClient.post(
|
|
1490
|
+
"/v1/volumes",
|
|
1491
|
+
payload,
|
|
1492
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
async list(refreshUsage = false) {
|
|
1496
|
+
this.ensureCloud("Volume listing");
|
|
1497
|
+
return this.httpClient.get(
|
|
1498
|
+
"/v1/volumes",
|
|
1499
|
+
{ refresh_usage: refreshUsage },
|
|
1500
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
async get(volumeId, refreshUsage = false) {
|
|
1504
|
+
this.ensureCloud("Volume lookup");
|
|
1505
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1506
|
+
return this.httpClient.get(
|
|
1507
|
+
`/v1/volumes/${safeVolumeId}`,
|
|
1508
|
+
{ refresh_usage: refreshUsage },
|
|
1509
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
async update(volumeId, payload) {
|
|
1513
|
+
this.ensureCloud("Volume update");
|
|
1514
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1515
|
+
return this.httpClient.patch(
|
|
1516
|
+
`/v1/volumes/${safeVolumeId}`,
|
|
1517
|
+
payload,
|
|
1518
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
async delete(volumeId) {
|
|
1522
|
+
this.ensureCloud("Volume deletion");
|
|
1523
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1524
|
+
return this.httpClient.delete(
|
|
1525
|
+
`/v1/volumes/${safeVolumeId}`,
|
|
1526
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
async createCheckpoint(volumeId, payload = {}) {
|
|
1530
|
+
this.ensureCloud("Volume checkpoint creation");
|
|
1531
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1532
|
+
return this.httpClient.post(
|
|
1533
|
+
`/v1/volumes/${safeVolumeId}/checkpoints`,
|
|
1534
|
+
payload,
|
|
1535
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
async listCheckpoints(volumeId) {
|
|
1539
|
+
this.ensureCloud("Volume checkpoint listing");
|
|
1540
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1541
|
+
return this.httpClient.get(
|
|
1542
|
+
`/v1/volumes/${safeVolumeId}/checkpoints`,
|
|
1543
|
+
void 0,
|
|
1544
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
async deleteCheckpoint(volumeId, checkpointId) {
|
|
1548
|
+
this.ensureCloud("Volume checkpoint deletion");
|
|
1549
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1550
|
+
const safeCheckpointId = encodePathSegment(checkpointId);
|
|
1551
|
+
return this.httpClient.delete(
|
|
1552
|
+
`/v1/volumes/${safeVolumeId}/checkpoints/${safeCheckpointId}`,
|
|
1553
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
async uploadFile(volumeId, payload) {
|
|
1557
|
+
this.ensureCloud("Volume file upload");
|
|
1558
|
+
if (!payload.path || payload.path.trim().length === 0) {
|
|
1559
|
+
throw new Error("payload.path is required");
|
|
1560
|
+
}
|
|
1561
|
+
const hasFilePath = typeof payload.filePath === "string" && payload.filePath.length > 0;
|
|
1562
|
+
const hasFileContent = payload.fileContent !== void 0;
|
|
1563
|
+
if (hasFilePath === hasFileContent) {
|
|
1564
|
+
throw new Error("Provide exactly one of payload.filePath or payload.fileContent");
|
|
1565
|
+
}
|
|
1566
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1567
|
+
const formData = new FormData();
|
|
1568
|
+
if (hasFilePath) {
|
|
1569
|
+
const sourcePath = payload.filePath;
|
|
1570
|
+
const filename = payload.filename || path.basename(sourcePath);
|
|
1571
|
+
formData.append("file", fs.createReadStream(sourcePath), { filename });
|
|
1572
|
+
} else {
|
|
1573
|
+
const filename = payload.filename || "upload.bin";
|
|
1574
|
+
const raw = payload.fileContent;
|
|
1575
|
+
const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
1576
|
+
formData.append("file", buffer, { filename });
|
|
1577
|
+
}
|
|
1578
|
+
formData.append("path", payload.path);
|
|
1579
|
+
if (payload.overwrite !== void 0) {
|
|
1580
|
+
formData.append("overwrite", String(payload.overwrite));
|
|
1581
|
+
}
|
|
1582
|
+
return this.httpClient.postFormData(
|
|
1583
|
+
`/v1/volumes/${safeVolumeId}/files/upload`,
|
|
1584
|
+
formData
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
async downloadFile(volumeId, path2) {
|
|
1588
|
+
this.ensureCloud("Volume file download");
|
|
1589
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1590
|
+
const response = await this.httpClient.post(
|
|
1591
|
+
`/v1/volumes/${safeVolumeId}/files/download`,
|
|
1592
|
+
{ path: path2 },
|
|
1593
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1594
|
+
);
|
|
1595
|
+
return {
|
|
1596
|
+
path: response.path,
|
|
1597
|
+
filename: response.filename,
|
|
1598
|
+
size: response.size,
|
|
1599
|
+
content: Buffer.from(response.content || "", "base64")
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
async listFiles(volumeId, options = {}) {
|
|
1603
|
+
this.ensureCloud("Volume file listing");
|
|
1604
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1605
|
+
return this.httpClient.get(
|
|
1606
|
+
`/v1/volumes/${safeVolumeId}/files`,
|
|
1607
|
+
{
|
|
1608
|
+
prefix: options.prefix ?? "",
|
|
1609
|
+
recursive: options.recursive ?? true,
|
|
1610
|
+
limit: options.limit ?? 1e3
|
|
1611
|
+
},
|
|
1612
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
async deleteFile(volumeId, targetPath) {
|
|
1616
|
+
this.ensureCloud("Volume file deletion");
|
|
1617
|
+
const safeVolumeId = encodePathSegment(volumeId);
|
|
1618
|
+
return this.httpClient.request({
|
|
1619
|
+
method: "DELETE",
|
|
1620
|
+
url: `/v1/volumes/${safeVolumeId}/files`,
|
|
1621
|
+
params: { path: targetPath },
|
|
1622
|
+
headers: { "X-API-Key": this.httpClient.apiKey }
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
// src/client/InstaVM.ts
|
|
1628
|
+
import FormData2 from "form-data";
|
|
1422
1629
|
var InstaVM = class {
|
|
1423
1630
|
constructor(apiKey, options = {}) {
|
|
1424
1631
|
this._sessionId = null;
|
|
@@ -1449,6 +1656,7 @@ var InstaVM = class {
|
|
|
1449
1656
|
this.apiKeys = new APIKeysManager(this.httpClient, this.local);
|
|
1450
1657
|
this.audit = new AuditManager(this.httpClient, this.local);
|
|
1451
1658
|
this.webhooks = new WebhooksManager(this.httpClient, this.local);
|
|
1659
|
+
this.volumes = new VolumesManager(this.httpClient, this.local);
|
|
1452
1660
|
}
|
|
1453
1661
|
/**
|
|
1454
1662
|
* Ensure operation is not called in local mode
|
|
@@ -1612,7 +1820,7 @@ var InstaVM = class {
|
|
|
1612
1820
|
if (!sessionId) {
|
|
1613
1821
|
throw new SessionError("Session ID not set. Please create a session first using createSession().");
|
|
1614
1822
|
}
|
|
1615
|
-
const formData = new
|
|
1823
|
+
const formData = new FormData2();
|
|
1616
1824
|
for (const file of files) {
|
|
1617
1825
|
if (Buffer.isBuffer(file.content)) {
|
|
1618
1826
|
formData.append("files", file.content, file.name);
|
|
@@ -1724,6 +1932,49 @@ var InstaVM = class {
|
|
|
1724
1932
|
return false;
|
|
1725
1933
|
}
|
|
1726
1934
|
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Get the app URL for a session
|
|
1937
|
+
*
|
|
1938
|
+
* @param sessionId - Session ID (uses current session if not provided)
|
|
1939
|
+
* @param port - Port to expose (1-65535, default: 80)
|
|
1940
|
+
*/
|
|
1941
|
+
async getSessionAppUrl(sessionId, port) {
|
|
1942
|
+
this.ensureNotLocal("Session app URL");
|
|
1943
|
+
const targetSessionId = sessionId || this._sessionId;
|
|
1944
|
+
if (!targetSessionId) {
|
|
1945
|
+
throw new SessionError("Session ID not set. Please create a session first.");
|
|
1946
|
+
}
|
|
1947
|
+
const params = {};
|
|
1948
|
+
if (port !== void 0) {
|
|
1949
|
+
params.port = port;
|
|
1950
|
+
}
|
|
1951
|
+
return this.httpClient.get(
|
|
1952
|
+
`/v1/sessions/app-url/${targetSessionId}`,
|
|
1953
|
+
params,
|
|
1954
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* List sandbox records with optional filtering
|
|
1959
|
+
*
|
|
1960
|
+
* @param options.metadata - JSON-serializable metadata filters
|
|
1961
|
+
* @param options.limit - Maximum number of results (1-1000, default: 100)
|
|
1962
|
+
*/
|
|
1963
|
+
async listSandboxes(options = {}) {
|
|
1964
|
+
this.ensureNotLocal("Sandbox listing");
|
|
1965
|
+
const params = {};
|
|
1966
|
+
if (options.metadata !== void 0) {
|
|
1967
|
+
params.metadata = JSON.stringify(options.metadata);
|
|
1968
|
+
}
|
|
1969
|
+
if (options.limit !== void 0) {
|
|
1970
|
+
params.limit = options.limit;
|
|
1971
|
+
}
|
|
1972
|
+
return this.httpClient.get(
|
|
1973
|
+
"/v1/sessions/sandboxes",
|
|
1974
|
+
params,
|
|
1975
|
+
{ "X-API-Key": this.httpClient.apiKey }
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1727
1978
|
/**
|
|
1728
1979
|
* Get usage statistics for a session
|
|
1729
1980
|
*/
|
|
@@ -1996,6 +2247,7 @@ export {
|
|
|
1996
2247
|
SnapshotsManager,
|
|
1997
2248
|
UnsupportedOperationError,
|
|
1998
2249
|
VMsManager,
|
|
2250
|
+
VolumesManager,
|
|
1999
2251
|
WebhooksManager
|
|
2000
2252
|
};
|
|
2001
2253
|
//# sourceMappingURL=index.mjs.map
|