@cloudflare/sandbox 0.0.0-fd5ec7f → 0.0.0-feafd32

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