@sandboxxjs/core 0.5.0 → 2.0.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/.turbo/turbo-build.log +1 -0
- package/CHANGELOG.md +17 -0
- package/dist/allocator.d.ts +44 -0
- package/dist/allocator.d.ts.map +1 -0
- package/dist/allocator.js +14 -0
- package/dist/allocator.js.map +1 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +21 -0
- package/dist/client.js.map +1 -0
- package/dist/create-client.d.ts +11 -0
- package/dist/create-client.d.ts.map +1 -0
- package/dist/create-client.js +159 -0
- package/dist/create-client.js.map +1 -0
- package/dist/index.d.ts +25 -326
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -16858
- package/dist/index.js.map +1 -295
- package/dist/protocol.d.ts +96 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +15 -0
- package/dist/protocol.js.map +1 -0
- package/dist/provider.d.ts +54 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +14 -0
- package/dist/provider.js.map +1 -0
- package/dist/registry.d.ts +28 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +19 -0
- package/dist/registry.js.map +1 -0
- package/dist/router.d.ts +24 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -0
- package/dist/sandbox.d.ts +54 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +15 -0
- package/dist/sandbox.js.map +1 -0
- package/package.json +10 -35
- package/src/allocator.ts +48 -0
- package/src/client.ts +51 -0
- package/src/create-client.ts +187 -0
- package/src/index.ts +45 -0
- package/src/protocol.ts +133 -0
- package/src/provider.ts +54 -0
- package/src/registry.ts +29 -0
- package/src/router.ts +25 -0
- package/src/sandbox.ts +52 -0
- package/tsconfig.json +12 -0
- package/README.md +0 -60
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createSandboxClient — the runtime implementation of SandboxClient.
|
|
3
|
+
*
|
|
4
|
+
* Platform-agnostic: uses standard WebSocket API (available in Node 22+, browsers).
|
|
5
|
+
* Platform differences are injected via SandboxProvider, which supplies
|
|
6
|
+
* Executor, FileSystem, and ProcessManager components.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SandboxClient, SandboxClientOptions } from "./client";
|
|
10
|
+
import type {
|
|
11
|
+
ClientMessage,
|
|
12
|
+
ErrorMessage,
|
|
13
|
+
FsResultMessage,
|
|
14
|
+
ResultMessage,
|
|
15
|
+
ServiceMessage,
|
|
16
|
+
} from "./protocol";
|
|
17
|
+
import type { SandboxProvider } from "./provider";
|
|
18
|
+
|
|
19
|
+
export function createSandboxClient(provider: SandboxProvider): SandboxClient {
|
|
20
|
+
const executor = provider.createExecutor();
|
|
21
|
+
const fs = provider.createFileSystem();
|
|
22
|
+
const pm = provider.createProcessManager();
|
|
23
|
+
|
|
24
|
+
let ws: WebSocket | null = null;
|
|
25
|
+
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
26
|
+
let isConnected = false;
|
|
27
|
+
|
|
28
|
+
async function connect(options: SandboxClientOptions): Promise<void> {
|
|
29
|
+
const { wsUrl, sandboxId, token, heartbeatInterval = 30_000 } = options;
|
|
30
|
+
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
ws = new WebSocket(wsUrl);
|
|
33
|
+
|
|
34
|
+
ws.onopen = () => {
|
|
35
|
+
// Step 3: Register
|
|
36
|
+
const registerMsg: ClientMessage = { type: "register", sandboxId, token };
|
|
37
|
+
ws!.send(JSON.stringify(registerMsg));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
ws.onmessage = (event) => {
|
|
41
|
+
const data = typeof event.data === "string" ? event.data : "";
|
|
42
|
+
let msg: ServiceMessage;
|
|
43
|
+
try {
|
|
44
|
+
msg = JSON.parse(data);
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (msg.type === "registered") {
|
|
50
|
+
// Step 4: Ready
|
|
51
|
+
isConnected = true;
|
|
52
|
+
heartbeatTimer = setInterval(() => {
|
|
53
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
54
|
+
const hb: ClientMessage = { type: "heartbeat" };
|
|
55
|
+
ws.send(JSON.stringify(hb));
|
|
56
|
+
}
|
|
57
|
+
}, heartbeatInterval);
|
|
58
|
+
resolve();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Step 5: Command — handle and respond
|
|
63
|
+
handleCommand(msg);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
ws.onerror = () => {
|
|
67
|
+
if (!isConnected) reject(new Error("WebSocket connection failed"));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
ws.onclose = () => {
|
|
71
|
+
cleanup();
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function handleCommand(msg: ServiceMessage): Promise<void> {
|
|
77
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
switch (msg.type) {
|
|
81
|
+
// Executor
|
|
82
|
+
case "exec": {
|
|
83
|
+
const result = await executor.exec(msg.command, {
|
|
84
|
+
cwd: msg.cwd,
|
|
85
|
+
timeout: msg.timeout,
|
|
86
|
+
});
|
|
87
|
+
const reply: ResultMessage = {
|
|
88
|
+
type: "result",
|
|
89
|
+
id: msg.id,
|
|
90
|
+
stdout: result.stdout,
|
|
91
|
+
stderr: result.stderr,
|
|
92
|
+
exitCode: result.exitCode,
|
|
93
|
+
};
|
|
94
|
+
ws.send(JSON.stringify(reply));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// FileSystem
|
|
99
|
+
case "fs.read": {
|
|
100
|
+
const content = await fs.readFile(msg.path);
|
|
101
|
+
sendFsResult(msg.id, { content });
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "fs.write": {
|
|
106
|
+
await fs.writeFile(msg.path, msg.content);
|
|
107
|
+
sendFsResult(msg.id, { written: true });
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "fs.list": {
|
|
112
|
+
const files = await fs.listFiles(msg.path);
|
|
113
|
+
sendFsResult(msg.id, files);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case "fs.mkdir": {
|
|
118
|
+
await fs.mkdir(msg.path, { recursive: msg.recursive });
|
|
119
|
+
sendFsResult(msg.id, { created: true });
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "fs.delete": {
|
|
124
|
+
await fs.deleteFile(msg.path);
|
|
125
|
+
sendFsResult(msg.id, { deleted: true });
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ProcessManager
|
|
130
|
+
case "process.start": {
|
|
131
|
+
const proc = await pm.start(msg.command, { cwd: msg.cwd });
|
|
132
|
+
sendFsResult(msg.id, proc);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "process.kill": {
|
|
137
|
+
await pm.kill(msg.processId);
|
|
138
|
+
sendFsResult(msg.id, { killed: true });
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case "process.list": {
|
|
143
|
+
const procs = await pm.list();
|
|
144
|
+
sendFsResult(msg.id, procs);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const errorMsg: ErrorMessage = {
|
|
150
|
+
type: "error",
|
|
151
|
+
id: (msg as { id?: string }).id || "unknown",
|
|
152
|
+
message: err instanceof Error ? err.message : "Command failed",
|
|
153
|
+
};
|
|
154
|
+
ws.send(JSON.stringify(errorMsg));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function sendFsResult(id: string, data: unknown): void {
|
|
159
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
160
|
+
const reply: FsResultMessage = { type: "fs.result", id, data };
|
|
161
|
+
ws.send(JSON.stringify(reply));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function cleanup(): void {
|
|
165
|
+
isConnected = false;
|
|
166
|
+
if (heartbeatTimer) {
|
|
167
|
+
clearInterval(heartbeatTimer);
|
|
168
|
+
heartbeatTimer = null;
|
|
169
|
+
}
|
|
170
|
+
ws = null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function disconnect(): Promise<void> {
|
|
174
|
+
if (ws) {
|
|
175
|
+
ws.close();
|
|
176
|
+
cleanup();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
connect,
|
|
182
|
+
disconnect,
|
|
183
|
+
get connected() {
|
|
184
|
+
return isConnected;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sandboxxjs/core — Sandbox abstraction framework.
|
|
3
|
+
*
|
|
4
|
+
* Unified lifecycle for any sandbox environment:
|
|
5
|
+
*
|
|
6
|
+
* Allocate → Prepare → Register → Ready → Command
|
|
7
|
+
*
|
|
8
|
+
* 1. Allocator provisions resources, returns SandboxContainer (status: pending)
|
|
9
|
+
* 2. Sandbox environment starts, sandbox-client prepares
|
|
10
|
+
* 3. sandbox-client connects to Registry via WebSocket, registers
|
|
11
|
+
* 4. Registry marks sandbox as ready
|
|
12
|
+
* 5. Router dispatches commands through Registry to sandbox-client
|
|
13
|
+
*
|
|
14
|
+
* Platform differences are injected via SandboxProvider:
|
|
15
|
+
* - @sandboxxjs/node-provider: child_process + node:fs
|
|
16
|
+
* - @sandboxxjs/web-provider: @webcontainer/api
|
|
17
|
+
* - Future: Docker, SSH, WASM, etc.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Allocator — Step 1: Allocate
|
|
21
|
+
export type {
|
|
22
|
+
AllocateRequest,
|
|
23
|
+
SandboxAllocator,
|
|
24
|
+
SandboxContainer,
|
|
25
|
+
SandboxContainerType,
|
|
26
|
+
SandboxStatus,
|
|
27
|
+
} from "./allocator";
|
|
28
|
+
|
|
29
|
+
// Client — Step 2-3: Prepare + Register
|
|
30
|
+
export type { SandboxClient, SandboxClientOptions } from "./client";
|
|
31
|
+
export { createSandboxClient } from "./create-client";
|
|
32
|
+
// Provider — Platform capability injection
|
|
33
|
+
export type {
|
|
34
|
+
SandboxExecutor,
|
|
35
|
+
SandboxFileSystem,
|
|
36
|
+
SandboxProcessManager,
|
|
37
|
+
SandboxProvider,
|
|
38
|
+
} from "./provider";
|
|
39
|
+
// Registry — Step 3-4: Register + Ready
|
|
40
|
+
export type { SandboxConnection, SandboxRegistry } from "./registry";
|
|
41
|
+
// Router — Step 5: Command (RPC dispatch)
|
|
42
|
+
export type { SandboxRouter } from "./router";
|
|
43
|
+
|
|
44
|
+
// Sandbox — Step 5: Command (consumer-facing)
|
|
45
|
+
export type { ExecOptions, ExecResult, FileInfo, ProcessInfo, Sandbox } from "./sandbox";
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox protocol — WebSocket message types between sandbox-client and service.
|
|
3
|
+
*
|
|
4
|
+
* This protocol covers lifecycle steps 3-5:
|
|
5
|
+
* Register: client sends RegisterMessage, service responds RegisteredMessage
|
|
6
|
+
* Ready: heartbeat keeps connection alive
|
|
7
|
+
* Command: service sends commands, client returns results
|
|
8
|
+
*
|
|
9
|
+
* All sandbox-clients (cloud, web, future types) speak this same protocol.
|
|
10
|
+
*
|
|
11
|
+
* Lifecycle: Allocate → Prepare → Register → Ready → Command
|
|
12
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ==================== Client → Service ====================
|
|
16
|
+
|
|
17
|
+
export interface RegisterMessage {
|
|
18
|
+
type: "register";
|
|
19
|
+
sandboxId: string;
|
|
20
|
+
token: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResultMessage {
|
|
24
|
+
type: "result";
|
|
25
|
+
id: string;
|
|
26
|
+
stdout: string;
|
|
27
|
+
stderr: string;
|
|
28
|
+
exitCode: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FsResultMessage {
|
|
32
|
+
type: "fs.result";
|
|
33
|
+
id: string;
|
|
34
|
+
data: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ErrorMessage {
|
|
38
|
+
type: "error";
|
|
39
|
+
id: string;
|
|
40
|
+
message: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface HeartbeatMessage {
|
|
44
|
+
type: "heartbeat";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ==================== Service → Client ====================
|
|
48
|
+
|
|
49
|
+
export interface ExecMessage {
|
|
50
|
+
type: "exec";
|
|
51
|
+
id: string;
|
|
52
|
+
command: string;
|
|
53
|
+
cwd?: string;
|
|
54
|
+
timeout?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface FsReadMessage {
|
|
58
|
+
type: "fs.read";
|
|
59
|
+
id: string;
|
|
60
|
+
path: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface FsWriteMessage {
|
|
64
|
+
type: "fs.write";
|
|
65
|
+
id: string;
|
|
66
|
+
path: string;
|
|
67
|
+
content: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface FsListMessage {
|
|
71
|
+
type: "fs.list";
|
|
72
|
+
id: string;
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface FsMkdirMessage {
|
|
77
|
+
type: "fs.mkdir";
|
|
78
|
+
id: string;
|
|
79
|
+
path: string;
|
|
80
|
+
recursive?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface FsDeleteMessage {
|
|
84
|
+
type: "fs.delete";
|
|
85
|
+
id: string;
|
|
86
|
+
path: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ProcessStartMessage {
|
|
90
|
+
type: "process.start";
|
|
91
|
+
id: string;
|
|
92
|
+
command: string;
|
|
93
|
+
cwd?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ProcessKillMessage {
|
|
97
|
+
type: "process.kill";
|
|
98
|
+
id: string;
|
|
99
|
+
processId: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ProcessListMessage {
|
|
103
|
+
type: "process.list";
|
|
104
|
+
id: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface RegisteredMessage {
|
|
108
|
+
type: "registered";
|
|
109
|
+
sandboxId: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ==================== Union types ====================
|
|
113
|
+
|
|
114
|
+
/** Messages sent from sandbox-client to service */
|
|
115
|
+
export type ClientMessage =
|
|
116
|
+
| RegisterMessage
|
|
117
|
+
| ResultMessage
|
|
118
|
+
| FsResultMessage
|
|
119
|
+
| ErrorMessage
|
|
120
|
+
| HeartbeatMessage;
|
|
121
|
+
|
|
122
|
+
/** Messages sent from service to sandbox-client */
|
|
123
|
+
export type ServiceMessage =
|
|
124
|
+
| ExecMessage
|
|
125
|
+
| FsReadMessage
|
|
126
|
+
| FsWriteMessage
|
|
127
|
+
| FsListMessage
|
|
128
|
+
| FsMkdirMessage
|
|
129
|
+
| FsDeleteMessage
|
|
130
|
+
| ProcessStartMessage
|
|
131
|
+
| ProcessKillMessage
|
|
132
|
+
| ProcessListMessage
|
|
133
|
+
| RegisteredMessage;
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxProvider — platform capability injection.
|
|
3
|
+
*
|
|
4
|
+
* Provider is a component factory that supplies platform-specific
|
|
5
|
+
* implementations of Executor, FileSystem, and ProcessManager.
|
|
6
|
+
*
|
|
7
|
+
* The core layer (createSandboxClient) only depends on this interface.
|
|
8
|
+
* Platform differences are isolated in provider implementations:
|
|
9
|
+
* - node-provider: child_process + node:fs
|
|
10
|
+
* - web-provider: @webcontainer/api
|
|
11
|
+
* - Future: Docker, SSH, etc.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExecOptions, ExecResult, FileInfo, ProcessInfo } from "./sandbox";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Command execution component.
|
|
18
|
+
*/
|
|
19
|
+
export interface SandboxExecutor {
|
|
20
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* File system component.
|
|
25
|
+
*/
|
|
26
|
+
export interface SandboxFileSystem {
|
|
27
|
+
readFile(path: string): Promise<string>;
|
|
28
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
29
|
+
listFiles(path: string): Promise<FileInfo[]>;
|
|
30
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
31
|
+
deleteFile(path: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Process management component.
|
|
36
|
+
*/
|
|
37
|
+
export interface SandboxProcessManager {
|
|
38
|
+
start(command: string, options?: { cwd?: string }): Promise<ProcessInfo>;
|
|
39
|
+
kill(processId: string): Promise<void>;
|
|
40
|
+
list(): Promise<ProcessInfo[]>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* SandboxProvider — the component factory.
|
|
45
|
+
*
|
|
46
|
+
* Each platform implements this interface to provide its components.
|
|
47
|
+
* createSandboxClient(provider) obtains components and dispatches
|
|
48
|
+
* incoming messages to the appropriate component.
|
|
49
|
+
*/
|
|
50
|
+
export interface SandboxProvider {
|
|
51
|
+
createExecutor(): SandboxExecutor;
|
|
52
|
+
createFileSystem(): SandboxFileSystem;
|
|
53
|
+
createProcessManager(): SandboxProcessManager;
|
|
54
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxRegistry — accepts sandbox-client connections and manages lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Step 3-4 of the lifecycle: Register → Ready.
|
|
5
|
+
*
|
|
6
|
+
* When a sandbox-client connects via WebSocket and sends a register message,
|
|
7
|
+
* the registry:
|
|
8
|
+
* 1. Validates the token
|
|
9
|
+
* 2. Binds the WebSocket connection to the sandboxId
|
|
10
|
+
* 3. Updates the sandbox status from "pending" to "ready"
|
|
11
|
+
* 4. Provides the command routing channel
|
|
12
|
+
*
|
|
13
|
+
* The registry also handles disconnection, heartbeat, and reconnection.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle: Allocate → Prepare → Register → Ready → Command
|
|
16
|
+
* ^^^^^^^^^^^^^^^^^
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface SandboxConnection {
|
|
20
|
+
sandboxId: string;
|
|
21
|
+
connectedAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SandboxRegistry {
|
|
25
|
+
/** Check if a sandbox-client is connected and ready */
|
|
26
|
+
has(sandboxId: string): boolean;
|
|
27
|
+
/** List all active connections */
|
|
28
|
+
connections(): SandboxConnection[];
|
|
29
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxRouter — routes RPC methods to sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Step 5 of the lifecycle: Command.
|
|
5
|
+
*
|
|
6
|
+
* The router is the external-facing RPC interface.
|
|
7
|
+
* It combines the allocator (lifecycle) and registry (connections)
|
|
8
|
+
* to provide a unified dispatch: method + params → result.
|
|
9
|
+
*
|
|
10
|
+
* Internally, it resolves the sandboxId from params, finds the
|
|
11
|
+
* connected sandbox-client through the registry, and forwards
|
|
12
|
+
* the command over WebSocket.
|
|
13
|
+
*
|
|
14
|
+
* Lifecycle: Allocate → Prepare → Register → Ready → Command
|
|
15
|
+
* ^^^^^^^
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Sandbox } from "./sandbox";
|
|
19
|
+
|
|
20
|
+
export interface SandboxRouter {
|
|
21
|
+
/** Get a Sandbox handle by id — routes through registry to connected client */
|
|
22
|
+
getSandbox(sandboxId: string): Sandbox;
|
|
23
|
+
/** Dispatch an RPC method with params */
|
|
24
|
+
dispatch(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
25
|
+
}
|
package/src/sandbox.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox — the unified interface for sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Step 5 of the lifecycle: Command.
|
|
5
|
+
*
|
|
6
|
+
* Consumers get a Sandbox instance and operate on it.
|
|
7
|
+
* They never need to know if it's a cloud container, a browser WebContainer,
|
|
8
|
+
* or any other sandbox type. All commands are routed through the registry
|
|
9
|
+
* to the connected sandbox-client.
|
|
10
|
+
*
|
|
11
|
+
* Lifecycle: Allocate → Prepare → Register → Ready → Command
|
|
12
|
+
* ^^^^^^^
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ExecOptions {
|
|
16
|
+
cwd?: string;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExecResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
exitCode: number;
|
|
24
|
+
success: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProcessInfo {
|
|
28
|
+
id: string;
|
|
29
|
+
pid?: number;
|
|
30
|
+
command: string;
|
|
31
|
+
status: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FileInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
type: "file" | "directory" | "symlink";
|
|
37
|
+
size?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Sandbox {
|
|
41
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
42
|
+
startProcess(command: string, options?: { cwd?: string }): Promise<ProcessInfo>;
|
|
43
|
+
killProcess(processId: string): Promise<void>;
|
|
44
|
+
listProcesses(): Promise<ProcessInfo[]>;
|
|
45
|
+
readFile(path: string): Promise<string>;
|
|
46
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
47
|
+
listFiles(path: string): Promise<FileInfo[]>;
|
|
48
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
49
|
+
deleteFile(path: string): Promise<void>;
|
|
50
|
+
exposePort(port: number, hostname: string): Promise<{ url: string }>;
|
|
51
|
+
destroy(): Promise<void>;
|
|
52
|
+
}
|
package/tsconfig.json
ADDED
package/README.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# @@sandboxxjs/sandbox/core
|
|
2
|
-
|
|
3
|
-
Core implementation for SandboX - secure code execution engine.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Multiple isolators (Local, Cloudflare, E2B, Firecracker)
|
|
8
|
-
- Multiple runtimes (Node.js, Python, Bash, Docker)
|
|
9
|
-
- File system operations
|
|
10
|
-
- Resource limits
|
|
11
|
-
- Event-driven architecture
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @@sandboxxjs/sandbox/core
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Usage
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { Sandbox, LocalIsolator } from "@@sandboxxjs/sandbox/core";
|
|
23
|
-
|
|
24
|
-
const isolator = new LocalIsolator("node");
|
|
25
|
-
const sandbox = new Sandbox({
|
|
26
|
-
runtime: "node",
|
|
27
|
-
isolator: "local",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const result = await sandbox.execute({
|
|
31
|
-
code: 'console.log("Hello")',
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
console.log(result.stdout); // "Hello"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## API
|
|
38
|
-
|
|
39
|
-
### Sandbox
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
class Sandbox {
|
|
43
|
-
execute(options: ExecuteOptions): Promise<ExecuteResult>;
|
|
44
|
-
writeFile(path: string, data: string): Promise<void>;
|
|
45
|
-
readFile(path: string): Promise<string>;
|
|
46
|
-
destroy(): Promise<void>;
|
|
47
|
-
on(event: string, handler: Function): void;
|
|
48
|
-
fs: FileSystem;
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Isolators
|
|
53
|
-
|
|
54
|
-
- `LocalIsolator` - Process isolation via execa
|
|
55
|
-
- `CloudflareContainerIsolator` - Docker via Bun binary
|
|
56
|
-
- `E2BIsolator` - E2B microVM (not yet implemented)
|
|
57
|
-
|
|
58
|
-
## License
|
|
59
|
-
|
|
60
|
-
MIT
|