@cloudflare/sandbox 0.0.8 → 0.0.9
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/CHANGELOG.md +6 -0
- package/Dockerfile +86 -9
- package/container_src/index.ts +428 -82
- package/dist/{chunk-7WZJ3TRE.js → chunk-4J5LQCCN.js} +85 -3
- package/dist/chunk-4J5LQCCN.js.map +1 -0
- package/dist/chunk-5SZ3RVJZ.js +250 -0
- package/dist/chunk-5SZ3RVJZ.js.map +1 -0
- package/dist/client-BuVjqV00.d.ts +247 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +3 -200
- package/dist/index.js +7 -106
- package/dist/index.js.map +1 -1
- package/dist/request-handler.d.ts +15 -0
- package/dist/request-handler.js +10 -0
- package/dist/request-handler.js.map +1 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -0
- package/dist/sandbox.js.map +1 -0
- package/package.json +2 -2
- package/src/client.ts +163 -34
- package/src/index.ts +14 -136
- package/src/request-handler.ts +95 -0
- package/src/sandbox.ts +252 -0
- package/dist/chunk-7WZJ3TRE.js.map +0 -1
package/src/client.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { DurableObject } from "cloudflare:workers";
|
|
2
1
|
import type { Sandbox } from "./index";
|
|
3
2
|
|
|
4
3
|
interface ExecuteRequest {
|
|
5
4
|
command: string;
|
|
6
5
|
args?: string[];
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
background?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface ExecuteResponse {
|
|
@@ -139,6 +140,37 @@ export interface MoveFileResponse {
|
|
|
139
140
|
timestamp: string;
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
interface PreviewInfo {
|
|
144
|
+
url: string;
|
|
145
|
+
port: number;
|
|
146
|
+
name?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ExposedPort extends PreviewInfo {
|
|
150
|
+
exposedAt: string;
|
|
151
|
+
timestamp: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface ExposePortResponse {
|
|
155
|
+
success: boolean;
|
|
156
|
+
port: number;
|
|
157
|
+
name?: string;
|
|
158
|
+
exposedAt: string;
|
|
159
|
+
timestamp: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface UnexposePortResponse {
|
|
163
|
+
success: boolean;
|
|
164
|
+
port: number;
|
|
165
|
+
timestamp: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface GetExposedPortsResponse {
|
|
169
|
+
ports: ExposedPort[];
|
|
170
|
+
count: number;
|
|
171
|
+
timestamp: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
142
174
|
interface PingResponse {
|
|
143
175
|
message: string;
|
|
144
176
|
timestamp: string;
|
|
@@ -276,13 +308,13 @@ export class HttpClient {
|
|
|
276
308
|
|
|
277
309
|
getOnCommandComplete():
|
|
278
310
|
| ((
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
311
|
+
success: boolean,
|
|
312
|
+
exitCode: number,
|
|
313
|
+
stdout: string,
|
|
314
|
+
stderr: string,
|
|
315
|
+
command: string,
|
|
316
|
+
args: string[]
|
|
317
|
+
) => void)
|
|
286
318
|
| undefined {
|
|
287
319
|
return this.options.onCommandComplete;
|
|
288
320
|
}
|
|
@@ -339,7 +371,8 @@ export class HttpClient {
|
|
|
339
371
|
async execute(
|
|
340
372
|
command: string,
|
|
341
373
|
args: string[] = [],
|
|
342
|
-
sessionId?: string
|
|
374
|
+
sessionId?: string,
|
|
375
|
+
background: boolean = false,
|
|
343
376
|
): Promise<ExecuteResponse> {
|
|
344
377
|
try {
|
|
345
378
|
const targetSessionId = sessionId || this.sessionId;
|
|
@@ -348,8 +381,9 @@ export class HttpClient {
|
|
|
348
381
|
body: JSON.stringify({
|
|
349
382
|
args,
|
|
350
383
|
command,
|
|
384
|
+
background,
|
|
351
385
|
sessionId: targetSessionId,
|
|
352
|
-
}),
|
|
386
|
+
} as ExecuteRequest),
|
|
353
387
|
headers: {
|
|
354
388
|
"Content-Type": "application/json",
|
|
355
389
|
},
|
|
@@ -395,7 +429,8 @@ export class HttpClient {
|
|
|
395
429
|
async executeStream(
|
|
396
430
|
command: string,
|
|
397
431
|
args: string[] = [],
|
|
398
|
-
sessionId?: string
|
|
432
|
+
sessionId?: string,
|
|
433
|
+
background: boolean = false
|
|
399
434
|
): Promise<void> {
|
|
400
435
|
try {
|
|
401
436
|
const targetSessionId = sessionId || this.sessionId;
|
|
@@ -404,6 +439,7 @@ export class HttpClient {
|
|
|
404
439
|
body: JSON.stringify({
|
|
405
440
|
args,
|
|
406
441
|
command,
|
|
442
|
+
background,
|
|
407
443
|
sessionId: targetSessionId,
|
|
408
444
|
}),
|
|
409
445
|
headers: {
|
|
@@ -451,8 +487,7 @@ export class HttpClient {
|
|
|
451
487
|
switch (event.type) {
|
|
452
488
|
case "command_start":
|
|
453
489
|
console.log(
|
|
454
|
-
`[HTTP Client] Command started: ${
|
|
455
|
-
event.command
|
|
490
|
+
`[HTTP Client] Command started: ${event.command
|
|
456
491
|
} ${event.args?.join(" ")}`
|
|
457
492
|
);
|
|
458
493
|
this.options.onCommandStart?.(
|
|
@@ -533,7 +568,7 @@ export class HttpClient {
|
|
|
533
568
|
repoUrl,
|
|
534
569
|
sessionId: targetSessionId,
|
|
535
570
|
targetDir,
|
|
536
|
-
}),
|
|
571
|
+
} as GitCheckoutRequest),
|
|
537
572
|
headers: {
|
|
538
573
|
"Content-Type": "application/json",
|
|
539
574
|
},
|
|
@@ -624,8 +659,7 @@ export class HttpClient {
|
|
|
624
659
|
switch (event.type) {
|
|
625
660
|
case "command_start":
|
|
626
661
|
console.log(
|
|
627
|
-
`[HTTP Client] Git checkout started: ${
|
|
628
|
-
event.command
|
|
662
|
+
`[HTTP Client] Git checkout started: ${event.command
|
|
629
663
|
} ${event.args?.join(" ")}`
|
|
630
664
|
);
|
|
631
665
|
this.options.onCommandStart?.(
|
|
@@ -704,7 +738,7 @@ export class HttpClient {
|
|
|
704
738
|
path,
|
|
705
739
|
recursive,
|
|
706
740
|
sessionId: targetSessionId,
|
|
707
|
-
}),
|
|
741
|
+
} as MkdirRequest),
|
|
708
742
|
headers: {
|
|
709
743
|
"Content-Type": "application/json",
|
|
710
744
|
},
|
|
@@ -745,7 +779,7 @@ export class HttpClient {
|
|
|
745
779
|
path,
|
|
746
780
|
recursive,
|
|
747
781
|
sessionId: targetSessionId,
|
|
748
|
-
}),
|
|
782
|
+
} as MkdirRequest),
|
|
749
783
|
headers: {
|
|
750
784
|
"Content-Type": "application/json",
|
|
751
785
|
},
|
|
@@ -791,8 +825,7 @@ export class HttpClient {
|
|
|
791
825
|
switch (event.type) {
|
|
792
826
|
case "command_start":
|
|
793
827
|
console.log(
|
|
794
|
-
`[HTTP Client] Mkdir started: ${
|
|
795
|
-
event.command
|
|
828
|
+
`[HTTP Client] Mkdir started: ${event.command
|
|
796
829
|
} ${event.args?.join(" ")}`
|
|
797
830
|
);
|
|
798
831
|
this.options.onCommandStart?.(
|
|
@@ -871,7 +904,7 @@ export class HttpClient {
|
|
|
871
904
|
encoding,
|
|
872
905
|
path,
|
|
873
906
|
sessionId: targetSessionId,
|
|
874
|
-
}),
|
|
907
|
+
} as WriteFileRequest),
|
|
875
908
|
headers: {
|
|
876
909
|
"Content-Type": "application/json",
|
|
877
910
|
},
|
|
@@ -914,7 +947,7 @@ export class HttpClient {
|
|
|
914
947
|
encoding,
|
|
915
948
|
path,
|
|
916
949
|
sessionId: targetSessionId,
|
|
917
|
-
}),
|
|
950
|
+
} as WriteFileRequest),
|
|
918
951
|
headers: {
|
|
919
952
|
"Content-Type": "application/json",
|
|
920
953
|
},
|
|
@@ -1037,7 +1070,7 @@ export class HttpClient {
|
|
|
1037
1070
|
encoding,
|
|
1038
1071
|
path,
|
|
1039
1072
|
sessionId: targetSessionId,
|
|
1040
|
-
}),
|
|
1073
|
+
} as ReadFileRequest),
|
|
1041
1074
|
headers: {
|
|
1042
1075
|
"Content-Type": "application/json",
|
|
1043
1076
|
},
|
|
@@ -1078,7 +1111,7 @@ export class HttpClient {
|
|
|
1078
1111
|
encoding,
|
|
1079
1112
|
path,
|
|
1080
1113
|
sessionId: targetSessionId,
|
|
1081
|
-
}),
|
|
1114
|
+
} as ReadFileRequest),
|
|
1082
1115
|
headers: {
|
|
1083
1116
|
"Content-Type": "application/json",
|
|
1084
1117
|
},
|
|
@@ -1133,10 +1166,8 @@ export class HttpClient {
|
|
|
1133
1166
|
|
|
1134
1167
|
case "command_complete":
|
|
1135
1168
|
console.log(
|
|
1136
|
-
`[HTTP Client] Read file completed: ${
|
|
1137
|
-
|
|
1138
|
-
}, Success: ${event.success}, Content length: ${
|
|
1139
|
-
event.content?.length || 0
|
|
1169
|
+
`[HTTP Client] Read file completed: ${event.path
|
|
1170
|
+
}, Success: ${event.success}, Content length: ${event.content?.length || 0
|
|
1140
1171
|
}`
|
|
1141
1172
|
);
|
|
1142
1173
|
this.options.onCommandComplete?.(
|
|
@@ -1193,7 +1224,7 @@ export class HttpClient {
|
|
|
1193
1224
|
body: JSON.stringify({
|
|
1194
1225
|
path,
|
|
1195
1226
|
sessionId: targetSessionId,
|
|
1196
|
-
}),
|
|
1227
|
+
} as DeleteFileRequest),
|
|
1197
1228
|
headers: {
|
|
1198
1229
|
"Content-Type": "application/json",
|
|
1199
1230
|
},
|
|
@@ -1229,7 +1260,7 @@ export class HttpClient {
|
|
|
1229
1260
|
body: JSON.stringify({
|
|
1230
1261
|
path,
|
|
1231
1262
|
sessionId: targetSessionId,
|
|
1232
|
-
}),
|
|
1263
|
+
} as DeleteFileRequest),
|
|
1233
1264
|
headers: {
|
|
1234
1265
|
"Content-Type": "application/json",
|
|
1235
1266
|
},
|
|
@@ -1339,7 +1370,7 @@ export class HttpClient {
|
|
|
1339
1370
|
newPath,
|
|
1340
1371
|
oldPath,
|
|
1341
1372
|
sessionId: targetSessionId,
|
|
1342
|
-
}),
|
|
1373
|
+
} as RenameFileRequest),
|
|
1343
1374
|
headers: {
|
|
1344
1375
|
"Content-Type": "application/json",
|
|
1345
1376
|
},
|
|
@@ -1380,7 +1411,7 @@ export class HttpClient {
|
|
|
1380
1411
|
newPath,
|
|
1381
1412
|
oldPath,
|
|
1382
1413
|
sessionId: targetSessionId,
|
|
1383
|
-
}),
|
|
1414
|
+
} as RenameFileRequest),
|
|
1384
1415
|
headers: {
|
|
1385
1416
|
"Content-Type": "application/json",
|
|
1386
1417
|
},
|
|
@@ -1493,7 +1524,7 @@ export class HttpClient {
|
|
|
1493
1524
|
destinationPath,
|
|
1494
1525
|
sessionId: targetSessionId,
|
|
1495
1526
|
sourcePath,
|
|
1496
|
-
}),
|
|
1527
|
+
} as MoveFileRequest),
|
|
1497
1528
|
headers: {
|
|
1498
1529
|
"Content-Type": "application/json",
|
|
1499
1530
|
},
|
|
@@ -1534,7 +1565,7 @@ export class HttpClient {
|
|
|
1534
1565
|
destinationPath,
|
|
1535
1566
|
sessionId: targetSessionId,
|
|
1536
1567
|
sourcePath,
|
|
1537
|
-
}),
|
|
1568
|
+
} as MoveFileRequest),
|
|
1538
1569
|
headers: {
|
|
1539
1570
|
"Content-Type": "application/json",
|
|
1540
1571
|
},
|
|
@@ -1637,6 +1668,104 @@ export class HttpClient {
|
|
|
1637
1668
|
}
|
|
1638
1669
|
}
|
|
1639
1670
|
|
|
1671
|
+
async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
|
|
1672
|
+
try {
|
|
1673
|
+
const response = await this.doFetch(`/api/expose-port`, {
|
|
1674
|
+
body: JSON.stringify({
|
|
1675
|
+
port,
|
|
1676
|
+
name,
|
|
1677
|
+
}),
|
|
1678
|
+
headers: {
|
|
1679
|
+
"Content-Type": "application/json",
|
|
1680
|
+
},
|
|
1681
|
+
method: "POST",
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
if (!response.ok) {
|
|
1685
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1686
|
+
error?: string;
|
|
1687
|
+
};
|
|
1688
|
+
console.log(errorData);
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const data: ExposePortResponse = await response.json();
|
|
1695
|
+
console.log(
|
|
1696
|
+
`[HTTP Client] Port exposed: ${port}${name ? ` (${name})` : ""}, Success: ${data.success}`
|
|
1697
|
+
);
|
|
1698
|
+
|
|
1699
|
+
return data;
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
console.error("[HTTP Client] Error exposing port:", error);
|
|
1702
|
+
throw error;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
async unexposePort(port: number): Promise<UnexposePortResponse> {
|
|
1707
|
+
try {
|
|
1708
|
+
const response = await this.doFetch(`/api/unexpose-port`, {
|
|
1709
|
+
body: JSON.stringify({
|
|
1710
|
+
port,
|
|
1711
|
+
}),
|
|
1712
|
+
headers: {
|
|
1713
|
+
"Content-Type": "application/json",
|
|
1714
|
+
},
|
|
1715
|
+
method: "DELETE",
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
if (!response.ok) {
|
|
1719
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1720
|
+
error?: string;
|
|
1721
|
+
};
|
|
1722
|
+
throw new Error(
|
|
1723
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
const data: UnexposePortResponse = await response.json();
|
|
1728
|
+
console.log(
|
|
1729
|
+
`[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`
|
|
1730
|
+
);
|
|
1731
|
+
|
|
1732
|
+
return data;
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
console.error("[HTTP Client] Error unexposing port:", error);
|
|
1735
|
+
throw error;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
async getExposedPorts(): Promise<GetExposedPortsResponse> {
|
|
1740
|
+
try {
|
|
1741
|
+
const response = await this.doFetch(`/api/exposed-ports`, {
|
|
1742
|
+
headers: {
|
|
1743
|
+
"Content-Type": "application/json",
|
|
1744
|
+
},
|
|
1745
|
+
method: "GET",
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
if (!response.ok) {
|
|
1749
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1750
|
+
error?: string;
|
|
1751
|
+
};
|
|
1752
|
+
throw new Error(
|
|
1753
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
const data: GetExposedPortsResponse = await response.json();
|
|
1758
|
+
console.log(
|
|
1759
|
+
`[HTTP Client] Got ${data.count} exposed ports`
|
|
1760
|
+
);
|
|
1761
|
+
|
|
1762
|
+
return data;
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
console.error("[HTTP Client] Error getting exposed ports:", error);
|
|
1765
|
+
throw error;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1640
1769
|
async ping(): Promise<string> {
|
|
1641
1770
|
try {
|
|
1642
1771
|
const response = await this.doFetch(`/api/ping`, {
|
package/src/index.ts
CHANGED
|
@@ -1,136 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.client = new HttpClient({
|
|
16
|
-
onCommandComplete: (success, exitCode, stdout, stderr, command, args) => {
|
|
17
|
-
console.log(
|
|
18
|
-
`[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`
|
|
19
|
-
);
|
|
20
|
-
},
|
|
21
|
-
onCommandStart: (command, args) => {
|
|
22
|
-
console.log(
|
|
23
|
-
`[Container] Command started: ${command} ${args.join(" ")}`
|
|
24
|
-
);
|
|
25
|
-
},
|
|
26
|
-
onError: (error, command, args) => {
|
|
27
|
-
console.error(`[Container] Command error: ${error}`);
|
|
28
|
-
},
|
|
29
|
-
onOutput: (stream, data, command) => {
|
|
30
|
-
console.log(`[Container] [${stream}] ${data}`);
|
|
31
|
-
},
|
|
32
|
-
port: this.defaultPort,
|
|
33
|
-
stub: this,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
envVars = {
|
|
38
|
-
MESSAGE: "I was passed in via the Sandbox class!",
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
override onStart() {
|
|
42
|
-
console.log("Sandbox successfully started");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
override onStop() {
|
|
46
|
-
console.log("Sandbox successfully shut down");
|
|
47
|
-
if (this.client) {
|
|
48
|
-
this.client.clearSession();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
override onError(error: unknown) {
|
|
53
|
-
console.log("Sandbox error:", error);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async exec(command: string, args: string[], options?: { stream?: boolean }) {
|
|
57
|
-
if (options?.stream) {
|
|
58
|
-
return this.client.executeStream(command, args);
|
|
59
|
-
}
|
|
60
|
-
return this.client.execute(command, args);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async gitCheckout(
|
|
64
|
-
repoUrl: string,
|
|
65
|
-
options: { branch?: string; targetDir?: string; stream?: boolean }
|
|
66
|
-
) {
|
|
67
|
-
if (options?.stream) {
|
|
68
|
-
return this.client.gitCheckoutStream(
|
|
69
|
-
repoUrl,
|
|
70
|
-
options.branch,
|
|
71
|
-
options.targetDir
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
return this.client.gitCheckout(repoUrl, options.branch, options.targetDir);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async mkdir(
|
|
78
|
-
path: string,
|
|
79
|
-
options: { recursive?: boolean; stream?: boolean } = {}
|
|
80
|
-
) {
|
|
81
|
-
if (options?.stream) {
|
|
82
|
-
return this.client.mkdirStream(path, options.recursive);
|
|
83
|
-
}
|
|
84
|
-
return this.client.mkdir(path, options.recursive);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async writeFile(
|
|
88
|
-
path: string,
|
|
89
|
-
content: string,
|
|
90
|
-
options: { encoding?: string; stream?: boolean } = {}
|
|
91
|
-
) {
|
|
92
|
-
if (options?.stream) {
|
|
93
|
-
return this.client.writeFileStream(path, content, options.encoding);
|
|
94
|
-
}
|
|
95
|
-
return this.client.writeFile(path, content, options.encoding);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async deleteFile(path: string, options: { stream?: boolean } = {}) {
|
|
99
|
-
if (options?.stream) {
|
|
100
|
-
return this.client.deleteFileStream(path);
|
|
101
|
-
}
|
|
102
|
-
return this.client.deleteFile(path);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async renameFile(
|
|
106
|
-
oldPath: string,
|
|
107
|
-
newPath: string,
|
|
108
|
-
options: { stream?: boolean } = {}
|
|
109
|
-
) {
|
|
110
|
-
if (options?.stream) {
|
|
111
|
-
return this.client.renameFileStream(oldPath, newPath);
|
|
112
|
-
}
|
|
113
|
-
return this.client.renameFile(oldPath, newPath);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async moveFile(
|
|
117
|
-
sourcePath: string,
|
|
118
|
-
destinationPath: string,
|
|
119
|
-
options: { stream?: boolean } = {}
|
|
120
|
-
) {
|
|
121
|
-
if (options?.stream) {
|
|
122
|
-
return this.client.moveFileStream(sourcePath, destinationPath);
|
|
123
|
-
}
|
|
124
|
-
return this.client.moveFile(sourcePath, destinationPath);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async readFile(
|
|
128
|
-
path: string,
|
|
129
|
-
options: { encoding?: string; stream?: boolean } = {}
|
|
130
|
-
) {
|
|
131
|
-
if (options?.stream) {
|
|
132
|
-
return this.client.readFileStream(path, options.encoding);
|
|
133
|
-
}
|
|
134
|
-
return this.client.readFile(path, options.encoding);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
1
|
+
// Export types from client
|
|
2
|
+
export type {
|
|
3
|
+
DeleteFileResponse, ExecuteResponse,
|
|
4
|
+
GitCheckoutResponse,
|
|
5
|
+
MkdirResponse, MoveFileResponse,
|
|
6
|
+
ReadFileResponse, RenameFileResponse, WriteFileResponse
|
|
7
|
+
} from "./client";
|
|
8
|
+
|
|
9
|
+
// Re-export request handler utilities
|
|
10
|
+
export {
|
|
11
|
+
proxyToSandbox, type RouteInfo, type SandboxEnv
|
|
12
|
+
} from './request-handler';
|
|
13
|
+
|
|
14
|
+
export { getSandbox, Sandbox } from "./sandbox";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getSandbox, type Sandbox } from "./sandbox";
|
|
2
|
+
|
|
3
|
+
export interface SandboxEnv {
|
|
4
|
+
Sandbox: DurableObjectNamespace<Sandbox>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RouteInfo {
|
|
8
|
+
port: number;
|
|
9
|
+
sandboxId: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function proxyToSandbox<E extends SandboxEnv>(
|
|
14
|
+
request: Request,
|
|
15
|
+
env: E
|
|
16
|
+
): Promise<Response | null> {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const routeInfo = extractSandboxRoute(url);
|
|
20
|
+
|
|
21
|
+
if (!routeInfo) {
|
|
22
|
+
return null; // Not a request to an exposed container port
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { sandboxId, port, path } = routeInfo;
|
|
26
|
+
const sandbox = getSandbox(env.Sandbox, sandboxId);
|
|
27
|
+
|
|
28
|
+
// Build proxy request with proper headers
|
|
29
|
+
let proxyUrl: string;
|
|
30
|
+
|
|
31
|
+
// Route based on the target port
|
|
32
|
+
if (port !== 3000) {
|
|
33
|
+
// Route directly to user's service on the specified port
|
|
34
|
+
proxyUrl = `http://localhost:${port}${path}${url.search}`;
|
|
35
|
+
} else {
|
|
36
|
+
// Port 3000 is our control plane - route normally
|
|
37
|
+
proxyUrl = `http://localhost:3000${path}${url.search}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const proxyRequest = new Request(proxyUrl, {
|
|
41
|
+
method: request.method,
|
|
42
|
+
headers: {
|
|
43
|
+
...Object.fromEntries(request.headers),
|
|
44
|
+
'X-Original-URL': request.url,
|
|
45
|
+
'X-Forwarded-Host': url.hostname,
|
|
46
|
+
'X-Forwarded-Proto': url.protocol.replace(':', ''),
|
|
47
|
+
'X-Sandbox-Name': sandboxId, // Pass the friendly name
|
|
48
|
+
},
|
|
49
|
+
body: request.body,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return sandbox.containerFetch(proxyRequest, port);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('[Sandbox] Proxy routing error:', error);
|
|
55
|
+
return new Response('Proxy routing error', { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractSandboxRoute(url: URL): RouteInfo | null {
|
|
60
|
+
// Production: subdomain pattern {port}-{sandboxId}.{domain}
|
|
61
|
+
const subdomainMatch = url.hostname.match(/^(\d+)-([a-zA-Z0-9-]+)\./);
|
|
62
|
+
if (subdomainMatch) {
|
|
63
|
+
return {
|
|
64
|
+
port: parseInt(subdomainMatch[1]),
|
|
65
|
+
sandboxId: subdomainMatch[2],
|
|
66
|
+
path: url.pathname,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Development: path pattern /preview/{port}/{sandboxId}/*
|
|
71
|
+
if (isLocalhostPattern(url.hostname)) {
|
|
72
|
+
const pathMatch = url.pathname.match(/^\/preview\/(\d+)\/([^/]+)(\/.*)?$/);
|
|
73
|
+
if (pathMatch) {
|
|
74
|
+
return {
|
|
75
|
+
port: parseInt(pathMatch[1]),
|
|
76
|
+
sandboxId: pathMatch[2],
|
|
77
|
+
path: pathMatch[3] || "/",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isLocalhostPattern(hostname: string): boolean {
|
|
86
|
+
const hostPart = hostname.split(":")[0];
|
|
87
|
+
return (
|
|
88
|
+
hostPart === "localhost" ||
|
|
89
|
+
hostPart === "127.0.0.1" ||
|
|
90
|
+
hostPart === "::1" ||
|
|
91
|
+
hostPart === "[::1]" ||
|
|
92
|
+
hostPart === "0.0.0.0"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|