@hyperbrowser/sdk 0.84.0 → 0.85.0
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/README.md +103 -0
- package/dist/client.d.ts +20 -2
- package/dist/client.js +12 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -3
- package/dist/sandbox/base.d.ts +28 -0
- package/dist/sandbox/base.js +242 -0
- package/dist/sandbox/files.d.ts +92 -0
- package/dist/sandbox/files.js +508 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/index.js +15 -0
- package/dist/sandbox/process.d.ts +25 -0
- package/dist/sandbox/process.js +230 -0
- package/dist/sandbox/terminal.d.ts +45 -0
- package/dist/sandbox/terminal.js +217 -0
- package/dist/sandbox/ws.d.ts +18 -0
- package/dist/sandbox/ws.js +188 -0
- package/dist/services/base.js +43 -3
- package/dist/services/sandboxes.d.ts +56 -0
- package/dist/services/sandboxes.js +273 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/sandbox.d.ts +270 -0
- package/dist/types/sandbox.js +2 -0
- package/package.json +9 -3
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SandboxProcessesApi = exports.SandboxProcessHandle = void 0;
|
|
4
|
+
const DEFAULT_PROCESS_KILL_WAIT_MS = 5000;
|
|
5
|
+
const normalizeProcessSummary = (process) => ({
|
|
6
|
+
id: process.id,
|
|
7
|
+
status: process.status,
|
|
8
|
+
command: process.command,
|
|
9
|
+
args: process.args,
|
|
10
|
+
cwd: process.cwd,
|
|
11
|
+
pid: process.pid,
|
|
12
|
+
exitCode: process.exit_code,
|
|
13
|
+
startedAt: process.started_at,
|
|
14
|
+
completedAt: process.completed_at,
|
|
15
|
+
});
|
|
16
|
+
const normalizeProcessResult = (result) => ({
|
|
17
|
+
id: result.id,
|
|
18
|
+
status: result.status,
|
|
19
|
+
exitCode: result.exit_code,
|
|
20
|
+
stdout: result.stdout,
|
|
21
|
+
stderr: result.stderr,
|
|
22
|
+
startedAt: result.started_at,
|
|
23
|
+
completedAt: result.completed_at,
|
|
24
|
+
error: result.error,
|
|
25
|
+
});
|
|
26
|
+
const normalizeResultToSummary = (result) => ({
|
|
27
|
+
id: result.id,
|
|
28
|
+
status: result.status,
|
|
29
|
+
command: "",
|
|
30
|
+
cwd: "",
|
|
31
|
+
exitCode: result.exitCode,
|
|
32
|
+
startedAt: result.startedAt,
|
|
33
|
+
completedAt: result.completedAt,
|
|
34
|
+
});
|
|
35
|
+
const normalizeStreamEvent = (event) => {
|
|
36
|
+
if (event.event === "output") {
|
|
37
|
+
const payload = event.data;
|
|
38
|
+
return {
|
|
39
|
+
type: payload.stream,
|
|
40
|
+
seq: payload.seq,
|
|
41
|
+
data: payload.data,
|
|
42
|
+
timestamp: payload.timestamp,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (event.event === "done") {
|
|
46
|
+
return {
|
|
47
|
+
type: "exit",
|
|
48
|
+
result: normalizeProcessResult(event.data),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
const buildProcessPayload = (input) => ({
|
|
54
|
+
command: input.command,
|
|
55
|
+
args: input.args,
|
|
56
|
+
cwd: input.cwd,
|
|
57
|
+
env: input.env,
|
|
58
|
+
timeoutMs: input.timeoutMs,
|
|
59
|
+
timeout_sec: input.timeoutSec,
|
|
60
|
+
useShell: input.useShell,
|
|
61
|
+
});
|
|
62
|
+
const encodeStdinPayload = (input) => {
|
|
63
|
+
if (input.data === undefined) {
|
|
64
|
+
return {
|
|
65
|
+
eof: input.eof,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (typeof input.data === "string") {
|
|
69
|
+
return {
|
|
70
|
+
data: input.data,
|
|
71
|
+
encoding: input.encoding || "utf8",
|
|
72
|
+
eof: input.eof,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
data: Buffer.from(input.data).toString("base64"),
|
|
77
|
+
encoding: "base64",
|
|
78
|
+
eof: input.eof,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
class SandboxProcessHandle {
|
|
82
|
+
constructor(transport, summary) {
|
|
83
|
+
this.transport = transport;
|
|
84
|
+
this.summary = summary;
|
|
85
|
+
}
|
|
86
|
+
get id() {
|
|
87
|
+
return this.summary.id;
|
|
88
|
+
}
|
|
89
|
+
get status() {
|
|
90
|
+
return this.summary.status;
|
|
91
|
+
}
|
|
92
|
+
toJSON() {
|
|
93
|
+
return { ...this.summary };
|
|
94
|
+
}
|
|
95
|
+
async refresh() {
|
|
96
|
+
const response = await this.transport.requestJSON(`/sandbox/processes/${this.id}`);
|
|
97
|
+
this.summary = normalizeProcessSummary(response.process);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
async wait(params = {}) {
|
|
101
|
+
const response = await this.transport.requestJSON(`/sandbox/processes/${this.id}/wait`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
timeoutMs: params.timeoutMs,
|
|
105
|
+
timeout_sec: params.timeoutSec,
|
|
106
|
+
}),
|
|
107
|
+
headers: {
|
|
108
|
+
"content-type": "application/json",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const result = normalizeProcessResult(response.result);
|
|
112
|
+
this.summary = {
|
|
113
|
+
...this.summary,
|
|
114
|
+
...normalizeResultToSummary(result),
|
|
115
|
+
command: this.summary.command,
|
|
116
|
+
args: this.summary.args,
|
|
117
|
+
cwd: this.summary.cwd,
|
|
118
|
+
pid: this.summary.pid,
|
|
119
|
+
};
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
async signal(signal) {
|
|
123
|
+
const response = await this.transport.requestJSON(`/sandbox/processes/${this.id}/signal`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: JSON.stringify({ signal }),
|
|
126
|
+
headers: {
|
|
127
|
+
"content-type": "application/json",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
this.summary = normalizeProcessSummary(response.process);
|
|
131
|
+
}
|
|
132
|
+
async kill(params = {}) {
|
|
133
|
+
const response = await this.transport.requestJSON(`/sandbox/processes/${this.id}`, {
|
|
134
|
+
method: "DELETE",
|
|
135
|
+
});
|
|
136
|
+
this.summary = normalizeProcessSummary(response.process);
|
|
137
|
+
const waitParams = params.timeoutMs !== undefined
|
|
138
|
+
? {
|
|
139
|
+
timeoutMs: params.timeoutMs,
|
|
140
|
+
timeoutSec: params.timeoutSec,
|
|
141
|
+
}
|
|
142
|
+
: params.timeoutSec !== undefined
|
|
143
|
+
? {
|
|
144
|
+
timeoutSec: params.timeoutSec,
|
|
145
|
+
}
|
|
146
|
+
: {
|
|
147
|
+
timeoutMs: DEFAULT_PROCESS_KILL_WAIT_MS,
|
|
148
|
+
};
|
|
149
|
+
return this.wait({
|
|
150
|
+
...waitParams,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async writeStdin(input) {
|
|
154
|
+
const payload = typeof input === "string" || input instanceof Uint8Array
|
|
155
|
+
? encodeStdinPayload({ data: input })
|
|
156
|
+
: encodeStdinPayload(input);
|
|
157
|
+
await this.transport.requestJSON(`/sandbox/processes/${this.id}/stdin`, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
body: JSON.stringify(payload),
|
|
160
|
+
headers: {
|
|
161
|
+
"content-type": "application/json",
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async *stream(fromSeq) {
|
|
166
|
+
const params = fromSeq && fromSeq > 0
|
|
167
|
+
? {
|
|
168
|
+
from_seq: fromSeq,
|
|
169
|
+
}
|
|
170
|
+
: undefined;
|
|
171
|
+
for await (const event of this.transport.streamSSE(`/sandbox/processes/${this.id}/stream`, params)) {
|
|
172
|
+
const normalized = normalizeStreamEvent(event);
|
|
173
|
+
if (normalized) {
|
|
174
|
+
yield normalized;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async result() {
|
|
179
|
+
return this.wait();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.SandboxProcessHandle = SandboxProcessHandle;
|
|
183
|
+
class SandboxProcessesApi {
|
|
184
|
+
constructor(transport) {
|
|
185
|
+
this.transport = transport;
|
|
186
|
+
}
|
|
187
|
+
async exec(input) {
|
|
188
|
+
const response = await this.transport.requestJSON("/sandbox/exec", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
body: JSON.stringify(buildProcessPayload(input)),
|
|
191
|
+
headers: {
|
|
192
|
+
"content-type": "application/json",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
return normalizeProcessResult(response.result);
|
|
196
|
+
}
|
|
197
|
+
async start(input) {
|
|
198
|
+
const response = await this.transport.requestJSON("/sandbox/processes", {
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: JSON.stringify(buildProcessPayload(input)),
|
|
201
|
+
headers: {
|
|
202
|
+
"content-type": "application/json",
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return new SandboxProcessHandle(this.transport, normalizeProcessSummary(response.process));
|
|
206
|
+
}
|
|
207
|
+
async get(processId) {
|
|
208
|
+
const response = await this.transport.requestJSON(`/sandbox/processes/${processId}`);
|
|
209
|
+
return new SandboxProcessHandle(this.transport, normalizeProcessSummary(response.process));
|
|
210
|
+
}
|
|
211
|
+
async list(params = {}) {
|
|
212
|
+
const status = Array.isArray(params.status)
|
|
213
|
+
? params.status.length > 0
|
|
214
|
+
? params.status.join(",")
|
|
215
|
+
: undefined
|
|
216
|
+
: params.status;
|
|
217
|
+
const response = await this.transport.requestJSON("/sandbox/processes", undefined, {
|
|
218
|
+
status,
|
|
219
|
+
limit: params.limit,
|
|
220
|
+
cursor: params.cursor,
|
|
221
|
+
created_after: params.createdAfter,
|
|
222
|
+
created_before: params.createdBefore,
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
data: response.data.map(normalizeProcessSummary),
|
|
226
|
+
nextCursor: response.next_cursor || undefined,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
exports.SandboxProcessesApi = SandboxProcessesApi;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { RuntimeTransport } from "./base";
|
|
3
|
+
import { SandboxTerminalCreateParams, SandboxTerminalEvent, SandboxTerminalKillParams, SandboxTerminalStatus, SandboxTerminalWaitParams } from "../types/sandbox";
|
|
4
|
+
interface RuntimeConnectionInfo {
|
|
5
|
+
sandboxId: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
token: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class SandboxTerminalConnection {
|
|
10
|
+
private readonly ws;
|
|
11
|
+
private readonly eventsQueue;
|
|
12
|
+
constructor(ws: WebSocket);
|
|
13
|
+
events(): AsyncIterable<SandboxTerminalEvent>;
|
|
14
|
+
write(data: string | Uint8Array): Promise<void>;
|
|
15
|
+
resize(rows: number, cols: number): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
private send;
|
|
18
|
+
}
|
|
19
|
+
export declare class SandboxTerminalHandle {
|
|
20
|
+
private readonly transport;
|
|
21
|
+
private readonly getConnectionInfo;
|
|
22
|
+
private status;
|
|
23
|
+
private readonly runtimeProxyOverride?;
|
|
24
|
+
constructor(transport: RuntimeTransport, getConnectionInfo: () => Promise<RuntimeConnectionInfo>, status: SandboxTerminalStatus, runtimeProxyOverride?: string | undefined);
|
|
25
|
+
get id(): string;
|
|
26
|
+
get current(): SandboxTerminalStatus;
|
|
27
|
+
toJSON(): SandboxTerminalStatus;
|
|
28
|
+
refresh(includeOutput?: boolean): Promise<SandboxTerminalHandle>;
|
|
29
|
+
wait(params?: SandboxTerminalWaitParams): Promise<SandboxTerminalStatus>;
|
|
30
|
+
signal(signal?: string): Promise<SandboxTerminalStatus>;
|
|
31
|
+
kill(): Promise<SandboxTerminalStatus>;
|
|
32
|
+
kill(signal: string): Promise<SandboxTerminalStatus>;
|
|
33
|
+
kill(params: SandboxTerminalKillParams): Promise<SandboxTerminalStatus>;
|
|
34
|
+
resize(rows: number, cols: number): Promise<SandboxTerminalStatus>;
|
|
35
|
+
attach(): Promise<SandboxTerminalConnection>;
|
|
36
|
+
}
|
|
37
|
+
export declare class SandboxTerminalApi {
|
|
38
|
+
private readonly transport;
|
|
39
|
+
private readonly getConnectionInfo;
|
|
40
|
+
private readonly runtimeProxyOverride?;
|
|
41
|
+
constructor(transport: RuntimeTransport, getConnectionInfo: () => Promise<RuntimeConnectionInfo>, runtimeProxyOverride?: string | undefined);
|
|
42
|
+
create(params: SandboxTerminalCreateParams): Promise<SandboxTerminalHandle>;
|
|
43
|
+
get(id: string, includeOutput?: boolean): Promise<SandboxTerminalHandle>;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SandboxTerminalApi = exports.SandboxTerminalHandle = exports.SandboxTerminalConnection = void 0;
|
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
|
8
|
+
const ws_2 = require("./ws");
|
|
9
|
+
const DEFAULT_TERMINAL_KILL_WAIT_MS = 5000;
|
|
10
|
+
const normalizeTerminalOutputChunk = (output) => {
|
|
11
|
+
const raw = Buffer.from(output.data, "base64");
|
|
12
|
+
return {
|
|
13
|
+
seq: output.seq,
|
|
14
|
+
data: raw.toString("utf8"),
|
|
15
|
+
raw,
|
|
16
|
+
timestamp: output.timestamp,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const cloneTerminalStatus = (status) => ({
|
|
20
|
+
...status,
|
|
21
|
+
output: status.output?.map((chunk) => ({
|
|
22
|
+
...chunk,
|
|
23
|
+
raw: Buffer.from(chunk.raw),
|
|
24
|
+
})),
|
|
25
|
+
});
|
|
26
|
+
const normalizeTerminalStatus = (pty) => ({
|
|
27
|
+
id: pty.id,
|
|
28
|
+
command: pty.command,
|
|
29
|
+
args: pty.args,
|
|
30
|
+
cwd: pty.cwd,
|
|
31
|
+
pid: pty.pid,
|
|
32
|
+
running: pty.running,
|
|
33
|
+
exitCode: pty.exitCode,
|
|
34
|
+
error: pty.error,
|
|
35
|
+
timedOut: pty.timedOut,
|
|
36
|
+
rows: pty.rows,
|
|
37
|
+
cols: pty.cols,
|
|
38
|
+
startedAt: pty.startedAt,
|
|
39
|
+
finishedAt: pty.finishedAt,
|
|
40
|
+
output: pty.output?.map(normalizeTerminalOutputChunk),
|
|
41
|
+
});
|
|
42
|
+
class SandboxTerminalConnection {
|
|
43
|
+
constructor(ws) {
|
|
44
|
+
this.ws = ws;
|
|
45
|
+
this.eventsQueue = new ws_2.AsyncEventQueue();
|
|
46
|
+
ws.on("message", (data) => {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(data.toString());
|
|
49
|
+
if (parsed.type === "output") {
|
|
50
|
+
const output = normalizeTerminalOutputChunk(parsed);
|
|
51
|
+
this.eventsQueue.push({
|
|
52
|
+
type: "output",
|
|
53
|
+
...output,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.eventsQueue.push({
|
|
58
|
+
type: "exit",
|
|
59
|
+
status: normalizeTerminalStatus(parsed.status),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.eventsQueue.fail(error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
ws.on("close", () => {
|
|
67
|
+
this.eventsQueue.close();
|
|
68
|
+
});
|
|
69
|
+
ws.on("error", (error) => {
|
|
70
|
+
this.eventsQueue.fail(error);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
events() {
|
|
74
|
+
return this.eventsQueue;
|
|
75
|
+
}
|
|
76
|
+
async write(data) {
|
|
77
|
+
const payload = typeof data === "string"
|
|
78
|
+
? {
|
|
79
|
+
type: "input",
|
|
80
|
+
data,
|
|
81
|
+
}
|
|
82
|
+
: {
|
|
83
|
+
type: "input",
|
|
84
|
+
data: Buffer.from(data).toString("base64"),
|
|
85
|
+
encoding: "base64",
|
|
86
|
+
};
|
|
87
|
+
await this.send(payload);
|
|
88
|
+
}
|
|
89
|
+
async resize(rows, cols) {
|
|
90
|
+
await this.send({
|
|
91
|
+
type: "resize",
|
|
92
|
+
rows,
|
|
93
|
+
cols,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async close() {
|
|
97
|
+
if (this.ws.readyState === ws_1.default.CLOSING || this.ws.readyState === ws_1.default.CLOSED) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await new Promise((resolve) => {
|
|
101
|
+
this.ws.once("close", () => resolve());
|
|
102
|
+
this.ws.close();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async send(payload) {
|
|
106
|
+
await new Promise((resolve, reject) => {
|
|
107
|
+
this.ws.send(JSON.stringify(payload), (error) => {
|
|
108
|
+
if (error) {
|
|
109
|
+
reject(error);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
resolve();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.SandboxTerminalConnection = SandboxTerminalConnection;
|
|
118
|
+
class SandboxTerminalHandle {
|
|
119
|
+
constructor(transport, getConnectionInfo, status, runtimeProxyOverride) {
|
|
120
|
+
this.transport = transport;
|
|
121
|
+
this.getConnectionInfo = getConnectionInfo;
|
|
122
|
+
this.status = status;
|
|
123
|
+
this.runtimeProxyOverride = runtimeProxyOverride;
|
|
124
|
+
}
|
|
125
|
+
get id() {
|
|
126
|
+
return this.status.id;
|
|
127
|
+
}
|
|
128
|
+
get current() {
|
|
129
|
+
return cloneTerminalStatus(this.status);
|
|
130
|
+
}
|
|
131
|
+
toJSON() {
|
|
132
|
+
return cloneTerminalStatus(this.status);
|
|
133
|
+
}
|
|
134
|
+
async refresh(includeOutput = false) {
|
|
135
|
+
const response = await this.transport.requestJSON(`/sandbox/pty/${this.id}`, undefined, includeOutput ? { includeOutput: true } : undefined);
|
|
136
|
+
this.status = normalizeTerminalStatus(response.pty);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
async wait(params = {}) {
|
|
140
|
+
const response = await this.transport.requestJSON(`/sandbox/pty/${this.id}/wait`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
timeoutMs: params.timeoutMs,
|
|
144
|
+
includeOutput: params.includeOutput,
|
|
145
|
+
}),
|
|
146
|
+
headers: {
|
|
147
|
+
"content-type": "application/json",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
this.status = normalizeTerminalStatus(response.pty);
|
|
151
|
+
return this.current;
|
|
152
|
+
}
|
|
153
|
+
async signal(signal) {
|
|
154
|
+
const response = await this.transport.requestJSON(`/sandbox/pty/${this.id}/kill`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
body: JSON.stringify({ signal }),
|
|
157
|
+
headers: {
|
|
158
|
+
"content-type": "application/json",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
this.status = normalizeTerminalStatus(response.pty);
|
|
162
|
+
return this.current;
|
|
163
|
+
}
|
|
164
|
+
async kill(input) {
|
|
165
|
+
const params = typeof input === "string" ? { signal: input } : (input ?? {});
|
|
166
|
+
await this.signal(params.signal);
|
|
167
|
+
return this.wait({
|
|
168
|
+
timeoutMs: params.timeoutMs ?? DEFAULT_TERMINAL_KILL_WAIT_MS,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async resize(rows, cols) {
|
|
172
|
+
const response = await this.transport.requestJSON(`/sandbox/pty/${this.id}/resize`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: JSON.stringify({ rows, cols }),
|
|
175
|
+
headers: {
|
|
176
|
+
"content-type": "application/json",
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
this.status = normalizeTerminalStatus(response.pty);
|
|
180
|
+
return this.current;
|
|
181
|
+
}
|
|
182
|
+
async attach() {
|
|
183
|
+
const connectionInfo = await this.getConnectionInfo();
|
|
184
|
+
const target = (0, ws_2.toWebSocketUrl)(connectionInfo.baseUrl, `/sandbox/pty/${this.id}/ws?sessionId=${encodeURIComponent(connectionInfo.sandboxId)}`, this.runtimeProxyOverride);
|
|
185
|
+
const headers = {
|
|
186
|
+
Authorization: `Bearer ${connectionInfo.token}`,
|
|
187
|
+
};
|
|
188
|
+
if (target.hostHeader) {
|
|
189
|
+
headers.Host = target.hostHeader;
|
|
190
|
+
}
|
|
191
|
+
const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers);
|
|
192
|
+
return new SandboxTerminalConnection(ws);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
exports.SandboxTerminalHandle = SandboxTerminalHandle;
|
|
196
|
+
class SandboxTerminalApi {
|
|
197
|
+
constructor(transport, getConnectionInfo, runtimeProxyOverride) {
|
|
198
|
+
this.transport = transport;
|
|
199
|
+
this.getConnectionInfo = getConnectionInfo;
|
|
200
|
+
this.runtimeProxyOverride = runtimeProxyOverride;
|
|
201
|
+
}
|
|
202
|
+
async create(params) {
|
|
203
|
+
const response = await this.transport.requestJSON("/sandbox/pty", {
|
|
204
|
+
method: "POST",
|
|
205
|
+
body: JSON.stringify(params),
|
|
206
|
+
headers: {
|
|
207
|
+
"content-type": "application/json",
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
return new SandboxTerminalHandle(this.transport, this.getConnectionInfo, normalizeTerminalStatus(response.pty), this.runtimeProxyOverride);
|
|
211
|
+
}
|
|
212
|
+
async get(id, includeOutput = false) {
|
|
213
|
+
const response = await this.transport.requestJSON(`/sandbox/pty/${id}`, undefined, includeOutput ? { includeOutput: true } : undefined);
|
|
214
|
+
return new SandboxTerminalHandle(this.transport, this.getConnectionInfo, normalizeTerminalStatus(response.pty), this.runtimeProxyOverride);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.SandboxTerminalApi = SandboxTerminalApi;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
export declare class AsyncEventQueue<T> implements AsyncIterable<T> {
|
|
3
|
+
private readonly values;
|
|
4
|
+
private readonly waiters;
|
|
5
|
+
private done;
|
|
6
|
+
private error;
|
|
7
|
+
push(value: T): void;
|
|
8
|
+
close(): void;
|
|
9
|
+
fail(error: unknown): void;
|
|
10
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
11
|
+
}
|
|
12
|
+
export interface RuntimeTransportTarget {
|
|
13
|
+
url: string;
|
|
14
|
+
hostHeader?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const resolveRuntimeTransportTarget: (baseUrl: string, path: string, runtimeProxyOverride?: string) => RuntimeTransportTarget;
|
|
17
|
+
export declare const toWebSocketUrl: (baseUrl: string, path: string, runtimeProxyOverride?: string) => RuntimeTransportTarget;
|
|
18
|
+
export declare const openRuntimeWebSocket: (target: RuntimeTransportTarget, headers: Record<string, string>) => Promise<WebSocket>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.openRuntimeWebSocket = exports.toWebSocketUrl = exports.resolveRuntimeTransportTarget = exports.AsyncEventQueue = void 0;
|
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
|
8
|
+
const client_1 = require("../client");
|
|
9
|
+
class AsyncEventQueue {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.values = [];
|
|
12
|
+
this.waiters = [];
|
|
13
|
+
this.done = false;
|
|
14
|
+
this.error = null;
|
|
15
|
+
}
|
|
16
|
+
push(value) {
|
|
17
|
+
if (this.done) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const waiter = this.waiters.shift();
|
|
21
|
+
if (waiter) {
|
|
22
|
+
waiter.resolve({ value, done: false });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.values.push(value);
|
|
26
|
+
}
|
|
27
|
+
close() {
|
|
28
|
+
if (this.done) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.done = true;
|
|
32
|
+
while (this.waiters.length > 0) {
|
|
33
|
+
this.waiters.shift()?.resolve({ value: undefined, done: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
fail(error) {
|
|
37
|
+
if (this.done) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.done = true;
|
|
41
|
+
this.error = error;
|
|
42
|
+
while (this.waiters.length > 0) {
|
|
43
|
+
this.waiters.shift()?.reject(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
[Symbol.asyncIterator]() {
|
|
47
|
+
return {
|
|
48
|
+
next: () => {
|
|
49
|
+
if (this.values.length > 0) {
|
|
50
|
+
return Promise.resolve({
|
|
51
|
+
value: this.values.shift(),
|
|
52
|
+
done: false,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (this.done) {
|
|
56
|
+
if (this.error) {
|
|
57
|
+
return Promise.reject(this.error);
|
|
58
|
+
}
|
|
59
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
60
|
+
}
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
this.waiters.push({ resolve, reject });
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.AsyncEventQueue = AsyncEventQueue;
|
|
69
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);
|
|
70
|
+
const RETRYABLE_NETWORK_CODES = new Set([
|
|
71
|
+
"ECONNRESET",
|
|
72
|
+
"ECONNREFUSED",
|
|
73
|
+
"EAI_AGAIN",
|
|
74
|
+
"ETIMEDOUT",
|
|
75
|
+
"ESOCKETTIMEDOUT",
|
|
76
|
+
]);
|
|
77
|
+
const hasScheme = (value) => /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
|
78
|
+
const resolveRuntimeTransportTarget = (baseUrl, path, runtimeProxyOverride) => {
|
|
79
|
+
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
80
|
+
if (!runtimeProxyOverride) {
|
|
81
|
+
return {
|
|
82
|
+
url: url.toString(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const override = new URL(hasScheme(runtimeProxyOverride)
|
|
86
|
+
? runtimeProxyOverride
|
|
87
|
+
: `${url.protocol}//${runtimeProxyOverride}`);
|
|
88
|
+
url.protocol = override.protocol;
|
|
89
|
+
url.username = override.username;
|
|
90
|
+
url.password = override.password;
|
|
91
|
+
url.hostname = override.hostname;
|
|
92
|
+
url.port = override.port || url.port;
|
|
93
|
+
return {
|
|
94
|
+
url: url.toString(),
|
|
95
|
+
hostHeader: new URL(baseUrl).host,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
exports.resolveRuntimeTransportTarget = resolveRuntimeTransportTarget;
|
|
99
|
+
const toWebSocketUrl = (baseUrl, path, runtimeProxyOverride) => {
|
|
100
|
+
const target = (0, exports.resolveRuntimeTransportTarget)(baseUrl, path, runtimeProxyOverride);
|
|
101
|
+
const url = new URL(target.url);
|
|
102
|
+
if (url.protocol === "https:") {
|
|
103
|
+
url.protocol = "wss:";
|
|
104
|
+
}
|
|
105
|
+
else if (url.protocol === "http:") {
|
|
106
|
+
url.protocol = "ws:";
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
url: url.toString(),
|
|
110
|
+
hostHeader: target.hostHeader,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
exports.toWebSocketUrl = toWebSocketUrl;
|
|
114
|
+
const readIncomingMessageBody = (response) => new Promise((resolve, reject) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
response.on("data", (chunk) => {
|
|
117
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
118
|
+
});
|
|
119
|
+
response.on("end", () => {
|
|
120
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
121
|
+
});
|
|
122
|
+
response.on("error", reject);
|
|
123
|
+
});
|
|
124
|
+
const normalizeWebSocketError = (error) => {
|
|
125
|
+
if (error instanceof client_1.HyperbrowserError) {
|
|
126
|
+
return error;
|
|
127
|
+
}
|
|
128
|
+
const networkError = error;
|
|
129
|
+
return new client_1.HyperbrowserError(error instanceof Error ? error.message : "Unknown runtime websocket error", {
|
|
130
|
+
retryable: Boolean(networkError?.code && RETRYABLE_NETWORK_CODES.has(networkError.code)),
|
|
131
|
+
service: "runtime",
|
|
132
|
+
cause: error,
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const buildHandshakeError = async (response) => {
|
|
136
|
+
const rawText = await readIncomingMessageBody(response);
|
|
137
|
+
let message = `Runtime websocket request failed: ${response.statusCode ?? 0}`;
|
|
138
|
+
let code;
|
|
139
|
+
let details;
|
|
140
|
+
if (rawText) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(rawText);
|
|
143
|
+
details = parsed;
|
|
144
|
+
code = typeof parsed.code === "string" ? parsed.code : undefined;
|
|
145
|
+
message = parsed.message || parsed.error || rawText;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
details = rawText;
|
|
149
|
+
message = rawText;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return new client_1.HyperbrowserError(message, {
|
|
153
|
+
statusCode: response.statusCode,
|
|
154
|
+
code,
|
|
155
|
+
requestId: (typeof response.headers["x-request-id"] === "string"
|
|
156
|
+
? response.headers["x-request-id"]
|
|
157
|
+
: undefined) ||
|
|
158
|
+
(typeof response.headers["request-id"] === "string"
|
|
159
|
+
? response.headers["request-id"]
|
|
160
|
+
: undefined),
|
|
161
|
+
retryable: Boolean(response.statusCode && RETRYABLE_STATUS_CODES.has(response.statusCode)),
|
|
162
|
+
service: "runtime",
|
|
163
|
+
details,
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
const openRuntimeWebSocket = async (target, headers) => new Promise((resolve, reject) => {
|
|
167
|
+
let settled = false;
|
|
168
|
+
const socket = new ws_1.default(target.url, { headers });
|
|
169
|
+
const rejectOnce = (error) => {
|
|
170
|
+
if (settled) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
settled = true;
|
|
174
|
+
reject(normalizeWebSocketError(error));
|
|
175
|
+
};
|
|
176
|
+
socket.once("open", () => {
|
|
177
|
+
if (settled) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
settled = true;
|
|
181
|
+
resolve(socket);
|
|
182
|
+
});
|
|
183
|
+
socket.once("unexpected-response", (_request, response) => {
|
|
184
|
+
void buildHandshakeError(response).then(rejectOnce).catch(rejectOnce);
|
|
185
|
+
});
|
|
186
|
+
socket.once("error", rejectOnce);
|
|
187
|
+
});
|
|
188
|
+
exports.openRuntimeWebSocket = openRuntimeWebSocket;
|