@cloudflare/sandbox 0.0.0-bb855ca → 0.0.0-c1f057e

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/Dockerfile +107 -66
  3. package/README.md +88 -710
  4. package/dist/chunk-BFVUNTP4.js +104 -0
  5. package/dist/chunk-BFVUNTP4.js.map +1 -0
  6. package/dist/chunk-EKSWCBCA.js +86 -0
  7. package/dist/chunk-EKSWCBCA.js.map +1 -0
  8. package/dist/chunk-JXZMAU2C.js +559 -0
  9. package/dist/chunk-JXZMAU2C.js.map +1 -0
  10. package/dist/chunk-KWOEDJUN.js +7 -0
  11. package/dist/chunk-KWOEDJUN.js.map +1 -0
  12. package/dist/chunk-Y52QYTSM.js +2420 -0
  13. package/dist/chunk-Y52QYTSM.js.map +1 -0
  14. package/dist/chunk-Z532A7QC.js +78 -0
  15. package/dist/chunk-Z532A7QC.js.map +1 -0
  16. package/dist/file-stream.d.ts +43 -0
  17. package/dist/file-stream.js +9 -0
  18. package/dist/file-stream.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.js +67 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/interpreter.d.ts +33 -0
  23. package/dist/interpreter.js +8 -0
  24. package/dist/interpreter.js.map +1 -0
  25. package/dist/request-handler.d.ts +18 -0
  26. package/dist/request-handler.js +13 -0
  27. package/dist/request-handler.js.map +1 -0
  28. package/dist/sandbox-DMlNr93l.d.ts +596 -0
  29. package/dist/sandbox.d.ts +4 -0
  30. package/dist/sandbox.js +13 -0
  31. package/dist/sandbox.js.map +1 -0
  32. package/dist/security.d.ts +31 -0
  33. package/dist/security.js +13 -0
  34. package/dist/security.js.map +1 -0
  35. package/dist/sse-parser.d.ts +28 -0
  36. package/dist/sse-parser.js +11 -0
  37. package/dist/sse-parser.js.map +1 -0
  38. package/dist/version.d.ts +8 -0
  39. package/dist/version.js +7 -0
  40. package/dist/version.js.map +1 -0
  41. package/package.json +13 -5
  42. package/src/clients/base-client.ts +280 -0
  43. package/src/clients/command-client.ts +115 -0
  44. package/src/clients/file-client.ts +269 -0
  45. package/src/clients/git-client.ts +92 -0
  46. package/src/clients/index.ts +64 -0
  47. package/src/clients/interpreter-client.ts +329 -0
  48. package/src/clients/port-client.ts +105 -0
  49. package/src/clients/process-client.ts +177 -0
  50. package/src/clients/sandbox-client.ts +41 -0
  51. package/src/clients/types.ts +84 -0
  52. package/src/clients/utility-client.ts +119 -0
  53. package/src/errors/adapter.ts +180 -0
  54. package/src/errors/classes.ts +469 -0
  55. package/src/errors/index.ts +105 -0
  56. package/src/file-stream.ts +164 -0
  57. package/src/index.ts +85 -21
  58. package/src/interpreter.ts +22 -13
  59. package/src/request-handler.ts +69 -43
  60. package/src/sandbox.ts +663 -444
  61. package/src/security.ts +14 -23
  62. package/src/sse-parser.ts +4 -8
  63. package/src/version.ts +6 -0
  64. package/startup.sh +3 -0
  65. package/tests/base-client.test.ts +328 -0
  66. package/tests/command-client.test.ts +407 -0
  67. package/tests/file-client.test.ts +643 -0
  68. package/tests/file-stream.test.ts +306 -0
  69. package/tests/get-sandbox.test.ts +110 -0
  70. package/tests/git-client.test.ts +328 -0
  71. package/tests/port-client.test.ts +301 -0
  72. package/tests/process-client.test.ts +658 -0
  73. package/tests/sandbox.test.ts +465 -0
  74. package/tests/sse-parser.test.ts +290 -0
  75. package/tests/utility-client.test.ts +332 -0
  76. package/tests/version.test.ts +16 -0
  77. package/tests/wrangler.jsonc +35 -0
  78. package/tsconfig.json +9 -1
  79. package/vitest.config.ts +31 -0
  80. package/container_src/bun.lock +0 -122
  81. package/container_src/handler/exec.ts +0 -340
  82. package/container_src/handler/file.ts +0 -844
  83. package/container_src/handler/git.ts +0 -182
  84. package/container_src/handler/ports.ts +0 -314
  85. package/container_src/handler/process.ts +0 -640
  86. package/container_src/index.ts +0 -531
  87. package/container_src/jupyter-server.ts +0 -336
  88. package/container_src/mime-processor.ts +0 -255
  89. package/container_src/package.json +0 -18
  90. package/container_src/startup.sh +0 -52
  91. package/container_src/types.ts +0 -108
  92. package/src/client.ts +0 -1021
  93. package/src/interpreter-types.ts +0 -383
  94. package/src/jupyter-client.ts +0 -266
  95. package/src/types.ts +0 -401
