@cloudflare/sandbox 0.0.0-6d1b288 → 0.0.0-6f16c81
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 +57 -0
- package/Dockerfile +86 -9
- package/README.md +3 -0
- package/container_src/index.ts +846 -214
- package/package.json +16 -3
- package/src/client.ts +396 -31
- package/src/index.ts +14 -79
- package/src/request-handler.ts +95 -0
- package/src/sandbox.ts +252 -0
- package/tests/test2.ts +219 -0
package/package.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/sandbox",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-6f16c81",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/cloudflare/sandbox-sdk"
|
|
7
|
+
},
|
|
4
8
|
"description": "A sandboxed environment for running commands",
|
|
5
9
|
"dependencies": {
|
|
6
|
-
"@cloudflare/containers": "^0.0.
|
|
10
|
+
"@cloudflare/containers": "^0.0.23"
|
|
7
11
|
},
|
|
12
|
+
"tags": [
|
|
13
|
+
"sandbox",
|
|
14
|
+
"codegen",
|
|
15
|
+
"containers",
|
|
16
|
+
"cloudflare",
|
|
17
|
+
"durable objects"
|
|
18
|
+
],
|
|
8
19
|
"scripts": {
|
|
9
|
-
"build": "tsup src/*.ts --outDir dist --dts --sourcemap --format esm"
|
|
20
|
+
"build": "rm -rf dist && tsup src/*.ts --outDir dist --dts --sourcemap --format esm",
|
|
21
|
+
"docker:build": "docker build -t ghostwriternr/cloudflare-sandbox:$npm_package_version .",
|
|
22
|
+
"docker:publish": "docker push docker.io/ghostwriternr/cloudflare-sandbox:$npm_package_version"
|
|
10
23
|
},
|
|
11
24
|
"exports": {
|
|
12
25
|
".": {
|
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 {
|
|
@@ -85,6 +86,20 @@ export interface WriteFileResponse {
|
|
|
85
86
|
timestamp: string;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
interface ReadFileRequest {
|
|
90
|
+
path: string;
|
|
91
|
+
encoding?: string;
|
|
92
|
+
sessionId?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ReadFileResponse {
|
|
96
|
+
success: boolean;
|
|
97
|
+
exitCode: number;
|
|
98
|
+
path: string;
|
|
99
|
+
content: string;
|
|
100
|
+
timestamp: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
interface DeleteFileRequest {
|
|
89
104
|
path: string;
|
|
90
105
|
sessionId?: string;
|
|
@@ -125,6 +140,37 @@ export interface MoveFileResponse {
|
|
|
125
140
|
timestamp: string;
|
|
126
141
|
}
|
|
127
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
|
+
|
|
128
174
|
interface PingResponse {
|
|
129
175
|
message: string;
|
|
130
176
|
timestamp: string;
|
|
@@ -142,6 +188,7 @@ interface StreamEvent {
|
|
|
142
188
|
newPath?: string;
|
|
143
189
|
sourcePath?: string;
|
|
144
190
|
destinationPath?: string;
|
|
191
|
+
content?: string;
|
|
145
192
|
success?: boolean;
|
|
146
193
|
exitCode?: number;
|
|
147
194
|
stdout?: string;
|
|
@@ -188,10 +235,41 @@ export class HttpClient {
|
|
|
188
235
|
path: string,
|
|
189
236
|
options?: RequestInit
|
|
190
237
|
): Promise<Response> {
|
|
191
|
-
|
|
192
|
-
|
|
238
|
+
const url = this.options.stub
|
|
239
|
+
? `http://localhost:${this.options.port}${path}`
|
|
240
|
+
: `${this.baseUrl}${path}`;
|
|
241
|
+
const method = options?.method || "GET";
|
|
242
|
+
|
|
243
|
+
console.log(`[HTTP Client] Making ${method} request to ${url}`);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
let response: Response;
|
|
247
|
+
|
|
248
|
+
if (this.options.stub) {
|
|
249
|
+
response = await this.options.stub.containerFetch(
|
|
250
|
+
url,
|
|
251
|
+
options,
|
|
252
|
+
this.options.port
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
response = await fetch(url, options);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log(
|
|
259
|
+
`[HTTP Client] Response: ${response.status} ${response.statusText}`
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
console.error(
|
|
264
|
+
`[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return response;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(`[HTTP Client] Request error: ${method} ${url}`, error);
|
|
271
|
+
throw error;
|
|
193
272
|
}
|
|
194
|
-
return fetch(this.baseUrl + path, options);
|
|
195
273
|
}
|
|
196
274
|
// Public methods to set event handlers
|
|
197
275
|
setOnOutput(
|
|
@@ -230,13 +308,13 @@ export class HttpClient {
|
|
|
230
308
|
|
|
231
309
|
getOnCommandComplete():
|
|
232
310
|
| ((
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
311
|
+
success: boolean,
|
|
312
|
+
exitCode: number,
|
|
313
|
+
stdout: string,
|
|
314
|
+
stderr: string,
|
|
315
|
+
command: string,
|
|
316
|
+
args: string[]
|
|
317
|
+
) => void)
|
|
240
318
|
| undefined {
|
|
241
319
|
return this.options.onCommandComplete;
|
|
242
320
|
}
|
|
@@ -293,7 +371,8 @@ export class HttpClient {
|
|
|
293
371
|
async execute(
|
|
294
372
|
command: string,
|
|
295
373
|
args: string[] = [],
|
|
296
|
-
sessionId?: string
|
|
374
|
+
sessionId?: string,
|
|
375
|
+
background: boolean = false,
|
|
297
376
|
): Promise<ExecuteResponse> {
|
|
298
377
|
try {
|
|
299
378
|
const targetSessionId = sessionId || this.sessionId;
|
|
@@ -302,8 +381,9 @@ export class HttpClient {
|
|
|
302
381
|
body: JSON.stringify({
|
|
303
382
|
args,
|
|
304
383
|
command,
|
|
384
|
+
background,
|
|
305
385
|
sessionId: targetSessionId,
|
|
306
|
-
}),
|
|
386
|
+
} as ExecuteRequest),
|
|
307
387
|
headers: {
|
|
308
388
|
"Content-Type": "application/json",
|
|
309
389
|
},
|
|
@@ -349,7 +429,8 @@ export class HttpClient {
|
|
|
349
429
|
async executeStream(
|
|
350
430
|
command: string,
|
|
351
431
|
args: string[] = [],
|
|
352
|
-
sessionId?: string
|
|
432
|
+
sessionId?: string,
|
|
433
|
+
background: boolean = false
|
|
353
434
|
): Promise<void> {
|
|
354
435
|
try {
|
|
355
436
|
const targetSessionId = sessionId || this.sessionId;
|
|
@@ -358,6 +439,7 @@ export class HttpClient {
|
|
|
358
439
|
body: JSON.stringify({
|
|
359
440
|
args,
|
|
360
441
|
command,
|
|
442
|
+
background,
|
|
361
443
|
sessionId: targetSessionId,
|
|
362
444
|
}),
|
|
363
445
|
headers: {
|
|
@@ -405,8 +487,7 @@ export class HttpClient {
|
|
|
405
487
|
switch (event.type) {
|
|
406
488
|
case "command_start":
|
|
407
489
|
console.log(
|
|
408
|
-
`[HTTP Client] Command started: ${
|
|
409
|
-
event.command
|
|
490
|
+
`[HTTP Client] Command started: ${event.command
|
|
410
491
|
} ${event.args?.join(" ")}`
|
|
411
492
|
);
|
|
412
493
|
this.options.onCommandStart?.(
|
|
@@ -487,7 +568,7 @@ export class HttpClient {
|
|
|
487
568
|
repoUrl,
|
|
488
569
|
sessionId: targetSessionId,
|
|
489
570
|
targetDir,
|
|
490
|
-
}),
|
|
571
|
+
} as GitCheckoutRequest),
|
|
491
572
|
headers: {
|
|
492
573
|
"Content-Type": "application/json",
|
|
493
574
|
},
|
|
@@ -578,8 +659,7 @@ export class HttpClient {
|
|
|
578
659
|
switch (event.type) {
|
|
579
660
|
case "command_start":
|
|
580
661
|
console.log(
|
|
581
|
-
`[HTTP Client] Git checkout started: ${
|
|
582
|
-
event.command
|
|
662
|
+
`[HTTP Client] Git checkout started: ${event.command
|
|
583
663
|
} ${event.args?.join(" ")}`
|
|
584
664
|
);
|
|
585
665
|
this.options.onCommandStart?.(
|
|
@@ -658,7 +738,7 @@ export class HttpClient {
|
|
|
658
738
|
path,
|
|
659
739
|
recursive,
|
|
660
740
|
sessionId: targetSessionId,
|
|
661
|
-
}),
|
|
741
|
+
} as MkdirRequest),
|
|
662
742
|
headers: {
|
|
663
743
|
"Content-Type": "application/json",
|
|
664
744
|
},
|
|
@@ -699,7 +779,7 @@ export class HttpClient {
|
|
|
699
779
|
path,
|
|
700
780
|
recursive,
|
|
701
781
|
sessionId: targetSessionId,
|
|
702
|
-
}),
|
|
782
|
+
} as MkdirRequest),
|
|
703
783
|
headers: {
|
|
704
784
|
"Content-Type": "application/json",
|
|
705
785
|
},
|
|
@@ -745,8 +825,7 @@ export class HttpClient {
|
|
|
745
825
|
switch (event.type) {
|
|
746
826
|
case "command_start":
|
|
747
827
|
console.log(
|
|
748
|
-
`[HTTP Client] Mkdir started: ${
|
|
749
|
-
event.command
|
|
828
|
+
`[HTTP Client] Mkdir started: ${event.command
|
|
750
829
|
} ${event.args?.join(" ")}`
|
|
751
830
|
);
|
|
752
831
|
this.options.onCommandStart?.(
|
|
@@ -825,7 +904,7 @@ export class HttpClient {
|
|
|
825
904
|
encoding,
|
|
826
905
|
path,
|
|
827
906
|
sessionId: targetSessionId,
|
|
828
|
-
}),
|
|
907
|
+
} as WriteFileRequest),
|
|
829
908
|
headers: {
|
|
830
909
|
"Content-Type": "application/json",
|
|
831
910
|
},
|
|
@@ -868,7 +947,7 @@ export class HttpClient {
|
|
|
868
947
|
encoding,
|
|
869
948
|
path,
|
|
870
949
|
sessionId: targetSessionId,
|
|
871
|
-
}),
|
|
950
|
+
} as WriteFileRequest),
|
|
872
951
|
headers: {
|
|
873
952
|
"Content-Type": "application/json",
|
|
874
953
|
},
|
|
@@ -978,6 +1057,162 @@ export class HttpClient {
|
|
|
978
1057
|
}
|
|
979
1058
|
}
|
|
980
1059
|
|
|
1060
|
+
async readFile(
|
|
1061
|
+
path: string,
|
|
1062
|
+
encoding: string = "utf-8",
|
|
1063
|
+
sessionId?: string
|
|
1064
|
+
): Promise<ReadFileResponse> {
|
|
1065
|
+
try {
|
|
1066
|
+
const targetSessionId = sessionId || this.sessionId;
|
|
1067
|
+
|
|
1068
|
+
const response = await this.doFetch(`/api/read`, {
|
|
1069
|
+
body: JSON.stringify({
|
|
1070
|
+
encoding,
|
|
1071
|
+
path,
|
|
1072
|
+
sessionId: targetSessionId,
|
|
1073
|
+
} as ReadFileRequest),
|
|
1074
|
+
headers: {
|
|
1075
|
+
"Content-Type": "application/json",
|
|
1076
|
+
},
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
if (!response.ok) {
|
|
1081
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1082
|
+
error?: string;
|
|
1083
|
+
};
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const data: ReadFileResponse = await response.json();
|
|
1090
|
+
console.log(
|
|
1091
|
+
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
return data;
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
console.error("[HTTP Client] Error reading file:", error);
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
async readFileStream(
|
|
1102
|
+
path: string,
|
|
1103
|
+
encoding: string = "utf-8",
|
|
1104
|
+
sessionId?: string
|
|
1105
|
+
): Promise<void> {
|
|
1106
|
+
try {
|
|
1107
|
+
const targetSessionId = sessionId || this.sessionId;
|
|
1108
|
+
|
|
1109
|
+
const response = await this.doFetch(`/api/read/stream`, {
|
|
1110
|
+
body: JSON.stringify({
|
|
1111
|
+
encoding,
|
|
1112
|
+
path,
|
|
1113
|
+
sessionId: targetSessionId,
|
|
1114
|
+
} as ReadFileRequest),
|
|
1115
|
+
headers: {
|
|
1116
|
+
"Content-Type": "application/json",
|
|
1117
|
+
},
|
|
1118
|
+
method: "POST",
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
if (!response.ok) {
|
|
1122
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1123
|
+
error?: string;
|
|
1124
|
+
};
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (!response.body) {
|
|
1131
|
+
throw new Error("No response body for streaming request");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const reader = response.body.getReader();
|
|
1135
|
+
const decoder = new TextDecoder();
|
|
1136
|
+
|
|
1137
|
+
try {
|
|
1138
|
+
while (true) {
|
|
1139
|
+
const { done, value } = await reader.read();
|
|
1140
|
+
|
|
1141
|
+
if (done) {
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1146
|
+
const lines = chunk.split("\n");
|
|
1147
|
+
|
|
1148
|
+
for (const line of lines) {
|
|
1149
|
+
if (line.startsWith("data: ")) {
|
|
1150
|
+
try {
|
|
1151
|
+
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
1152
|
+
const event: StreamEvent = JSON.parse(eventData);
|
|
1153
|
+
|
|
1154
|
+
console.log(
|
|
1155
|
+
`[HTTP Client] Read file stream event: ${event.type}`
|
|
1156
|
+
);
|
|
1157
|
+
this.options.onStreamEvent?.(event);
|
|
1158
|
+
|
|
1159
|
+
switch (event.type) {
|
|
1160
|
+
case "command_start":
|
|
1161
|
+
console.log(
|
|
1162
|
+
`[HTTP Client] Read file started: ${event.path}`
|
|
1163
|
+
);
|
|
1164
|
+
this.options.onCommandStart?.("read", [path, encoding]);
|
|
1165
|
+
break;
|
|
1166
|
+
|
|
1167
|
+
case "command_complete":
|
|
1168
|
+
console.log(
|
|
1169
|
+
`[HTTP Client] Read file completed: ${event.path
|
|
1170
|
+
}, Success: ${event.success}, Content length: ${event.content?.length || 0
|
|
1171
|
+
}`
|
|
1172
|
+
);
|
|
1173
|
+
this.options.onCommandComplete?.(
|
|
1174
|
+
event.success!,
|
|
1175
|
+
0,
|
|
1176
|
+
event.content || "",
|
|
1177
|
+
"",
|
|
1178
|
+
"read",
|
|
1179
|
+
[path, encoding]
|
|
1180
|
+
);
|
|
1181
|
+
break;
|
|
1182
|
+
|
|
1183
|
+
case "error":
|
|
1184
|
+
console.error(
|
|
1185
|
+
`[HTTP Client] Read file error: ${event.error}`
|
|
1186
|
+
);
|
|
1187
|
+
this.options.onError?.(event.error!, "read", [
|
|
1188
|
+
path,
|
|
1189
|
+
encoding,
|
|
1190
|
+
]);
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
} catch (parseError) {
|
|
1194
|
+
console.warn(
|
|
1195
|
+
"[HTTP Client] Failed to parse read file stream event:",
|
|
1196
|
+
parseError
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
} finally {
|
|
1203
|
+
reader.releaseLock();
|
|
1204
|
+
}
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
console.error("[HTTP Client] Error in streaming read file:", error);
|
|
1207
|
+
this.options.onError?.(
|
|
1208
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
1209
|
+
"read",
|
|
1210
|
+
[path, encoding]
|
|
1211
|
+
);
|
|
1212
|
+
throw error;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
981
1216
|
async deleteFile(
|
|
982
1217
|
path: string,
|
|
983
1218
|
sessionId?: string
|
|
@@ -989,7 +1224,7 @@ export class HttpClient {
|
|
|
989
1224
|
body: JSON.stringify({
|
|
990
1225
|
path,
|
|
991
1226
|
sessionId: targetSessionId,
|
|
992
|
-
}),
|
|
1227
|
+
} as DeleteFileRequest),
|
|
993
1228
|
headers: {
|
|
994
1229
|
"Content-Type": "application/json",
|
|
995
1230
|
},
|
|
@@ -1025,7 +1260,7 @@ export class HttpClient {
|
|
|
1025
1260
|
body: JSON.stringify({
|
|
1026
1261
|
path,
|
|
1027
1262
|
sessionId: targetSessionId,
|
|
1028
|
-
}),
|
|
1263
|
+
} as DeleteFileRequest),
|
|
1029
1264
|
headers: {
|
|
1030
1265
|
"Content-Type": "application/json",
|
|
1031
1266
|
},
|
|
@@ -1135,7 +1370,7 @@ export class HttpClient {
|
|
|
1135
1370
|
newPath,
|
|
1136
1371
|
oldPath,
|
|
1137
1372
|
sessionId: targetSessionId,
|
|
1138
|
-
}),
|
|
1373
|
+
} as RenameFileRequest),
|
|
1139
1374
|
headers: {
|
|
1140
1375
|
"Content-Type": "application/json",
|
|
1141
1376
|
},
|
|
@@ -1176,7 +1411,7 @@ export class HttpClient {
|
|
|
1176
1411
|
newPath,
|
|
1177
1412
|
oldPath,
|
|
1178
1413
|
sessionId: targetSessionId,
|
|
1179
|
-
}),
|
|
1414
|
+
} as RenameFileRequest),
|
|
1180
1415
|
headers: {
|
|
1181
1416
|
"Content-Type": "application/json",
|
|
1182
1417
|
},
|
|
@@ -1289,7 +1524,7 @@ export class HttpClient {
|
|
|
1289
1524
|
destinationPath,
|
|
1290
1525
|
sessionId: targetSessionId,
|
|
1291
1526
|
sourcePath,
|
|
1292
|
-
}),
|
|
1527
|
+
} as MoveFileRequest),
|
|
1293
1528
|
headers: {
|
|
1294
1529
|
"Content-Type": "application/json",
|
|
1295
1530
|
},
|
|
@@ -1330,7 +1565,7 @@ export class HttpClient {
|
|
|
1330
1565
|
destinationPath,
|
|
1331
1566
|
sessionId: targetSessionId,
|
|
1332
1567
|
sourcePath,
|
|
1333
|
-
}),
|
|
1568
|
+
} as MoveFileRequest),
|
|
1334
1569
|
headers: {
|
|
1335
1570
|
"Content-Type": "application/json",
|
|
1336
1571
|
},
|
|
@@ -1433,6 +1668,104 @@ export class HttpClient {
|
|
|
1433
1668
|
}
|
|
1434
1669
|
}
|
|
1435
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
|
+
|
|
1436
1769
|
async ping(): Promise<string> {
|
|
1437
1770
|
try {
|
|
1438
1771
|
const response = await this.doFetch(`/api/ping`, {
|
|
@@ -1629,6 +1962,38 @@ export async function quickWriteFileStream(
|
|
|
1629
1962
|
}
|
|
1630
1963
|
}
|
|
1631
1964
|
|
|
1965
|
+
// Convenience function for quick file reading
|
|
1966
|
+
export async function quickReadFile(
|
|
1967
|
+
path: string,
|
|
1968
|
+
encoding: string = "utf-8",
|
|
1969
|
+
options?: HttpClientOptions
|
|
1970
|
+
): Promise<ReadFileResponse> {
|
|
1971
|
+
const client = createClient(options);
|
|
1972
|
+
await client.createSession();
|
|
1973
|
+
|
|
1974
|
+
try {
|
|
1975
|
+
return await client.readFile(path, encoding);
|
|
1976
|
+
} finally {
|
|
1977
|
+
client.clearSession();
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// Convenience function for quick streaming file reading
|
|
1982
|
+
export async function quickReadFileStream(
|
|
1983
|
+
path: string,
|
|
1984
|
+
encoding: string = "utf-8",
|
|
1985
|
+
options?: HttpClientOptions
|
|
1986
|
+
): Promise<void> {
|
|
1987
|
+
const client = createClient(options);
|
|
1988
|
+
await client.createSession();
|
|
1989
|
+
|
|
1990
|
+
try {
|
|
1991
|
+
await client.readFileStream(path, encoding);
|
|
1992
|
+
} finally {
|
|
1993
|
+
client.clearSession();
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1632
1997
|
// Convenience function for quick file deletion
|
|
1633
1998
|
export async function quickDeleteFile(
|
|
1634
1999
|
path: string,
|