RaBU1234 commited on
Commit
0447e79
Β·
verified Β·
1 Parent(s): 7a70d75

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +92 -146
server.js CHANGED
@@ -13,6 +13,7 @@ const sandboxManager = new SimpleSandboxManager();
13
  console.log('πŸš€ Sandbox Manager ready');
14
  })();
15
 
 
16
  app.get('/health', (req, res) => {
17
  res.status(200).json({
18
  status: 'healthy',
@@ -20,126 +21,117 @@ app.get('/health', (req, res) => {
20
  });
21
  });
22
 
 
23
  app.get('/', (req, res) => {
24
  res.json({
25
  status: 'running',
26
  sandboxes: sandboxManager.sandboxes.size,
27
  processes: sandboxManager.processes.size,
28
- message: 'πŸš€ Simple Sandbox API'
29
  });
30
  });
31
 
 
32
  app.post('/api/sandbox/create', async (req, res) => {
33
  try {
34
  const { timeout = 600000, ports = [] } = req.body;
35
- const sandboxId = 'sbx-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
 
36
  const sandbox = await sandboxManager.createSandbox(sandboxId, { timeout, ports });
37
 
38
  res.json({
39
  sandboxId,
40
  status: 'ready',
41
  path: sandbox.workspacePath,
42
- ports: sandbox.ports
 
 
43
  });
44
  } catch (error) {
45
  console.error('❌ Create error:', error);
46
- res.status(500).json({
47
- error: {
48
- code: 'sandbox_creation_failed',
49
- message: error.message
50
- }
51
- });
52
  }
53
  });
54
 
55
- app.get('/api/sandbox/:sandboxId', async (req, res) => {
 
56
  try {
57
- const { sandboxId } = req.params;
58
- const sandbox = sandboxManager.getSandbox(sandboxId);
59
 
60
  if (!sandbox) {
61
  return res.status(404).json({
62
- error: {
63
- code: 'sandbox_not_found',
64
- message: 'Sandbox not found'
65
- }
66
  });
67
  }
68
-
69
- if (sandbox.status === 'stopped') {
70
- return res.json({
71
- error: {
72
- code: 'sandbox_stopped',
73
- message: 'Sandbox has been stopped'
74
- }
75
- });
76
  }
77
-
78
- res.json({
79
- sandboxId: sandbox.sandboxId,
80
- status: sandbox.status
81
- });
82
  } catch (error) {
83
- console.error('❌ Get sandbox error:', error);
84
  res.status(500).json({ error: error.message });
85
  }
86
  });
87
 
88
- app.post('/api/sandbox/:sandboxId/command', async (req, res) => {
 
89
  try {
90
- const { sandboxId } = req.params;
91
- const { command, args = [], sudo = false, wait = true } = req.body;
92
 
93
- const result = await sandboxManager.executeCommand(sandboxId, command, args, {
94
- wait,
95
- sudo,
96
- background: !wait
97
- });
98
 
 
99
  res.json(result);
100
  } catch (error) {
101
  console.error('❌ Command error:', error);
102
- res.status(500).json({
103
- error: {
104
- code: 'command_execution_failed',
105
- message: error.message
106
- }
107
- });
108
  }
109
  });
110
 
111
- app.get('/api/sandbox/:sandboxId/cmds/:cmdId', async (req, res) => {
 
112
  try {
113
- const { sandboxId, cmdId } = req.params;
114
- const command = sandboxManager.getCommand(sandboxId, cmdId);
115
 
116
- if (!command) {
117
- return res.status(404).json({
118
- error: {
119
- code: 'command_not_found',
120
- message: 'Command not found'
121
- }
122
  });
123
  }
124
-
125
  res.json({
126
- sandboxId,
127
- cmdId: command.cmdId,
128
- startedAt: command.startedAt,
129
- exitCode: command.exitCode,
130
- status: command.status
131
  });
132
  } catch (error) {
133
- console.error('❌ Get command error:', error);
134
  res.status(500).json({ error: error.message });
135
  }
136
  });
137
 
138
- app.get('/api/sandbox/:sandboxId/cmds/:cmdId/logs', (req, res) => {
139
- const { sandboxId, cmdId } = req.params;
 
140
 
141
- console.log('πŸ“‘ SSE logs connection for ' + sandboxId + '/' + cmdId);
142
 
 
143
  res.setHeader('Content-Type', 'text/event-stream');
144
  res.setHeader('Cache-Control', 'no-cache');
145
  res.setHeader('Connection', 'keep-alive');
@@ -148,128 +140,79 @@ app.get('/api/sandbox/:sandboxId/cmds/:cmdId/logs', (req, res) => {
148
 
149
  res.flushHeaders();
150
 
151
- const command = sandboxManager.getCommand(sandboxId, cmdId);
152
-
153
- if (!command) {
154
- res.write('data: ' + JSON.stringify({
155
- error: 'Command not found'
156
- }) + '
157
 
158
- ');
159
- res.end();
160
- return;
161
- }
162
-
163
- if (command.logs && command.logs.length > 0) {
164
- command.logs.forEach(log => {
165
- res.write(JSON.stringify({
166
- data: log.data,
167
- stream: log.stream,
168
- timestamp: log.timestamp
169
- }) + '
170
- ');
171
- });
172
- }
173
-
174
- sandboxManager.addLogStream(sandboxId, cmdId, res);
175
 
 
176
  const keepAlive = setInterval(() => {
177
- try {
178
- res.write(': keepalive
179
 
180
- ');
181
- } catch (e) {
182
- clearInterval(keepAlive);
183
- }
184
  }, 15000);
185
 
186
  req.on('close', () => {
187
  clearInterval(keepAlive);
188
- console.log('πŸ“‘ SSE logs connection closed for ' + sandboxId + '/' + cmdId);
189
  res.end();
190
  });
191
  });
192
 
193
- app.post('/api/sandbox/:sandboxId/files', async (req, res) => {
 
194
  try {
195
- const { sandboxId } = req.params;
196
  const { files } = req.body;
197
 
198
- await sandboxManager.writeFiles(sandboxId, files);
199
  res.json({ success: true, filesWritten: files.length });
200
  } catch (error) {
201
  console.error('❌ File write error:', error);
202
- res.status(500).json({
203
- error: {
204
- code: 'file_write_failed',
205
- message: error.message
206
- }
207
- });
208
  }
209
  });
210
 
211
- app.get('/api/sandbox/:sandboxId/files', async (req, res) => {
 
212
  try {
213
- const { sandboxId } = req.params;
214
  const { path: filePath } = req.query;
215
 
216
- if (!filePath) {
217
- return res.status(400).json({
218
- error: 'Invalid parameters. You must pass a `path` as query'
219
- });
220
- }
221
-
222
- const stream = await sandboxManager.readFileStream(sandboxId, filePath);
223
-
224
- if (!stream) {
225
- return res.status(404).json({
226
- error: 'File not found in the Sandbox'
227
- });
228
- }
229
-
230
- res.setHeader('Content-Type', 'application/octet-stream');
231
- stream.pipe(res);
232
  } catch (error) {
233
  console.error('❌ File read error:', error);
234
  res.status(404).json({ error: 'File not found' });
235
  }
236
  });
237
 
238
- app.get('/api/sandbox/:sandboxId/url', async (req, res) => {
 
239
  try {
240
- const { sandboxId } = req.params;
241
- const { port } = req.query;
 
242
 
243
- const sandbox = sandboxManager.getSandbox(sandboxId);
244
- if (!sandbox) {
245
- return res.status(404).json({ error: 'Sandbox not found' });
246
- }
247
-
248
- const targetPort = port ? parseInt(port) : sandbox.ports[0];
249
-
250
- if (!targetPort) {
251
- return res.json({
252
- url: null,
253
- status: 'no port exposed'
254
- });
255
- }
256
-
257
- const baseUrl = process.env.SPACE_HOST || 'localhost:7860';
258
- const url = 'https://' + baseUrl + '/preview/' + targetPort + '/';
259
 
260
  res.json({
261
- url,
262
- status: 'running',
263
- port: targetPort
264
  });
265
  } catch (error) {
266
  res.status(500).json({ error: error.message });
267
  }
268
  });
269
 
270
- app.delete('/api/sandbox/:sandboxId', async (req, res) => {
 
271
  try {
272
- await sandboxManager.destroySandbox(req.params.sandboxId);
273
  res.json({ success: true });
274
  } catch (error) {
275
  console.error('❌ Delete error:', error);
@@ -277,15 +220,18 @@ app.delete('/api/sandbox/:sandboxId', async (req, res) => {
277
  }
278
  });
279
 
 
280
  setInterval(() => {
281
  sandboxManager.cleanupInactive();
282
  }, 2 * 60 * 1000);
283
 
284
- const PORT = process.env.PORT || 7860;
285
  app.listen(PORT, '0.0.0.0', () => {
286
- console.log('πŸš€ Sandbox API running on port ' + PORT);
 
287
  });
288
 
 
289
  process.on('SIGTERM', async () => {
290
  console.log('⚠️ Shutting down...');
291
  for (const [id] of sandboxManager.sandboxes.entries()) {
 
13
  console.log('πŸš€ Sandbox Manager ready');
14
  })();
15
 
16
+ // Health check
17
  app.get('/health', (req, res) => {
18
  res.status(200).json({
19
  status: 'healthy',
 
21
  });
22
  });
23
 
24
+ // Root endpoint
25
  app.get('/', (req, res) => {
26
  res.json({
27
  status: 'running',
28
  sandboxes: sandboxManager.sandboxes.size,
29
  processes: sandboxManager.processes.size,
30
+ message: 'πŸš€ Vercel-Compatible Sandbox API'
31
  });
32
  });
33
 
34
+ // Create sandbox (with Vercel-compatible params)
35
  app.post('/api/sandbox/create', async (req, res) => {
36
  try {
37
  const { timeout = 600000, ports = [] } = req.body;
38
+ const sandboxId = `sbx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
39
+
40
  const sandbox = await sandboxManager.createSandbox(sandboxId, { timeout, ports });
41
 
42
  res.json({
43
  sandboxId,
44
  status: 'ready',
45
  path: sandbox.workspacePath,
46
+ port: sandbox.port,
47
+ timeout,
48
+ exposedPorts: ports
49
  });
50
  } catch (error) {
51
  console.error('❌ Create error:', error);
52
+ res.status(500).json({ error: error.message });
 
 
 
 
 
53
  }
54
  });
55
 
56
+ // Get sandbox status (Vercel-compatible)
57
+ app.get('/api/sandbox/:id/status', async (req, res) => {
58
  try {
59
+ const { id } = req.params;
60
+ const sandbox = sandboxManager.getSandbox(id);
61
 
62
  if (!sandbox) {
63
  return res.status(404).json({
64
+ status: 'stopped',
65
+ error: 'Sandbox not found'
 
 
66
  });
67
  }
68
+
69
+ // Check if sandbox timed out
70
+ const elapsed = Date.now() - sandbox.createdAt;
71
+ if (elapsed > sandbox.timeout) {
72
+ await sandboxManager.destroySandbox(id);
73
+ return res.json({ status: 'stopped' });
 
 
74
  }
75
+
76
+ res.json({ status: 'running', sandboxId: id });
 
 
 
77
  } catch (error) {
78
+ console.error('❌ Status error:', error);
79
  res.status(500).json({ error: error.message });
80
  }
81
  });
82
 
83
+ // Execute command (sync)
84
+ app.post('/api/sandbox/:id/command', async (req, res) => {
85
  try {
86
+ const { id } = req.params;
87
+ const { command, args = [], background = false, sudo = false } = req.body;
88
 
89
+ // Security: Block sudo if not explicitly allowed
90
+ if (sudo) {
91
+ return res.status(403).json({ error: 'Sudo commands not allowed' });
92
+ }
 
93
 
94
+ const result = await sandboxManager.executeCommand(id, command, args, { background });
95
  res.json(result);
96
  } catch (error) {
97
  console.error('❌ Command error:', error);
98
+ res.status(500).json({ error: error.message });
 
 
 
 
 
99
  }
100
  });
101
 
102
+ // Get command info (Vercel-compatible)
103
+ app.get('/api/sandbox/:id/cmds/:cmdId', async (req, res) => {
104
  try {
105
+ const { id, cmdId } = req.params;
106
+ const procInfo = sandboxManager.getProcess(id);
107
 
108
+ if (!procInfo || procInfo.cmdId !== cmdId) {
109
+ return res.json({
110
+ sandboxId: id,
111
+ cmdId,
112
+ exitCode: null,
113
+ startedAt: null
114
  });
115
  }
116
+
117
  res.json({
118
+ sandboxId: id,
119
+ cmdId: procInfo.cmdId,
120
+ startedAt: procInfo.startedAt,
121
+ exitCode: procInfo.process.exitCode
 
122
  });
123
  } catch (error) {
 
124
  res.status(500).json({ error: error.message });
125
  }
126
  });
127
 
128
+ // SSE endpoint for command logs (Vercel-compatible)
129
+ app.get('/api/sandbox/:id/command/:cmdId/stream', (req, res) => {
130
+ const { id, cmdId } = req.params;
131
 
132
+ console.log(`πŸ“‘ SSE connection opened for ${id}/${cmdId}`);
133
 
134
+ // Set SSE headers
135
  res.setHeader('Content-Type', 'text/event-stream');
136
  res.setHeader('Cache-Control', 'no-cache');
137
  res.setHeader('Connection', 'keep-alive');
 
140
 
141
  res.flushHeaders();
142
 
143
+ // Send connection message
144
+ res.write(`data: ${JSON.stringify({ type: 'connected', message: 'Stream connected' })}
 
 
 
 
145
 
146
+ `);
147
+
148
+ sandboxManager.addStreamClient(id, cmdId, res);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ // Keep-alive
151
  const keepAlive = setInterval(() => {
152
+ res.write(`: keepalive
 
153
 
154
+ `);
 
 
 
155
  }, 15000);
156
 
157
  req.on('close', () => {
158
  clearInterval(keepAlive);
159
+ console.log(`πŸ“‘ SSE connection closed for ${id}/${cmdId}`);
160
  res.end();
161
  });
162
  });
163
 
164
+ // Write files (Vercel-compatible format)
165
+ app.post('/api/sandbox/:id/files', async (req, res) => {
166
  try {
167
+ const { id } = req.params;
168
  const { files } = req.body;
169
 
170
+ await sandboxManager.writeFiles(id, files);
171
  res.json({ success: true, filesWritten: files.length });
172
  } catch (error) {
173
  console.error('❌ File write error:', error);
174
+ res.status(500).json({ error: error.message });
 
 
 
 
 
175
  }
176
  });
177
 
178
+ // Read file (streaming)
179
+ app.get('/api/sandbox/:id/files', async (req, res) => {
180
  try {
181
+ const { id } = req.params;
182
  const { path: filePath } = req.query;
183
 
184
+ const content = await sandboxManager.readFile(id, filePath);
185
+ res.send(content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  } catch (error) {
187
  console.error('❌ File read error:', error);
188
  res.status(404).json({ error: 'File not found' });
189
  }
190
  });
191
 
192
+ // Get sandbox URL
193
+ app.get('/api/sandbox/:id/url', async (req, res) => {
194
  try {
195
+ const { id } = req.params;
196
+ const { port = 3000 } = req.query;
197
+ const procInfo = sandboxManager.getProcess(id);
198
 
199
+ const spaceHost = process.env.SPACE_HOST || 'http://localhost:7860';
200
+ const previewPort = procInfo?.port || port;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  res.json({
203
+ url: `${spaceHost}/preview/${previewPort}/`,
204
+ status: procInfo ? 'running' : 'no process',
205
+ port: previewPort
206
  });
207
  } catch (error) {
208
  res.status(500).json({ error: error.message });
209
  }
210
  });
211
 
212
+ // Delete sandbox
213
+ app.delete('/api/sandbox/:id', async (req, res) => {
214
  try {
215
+ await sandboxManager.destroySandbox(req.params.id);
216
  res.json({ success: true });
217
  } catch (error) {
218
  console.error('❌ Delete error:', error);
 
220
  }
221
  });
222
 
223
+ // Auto cleanup with timeout awareness
224
  setInterval(() => {
225
  sandboxManager.cleanupInactive();
226
  }, 2 * 60 * 1000);
227
 
228
+ const PORT = 3001; // Internal port, Nginx proxies from 7860
229
  app.listen(PORT, '0.0.0.0', () => {
230
+ console.log(`πŸš€ Sandbox API running on port ${PORT}`);
231
+ console.log(`🌐 Nginx proxying from port 7860`);
232
  });
233
 
234
+ // Graceful shutdown
235
  process.on('SIGTERM', async () => {
236
  console.log('⚠️ Shutting down...');
237
  for (const [id] of sandboxManager.sandboxes.entries()) {