@sandbox-engine/sdk 0.2.0 → 0.2.2

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.d.mts CHANGED
@@ -1,6 +1,20 @@
1
- import WebSocket from 'ws';
2
1
  import { EventEmitter } from 'node:events';
3
2
 
3
+ interface WsLike {
4
+ on(event: 'message', handler: (data: unknown) => void): void;
5
+ on(event: 'error', handler: (err: Error) => void): void;
6
+ on(event: 'close', handler: () => void): void;
7
+ on(event: 'open', handler: () => void): void;
8
+ once(event: 'message', handler: (data: unknown) => void): void;
9
+ once(event: 'error', handler: (err: Error) => void): void;
10
+ once(event: 'open', handler: () => void): void;
11
+ send(data: string): void;
12
+ close(): void;
13
+ readyState: number;
14
+ readonly OPEN: number;
15
+ readonly CONNECTING: number;
16
+ }
17
+
4
18
  declare class HttpClient {
5
19
  private readonly baseUrl;
6
20
  private readonly apiKey;
@@ -9,7 +23,7 @@ declare class HttpClient {
9
23
  get<T>(path: string, query?: Record<string, string>): Promise<T>;
10
24
  post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
11
25
  delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
12
- openWebSocket(path: string): WebSocket;
26
+ openWebSocket(path: string): Promise<WsLike>;
13
27
  }
14
28
 
15
29
  interface SandboxOptions {
@@ -129,10 +143,21 @@ interface FileWatchOptions {
129
143
  recursive?: boolean;
130
144
  onEvent: (event: FileWatchEvent) => void;
131
145
  }
146
+ interface ExposePortOptions {
147
+ /** Human-readable label for this port (e.g. 'api', 'frontend'). */
148
+ name?: string;
149
+ /**
150
+ * Access token embedded in the preview URL.
151
+ * Use a stable value to get a consistent URL across sandbox restarts.
152
+ * When set, the URL becomes: https://{port}-{id}-{token}.{domain}
153
+ */
154
+ token?: string;
155
+ }
132
156
  interface PortMapping {
133
157
  containerPort: number;
134
- hostPort: number;
135
158
  url: string;
159
+ name?: string;
160
+ token?: string;
136
161
  }
137
162
  type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
138
163
  interface WsMessage {
@@ -163,7 +188,7 @@ declare class Filesystem {
163
188
 
164
189
  declare class Process extends EventEmitter {
165
190
  private ws;
166
- constructor(ws: WebSocket);
191
+ constructor(ws: WsLike);
167
192
  kill(): void;
168
193
  wait(): Promise<ExecResult>;
169
194
  }
@@ -187,9 +212,9 @@ declare class BackgroundProcess {
187
212
  }>;
188
213
  waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
189
214
  waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
190
- streamLogs(opts?: StreamLogsOptions): {
215
+ streamLogs(opts?: StreamLogsOptions): Promise<{
191
216
  close: () => void;
192
- };
217
+ }>;
193
218
  }
194
219
  declare class ProcessManager {
195
220
  private readonly sandboxId;
@@ -204,7 +229,7 @@ declare class ProcessManager {
204
229
 
205
230
  declare class Terminal extends EventEmitter {
206
231
  private ws;
207
- constructor(ws: WebSocket);
232
+ constructor(ws: WsLike);
208
233
  write(data: string): void;
209
234
  resize(cols: number, rows: number): void;
210
235
  close(): void;
@@ -243,9 +268,27 @@ declare class Sandbox {
243
268
  getProxyEnv: (secretNames: string[]) => Promise<ProxyEnvResult>;
244
269
  };
245
270
  readonly ports: {
271
+ /**
272
+ * List all currently exposed ports for this sandbox.
273
+ */
246
274
  list: () => Promise<PortMapping[]>;
247
- expose: (containerPort: number) => Promise<void>;
275
+ /**
276
+ * Expose any port (1024–65535) and get a public HTTPS URL.
277
+ *
278
+ * @example
279
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
280
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
281
+ */
282
+ expose: (containerPort: number, opts?: ExposePortOptions) => Promise<PortMapping>;
283
+ /**
284
+ * Remove a port from public access.
285
+ */
248
286
  unexpose: (containerPort: number) => Promise<void>;
287
+ /**
288
+ * Check whether a token grants access to an exposed port.
289
+ * Returns true if the port has no token requirement or if the token matches.
290
+ */
291
+ validateToken: (containerPort: number, token: string) => Promise<boolean>;
249
292
  };
250
293
  info(): Promise<SandboxApiResponse>;
251
294
  keepalive(timeout?: number): Promise<SandboxApiResponse>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,20 @@
1
- import WebSocket from 'ws';
2
1
  import { EventEmitter } from 'node:events';
3
2
 
3
+ interface WsLike {
4
+ on(event: 'message', handler: (data: unknown) => void): void;
5
+ on(event: 'error', handler: (err: Error) => void): void;
6
+ on(event: 'close', handler: () => void): void;
7
+ on(event: 'open', handler: () => void): void;
8
+ once(event: 'message', handler: (data: unknown) => void): void;
9
+ once(event: 'error', handler: (err: Error) => void): void;
10
+ once(event: 'open', handler: () => void): void;
11
+ send(data: string): void;
12
+ close(): void;
13
+ readyState: number;
14
+ readonly OPEN: number;
15
+ readonly CONNECTING: number;
16
+ }
17
+
4
18
  declare class HttpClient {
5
19
  private readonly baseUrl;
6
20
  private readonly apiKey;
@@ -9,7 +23,7 @@ declare class HttpClient {
9
23
  get<T>(path: string, query?: Record<string, string>): Promise<T>;
10
24
  post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
11
25
  delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
12
- openWebSocket(path: string): WebSocket;
26
+ openWebSocket(path: string): Promise<WsLike>;
13
27
  }
14
28
 
15
29
  interface SandboxOptions {
@@ -129,10 +143,21 @@ interface FileWatchOptions {
129
143
  recursive?: boolean;
130
144
  onEvent: (event: FileWatchEvent) => void;
131
145
  }
146
+ interface ExposePortOptions {
147
+ /** Human-readable label for this port (e.g. 'api', 'frontend'). */
148
+ name?: string;
149
+ /**
150
+ * Access token embedded in the preview URL.
151
+ * Use a stable value to get a consistent URL across sandbox restarts.
152
+ * When set, the URL becomes: https://{port}-{id}-{token}.{domain}
153
+ */
154
+ token?: string;
155
+ }
132
156
  interface PortMapping {
133
157
  containerPort: number;
134
- hostPort: number;
135
158
  url: string;
159
+ name?: string;
160
+ token?: string;
136
161
  }
137
162
  type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
138
163
  interface WsMessage {
@@ -163,7 +188,7 @@ declare class Filesystem {
163
188
 
164
189
  declare class Process extends EventEmitter {
165
190
  private ws;
166
- constructor(ws: WebSocket);
191
+ constructor(ws: WsLike);
167
192
  kill(): void;
168
193
  wait(): Promise<ExecResult>;
169
194
  }
@@ -187,9 +212,9 @@ declare class BackgroundProcess {
187
212
  }>;
188
213
  waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
189
214
  waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
190
- streamLogs(opts?: StreamLogsOptions): {
215
+ streamLogs(opts?: StreamLogsOptions): Promise<{
191
216
  close: () => void;
192
- };
217
+ }>;
193
218
  }
194
219
  declare class ProcessManager {
195
220
  private readonly sandboxId;
@@ -204,7 +229,7 @@ declare class ProcessManager {
204
229
 
205
230
  declare class Terminal extends EventEmitter {
206
231
  private ws;
207
- constructor(ws: WebSocket);
232
+ constructor(ws: WsLike);
208
233
  write(data: string): void;
209
234
  resize(cols: number, rows: number): void;
210
235
  close(): void;
@@ -243,9 +268,27 @@ declare class Sandbox {
243
268
  getProxyEnv: (secretNames: string[]) => Promise<ProxyEnvResult>;
244
269
  };
245
270
  readonly ports: {
271
+ /**
272
+ * List all currently exposed ports for this sandbox.
273
+ */
246
274
  list: () => Promise<PortMapping[]>;
247
- expose: (containerPort: number) => Promise<void>;
275
+ /**
276
+ * Expose any port (1024–65535) and get a public HTTPS URL.
277
+ *
278
+ * @example
279
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
280
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
281
+ */
282
+ expose: (containerPort: number, opts?: ExposePortOptions) => Promise<PortMapping>;
283
+ /**
284
+ * Remove a port from public access.
285
+ */
248
286
  unexpose: (containerPort: number) => Promise<void>;
287
+ /**
288
+ * Check whether a token grants access to an exposed port.
289
+ * Returns true if the port has no token requirement or if the token matches.
290
+ */
291
+ validateToken: (containerPort: number, token: string) => Promise<boolean>;
249
292
  };
250
293
  info(): Promise<SandboxApiResponse>;
251
294
  keepalive(timeout?: number): Promise<SandboxApiResponse>;
package/dist/index.js CHANGED
@@ -39,8 +39,37 @@ __export(index_exports, {
39
39
  });
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
+ // src/ws-compat.ts
43
+ var import_node_events = require("events");
44
+ var WsEmitterAdapter = class extends import_node_events.EventEmitter {
45
+ constructor(ws) {
46
+ super();
47
+ this.ws = ws;
48
+ ws.accept?.();
49
+ ws.addEventListener("message", (e) => {
50
+ const raw = e.data;
51
+ this.emit("message", typeof raw === "string" ? raw : String(raw));
52
+ });
53
+ ws.addEventListener("error", (e) => this.emit("error", e));
54
+ ws.addEventListener("close", () => this.emit("close"));
55
+ ws.addEventListener("open", () => this.emit("open"));
56
+ }
57
+ ws;
58
+ OPEN = 1;
59
+ CONNECTING = 0;
60
+ get readyState() {
61
+ return this.ws.readyState;
62
+ }
63
+ send(data) {
64
+ this.ws.send(data);
65
+ }
66
+ close() {
67
+ this.ws.close();
68
+ }
69
+ };
70
+ var IS_CF_WORKERS = typeof globalThis.WebSocketPair !== "undefined";
71
+
42
72
  // src/client.ts
43
- var import_ws = __toESM(require("ws"));
44
73
  var HttpClient = class {
45
74
  constructor(baseUrl, apiKey) {
46
75
  this.baseUrl = baseUrl;
@@ -98,11 +127,26 @@ var HttpClient = class {
98
127
  if (!text) return void 0;
99
128
  return JSON.parse(text);
100
129
  }
101
- openWebSocket(path) {
130
+ // ── WebSocket ───────────────────────────────────────────────────────────────
131
+ // CF Workers: uses fetch() with Upgrade header (supports Authorization).
132
+ // Node.js: uses the ws package (installed as an optional dependency).
133
+ async openWebSocket(path) {
102
134
  const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
103
- return new import_ws.default(wsUrl, {
104
- headers: { authorization: `Bearer ${this.apiKey}` }
105
- });
135
+ const authHeader = `Bearer ${this.apiKey}`;
136
+ if (IS_CF_WORKERS) {
137
+ const resp = await fetch(wsUrl, {
138
+ headers: {
139
+ Upgrade: "websocket",
140
+ Connection: "Upgrade",
141
+ Authorization: authHeader
142
+ }
143
+ });
144
+ const cfWs = resp.webSocket;
145
+ if (!cfWs) throw new Error("WebSocket upgrade failed \u2014 server did not accept");
146
+ return new WsEmitterAdapter(cfWs);
147
+ }
148
+ const { default: WS } = await import("ws");
149
+ return new WS(wsUrl, { headers: { authorization: authHeader } });
106
150
  }
107
151
  };
108
152
 
@@ -169,7 +213,7 @@ var Filesystem = class {
169
213
  });
170
214
  }
171
215
  async watch(dirPath, opts) {
172
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
216
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
173
217
  await new Promise((resolve, reject) => {
174
218
  ws.once("open", () => {
175
219
  ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
@@ -179,7 +223,7 @@ var Filesystem = class {
179
223
  });
180
224
  ws.on("message", (raw) => {
181
225
  try {
182
- const event = JSON.parse(raw.toString());
226
+ const event = JSON.parse(String(raw));
183
227
  opts.onEvent(event);
184
228
  } catch {
185
229
  }
@@ -195,8 +239,8 @@ var Filesystem = class {
195
239
  };
196
240
 
197
241
  // src/process.ts
198
- var import_node_events = require("events");
199
- var Process = class extends import_node_events.EventEmitter {
242
+ var import_node_events2 = require("events");
243
+ var Process = class extends import_node_events2.EventEmitter {
200
244
  ws;
201
245
  constructor(ws) {
202
246
  super();
@@ -282,8 +326,8 @@ var BackgroundProcess = class {
282
326
  );
283
327
  return res.match;
284
328
  }
285
- streamLogs(opts = {}) {
286
- const ws = this.client.openWebSocket(
329
+ async streamLogs(opts = {}) {
330
+ const ws = await this.client.openWebSocket(
287
331
  `/api/sandboxes/${this.sandboxId}/processes/logs/stream`
288
332
  );
289
333
  ws.on("open", () => {
@@ -344,8 +388,8 @@ var ProcessManager = class {
344
388
  };
345
389
 
346
390
  // src/terminal.ts
347
- var import_node_events2 = require("events");
348
- var Terminal = class extends import_node_events2.EventEmitter {
391
+ var import_node_events3 = require("events");
392
+ var Terminal = class extends import_node_events3.EventEmitter {
349
393
  ws;
350
394
  constructor(ws) {
351
395
  super();
@@ -427,7 +471,7 @@ var Sandbox = class _Sandbox {
427
471
  async exec(cmd, opts = {}) {
428
472
  const { args, cwd, env, timeout, stream } = opts;
429
473
  if (stream) {
430
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
474
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
431
475
  const proc = new Process(ws);
432
476
  await new Promise((resolve, reject) => {
433
477
  ws.once("open", () => {
@@ -454,7 +498,7 @@ var Sandbox = class _Sandbox {
454
498
  });
455
499
  }
456
500
  async terminal() {
457
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
501
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
458
502
  await new Promise((resolve, reject) => {
459
503
  ws.once("open", resolve);
460
504
  ws.once("error", reject);
@@ -469,22 +513,45 @@ var Sandbox = class _Sandbox {
469
513
  }
470
514
  };
471
515
  ports = {
516
+ /**
517
+ * List all currently exposed ports for this sandbox.
518
+ */
472
519
  list: async () => {
473
520
  const res = await this.client.get(
474
521
  `/api/sandboxes/${this.id}/ports`
475
522
  );
476
523
  return res.ports;
477
524
  },
478
- expose: async (containerPort) => {
479
- await this.client.post(
525
+ /**
526
+ * Expose any port (1024–65535) and get a public HTTPS URL.
527
+ *
528
+ * @example
529
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
530
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
531
+ */
532
+ expose: async (containerPort, opts = {}) => {
533
+ return this.client.post(
480
534
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`,
481
- {}
535
+ opts
482
536
  );
483
537
  },
538
+ /**
539
+ * Remove a port from public access.
540
+ */
484
541
  unexpose: async (containerPort) => {
485
542
  await this.client.delete(
486
543
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`
487
544
  );
545
+ },
546
+ /**
547
+ * Check whether a token grants access to an exposed port.
548
+ * Returns true if the port has no token requirement or if the token matches.
549
+ */
550
+ validateToken: async (containerPort, token) => {
551
+ const res = await this.client.get(
552
+ `/api/sandboxes/${this.id}/ports/${containerPort}/validate?token=${encodeURIComponent(token)}`
553
+ );
554
+ return res.valid;
488
555
  }
489
556
  };
490
557
  async info() {
package/dist/index.mjs CHANGED
@@ -1,5 +1,34 @@
1
+ // src/ws-compat.ts
2
+ import { EventEmitter } from "events";
3
+ var WsEmitterAdapter = class extends EventEmitter {
4
+ constructor(ws) {
5
+ super();
6
+ this.ws = ws;
7
+ ws.accept?.();
8
+ ws.addEventListener("message", (e) => {
9
+ const raw = e.data;
10
+ this.emit("message", typeof raw === "string" ? raw : String(raw));
11
+ });
12
+ ws.addEventListener("error", (e) => this.emit("error", e));
13
+ ws.addEventListener("close", () => this.emit("close"));
14
+ ws.addEventListener("open", () => this.emit("open"));
15
+ }
16
+ ws;
17
+ OPEN = 1;
18
+ CONNECTING = 0;
19
+ get readyState() {
20
+ return this.ws.readyState;
21
+ }
22
+ send(data) {
23
+ this.ws.send(data);
24
+ }
25
+ close() {
26
+ this.ws.close();
27
+ }
28
+ };
29
+ var IS_CF_WORKERS = typeof globalThis.WebSocketPair !== "undefined";
30
+
1
31
  // src/client.ts
2
- import WebSocket from "ws";
3
32
  var HttpClient = class {
4
33
  constructor(baseUrl, apiKey) {
5
34
  this.baseUrl = baseUrl;
@@ -57,11 +86,26 @@ var HttpClient = class {
57
86
  if (!text) return void 0;
58
87
  return JSON.parse(text);
59
88
  }
60
- openWebSocket(path) {
89
+ // ── WebSocket ───────────────────────────────────────────────────────────────
90
+ // CF Workers: uses fetch() with Upgrade header (supports Authorization).
91
+ // Node.js: uses the ws package (installed as an optional dependency).
92
+ async openWebSocket(path) {
61
93
  const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
62
- return new WebSocket(wsUrl, {
63
- headers: { authorization: `Bearer ${this.apiKey}` }
64
- });
94
+ const authHeader = `Bearer ${this.apiKey}`;
95
+ if (IS_CF_WORKERS) {
96
+ const resp = await fetch(wsUrl, {
97
+ headers: {
98
+ Upgrade: "websocket",
99
+ Connection: "Upgrade",
100
+ Authorization: authHeader
101
+ }
102
+ });
103
+ const cfWs = resp.webSocket;
104
+ if (!cfWs) throw new Error("WebSocket upgrade failed \u2014 server did not accept");
105
+ return new WsEmitterAdapter(cfWs);
106
+ }
107
+ const { default: WS } = await import("ws");
108
+ return new WS(wsUrl, { headers: { authorization: authHeader } });
65
109
  }
66
110
  };
67
111
 
@@ -128,7 +172,7 @@ var Filesystem = class {
128
172
  });
129
173
  }
130
174
  async watch(dirPath, opts) {
131
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
175
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
132
176
  await new Promise((resolve, reject) => {
133
177
  ws.once("open", () => {
134
178
  ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
@@ -138,7 +182,7 @@ var Filesystem = class {
138
182
  });
139
183
  ws.on("message", (raw) => {
140
184
  try {
141
- const event = JSON.parse(raw.toString());
185
+ const event = JSON.parse(String(raw));
142
186
  opts.onEvent(event);
143
187
  } catch {
144
188
  }
@@ -154,8 +198,8 @@ var Filesystem = class {
154
198
  };
155
199
 
156
200
  // src/process.ts
157
- import { EventEmitter } from "events";
158
- var Process = class extends EventEmitter {
201
+ import { EventEmitter as EventEmitter2 } from "events";
202
+ var Process = class extends EventEmitter2 {
159
203
  ws;
160
204
  constructor(ws) {
161
205
  super();
@@ -241,8 +285,8 @@ var BackgroundProcess = class {
241
285
  );
242
286
  return res.match;
243
287
  }
244
- streamLogs(opts = {}) {
245
- const ws = this.client.openWebSocket(
288
+ async streamLogs(opts = {}) {
289
+ const ws = await this.client.openWebSocket(
246
290
  `/api/sandboxes/${this.sandboxId}/processes/logs/stream`
247
291
  );
248
292
  ws.on("open", () => {
@@ -303,8 +347,8 @@ var ProcessManager = class {
303
347
  };
304
348
 
305
349
  // src/terminal.ts
306
- import { EventEmitter as EventEmitter2 } from "events";
307
- var Terminal = class extends EventEmitter2 {
350
+ import { EventEmitter as EventEmitter3 } from "events";
351
+ var Terminal = class extends EventEmitter3 {
308
352
  ws;
309
353
  constructor(ws) {
310
354
  super();
@@ -386,7 +430,7 @@ var Sandbox = class _Sandbox {
386
430
  async exec(cmd, opts = {}) {
387
431
  const { args, cwd, env, timeout, stream } = opts;
388
432
  if (stream) {
389
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
433
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
390
434
  const proc = new Process(ws);
391
435
  await new Promise((resolve, reject) => {
392
436
  ws.once("open", () => {
@@ -413,7 +457,7 @@ var Sandbox = class _Sandbox {
413
457
  });
414
458
  }
415
459
  async terminal() {
416
- const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
460
+ const ws = await this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
417
461
  await new Promise((resolve, reject) => {
418
462
  ws.once("open", resolve);
419
463
  ws.once("error", reject);
@@ -428,22 +472,45 @@ var Sandbox = class _Sandbox {
428
472
  }
429
473
  };
430
474
  ports = {
475
+ /**
476
+ * List all currently exposed ports for this sandbox.
477
+ */
431
478
  list: async () => {
432
479
  const res = await this.client.get(
433
480
  `/api/sandboxes/${this.id}/ports`
434
481
  );
435
482
  return res.ports;
436
483
  },
437
- expose: async (containerPort) => {
438
- await this.client.post(
484
+ /**
485
+ * Expose any port (1024–65535) and get a public HTTPS URL.
486
+ *
487
+ * @example
488
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
489
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
490
+ */
491
+ expose: async (containerPort, opts = {}) => {
492
+ return this.client.post(
439
493
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`,
440
- {}
494
+ opts
441
495
  );
442
496
  },
497
+ /**
498
+ * Remove a port from public access.
499
+ */
443
500
  unexpose: async (containerPort) => {
444
501
  await this.client.delete(
445
502
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`
446
503
  );
504
+ },
505
+ /**
506
+ * Check whether a token grants access to an exposed port.
507
+ * Returns true if the port has no token requirement or if the token matches.
508
+ */
509
+ validateToken: async (containerPort, token) => {
510
+ const res = await this.client.get(
511
+ `/api/sandboxes/${this.id}/ports/${containerPort}/validate?token=${encodeURIComponent(token)}`
512
+ );
513
+ return res.valid;
447
514
  }
448
515
  };
449
516
  async info() {
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@sandbox-engine/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "SDK for creating and managing isolated sandbox environments",
5
- "keywords": ["sandbox", "docker", "code-execution", "isolated", "e2b"],
5
+ "keywords": [
6
+ "sandbox",
7
+ "docker",
8
+ "code-execution",
9
+ "isolated",
10
+ "e2b"
11
+ ],
6
12
  "license": "MIT",
7
13
  "main": "dist/index.js",
8
14
  "module": "dist/index.mjs",