@@ -1,531 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { serve } from "bun";
3
- import { handleExecuteRequest, handleStreamingExecuteRequest } from "./handler/exec";
4
- import {
5
- handleDeleteFileRequest,
6
- handleMkdirRequest,
7
- handleMoveFileRequest,
8
- handleReadFileRequest,
9
- handleRenameFileRequest,
10
- handleWriteFileRequest,
11
- } from "./handler/file";
12
- import { handleGitCheckoutRequest } from "./handler/git";
13
- import {
14
- handleExposePortRequest,
15
- handleGetExposedPortsRequest,
16
- handleProxyRequest,
17
- handleUnexposePortRequest,
18
- } from "./handler/ports";
19
- import {
20
- handleGetProcessLogsRequest,
21
- handleGetProcessRequest,
22
- handleKillAllProcessesRequest,
23
- handleKillProcessRequest,
24
- handleListProcessesRequest,
25
- handleStartProcessRequest,
26
- handleStreamProcessLogsRequest,
27
- } from "./handler/process";
28
- import { type CreateContextRequest, JupyterServer } from "./jupyter-server";
29
- import type { ProcessRecord, SessionData } from "./types";
30
-
31
- // In-memory session storage (in production, you'd want to use a proper database)
32
- const sessions = new Map<string, SessionData>();
33
-
34
- // In-memory storage for exposed ports
35
- const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
36
-
37
- // In-memory process storage - cleared on container restart
38
- const processes = new Map<string, ProcessRecord>();
39
-
40
- // Generate a unique session ID using cryptographically secure randomness
41
- function generateSessionId(): string {
42
- return `session_${Date.now()}_${randomBytes(6).toString('hex')}`;
43
- }
44
-
45
- // Clean up old sessions (older than 1 hour)
46
- function cleanupOldSessions() {
47
- const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
48
- for (const [sessionId, session] of sessions.entries()) {
49
- if (session.createdAt < oneHourAgo && !session.activeProcess) {
50
- sessions.delete(sessionId);
51
- console.log(`[Server] Cleaned up old session: ${sessionId}`);
52
- }
53
- }
54
- }
55
-
56
- // Run cleanup every 10 minutes
57
- setInterval(cleanupOldSessions, 10 * 60 * 1000);
58
-
59
- // Initialize Jupyter server
60
- const jupyterServer = new JupyterServer();
61
- let jupyterInitialized = false;
62
-
63
- // Initialize Jupyter immediately since startup.sh ensures it's ready
64
- (async () => {
65
- try {
66
- await jupyterServer.initialize();
67
- jupyterInitialized = true;
68
- console.log("[Container] Jupyter integration initialized successfully");
69
- } catch (error) {
70
- console.error("[Container] Failed to initialize Jupyter:", error);
71
- // Log more details to help debug
72
- if (error instanceof Error) {
73
- console.error("[Container] Error details:", error.message);
74
- console.error("[Container] Stack trace:", error.stack);
75
- }
76
- }
77
- })();
78
-
79
- const server = serve({
80
- async fetch(req: Request) {
81
- const url = new URL(req.url);
82
- const pathname = url.pathname;
83
-
84
- console.log(`[Container] Incoming ${req.method} request to ${pathname}`);
85
-
86
- // Handle CORS
87
- const corsHeaders = {
88
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
89
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
90
- "Access-Control-Allow-Origin": "*",
91
- };
92
-
93
- // Handle preflight requests
94
- if (req.method === "OPTIONS") {
95
- console.log(`[Container] Handling CORS preflight for ${pathname}`);
96
- return new Response(null, { headers: corsHeaders, status: 200 });
97
- }
98
-
99
- try {
100
- // Handle different routes
101
- console.log(`[Container] Processing ${req.method} ${pathname}`);
102
- switch (pathname) {
103
- case "/":
104
- return new Response("Hello from Bun server! 🚀", {
105
- headers: {
106
- "Content-Type": "text/plain; charset=utf-8",
107
- ...corsHeaders,
108
- },
109
- });
110
-
111
- case "/api/session/create":
112
- if (req.method === "POST") {
113
- const sessionId = generateSessionId();
114
- const sessionData: SessionData = {
115
- activeProcess: null,
116
- createdAt: new Date(),
117
- sessionId,
118
- };
119
- sessions.set(sessionId, sessionData);
120
-
121
- console.log(`[Server] Created new session: ${sessionId}`);
122
-
123
- return new Response(
124
- JSON.stringify({
125
- message: "Session created successfully",
126
- sessionId,
127
- timestamp: new Date().toISOString(),
128
- }),
129
- {
130
- headers: {
131
- "Content-Type": "application/json",
132
- ...corsHeaders,
133
- },
134
- }
135
- );
136
- }
137
- break;
138
-
139
- case "/api/session/list":
140
- if (req.method === "GET") {
141
- const sessionList = Array.from(sessions.values()).map(
142
- (session) => ({
143
- createdAt: session.createdAt.toISOString(),
144
- hasActiveProcess: !!session.activeProcess,
145
- sessionId: session.sessionId,
146
- })
147
- );
148
-
149
- return new Response(
150
- JSON.stringify({
151
- count: sessionList.length,
152
- sessions: sessionList,
153
- timestamp: new Date().toISOString(),
154
- }),
155
- {
156
- headers: {
157
- "Content-Type": "application/json",
158
- ...corsHeaders,
159
- },
160
- }
161
- );
162
- }
163
- break;
164
-
165
- case "/api/execute":
166
- if (req.method === "POST") {
167
- return handleExecuteRequest(sessions, req, corsHeaders);
168
- }
169
- break;
170
-
171
- case "/api/execute/stream":
172
- if (req.method === "POST") {
173
- return handleStreamingExecuteRequest(sessions, req, corsHeaders);
174
- }
175
- break;
176
-
177
- case "/api/ping":
178
- if (req.method === "GET") {
179
- return new Response(
180
- JSON.stringify({
181
- message: "pong",
182
- timestamp: new Date().toISOString(),
183
- jupyter: jupyterInitialized ? "ready" : "not ready",
184
- }),
185
- {
186
- headers: {
187
- "Content-Type": "application/json",
188
- ...corsHeaders,
189
- },
190
- }
191
- );
192
- }
193
- break;
194
-
195
- case "/api/commands":
196
- if (req.method === "GET") {
197
- return new Response(
198
- JSON.stringify({
199
- availableCommands: [
200
- "ls",
201
- "pwd",
202
- "echo",
203
- "cat",
204
- "grep",
205
- "find",
206
- "whoami",
207
- "date",
208
- "uptime",
209
- "ps",
210
- "top",
211
- "df",
212
- "du",
213
- "free",
214
- ],
215
- timestamp: new Date().toISOString(),
216
- }),
217
- {
218
- headers: {
219
- "Content-Type": "application/json",
220
- ...corsHeaders,
221
- },
222
- }
223
- );
224
- }
225
- break;
226
-
227
- case "/api/git/checkout":
228
- if (req.method === "POST") {
229
- return handleGitCheckoutRequest(sessions, req, corsHeaders);
230
- }
231
- break;
232
-
233
- case "/api/mkdir":
234
- if (req.method === "POST") {
235
- return handleMkdirRequest(sessions, req, corsHeaders);
236
- }
237
- break;
238
-
239
- case "/api/write":
240
- if (req.method === "POST") {
241
- return handleWriteFileRequest(req, corsHeaders);
242
- }
243
- break;
244
-
245
- case "/api/read":
246
- if (req.method === "POST") {
247
- return handleReadFileRequest(req, corsHeaders);
248
- }
249
- break;
250
-
251
- case "/api/delete":
252
- if (req.method === "POST") {
253
- return handleDeleteFileRequest(req, corsHeaders);
254
- }
255
- break;
256
-
257
- case "/api/rename":
258
- if (req.method === "POST") {
259
- return handleRenameFileRequest(req, corsHeaders);
260
- }
261
- break;
262
-
263
- case "/api/move":
264
- if (req.method === "POST") {
265
- return handleMoveFileRequest(req, corsHeaders);
266
- }
267
- break;
268
-
269
- case "/api/expose-port":
270
- if (req.method === "POST") {
271
- return handleExposePortRequest(exposedPorts, req, corsHeaders);
272
- }
273
- break;
274
-
275
- case "/api/unexpose-port":
276
- if (req.method === "DELETE") {
277
- return handleUnexposePortRequest(exposedPorts, req, corsHeaders);
278
- }
279
- break;
280
-
281
- case "/api/exposed-ports":
282
- if (req.method === "GET") {
283
- return handleGetExposedPortsRequest(exposedPorts, req, corsHeaders);
284
- }
285
- break;
286
-
287
- case "/api/process/start":
288
- if (req.method === "POST") {
289
- return handleStartProcessRequest(processes, req, corsHeaders);
290
- }
291
- break;
292
-
293
- case "/api/process/list":
294
- if (req.method === "GET") {
295
- return handleListProcessesRequest(processes, req, corsHeaders);
296
- }
297
- break;
298
-
299
- case "/api/process/kill-all":
300
- if (req.method === "DELETE") {
301
- return handleKillAllProcessesRequest(processes, req, corsHeaders);
302
- }
303
- break;
304
-
305
- // Code interpreter endpoints
306
- case "/api/contexts":
307
- if (req.method === "POST") {
308
- if (!jupyterInitialized) {
309
- return new Response(
310
- JSON.stringify({
311
- error: "Jupyter server is not ready. Please try again in a moment."
312
- }),
313
- {
314
- status: 503,
315
- headers: {
316
- "Content-Type": "application/json",
317
- ...corsHeaders,
318
- },
319
- }
320
- );
321
- }
322
- try {
323
- const body = await req.json() as CreateContextRequest;
324
- const context = await jupyterServer.createContext(body);
325
- return new Response(
326
- JSON.stringify({
327
- id: context.id,
328
- language: context.language,
329
- cwd: context.cwd,
330
- createdAt: context.createdAt,
331
- lastUsed: context.lastUsed
332
- }),
333
- {
334
- headers: {
335
- "Content-Type": "application/json",
336
- ...corsHeaders,
337
- },
338
- }
339
- );
340
- } catch (error) {
341
- console.error("[Container] Error creating context:", error);
342
- return new Response(
343
- JSON.stringify({
344
- error: error instanceof Error ? error.message : "Failed to create context"
345
- }),
346
- {
347
- status: 500,
348
- headers: {
349
- "Content-Type": "application/json",
350
- ...corsHeaders,
351
- },
352
- }
353
- );
354
- }
355
- } else if (req.method === "GET") {
356
- if (!jupyterInitialized) {
357
- return new Response(
358
- JSON.stringify({ contexts: [] }),
359
- {
360
- headers: {
361
- "Content-Type": "application/json",
362
- ...corsHeaders,
363
- },
364
- }
365
- );
366
- }
367
- const contexts = await jupyterServer.listContexts();
368
- return new Response(
369
- JSON.stringify({ contexts }),
370
- {
371
- headers: {
372
- "Content-Type": "application/json",
373
- ...corsHeaders,
374
- },
375
- }
376
- );
377
- }
378
- break;
379
-
380
- case "/api/execute/code":
381
- if (req.method === "POST") {
382
- if (!jupyterInitialized) {
383
- return new Response(
384
- JSON.stringify({
385
- error: "Jupyter server is not ready. Please try again in a moment."
386
- }),
387
- {
388
- status: 503,
389
- headers: {
390
- "Content-Type": "application/json",
391
- ...corsHeaders,
392
- },
393
- }
394
- );
395
- }
396
- try {
397
- const body = await req.json() as { context_id: string; code: string; language?: string };
398
- return await jupyterServer.executeCode(body.context_id, body.code, body.language);
399
- } catch (error) {
400
- console.error("[Container] Error executing code:", error);
401
- return new Response(
402
- JSON.stringify({
403
- error: error instanceof Error ? error.message : "Failed to execute code"
404
- }),
405
- {
406
- status: 500,
407
- headers: {
408
- "Content-Type": "application/json",
409
- ...corsHeaders,
410
- },
411
- }
412
- );
413
- }
414
- }
415
- break;
416
-
417
- default:
418
- // Handle dynamic routes for contexts
419
- if (pathname.startsWith("/api/contexts/") && pathname.split('/').length === 4) {
420
- const contextId = pathname.split('/')[3];
421
- if (req.method === "DELETE") {
422
- try {
423
- await jupyterServer.deleteContext(contextId);
424
- return new Response(
425
- JSON.stringify({ success: true }),
426
- {
427
- headers: {
428
- "Content-Type": "application/json",
429
- ...corsHeaders,
430
- },
431
- }
432
- );
433
- } catch (error) {
434
- return new Response(
435
- JSON.stringify({
436
- error: error instanceof Error ? error.message : "Failed to delete context"
437
- }),
438
- {
439
- status: error instanceof Error && error.message.includes("not found") ? 404 : 500,
440
- headers: {
441
- "Content-Type": "application/json",
442
- ...corsHeaders,
443
- },
444
- }
445
- );
446
- }
447
- }
448
- }
449
-
450
- // Handle dynamic routes for individual processes
451
- if (pathname.startsWith("/api/process/")) {
452
- const segments = pathname.split('/');
453
- if (segments.length >= 4) {
454
- const processId = segments[3];
455
- const action = segments[4]; // Optional: logs, stream, etc.
456
-
457
- if (!action && req.method === "GET") {
458
- return handleGetProcessRequest(processes, req, corsHeaders, processId);
459
- } else if (!action && req.method === "DELETE") {
460
- return handleKillProcessRequest(processes, req, corsHeaders, processId);
461
- } else if (action === "logs" && req.method === "GET") {
462
- return handleGetProcessLogsRequest(processes, req, corsHeaders, processId);
463
- } else if (action === "stream" && req.method === "GET") {
464
- return handleStreamProcessLogsRequest(processes, req, corsHeaders, processId);
465
- }
466
- }
467
- }
468
- // Check if this is a proxy request for an exposed port
469
- if (pathname.startsWith("/proxy/")) {
470
- return handleProxyRequest(exposedPorts, req, corsHeaders);
471
- }
472
-
473
- console.log(`[Container] Route not found: ${pathname}`);
474
- return new Response("Not Found", {
475
- headers: corsHeaders,
476
- status: 404,
477
- });
478
- }
479
- } catch (error) {
480
- console.error(`[Container] Error handling ${req.method} ${pathname}:`, error);
481
- return new Response(
482
- JSON.stringify({
483
- error: "Internal server error",
484
- message: error instanceof Error ? error.message : "Unknown error",
485
- }),
486
- {
487
- headers: {
488
- "Content-Type": "application/json",
489
- ...corsHeaders,
490
- },
491
- status: 500,
492
- }
493
- );
494
- }
495
- },
496
- hostname: "0.0.0.0",
497
- port: 3000,
498
- // We don't need this, but typescript complains
499
- websocket: { async message() { } },
500
- });
501
-
502
- console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
503
- console.log(`📡 HTTP API endpoints available:`);
504
- console.log(` POST /api/session/create - Create a new session`);
505
- console.log(` GET /api/session/list - List all sessions`);
506
- console.log(` POST /api/execute - Execute a command (non-streaming)`);
507
- console.log(` POST /api/execute/stream - Execute a command (streaming)`);
508
- console.log(` POST /api/git/checkout - Checkout a git repository`);
509
- console.log(` POST /api/mkdir - Create a directory`);
510
- console.log(` POST /api/write - Write a file`);
511
- console.log(` POST /api/read - Read a file`);
512
- console.log(` POST /api/delete - Delete a file`);
513
- console.log(` POST /api/rename - Rename a file`);
514
- console.log(` POST /api/move - Move a file`);
515
- console.log(` POST /api/expose-port - Expose a port for external access`);
516
- console.log(` DELETE /api/unexpose-port - Unexpose a port`);
517
- console.log(` GET /api/exposed-ports - List exposed ports`);
518
- console.log(` POST /api/process/start - Start a background process`);
519
- console.log(` GET /api/process/list - List all processes`);
520
- console.log(` GET /api/process/{id} - Get process status`);
521
- console.log(` DELETE /api/process/{id} - Kill a process`);
522
- console.log(` GET /api/process/{id}/logs - Get process logs`);
523
- console.log(` GET /api/process/{id}/stream - Stream process logs (SSE)`);
524
- console.log(` DELETE /api/process/kill-all - Kill all processes`);
525
- console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
526
- console.log(` POST /api/contexts - Create a code execution context`);
527
- console.log(` GET /api/contexts - List all contexts`);
528
- console.log(` DELETE /api/contexts/{id} - Delete a context`);
529
- console.log(` POST /api/execute/code - Execute code in a context (streaming)`);
530
- console.log(` GET /api/ping - Health check`);
531
- console.log(` GET /api/commands - List available commands`);