@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 +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.js +85 -18
- package/dist/index.mjs +85 -18
- package/package.json +8 -2
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):
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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):
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
|
199
|
-
var Process = class extends
|
|
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
|
|
348
|
-
var Terminal = class extends
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
|
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
|
|
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
|
|
307
|
-
var Terminal = class extends
|
|
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
|
-
|
|
438
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "SDK for creating and managing isolated sandbox environments",
|
|
5
|
-
"keywords": [
|
|
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",
|