@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
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ Hyperbrowser can be installed via npm by running:
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install @hyperbrowser/sdk
|
|
11
11
|
```
|
|
12
|
+
|
|
12
13
|
or
|
|
14
|
+
|
|
13
15
|
```bash
|
|
14
16
|
yarn add @hyperbrowser/sdk
|
|
15
17
|
```
|
|
@@ -17,6 +19,7 @@ yarn add @hyperbrowser/sdk
|
|
|
17
19
|
## Usage
|
|
18
20
|
|
|
19
21
|
### Playwright
|
|
22
|
+
|
|
20
23
|
```typescript
|
|
21
24
|
import { chromium } from "playwright-core";
|
|
22
25
|
import { Hyperbrowser } from "@hyperbrowser/sdk";
|
|
@@ -74,6 +77,7 @@ main();
|
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
### Puppeteer
|
|
80
|
+
|
|
77
81
|
```typescript
|
|
78
82
|
import { connect } from "puppeteer-core";
|
|
79
83
|
import { Hyperbrowser } from "@hyperbrowser/sdk";
|
|
@@ -131,3 +135,102 @@ const main = async () => {
|
|
|
131
135
|
|
|
132
136
|
main();
|
|
133
137
|
```
|
|
138
|
+
|
|
139
|
+
### Sandboxes
|
|
140
|
+
|
|
141
|
+
For local sandbox development, you can explicitly route sandbox runtime traffic
|
|
142
|
+
through a regional proxy override:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const client = new Hyperbrowser({
|
|
146
|
+
apiKey: process.env.HYPERBROWSER_API_KEY,
|
|
147
|
+
runtimeProxyOverride: process.env.REGIONAL_PROXY_DEV_HOST,
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { Hyperbrowser } from "@hyperbrowser/sdk";
|
|
153
|
+
|
|
154
|
+
const client = new Hyperbrowser({
|
|
155
|
+
apiKey: process.env.HYPERBROWSER_API_KEY,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const main = async () => {
|
|
159
|
+
const sandbox = await client.sandboxes.create({
|
|
160
|
+
imageName: "ubuntu-24-node",
|
|
161
|
+
region: "us-west",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Provide exactly one launch source:
|
|
165
|
+
// snapshotName or imageName.
|
|
166
|
+
// snapshotId requires snapshotName and imageId requires imageName.
|
|
167
|
+
|
|
168
|
+
const version = await sandbox.exec("node -v");
|
|
169
|
+
console.log(version.stdout.trim());
|
|
170
|
+
|
|
171
|
+
await sandbox.files.writeText("/tmp/hello.txt", "hello from sdk");
|
|
172
|
+
|
|
173
|
+
const content = await sandbox.files.readText("/tmp/hello.txt");
|
|
174
|
+
console.log(content);
|
|
175
|
+
|
|
176
|
+
const watch = await sandbox.files.watchDir(
|
|
177
|
+
"/tmp",
|
|
178
|
+
(event) => {
|
|
179
|
+
if (event.type === "write") {
|
|
180
|
+
console.log(event.name);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
recursive: false,
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
await sandbox.files.writeText("/tmp/watch-demo.txt", "watch me");
|
|
189
|
+
await watch.stop();
|
|
190
|
+
|
|
191
|
+
const proc = await sandbox.processes.start({
|
|
192
|
+
command: "bash",
|
|
193
|
+
args: ["-lc", "echo process-started && sleep 1 && echo process-finished"],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
for await (const event of proc.stream()) {
|
|
197
|
+
if (event.type === "stdout") {
|
|
198
|
+
process.stdout.write(event.data);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const terminal = await sandbox.terminal.create({
|
|
203
|
+
command: "bash",
|
|
204
|
+
cols: 120,
|
|
205
|
+
rows: 30,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const connection = await terminal.attach();
|
|
209
|
+
await connection.write("echo terminal-ok\n");
|
|
210
|
+
|
|
211
|
+
for await (const event of connection.events()) {
|
|
212
|
+
if (event.type === "output" && event.data.includes("terminal-ok")) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await connection.close();
|
|
218
|
+
|
|
219
|
+
const snapshot = await sandbox.createMemorySnapshot();
|
|
220
|
+
console.log(snapshot.snapshotId);
|
|
221
|
+
|
|
222
|
+
await sandbox.stop();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
main().catch(console.error);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Reconnect an existing sandbox:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const sandbox = await client.sandboxes.connect("sandbox-id");
|
|
232
|
+
await sandbox.files.readText("/tmp/hello.txt");
|
|
233
|
+
await sandbox.stop();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`connect()` refreshes runtime auth and throws if the sandbox is no longer running.
|
package/dist/client.d.ts
CHANGED
|
@@ -13,9 +13,26 @@ import { TeamService } from "./services/team";
|
|
|
13
13
|
import { ComputerActionService } from "./services/computer-action";
|
|
14
14
|
import { GeminiComputerUseService } from "./services/agents/gemini-computer-use";
|
|
15
15
|
import { WebService } from "./services/web";
|
|
16
|
+
import { SandboxesService } from "./services/sandboxes";
|
|
17
|
+
export type HyperbrowserService = "control" | "runtime";
|
|
18
|
+
export interface HyperbrowserErrorOptions {
|
|
19
|
+
statusCode?: number;
|
|
20
|
+
code?: string;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
retryable?: boolean;
|
|
23
|
+
service?: HyperbrowserService;
|
|
24
|
+
details?: unknown;
|
|
25
|
+
cause?: unknown;
|
|
26
|
+
}
|
|
16
27
|
export declare class HyperbrowserError extends Error {
|
|
17
|
-
statusCode?: number
|
|
18
|
-
|
|
28
|
+
readonly statusCode?: number;
|
|
29
|
+
readonly code?: string;
|
|
30
|
+
readonly requestId?: string;
|
|
31
|
+
readonly retryable: boolean;
|
|
32
|
+
readonly service?: HyperbrowserService;
|
|
33
|
+
readonly details?: unknown;
|
|
34
|
+
readonly cause?: unknown;
|
|
35
|
+
constructor(message: string, options?: number | HyperbrowserErrorOptions);
|
|
19
36
|
}
|
|
20
37
|
export declare class HyperbrowserClient {
|
|
21
38
|
readonly sessions: SessionsService;
|
|
@@ -34,5 +51,6 @@ export declare class HyperbrowserClient {
|
|
|
34
51
|
};
|
|
35
52
|
readonly team: TeamService;
|
|
36
53
|
readonly computerAction: ComputerActionService;
|
|
54
|
+
readonly sandboxes: SandboxesService;
|
|
37
55
|
constructor(config?: HyperbrowserConfig);
|
|
38
56
|
}
|
package/dist/client.js
CHANGED
|
@@ -15,11 +15,19 @@ const team_1 = require("./services/team");
|
|
|
15
15
|
const computer_action_1 = require("./services/computer-action");
|
|
16
16
|
const gemini_computer_use_1 = require("./services/agents/gemini-computer-use");
|
|
17
17
|
const web_1 = require("./services/web");
|
|
18
|
+
const sandboxes_1 = require("./services/sandboxes");
|
|
18
19
|
class HyperbrowserError extends Error {
|
|
19
|
-
constructor(message,
|
|
20
|
+
constructor(message, options = {}) {
|
|
20
21
|
super(`[Hyperbrowser]: ${message}`);
|
|
21
|
-
this.statusCode = statusCode;
|
|
22
22
|
this.name = "HyperbrowserError";
|
|
23
|
+
const normalized = typeof options === "number" ? { statusCode: options } : options;
|
|
24
|
+
this.statusCode = normalized.statusCode;
|
|
25
|
+
this.code = normalized.code;
|
|
26
|
+
this.requestId = normalized.requestId;
|
|
27
|
+
this.retryable = normalized.retryable ?? false;
|
|
28
|
+
this.service = normalized.service;
|
|
29
|
+
this.details = normalized.details;
|
|
30
|
+
this.cause = normalized.cause;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
exports.HyperbrowserError = HyperbrowserError;
|
|
@@ -28,6 +36,7 @@ class HyperbrowserClient {
|
|
|
28
36
|
const apiKey = config.apiKey || process.env["HYPERBROWSER_API_KEY"];
|
|
29
37
|
const baseUrl = config.baseUrl || "https://api.hyperbrowser.ai";
|
|
30
38
|
const timeout = config.timeout || 30000;
|
|
39
|
+
const runtimeProxyOverride = config.runtimeProxyOverride?.trim() || undefined;
|
|
31
40
|
if (!apiKey) {
|
|
32
41
|
throw new HyperbrowserError("API key is required - either pass it in config or set HYPERBROWSER_API_KEY environment variable");
|
|
33
42
|
}
|
|
@@ -40,6 +49,7 @@ class HyperbrowserClient {
|
|
|
40
49
|
this.web = new web_1.WebService(apiKey, baseUrl, timeout);
|
|
41
50
|
this.team = new team_1.TeamService(apiKey, baseUrl, timeout);
|
|
42
51
|
this.computerAction = new computer_action_1.ComputerActionService(apiKey, baseUrl, timeout);
|
|
52
|
+
this.sandboxes = new sandboxes_1.SandboxesService(apiKey, baseUrl, timeout, runtimeProxyOverride);
|
|
43
53
|
this.agents = {
|
|
44
54
|
browserUse: new browser_use_1.BrowserUseService(apiKey, baseUrl, timeout),
|
|
45
55
|
claudeComputerUse: new claude_computer_use_1.ClaudeComputerUseService(apiKey, baseUrl, timeout),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HyperbrowserClient } from "./client";
|
|
2
|
-
export { HyperbrowserError } from "./client";
|
|
1
|
+
import { HyperbrowserClient, HyperbrowserError } from "./client";
|
|
3
2
|
export declare const Hyperbrowser: typeof HyperbrowserClient;
|
|
4
3
|
export type Hyperbrowser = HyperbrowserClient;
|
|
4
|
+
export { HyperbrowserClient, HyperbrowserError };
|
|
5
5
|
export default HyperbrowserClient;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.HyperbrowserError = exports.HyperbrowserClient = exports.Hyperbrowser = void 0;
|
|
4
4
|
const client_1 = require("./client");
|
|
5
|
-
|
|
6
|
-
Object.defineProperty(exports, "HyperbrowserError", { enumerable: true, get: function () { return
|
|
5
|
+
Object.defineProperty(exports, "HyperbrowserClient", { enumerable: true, get: function () { return client_1.HyperbrowserClient; } });
|
|
6
|
+
Object.defineProperty(exports, "HyperbrowserError", { enumerable: true, get: function () { return client_1.HyperbrowserError; } });
|
|
7
7
|
// Export HyperbrowserClient as Hyperbrowser for named imports
|
|
8
8
|
exports.Hyperbrowser = client_1.HyperbrowserClient;
|
|
9
9
|
exports.default = client_1.HyperbrowserClient;
|
|
@@ -12,5 +12,6 @@ if (typeof module !== "undefined" && module.exports) {
|
|
|
12
12
|
module.exports = client_1.HyperbrowserClient;
|
|
13
13
|
module.exports.Hyperbrowser = client_1.HyperbrowserClient;
|
|
14
14
|
module.exports.HyperbrowserClient = client_1.HyperbrowserClient;
|
|
15
|
+
module.exports.HyperbrowserError = client_1.HyperbrowserError;
|
|
15
16
|
module.exports.default = client_1.HyperbrowserClient;
|
|
16
17
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RequestInit } from "node-fetch";
|
|
2
|
+
export interface RuntimeConnection {
|
|
3
|
+
sandboxId: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
token: string;
|
|
6
|
+
directSessionHeader?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RuntimeSSEEvent {
|
|
9
|
+
event: string;
|
|
10
|
+
data: unknown;
|
|
11
|
+
id?: string;
|
|
12
|
+
}
|
|
13
|
+
type RuntimeParams = Record<string, string | number | boolean | undefined>;
|
|
14
|
+
export declare class RuntimeTransport {
|
|
15
|
+
private readonly resolveConnection;
|
|
16
|
+
private readonly timeout;
|
|
17
|
+
private readonly runtimeProxyOverride?;
|
|
18
|
+
constructor(resolveConnection: (forceRefresh?: boolean) => Promise<RuntimeConnection>, timeout?: number, runtimeProxyOverride?: string | undefined);
|
|
19
|
+
requestJSON<T>(path: string, init?: RequestInit, params?: RuntimeParams): Promise<T>;
|
|
20
|
+
requestBuffer(path: string, init?: RequestInit, params?: RuntimeParams): Promise<Buffer>;
|
|
21
|
+
streamSSE(path: string, params?: RuntimeParams): AsyncGenerator<RuntimeSSEEvent>;
|
|
22
|
+
private fetchWithAuth;
|
|
23
|
+
private fetchForConnection;
|
|
24
|
+
private assertResponse;
|
|
25
|
+
private buildHeaders;
|
|
26
|
+
private normalizeBaseUrl;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
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.RuntimeTransport = void 0;
|
|
7
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
8
|
+
const client_1 = require("../client");
|
|
9
|
+
const ws_1 = require("./ws");
|
|
10
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);
|
|
11
|
+
const RETRYABLE_NETWORK_CODES = new Set([
|
|
12
|
+
"ECONNRESET",
|
|
13
|
+
"ECONNREFUSED",
|
|
14
|
+
"EAI_AGAIN",
|
|
15
|
+
"ETIMEDOUT",
|
|
16
|
+
"ESOCKETTIMEDOUT",
|
|
17
|
+
]);
|
|
18
|
+
const getRequestId = (response) => {
|
|
19
|
+
return response.headers.get("x-request-id") || response.headers.get("request-id") || undefined;
|
|
20
|
+
};
|
|
21
|
+
const isRetryableNetworkError = (error) => {
|
|
22
|
+
if (!(error instanceof Error)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const networkError = error;
|
|
26
|
+
return (networkError.name === "AbortError" ||
|
|
27
|
+
networkError.type === "request-timeout" ||
|
|
28
|
+
(networkError.code ? RETRYABLE_NETWORK_CODES.has(networkError.code) : false));
|
|
29
|
+
};
|
|
30
|
+
class RuntimeTransport {
|
|
31
|
+
constructor(resolveConnection, timeout = 30000, runtimeProxyOverride) {
|
|
32
|
+
this.resolveConnection = resolveConnection;
|
|
33
|
+
this.timeout = timeout;
|
|
34
|
+
this.runtimeProxyOverride = runtimeProxyOverride;
|
|
35
|
+
}
|
|
36
|
+
async requestJSON(path, init, params) {
|
|
37
|
+
const response = await this.fetchWithAuth(path, init, params);
|
|
38
|
+
if (response.headers.get("content-length") === "0") {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return (await response.json());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new client_1.HyperbrowserError("Failed to parse JSON response", {
|
|
46
|
+
statusCode: response.status,
|
|
47
|
+
requestId: getRequestId(response),
|
|
48
|
+
retryable: false,
|
|
49
|
+
service: "runtime",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async requestBuffer(path, init, params) {
|
|
54
|
+
const response = await this.fetchWithAuth(path, init, params);
|
|
55
|
+
return response.buffer();
|
|
56
|
+
}
|
|
57
|
+
async *streamSSE(path, params) {
|
|
58
|
+
const response = await this.fetchWithAuth(path, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
headers: {
|
|
61
|
+
Accept: "text/event-stream",
|
|
62
|
+
},
|
|
63
|
+
}, params);
|
|
64
|
+
const body = response.body;
|
|
65
|
+
if (!body) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let buffer = "";
|
|
69
|
+
let eventName = "message";
|
|
70
|
+
let eventId;
|
|
71
|
+
let dataLines = [];
|
|
72
|
+
const flushEvent = () => {
|
|
73
|
+
if (dataLines.length === 0 && eventName === "message" && eventId === undefined) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const rawData = dataLines.join("\n");
|
|
77
|
+
let data = rawData;
|
|
78
|
+
if (rawData) {
|
|
79
|
+
try {
|
|
80
|
+
data = JSON.parse(rawData);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
data = rawData;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const event = {
|
|
87
|
+
event: eventName,
|
|
88
|
+
data,
|
|
89
|
+
id: eventId,
|
|
90
|
+
};
|
|
91
|
+
eventName = "message";
|
|
92
|
+
eventId = undefined;
|
|
93
|
+
dataLines = [];
|
|
94
|
+
return event;
|
|
95
|
+
};
|
|
96
|
+
for await (const chunk of body) {
|
|
97
|
+
buffer += Buffer.from(chunk).toString("utf8");
|
|
98
|
+
while (true) {
|
|
99
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
100
|
+
if (newlineIndex === -1) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
let line = buffer.slice(0, newlineIndex);
|
|
104
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
105
|
+
if (line.endsWith("\r")) {
|
|
106
|
+
line = line.slice(0, -1);
|
|
107
|
+
}
|
|
108
|
+
if (line === "") {
|
|
109
|
+
const event = flushEvent();
|
|
110
|
+
if (event) {
|
|
111
|
+
yield event;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (line.startsWith(":")) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const separator = line.indexOf(":");
|
|
119
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
120
|
+
const value = separator === -1 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
121
|
+
switch (field) {
|
|
122
|
+
case "event":
|
|
123
|
+
eventName = value || "message";
|
|
124
|
+
break;
|
|
125
|
+
case "data":
|
|
126
|
+
dataLines.push(value);
|
|
127
|
+
break;
|
|
128
|
+
case "id":
|
|
129
|
+
eventId = value;
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const trailing = flushEvent();
|
|
137
|
+
if (trailing) {
|
|
138
|
+
yield trailing;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async fetchWithAuth(path, init, params, allowRefresh = true) {
|
|
142
|
+
const connection = await this.resolveConnection(false);
|
|
143
|
+
const response = await this.fetchForConnection(connection, path, init, params);
|
|
144
|
+
if (response.status === 401 && allowRefresh) {
|
|
145
|
+
const refreshed = await this.resolveConnection(true);
|
|
146
|
+
const retryResponse = await this.fetchForConnection(refreshed, path, init, params);
|
|
147
|
+
return this.assertResponse(retryResponse);
|
|
148
|
+
}
|
|
149
|
+
return this.assertResponse(response);
|
|
150
|
+
}
|
|
151
|
+
async fetchForConnection(connection, path, init, params) {
|
|
152
|
+
const url = new URL(path, this.normalizeBaseUrl(connection.baseUrl));
|
|
153
|
+
if (params) {
|
|
154
|
+
for (const [key, value] of Object.entries(params)) {
|
|
155
|
+
if (value !== undefined) {
|
|
156
|
+
url.searchParams.append(key, String(value));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const target = (0, ws_1.resolveRuntimeTransportTarget)(connection.baseUrl, `${url.pathname}${url.search}`, this.runtimeProxyOverride);
|
|
161
|
+
const headers = this.buildHeaders(connection, init?.headers, target.hostHeader);
|
|
162
|
+
const controller = new AbortController();
|
|
163
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
164
|
+
try {
|
|
165
|
+
return await (0, node_fetch_1.default)(target.url, {
|
|
166
|
+
...init,
|
|
167
|
+
headers,
|
|
168
|
+
signal: controller.signal,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error instanceof client_1.HyperbrowserError) {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
throw new client_1.HyperbrowserError(error instanceof Error ? error.message : "Unknown runtime request error", {
|
|
176
|
+
retryable: isRetryableNetworkError(error),
|
|
177
|
+
service: "runtime",
|
|
178
|
+
cause: error,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
clearTimeout(timeoutId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async assertResponse(response) {
|
|
186
|
+
if (response.ok) {
|
|
187
|
+
return response;
|
|
188
|
+
}
|
|
189
|
+
let message = `Runtime request failed: ${response.status} ${response.statusText}`;
|
|
190
|
+
let details;
|
|
191
|
+
let code;
|
|
192
|
+
try {
|
|
193
|
+
const rawText = await response.text();
|
|
194
|
+
if (rawText) {
|
|
195
|
+
try {
|
|
196
|
+
const parsed = JSON.parse(rawText);
|
|
197
|
+
details = parsed;
|
|
198
|
+
code = typeof parsed.code === "string" ? parsed.code : undefined;
|
|
199
|
+
message = parsed.message || parsed.error || rawText;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
details = rawText;
|
|
203
|
+
message = rawText;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Keep the fallback message.
|
|
209
|
+
}
|
|
210
|
+
throw new client_1.HyperbrowserError(message, {
|
|
211
|
+
statusCode: response.status,
|
|
212
|
+
code,
|
|
213
|
+
requestId: getRequestId(response),
|
|
214
|
+
retryable: RETRYABLE_STATUS_CODES.has(response.status),
|
|
215
|
+
service: "runtime",
|
|
216
|
+
details,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
buildHeaders(connection, rawHeaders, hostHeader) {
|
|
220
|
+
const headers = {
|
|
221
|
+
Authorization: `Bearer ${connection.token}`,
|
|
222
|
+
};
|
|
223
|
+
if (connection.directSessionHeader) {
|
|
224
|
+
headers["x-session-id"] = connection.sandboxId;
|
|
225
|
+
}
|
|
226
|
+
if (rawHeaders && typeof rawHeaders === "object" && !Array.isArray(rawHeaders)) {
|
|
227
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
228
|
+
if (value !== undefined) {
|
|
229
|
+
headers[key] = String(value);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (hostHeader && headers.host === undefined && headers.Host === undefined) {
|
|
234
|
+
headers.Host = hostHeader;
|
|
235
|
+
}
|
|
236
|
+
return headers;
|
|
237
|
+
}
|
|
238
|
+
normalizeBaseUrl(baseUrl) {
|
|
239
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.RuntimeTransport = RuntimeTransport;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Blob, Buffer } from "buffer";
|
|
2
|
+
import { ReadableStream } from "node:stream/web";
|
|
3
|
+
import { RuntimeTransport } from "./base";
|
|
4
|
+
import { SandboxFileChmodParams, SandboxFileChownParams, SandboxFileCopyParams, SandboxFileInfo, SandboxFileListOptions, SandboxFileMakeDirOptions, SandboxFileReadOptions, SandboxFileSystemEvent, SandboxFileTransferResult, SandboxFileWriteData, SandboxFileWriteEntry, SandboxFileWriteInfo, SandboxFileBytesWriteOptions, SandboxFileTextWriteOptions, SandboxPresignFileParams, SandboxPresignedUrl, SandboxWatchDirOptions } from "../types/sandbox";
|
|
5
|
+
interface RawFileWatchEvent {
|
|
6
|
+
seq: number;
|
|
7
|
+
path: string;
|
|
8
|
+
op: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
interface RawFileWatchStatus {
|
|
12
|
+
id: string;
|
|
13
|
+
path: string;
|
|
14
|
+
recursive: boolean;
|
|
15
|
+
active: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
stoppedAt?: number;
|
|
19
|
+
oldestSeq?: number;
|
|
20
|
+
lastSeq?: number;
|
|
21
|
+
eventCount?: number;
|
|
22
|
+
}
|
|
23
|
+
interface RuntimeConnectionInfo {
|
|
24
|
+
sandboxId: string;
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
token: string;
|
|
27
|
+
}
|
|
28
|
+
declare class RuntimeFileWatchHandle {
|
|
29
|
+
private readonly transport;
|
|
30
|
+
private readonly getConnectionInfo;
|
|
31
|
+
private readonly status;
|
|
32
|
+
private readonly runtimeProxyOverride?;
|
|
33
|
+
constructor(transport: RuntimeTransport, getConnectionInfo: () => Promise<RuntimeConnectionInfo>, status: RawFileWatchStatus, runtimeProxyOverride?: string | undefined);
|
|
34
|
+
get id(): string;
|
|
35
|
+
get path(): string;
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
events(cursor?: number): AsyncGenerator<RawFileWatchEvent>;
|
|
38
|
+
}
|
|
39
|
+
export declare class SandboxWatchDirHandle {
|
|
40
|
+
private readonly watch;
|
|
41
|
+
private readonly onExit?;
|
|
42
|
+
private readonly runPromise;
|
|
43
|
+
private timeout?;
|
|
44
|
+
private stopRequested;
|
|
45
|
+
private exitNotified;
|
|
46
|
+
constructor(watch: RuntimeFileWatchHandle, onEvent: (event: SandboxFileSystemEvent) => void | Promise<void>, onExit?: ((error?: Error) => void | Promise<void>) | undefined, timeoutMs?: number);
|
|
47
|
+
stop(): Promise<void>;
|
|
48
|
+
private run;
|
|
49
|
+
}
|
|
50
|
+
export declare class SandboxFilesApi {
|
|
51
|
+
private readonly transport;
|
|
52
|
+
private readonly getConnectionInfo;
|
|
53
|
+
private readonly runtimeProxyOverride?;
|
|
54
|
+
constructor(transport: RuntimeTransport, getConnectionInfo: () => Promise<RuntimeConnectionInfo>, runtimeProxyOverride?: string | undefined);
|
|
55
|
+
exists(path: string): Promise<boolean>;
|
|
56
|
+
getInfo(path: string): Promise<SandboxFileInfo>;
|
|
57
|
+
list(path: string, options?: SandboxFileListOptions): Promise<SandboxFileInfo[]>;
|
|
58
|
+
read(path: string, options?: SandboxFileReadOptions & {
|
|
59
|
+
format?: "text";
|
|
60
|
+
}): Promise<string>;
|
|
61
|
+
read(path: string, options: SandboxFileReadOptions & {
|
|
62
|
+
format: "bytes";
|
|
63
|
+
}): Promise<Buffer>;
|
|
64
|
+
read(path: string, options: SandboxFileReadOptions & {
|
|
65
|
+
format: "blob";
|
|
66
|
+
}): Promise<Blob>;
|
|
67
|
+
read(path: string, options: SandboxFileReadOptions & {
|
|
68
|
+
format: "stream";
|
|
69
|
+
}): Promise<ReadableStream<Uint8Array>>;
|
|
70
|
+
readText(path: string, options?: Omit<SandboxFileReadOptions, "format">): Promise<string>;
|
|
71
|
+
readBytes(path: string, options?: Omit<SandboxFileReadOptions, "format">): Promise<Buffer>;
|
|
72
|
+
write(path: string, data: SandboxFileWriteData): Promise<SandboxFileWriteInfo>;
|
|
73
|
+
write(files: SandboxFileWriteEntry[]): Promise<SandboxFileWriteInfo[]>;
|
|
74
|
+
writeText(path: string, data: string, options?: SandboxFileTextWriteOptions): Promise<SandboxFileWriteInfo>;
|
|
75
|
+
writeBytes(path: string, data: Uint8Array, options?: SandboxFileBytesWriteOptions): Promise<SandboxFileWriteInfo>;
|
|
76
|
+
upload(path: string, data: Buffer | Uint8Array | string): Promise<SandboxFileTransferResult>;
|
|
77
|
+
download(path: string): Promise<Buffer>;
|
|
78
|
+
makeDir(path: string, options?: SandboxFileMakeDirOptions): Promise<boolean>;
|
|
79
|
+
rename(oldPath: string, newPath: string): Promise<SandboxFileInfo>;
|
|
80
|
+
remove(path: string, options?: {
|
|
81
|
+
recursive?: boolean;
|
|
82
|
+
}): Promise<void>;
|
|
83
|
+
copy(params: SandboxFileCopyParams): Promise<SandboxFileInfo>;
|
|
84
|
+
chmod(params: SandboxFileChmodParams): Promise<void>;
|
|
85
|
+
chown(params: SandboxFileChownParams): Promise<void>;
|
|
86
|
+
watchDir(path: string, onEvent: (event: SandboxFileSystemEvent) => void | Promise<void>, options?: SandboxWatchDirOptions): Promise<SandboxWatchDirHandle>;
|
|
87
|
+
uploadUrl(path: string, options?: Omit<SandboxPresignFileParams, "path">): Promise<SandboxPresignedUrl>;
|
|
88
|
+
downloadUrl(path: string, options?: Omit<SandboxPresignFileParams, "path">): Promise<SandboxPresignedUrl>;
|
|
89
|
+
private readWire;
|
|
90
|
+
private writeSingle;
|
|
91
|
+
}
|
|
92
|
+
export {};
|