@cloudflare/sandbox 0.0.0-d55b0f4 → 0.0.0-d86b60e

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.
@@ -1,336 +1,184 @@
1
- import { type SpawnOptions, spawn } from "node:child_process";
2
- import type { ExecuteRequest, SessionData } from "../types";
1
+ import type { SessionManager } from "../isolation";
2
+ import type { SessionExecRequest } from "../types";
3
3
 
4
- function executeCommand(
5
- sessions: Map<string, SessionData>,
6
- command: string,
7
- sessionId?: string,
8
- background?: boolean
9
- ): Promise<{
10
- success: boolean;
11
- stdout: string;
12
- stderr: string;
13
- exitCode: number;
14
- }> {
15
- return new Promise((resolve, reject) => {
16
- const spawnOptions: SpawnOptions = {
17
- shell: true,
18
- stdio: ["pipe", "pipe", "pipe"] as const,
19
- detached: background || false,
20
- };
21
-
22
- const child = spawn(command, spawnOptions);
23
-
24
- // Store the process reference for cleanup if sessionId is provided
25
- if (sessionId && sessions.has(sessionId)) {
26
- const session = sessions.get(sessionId)!;
27
- session.activeProcess = child;
28
- }
29
-
30
- let stdout = "";
31
- let stderr = "";
32
-
33
- child.stdout?.on("data", (data) => {
34
- stdout += data.toString();
35
- });
36
-
37
- child.stderr?.on("data", (data) => {
38
- stderr += data.toString();
39
- });
40
-
41
- if (background) {
42
- // For background processes, unref and return quickly
43
- child.unref();
44
-
45
- // Collect initial output for 100ms then return
46
- setTimeout(() => {
47
- resolve({
48
- exitCode: 0, // Process is still running
49
- stderr,
50
- stdout,
51
- success: true,
52
- });
53
- }, 100);
54
-
55
- // Still handle errors
56
- child.on("error", (error) => {
57
- console.error(`[Server] Background process error: ${command}`, error);
58
- // Don't reject since we might have already resolved
59
- });
60
- } else {
61
- // Normal synchronous execution
62
- child.on("close", (code) => {
63
- // Clear the active process reference
64
- if (sessionId && sessions.has(sessionId)) {
65
- const session = sessions.get(sessionId)!;
66
- session.activeProcess = null;
67
- }
68
-
69
- console.log(`[Server] Command completed: ${command}, Exit code: ${code}`);
4
+ export async function handleExecuteRequest(
5
+ req: Request,
6
+ corsHeaders: Record<string, string>,
7
+ sessionManager: SessionManager
8
+ ) {
9
+ try {
10
+ const body = (await req.json()) as SessionExecRequest;
11
+ const { id, command } = body;
70
12
 
71
- resolve({
72
- exitCode: code || 0,
73
- stderr,
74
- stdout,
75
- success: code === 0,
76
- });
77
- });
13
+ console.log(
14
+ `[Container] Session exec request for '${id}': ${command}`
15
+ );
78
16
 
79
- child.on("error", (error) => {
80
- // Clear the active process reference
81
- if (sessionId && sessions.has(sessionId)) {
82
- const session = sessions.get(sessionId)!;
83
- session.activeProcess = null;
17
+ if (!id || !command) {
18
+ return new Response(
19
+ JSON.stringify({
20
+ error: "Session ID and command are required",
21
+ }),
22
+ {
23
+ status: 400,
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ ...corsHeaders,
27
+ },
84
28
  }
85
-
86
- reject(error);
87
- });
29
+ );
88
30
  }
89
- });
90
- }
91
31
 
92
- export async function handleExecuteRequest(
93
- sessions: Map<string, SessionData>,
94
- req: Request,
95
- corsHeaders: Record<string, string>
96
- ): Promise<Response> {
97
- try {
98
- const body = (await req.json()) as ExecuteRequest;
99
- const { command, sessionId, background } = body;
32
+ const session = sessionManager.getSession(id);
33
+ if (!session) {
34
+ console.error(`[Container] Session '${id}' not found!`);
35
+ const availableSessions = sessionManager.listSessions();
36
+ console.log(
37
+ `[Container] Available sessions: ${
38
+ availableSessions.join(", ") || "none"
39
+ }`
40
+ );
100
41
 
101
- if (!command || typeof command !== "string") {
102
42
  return new Response(
103
43
  JSON.stringify({
104
- error: "Command is required and must be a string",
44
+ error: `Session '${id}' not found`,
45
+ availableSessions,
105
46
  }),
106
47
  {
48
+ status: 404,
107
49
  headers: {
108
50
  "Content-Type": "application/json",
109
51
  ...corsHeaders,
110
52
  },
111
- status: 400,
112
53
  }
113
54
  );
114
55
  }
115
56
 
116
- console.log(`[Server] Executing command: ${command}`);
117
-
118
- const result = await executeCommand(sessions, command, sessionId, background);
57
+ const result = await session.exec(command);
119
58
 
120
- return new Response(
121
- JSON.stringify({
122
- command,
123
- exitCode: result.exitCode,
124
- stderr: result.stderr,
125
- stdout: result.stdout,
126
- success: result.success,
127
- timestamp: new Date().toISOString(),
128
- }),
129
- {
130
- headers: {
131
- "Content-Type": "application/json",
132
- ...corsHeaders,
133
- },
134
- }
135
- );
59
+ return new Response(JSON.stringify(result), {
60
+ headers: { "Content-Type": "application/json", ...corsHeaders },
61
+ });
136
62
  } catch (error) {
137
- console.error("[Server] Error in handleExecuteRequest:", error);
63
+ console.error("[Container] Session exec failed:", error);
138
64
  return new Response(
139
65
  JSON.stringify({
140
- error: "Failed to execute command",
141
- message: error instanceof Error ? error.message : "Unknown error",
66
+ error: "Command execution failed",
67
+ message:
68
+ error instanceof Error ? error.message : String(error),
142
69
  }),
143
70
  {
71
+ status: 500,
144
72
  headers: {
145
73
  "Content-Type": "application/json",
146
74
  ...corsHeaders,
147
75
  },
148
- status: 500,
149
76
  }
150
77
  );
151
78
  }
152
79
  }
153
80
 
154
81
  export async function handleStreamingExecuteRequest(
155
- sessions: Map<string, SessionData>,
156
82
  req: Request,
83
+ sessionManager: SessionManager,
157
84
  corsHeaders: Record<string, string>
158
- ): Promise<Response> {
85
+ ) {
159
86
  try {
160
- const body = (await req.json()) as ExecuteRequest;
161
- const { command, sessionId, background } = body;
87
+ const body = (await req.json()) as SessionExecRequest;
88
+ const { id, command } = body;
89
+
90
+ console.log(
91
+ `[Container] Session streaming exec request for '${id}': ${command}`
92
+ );
162
93
 
163
- if (!command || typeof command !== "string") {
94
+ if (!id || !command) {
164
95
  return new Response(
165
96
  JSON.stringify({
166
- error: "Command is required and must be a string",
97
+ error: "Session ID and command are required",
167
98
  }),
168
99
  {
100
+ status: 400,
169
101
  headers: {
170
102
  "Content-Type": "application/json",
171
103
  ...corsHeaders,
172
104
  },
173
- status: 400,
174
105
  }
175
106
  );
176
107
  }
177
108
 
178
- console.log(
179
- `[Server] Executing streaming command: ${command}`
180
- );
181
-
182
- const stream = new ReadableStream({
183
- start(controller) {
184
- const spawnOptions: SpawnOptions = {
185
- shell: true,
186
- stdio: ["pipe", "pipe", "pipe"] as const,
187
- detached: background || false,
188
- };
189
-
190
- const child = spawn(command, spawnOptions);
109
+ const session = sessionManager.getSession(id);
110
+ if (!session) {
111
+ console.error(`[Container] Session '${id}' not found!`);
112
+ const availableSessions = sessionManager.listSessions();
191
113
 
192
- // Store the process reference for cleanup if sessionId is provided
193
- if (sessionId && sessions.has(sessionId)) {
194
- const session = sessions.get(sessionId)!;
195
- session.activeProcess = child;
196
- }
197
-
198
- // For background processes, unref to prevent blocking
199
- if (background) {
200
- child.unref();
114
+ return new Response(
115
+ JSON.stringify({
116
+ error: `Session '${id}' not found`,
117
+ availableSessions,
118
+ }),
119
+ {
120
+ status: 404,
121
+ headers: {
122
+ "Content-Type": "application/json",
123
+ ...corsHeaders,
124
+ },
201
125
  }
126
+ );
127
+ }
202
128
 
203
- let stdout = "";
204
- let stderr = "";
205
-
206
- // Send command start event
207
- controller.enqueue(
208
- new TextEncoder().encode(
209
- `data: ${JSON.stringify({
210
- type: "start",
211
- timestamp: new Date().toISOString(),
212
- command,
213
- background: background || false,
214
- })}\n\n`
215
- )
216
- );
217
-
218
- child.stdout?.on("data", (data) => {
219
- const output = data.toString();
220
- stdout += output;
221
-
222
- // Send real-time output
223
- controller.enqueue(
224
- new TextEncoder().encode(
225
- `data: ${JSON.stringify({
226
- type: "stdout",
227
- timestamp: new Date().toISOString(),
228
- data: output,
229
- command,
230
- })}\n\n`
231
- )
232
- );
233
- });
234
-
235
- child.stderr?.on("data", (data) => {
236
- const output = data.toString();
237
- stderr += output;
238
-
239
- // Send real-time error output
240
- controller.enqueue(
241
- new TextEncoder().encode(
242
- `data: ${JSON.stringify({
243
- type: "stderr",
244
- timestamp: new Date().toISOString(),
245
- data: output,
246
- command,
247
- })}\n\n`
248
- )
249
- );
250
- });
251
-
252
- child.on("close", (code) => {
253
- // Clear the active process reference
254
- if (sessionId && sessions.has(sessionId)) {
255
- const session = sessions.get(sessionId)!;
256
- session.activeProcess = null;
257
- }
258
-
259
- console.log(
260
- `[Server] Command completed: ${command}, Exit code: ${code}`
261
- );
262
-
263
- // Send command completion event
264
- controller.enqueue(
265
- new TextEncoder().encode(
266
- `data: ${JSON.stringify({
267
- type: "complete",
268
- timestamp: new Date().toISOString(),
269
- command,
270
- exitCode: code,
271
- result: {
272
- success: code === 0,
273
- exitCode: code,
274
- stdout,
275
- stderr,
276
- command,
277
- timestamp: new Date().toISOString(),
278
- },
279
- })}\n\n`
280
- )
281
- );
282
-
283
- // For non-background processes, close the stream
284
- // For background processes with streaming, the stream stays open
285
- if (!background) {
286
- controller.close();
287
- }
288
- });
289
-
290
- child.on("error", (error) => {
291
- // Clear the active process reference
292
- if (sessionId && sessions.has(sessionId)) {
293
- const session = sessions.get(sessionId)!;
294
- session.activeProcess = null;
129
+ // Create a streaming response using the actual streaming method
130
+ const stream = new ReadableStream({
131
+ async start(controller) {
132
+ try {
133
+ // Use the streaming generator method
134
+ for await (const event of session.execStream(command)) {
135
+ // Forward each event as SSE
136
+ controller.enqueue(
137
+ new TextEncoder().encode(
138
+ `data: ${JSON.stringify(event)}\n\n`
139
+ )
140
+ );
295
141
  }
296
-
142
+ controller.close();
143
+ } catch (error) {
297
144
  controller.enqueue(
298
145
  new TextEncoder().encode(
299
146
  `data: ${JSON.stringify({
300
147
  type: "error",
301
- timestamp: new Date().toISOString(),
302
- error: error.message,
303
- command,
148
+ message:
149
+ error instanceof Error
150
+ ? error.message
151
+ : String(error),
304
152
  })}\n\n`
305
153
  )
306
154
  );
307
-
308
155
  controller.close();
309
- });
156
+ }
310
157
  },
311
158
  });
312
159
 
313
160
  return new Response(stream, {
314
161
  headers: {
162
+ "Content-Type": "text/event-stream",
315
163
  "Cache-Control": "no-cache",
316
164
  Connection: "keep-alive",
317
- "Content-Type": "text/event-stream",
318
165
  ...corsHeaders,
319
166
  },
320
167
  });
321
168
  } catch (error) {
322
- console.error("[Server] Error in handleStreamingExecuteRequest:", error);
169
+ console.error("[Container] Session stream exec failed:", error);
323
170
  return new Response(
324
171
  JSON.stringify({
325
- error: "Failed to execute streaming command",
326
- message: error instanceof Error ? error.message : "Unknown error",
172
+ error: "Stream execution failed",
173
+ message:
174
+ error instanceof Error ? error.message : String(error),
327
175
  }),
328
176
  {
177
+ status: 500,
329
178
  headers: {
330
179
  "Content-Type": "application/json",
331
180
  ...corsHeaders,
332
181
  },
333
- status: 500,
334
182
  }
335
183
  );
336
184
  }