open-browser-use-sdk 0.1.23
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 +228 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +463 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Open Browser Use JavaScript SDK
|
|
2
|
+
|
|
3
|
+
JavaScript/TypeScript client for controlling a real Chrome profile through
|
|
4
|
+
Open Browser Use. The SDK keeps the low-level JSON-RPC/CDP surface available and
|
|
5
|
+
also provides higher-level browser/tab helpers for common agent workflows.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- The `open-browser-use` CLI and Chrome extension are installed and connected.
|
|
10
|
+
- `open-browser-use ping` returns `"pong"`.
|
|
11
|
+
- The native host has written an active socket registry at
|
|
12
|
+
`/tmp/open-browser-use/active.json`.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
open-browser-use ping
|
|
16
|
+
open-browser-use info
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install open-browser-use-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## High-Level Browser API
|
|
26
|
+
|
|
27
|
+
Use `connectOpenBrowserUse` when you want a Playwright-like flow in a normal
|
|
28
|
+
Node runtime.
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { readFile } from "node:fs/promises";
|
|
32
|
+
import { connectOpenBrowserUse } from "open-browser-use-sdk";
|
|
33
|
+
|
|
34
|
+
const registry = JSON.parse(
|
|
35
|
+
await readFile("/tmp/open-browser-use/active.json", "utf8")
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const browser = await connectOpenBrowserUse({
|
|
39
|
+
socketPath: registry.socketPath,
|
|
40
|
+
sessionId: "github-issue-scan",
|
|
41
|
+
turnId: `turn-${Date.now()}`,
|
|
42
|
+
timeoutMs: 20000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let tab;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await browser.client.nameSession("GitHub issue scan - OBU");
|
|
49
|
+
|
|
50
|
+
tab = await browser.newTab();
|
|
51
|
+
|
|
52
|
+
await tab.goto("https://github.com/iFurySt/open-codex-computer-use/issues", {
|
|
53
|
+
waitUntil: "domcontentloaded",
|
|
54
|
+
timeoutMs: 15000,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await tab.playwright.waitForLoadState({
|
|
58
|
+
state: "domcontentloaded",
|
|
59
|
+
timeoutMs: 15000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const snapshot = await tab.playwright.domSnapshot();
|
|
63
|
+
const relevant = snapshot
|
|
64
|
+
.split("\n")
|
|
65
|
+
.filter((line) =>
|
|
66
|
+
/Open|Closed|Issues|issue|No results|open-codex-computer-use|Pull requests|Starred/.test(line)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
console.log(relevant.slice(0, 160).join("\n"));
|
|
70
|
+
} finally {
|
|
71
|
+
await browser.client.finalizeTabs([]);
|
|
72
|
+
browser.close();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The same tab helpers are available without the `playwright` alias:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
await tab.goto(url, { waitUntil: "load", timeoutMs: 15000 });
|
|
80
|
+
await tab.waitForLoadState({ state: "domcontentloaded", timeoutMs: 15000 });
|
|
81
|
+
const text = await tab.domSnapshot();
|
|
82
|
+
const value = await tab.evaluate("document.title");
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Multi-Tab Workflows
|
|
86
|
+
|
|
87
|
+
For multiple pages, create tabs first, then run navigation and extraction in
|
|
88
|
+
parallel. This avoids backend session races while still parallelizing the slow
|
|
89
|
+
page work.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const targets = [
|
|
93
|
+
["repo", "https://github.com/iFurySt/open-codex-computer-use"],
|
|
94
|
+
["issues", "https://github.com/iFurySt/open-codex-computer-use/issues"],
|
|
95
|
+
["pulls", "https://github.com/iFurySt/open-codex-computer-use/pulls"],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const tabs = [];
|
|
99
|
+
for (const [kind] of targets) {
|
|
100
|
+
tabs.push([kind, await browser.newTab()]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await Promise.all(
|
|
104
|
+
targets.map(([_, url], index) =>
|
|
105
|
+
tabs[index][1].goto(url, { waitUntil: "load", timeoutMs: 25000 })
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const results = await Promise.all(
|
|
110
|
+
tabs.map(async ([kind, tab]) => ({
|
|
111
|
+
kind,
|
|
112
|
+
tabId: tab.id,
|
|
113
|
+
text: await tab.playwright.domSnapshot(),
|
|
114
|
+
}))
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Low-Level Client API
|
|
119
|
+
|
|
120
|
+
Use `OpenBrowserUseClient` when you need direct Browser Use JSON-RPC methods or
|
|
121
|
+
raw CDP commands.
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { readFile } from "node:fs/promises";
|
|
125
|
+
import { OpenBrowserUseClient } from "open-browser-use-sdk";
|
|
126
|
+
|
|
127
|
+
const { socketPath } = JSON.parse(
|
|
128
|
+
await readFile("/tmp/open-browser-use/active.json", "utf8")
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const client = new OpenBrowserUseClient({
|
|
132
|
+
socketPath,
|
|
133
|
+
sessionId: "raw-cdp-example",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await client.connect();
|
|
138
|
+
await client.nameSession("Raw CDP example - OBU");
|
|
139
|
+
|
|
140
|
+
const tab = await client.createTab();
|
|
141
|
+
await client.attach(tab.id);
|
|
142
|
+
await client.executeCdp(tab.id, "Page.navigate", {
|
|
143
|
+
url: "https://example.com",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const title = await client.executeCdp(tab.id, "Runtime.evaluate", {
|
|
147
|
+
expression: "document.title",
|
|
148
|
+
returnByValue: true,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
console.log(title.result.value);
|
|
152
|
+
} finally {
|
|
153
|
+
await client.finalizeTabs([]);
|
|
154
|
+
client.close();
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The unrestricted escape hatch is `request(method, params)`:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
await client.request("executeCdp", {
|
|
162
|
+
target: { tabId: 123 },
|
|
163
|
+
method: "Runtime.evaluate",
|
|
164
|
+
commandParams: {
|
|
165
|
+
expression: "document.body.innerText",
|
|
166
|
+
returnByValue: true,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Notifications
|
|
172
|
+
|
|
173
|
+
The JS SDK can subscribe to JSON-RPC notifications from the native socket. This
|
|
174
|
+
is useful for downloads and CDP events.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const unsubscribe = client.onNotification((event) => {
|
|
178
|
+
if (event.method === "onDownloadChange") {
|
|
179
|
+
console.log("download", event.params);
|
|
180
|
+
}
|
|
181
|
+
if (event.method === "onCDPEvent") {
|
|
182
|
+
console.log("cdp", event.params);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Later:
|
|
187
|
+
unsubscribe();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Common Methods
|
|
191
|
+
|
|
192
|
+
- Browser/session: `getInfo`, `nameSession`, `turnEnded`
|
|
193
|
+
- Tabs: `createTab`, `getTabs`, `getUserTabs`, `claimUserTab`, `finalizeTabs`
|
|
194
|
+
- CDP: `attach`, `detach`, `executeCdp`, `request`
|
|
195
|
+
- Input: `moveMouse`
|
|
196
|
+
- File chooser: `waitForFileChooser`, `setFileChooserFiles`
|
|
197
|
+
- Downloads: `waitForDownload`, `downloadPath`
|
|
198
|
+
- Clipboard: `readClipboardText`, `writeClipboardText`, `readClipboard`,
|
|
199
|
+
`writeClipboard`
|
|
200
|
+
|
|
201
|
+
## Cleanup
|
|
202
|
+
|
|
203
|
+
Always finish browser work by finalizing tabs and closing the socket:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
try {
|
|
207
|
+
// Browser work here.
|
|
208
|
+
} finally {
|
|
209
|
+
await browser.client.finalizeTabs([]);
|
|
210
|
+
browser.close();
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Use `finalizeTabs([])` unless the user explicitly needs a tab left open. To keep
|
|
215
|
+
a tab, pass a keep item such as:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
await browser.client.finalizeTabs([{ tabId: tab.id, status: "handoff" }]);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Notes
|
|
222
|
+
|
|
223
|
+
- The browser is the user's real Chrome profile. Do not inspect unrelated data.
|
|
224
|
+
- The SDK does not include Codex-specific site policy or approval prompts; add
|
|
225
|
+
policy in your own runtime if needed.
|
|
226
|
+
- If connection fails, run `open-browser-use ping` and `open-browser-use info`.
|
|
227
|
+
- If there is no active socket, open Chrome with the extension enabled or rerun
|
|
228
|
+
setup according to the Open Browser Use installation guide.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export type BrowserUseRequestParams = {
|
|
5
|
+
[key: string]: JsonValue;
|
|
6
|
+
};
|
|
7
|
+
export type OpenBrowserUseClientOptions = {
|
|
8
|
+
socketPath: string;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
turnId?: string;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
};
|
|
13
|
+
export type OpenBrowserUseNotification = {
|
|
14
|
+
method: string;
|
|
15
|
+
params?: JsonValue;
|
|
16
|
+
};
|
|
17
|
+
export type NotificationHandler = (notification: OpenBrowserUseNotification) => void;
|
|
18
|
+
export type OpenBrowserUseLoadState = "domcontentloaded" | "load";
|
|
19
|
+
export type OpenBrowserUseGotoOptions = {
|
|
20
|
+
waitUntil?: OpenBrowserUseLoadState;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
};
|
|
23
|
+
export type OpenBrowserUseWaitForLoadStateOptions = {
|
|
24
|
+
state?: OpenBrowserUseLoadState;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
};
|
|
27
|
+
export type OpenBrowserUseBrowserOptions = OpenBrowserUseClientOptions | {
|
|
28
|
+
client: OpenBrowserUseClient;
|
|
29
|
+
};
|
|
30
|
+
export declare class OpenBrowserUseClient {
|
|
31
|
+
#private;
|
|
32
|
+
readonly socketPath: string;
|
|
33
|
+
readonly sessionId: string;
|
|
34
|
+
readonly turnId: string;
|
|
35
|
+
readonly timeoutMs: number;
|
|
36
|
+
constructor(options: OpenBrowserUseClientOptions);
|
|
37
|
+
connect(): Promise<this>;
|
|
38
|
+
close(): void;
|
|
39
|
+
onNotification(handler: NotificationHandler): () => void;
|
|
40
|
+
request(method: string, params?: BrowserUseRequestParams): Promise<JsonValue>;
|
|
41
|
+
getInfo(): Promise<JsonValue>;
|
|
42
|
+
createTab(): Promise<JsonValue>;
|
|
43
|
+
getTabs(): Promise<JsonValue>;
|
|
44
|
+
getUserTabs(): Promise<JsonValue>;
|
|
45
|
+
getUserHistory(params?: BrowserUseRequestParams): Promise<JsonValue>;
|
|
46
|
+
claimUserTab(tabId: number): Promise<JsonValue>;
|
|
47
|
+
finalizeTabs(keep: JsonValue[]): Promise<JsonValue>;
|
|
48
|
+
nameSession(name: string): Promise<JsonValue>;
|
|
49
|
+
attach(tabId: number): Promise<JsonValue>;
|
|
50
|
+
detach(tabId: number): Promise<JsonValue>;
|
|
51
|
+
executeCdp(tabId: number, method: string, commandParams?: BrowserUseRequestParams): Promise<JsonValue>;
|
|
52
|
+
moveMouse(tabId: number, x: number, y: number, waitForArrival?: boolean): Promise<JsonValue>;
|
|
53
|
+
waitForFileChooser(tabId: number, timeoutMs?: number): Promise<JsonValue>;
|
|
54
|
+
setFileChooserFiles(fileChooserId: string, files: string[]): Promise<JsonValue>;
|
|
55
|
+
waitForDownload(tabId: number, timeoutMs?: number): Promise<JsonValue>;
|
|
56
|
+
downloadPath(downloadId: string, timeoutMs?: number): Promise<JsonValue>;
|
|
57
|
+
browserUserHistory(params?: BrowserUseRequestParams): Promise<JsonValue>;
|
|
58
|
+
readClipboardText(tabId: number): Promise<JsonValue>;
|
|
59
|
+
writeClipboardText(tabId: number, text: string): Promise<JsonValue>;
|
|
60
|
+
readClipboard(tabId: number): Promise<JsonValue>;
|
|
61
|
+
writeClipboard(tabId: number, items: JsonValue[]): Promise<JsonValue>;
|
|
62
|
+
turnEnded(): Promise<JsonValue>;
|
|
63
|
+
}
|
|
64
|
+
export declare function connectOpenBrowserUse(options: OpenBrowserUseBrowserOptions): Promise<OpenBrowserUseBrowser>;
|
|
65
|
+
export declare class OpenBrowserUseBrowser {
|
|
66
|
+
readonly client: OpenBrowserUseClient;
|
|
67
|
+
readonly cdp: OpenBrowserUseCdp;
|
|
68
|
+
constructor(options: OpenBrowserUseBrowserOptions);
|
|
69
|
+
connect(): Promise<this>;
|
|
70
|
+
close(): void;
|
|
71
|
+
newTab(options?: OpenBrowserUseGotoOptions & {
|
|
72
|
+
url?: string;
|
|
73
|
+
}): Promise<OpenBrowserUseTab>;
|
|
74
|
+
tab(tabId: number): OpenBrowserUseTab;
|
|
75
|
+
getTabs(): Promise<JsonValue>;
|
|
76
|
+
}
|
|
77
|
+
export declare class OpenBrowserUseTab {
|
|
78
|
+
readonly browser: OpenBrowserUseBrowser;
|
|
79
|
+
readonly id: number;
|
|
80
|
+
readonly playwright: OpenBrowserUseTabPlaywright;
|
|
81
|
+
constructor(browser: OpenBrowserUseBrowser, id: number);
|
|
82
|
+
goto(url: string, options?: OpenBrowserUseGotoOptions): Promise<JsonValue>;
|
|
83
|
+
waitForLoadState(options?: OpenBrowserUseWaitForLoadStateOptions): Promise<void>;
|
|
84
|
+
domSnapshot(): Promise<string>;
|
|
85
|
+
evaluate(expression: string, options?: {
|
|
86
|
+
awaitPromise?: boolean;
|
|
87
|
+
}): Promise<JsonValue>;
|
|
88
|
+
close(): Promise<JsonValue>;
|
|
89
|
+
}
|
|
90
|
+
export declare class OpenBrowserUseTabPlaywright {
|
|
91
|
+
readonly tab: OpenBrowserUseTab;
|
|
92
|
+
constructor(tab: OpenBrowserUseTab);
|
|
93
|
+
waitForLoadState(options?: OpenBrowserUseWaitForLoadStateOptions): Promise<void>;
|
|
94
|
+
domSnapshot(): Promise<string>;
|
|
95
|
+
}
|
|
96
|
+
export declare class OpenBrowserUseCdp {
|
|
97
|
+
#private;
|
|
98
|
+
readonly client: OpenBrowserUseClient;
|
|
99
|
+
constructor(client: OpenBrowserUseClient);
|
|
100
|
+
call(tabId: number, method: string, commandParams?: BrowserUseRequestParams, options?: {
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
}): Promise<JsonValue>;
|
|
103
|
+
evaluate(tabId: number, expression: string, options?: {
|
|
104
|
+
awaitPromise?: boolean;
|
|
105
|
+
}): Promise<JsonValue>;
|
|
106
|
+
navigate(tabId: number, url: string, options?: OpenBrowserUseGotoOptions): Promise<JsonValue>;
|
|
107
|
+
waitForLoadState(tabId: number, options?: OpenBrowserUseWaitForLoadStateOptions): Promise<void>;
|
|
108
|
+
readDocumentState(tabId: number): Promise<{
|
|
109
|
+
href?: string;
|
|
110
|
+
readyState?: string;
|
|
111
|
+
} | undefined>;
|
|
112
|
+
waitForEvent(tabId: number, predicate: (notification: OpenBrowserUseNotification) => boolean, options?: {
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
timeoutMessage?: string;
|
|
115
|
+
}): Promise<OpenBrowserUseNotification>;
|
|
116
|
+
waitForLoadEvent(tabId: number, state: OpenBrowserUseLoadState, timeoutMs: number): Promise<void>;
|
|
117
|
+
ensureAttached(tabId: number): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
export declare function encodeFrame(value: JsonValue | Record<string, unknown>): Buffer;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
import { endianness } from "node:os";
|
|
3
|
+
const headerBytes = 4;
|
|
4
|
+
const defaultNavigationTimeoutMs = 10_000;
|
|
5
|
+
export class OpenBrowserUseClient {
|
|
6
|
+
socketPath;
|
|
7
|
+
sessionId;
|
|
8
|
+
turnId;
|
|
9
|
+
timeoutMs;
|
|
10
|
+
#socket = null;
|
|
11
|
+
#pendingData = Buffer.alloc(0);
|
|
12
|
+
#nextId = 1;
|
|
13
|
+
#pending = new Map();
|
|
14
|
+
#notificationHandlers = new Set();
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.socketPath = options.socketPath;
|
|
17
|
+
this.sessionId = options.sessionId ?? "open-browser-use-js";
|
|
18
|
+
this.turnId = options.turnId ?? `turn-${Date.now()}`;
|
|
19
|
+
this.timeoutMs = options.timeoutMs ?? 10_000;
|
|
20
|
+
}
|
|
21
|
+
async connect() {
|
|
22
|
+
if (this.#socket) {
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
const socket = createConnection(this.socketPath);
|
|
26
|
+
this.#socket = socket;
|
|
27
|
+
socket.on("data", (chunk) => this.#handleData(Buffer.from(chunk)));
|
|
28
|
+
socket.on("close", () => this.#rejectAll(new Error("Open Browser Use socket closed")));
|
|
29
|
+
socket.on("error", (error) => this.#rejectAll(error));
|
|
30
|
+
await new Promise((resolve, reject) => {
|
|
31
|
+
socket.once("connect", resolve);
|
|
32
|
+
socket.once("error", reject);
|
|
33
|
+
});
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
close() {
|
|
37
|
+
this.#socket?.end();
|
|
38
|
+
this.#socket = null;
|
|
39
|
+
}
|
|
40
|
+
onNotification(handler) {
|
|
41
|
+
this.#notificationHandlers.add(handler);
|
|
42
|
+
return () => {
|
|
43
|
+
this.#notificationHandlers.delete(handler);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async request(method, params = {}) {
|
|
47
|
+
await this.connect();
|
|
48
|
+
const socket = this.#socket;
|
|
49
|
+
if (!socket) {
|
|
50
|
+
throw new Error("Open Browser Use socket is not connected");
|
|
51
|
+
}
|
|
52
|
+
const id = String(this.#nextId++);
|
|
53
|
+
const mergedParams = {
|
|
54
|
+
session_id: this.sessionId,
|
|
55
|
+
turn_id: this.turnId,
|
|
56
|
+
...params
|
|
57
|
+
};
|
|
58
|
+
const message = {
|
|
59
|
+
jsonrpc: "2.0",
|
|
60
|
+
id,
|
|
61
|
+
method,
|
|
62
|
+
params: mergedParams
|
|
63
|
+
};
|
|
64
|
+
const payload = encodeFrame(message);
|
|
65
|
+
const promise = new Promise((resolve, reject) => {
|
|
66
|
+
const timeout = setTimeout(() => {
|
|
67
|
+
this.#pending.delete(id);
|
|
68
|
+
reject(new Error(`Open Browser Use request timed out: ${method}`));
|
|
69
|
+
}, this.timeoutMs);
|
|
70
|
+
this.#pending.set(id, { resolve, reject, timeout });
|
|
71
|
+
});
|
|
72
|
+
socket.write(payload);
|
|
73
|
+
return await promise;
|
|
74
|
+
}
|
|
75
|
+
getInfo() {
|
|
76
|
+
return this.request("getInfo");
|
|
77
|
+
}
|
|
78
|
+
createTab() {
|
|
79
|
+
return this.request("createTab");
|
|
80
|
+
}
|
|
81
|
+
getTabs() {
|
|
82
|
+
return this.request("getTabs");
|
|
83
|
+
}
|
|
84
|
+
getUserTabs() {
|
|
85
|
+
return this.request("getUserTabs");
|
|
86
|
+
}
|
|
87
|
+
getUserHistory(params = {}) {
|
|
88
|
+
return this.request("getUserHistory", params);
|
|
89
|
+
}
|
|
90
|
+
claimUserTab(tabId) {
|
|
91
|
+
return this.request("claimUserTab", { tabId });
|
|
92
|
+
}
|
|
93
|
+
finalizeTabs(keep) {
|
|
94
|
+
return this.request("finalizeTabs", { keep });
|
|
95
|
+
}
|
|
96
|
+
nameSession(name) {
|
|
97
|
+
return this.request("nameSession", { name });
|
|
98
|
+
}
|
|
99
|
+
attach(tabId) {
|
|
100
|
+
return this.request("attach", { tabId });
|
|
101
|
+
}
|
|
102
|
+
detach(tabId) {
|
|
103
|
+
return this.request("detach", { tabId });
|
|
104
|
+
}
|
|
105
|
+
executeCdp(tabId, method, commandParams = {}) {
|
|
106
|
+
return this.request("executeCdp", {
|
|
107
|
+
target: { tabId },
|
|
108
|
+
method,
|
|
109
|
+
commandParams
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
moveMouse(tabId, x, y, waitForArrival = true) {
|
|
113
|
+
return this.request("moveMouse", { tabId, x, y, waitForArrival });
|
|
114
|
+
}
|
|
115
|
+
waitForFileChooser(tabId, timeoutMs) {
|
|
116
|
+
return this.request("waitForFileChooser", {
|
|
117
|
+
tabId,
|
|
118
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
setFileChooserFiles(fileChooserId, files) {
|
|
122
|
+
return this.request("setFileChooserFiles", { fileChooserId, files });
|
|
123
|
+
}
|
|
124
|
+
waitForDownload(tabId, timeoutMs) {
|
|
125
|
+
return this.request("waitForDownload", {
|
|
126
|
+
tabId,
|
|
127
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
downloadPath(downloadId, timeoutMs) {
|
|
131
|
+
return this.request("downloadPath", {
|
|
132
|
+
downloadId,
|
|
133
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
browserUserHistory(params = {}) {
|
|
137
|
+
return this.getUserHistory(params);
|
|
138
|
+
}
|
|
139
|
+
readClipboardText(tabId) {
|
|
140
|
+
return this.request("readClipboardText", { tabId });
|
|
141
|
+
}
|
|
142
|
+
writeClipboardText(tabId, text) {
|
|
143
|
+
return this.request("writeClipboardText", { tabId, text });
|
|
144
|
+
}
|
|
145
|
+
readClipboard(tabId) {
|
|
146
|
+
return this.request("readClipboard", { tabId });
|
|
147
|
+
}
|
|
148
|
+
writeClipboard(tabId, items) {
|
|
149
|
+
return this.request("writeClipboard", { tabId, items });
|
|
150
|
+
}
|
|
151
|
+
turnEnded() {
|
|
152
|
+
return this.request("turnEnded");
|
|
153
|
+
}
|
|
154
|
+
#handleData(chunk) {
|
|
155
|
+
this.#pendingData = Buffer.concat([this.#pendingData, chunk]);
|
|
156
|
+
while (this.#pendingData.length >= headerBytes) {
|
|
157
|
+
const length = endianness() === "LE"
|
|
158
|
+
? this.#pendingData.readUInt32LE(0)
|
|
159
|
+
: this.#pendingData.readUInt32BE(0);
|
|
160
|
+
const total = headerBytes + length;
|
|
161
|
+
if (this.#pendingData.length < total) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const payload = this.#pendingData.subarray(headerBytes, total);
|
|
165
|
+
this.#pendingData = this.#pendingData.subarray(total);
|
|
166
|
+
this.#handleMessage(JSON.parse(payload.toString("utf8")));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
#handleMessage(message) {
|
|
170
|
+
if (!isObject(message)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const id = typeof message.id === "string" || typeof message.id === "number" ? String(message.id) : null;
|
|
174
|
+
if (!id && typeof message.method === "string") {
|
|
175
|
+
const notification = {
|
|
176
|
+
method: message.method,
|
|
177
|
+
params: message.params
|
|
178
|
+
};
|
|
179
|
+
for (const handler of this.#notificationHandlers) {
|
|
180
|
+
handler(notification);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!id) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const pending = this.#pending.get(id);
|
|
188
|
+
if (!pending) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
this.#pending.delete(id);
|
|
192
|
+
clearTimeout(pending.timeout);
|
|
193
|
+
if (isObject(message.error)) {
|
|
194
|
+
pending.reject(new Error(String(message.error.message ?? "Open Browser Use request failed")));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
pending.resolve((message.result ?? null));
|
|
198
|
+
}
|
|
199
|
+
#rejectAll(error) {
|
|
200
|
+
for (const [id, pending] of this.#pending) {
|
|
201
|
+
this.#pending.delete(id);
|
|
202
|
+
clearTimeout(pending.timeout);
|
|
203
|
+
pending.reject(error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export async function connectOpenBrowserUse(options) {
|
|
208
|
+
const browser = new OpenBrowserUseBrowser(options);
|
|
209
|
+
await browser.connect();
|
|
210
|
+
return browser;
|
|
211
|
+
}
|
|
212
|
+
export class OpenBrowserUseBrowser {
|
|
213
|
+
client;
|
|
214
|
+
cdp;
|
|
215
|
+
constructor(options) {
|
|
216
|
+
this.client = "client" in options ? options.client : new OpenBrowserUseClient(options);
|
|
217
|
+
this.cdp = new OpenBrowserUseCdp(this.client);
|
|
218
|
+
}
|
|
219
|
+
async connect() {
|
|
220
|
+
await this.client.connect();
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
close() {
|
|
224
|
+
this.client.close();
|
|
225
|
+
}
|
|
226
|
+
async newTab(options = {}) {
|
|
227
|
+
const created = await this.client.createTab();
|
|
228
|
+
const tabId = tabIdFromValue(created, "createTab response");
|
|
229
|
+
const tab = this.tab(tabId);
|
|
230
|
+
if (options.url) {
|
|
231
|
+
await tab.goto(options.url, options);
|
|
232
|
+
}
|
|
233
|
+
return tab;
|
|
234
|
+
}
|
|
235
|
+
tab(tabId) {
|
|
236
|
+
return new OpenBrowserUseTab(this, tabId);
|
|
237
|
+
}
|
|
238
|
+
getTabs() {
|
|
239
|
+
return this.client.getTabs();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export class OpenBrowserUseTab {
|
|
243
|
+
browser;
|
|
244
|
+
id;
|
|
245
|
+
playwright;
|
|
246
|
+
constructor(browser, id) {
|
|
247
|
+
this.browser = browser;
|
|
248
|
+
this.id = id;
|
|
249
|
+
this.playwright = new OpenBrowserUseTabPlaywright(this);
|
|
250
|
+
}
|
|
251
|
+
goto(url, options = {}) {
|
|
252
|
+
return this.browser.cdp.navigate(this.id, url, options);
|
|
253
|
+
}
|
|
254
|
+
waitForLoadState(options = {}) {
|
|
255
|
+
return this.browser.cdp.waitForLoadState(this.id, options);
|
|
256
|
+
}
|
|
257
|
+
domSnapshot() {
|
|
258
|
+
return this.browser.cdp.evaluate(this.id, "document.body?.innerText ?? ''").then((value) => String(value ?? ""));
|
|
259
|
+
}
|
|
260
|
+
evaluate(expression, options = {}) {
|
|
261
|
+
return this.browser.cdp.evaluate(this.id, expression, options);
|
|
262
|
+
}
|
|
263
|
+
close() {
|
|
264
|
+
return this.browser.cdp.call(this.id, "Page.close");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export class OpenBrowserUseTabPlaywright {
|
|
268
|
+
tab;
|
|
269
|
+
constructor(tab) {
|
|
270
|
+
this.tab = tab;
|
|
271
|
+
}
|
|
272
|
+
waitForLoadState(options = {}) {
|
|
273
|
+
return this.tab.waitForLoadState(options);
|
|
274
|
+
}
|
|
275
|
+
domSnapshot() {
|
|
276
|
+
return this.tab.domSnapshot();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
export class OpenBrowserUseCdp {
|
|
280
|
+
client;
|
|
281
|
+
#attachedTabIds = new Set();
|
|
282
|
+
constructor(client) {
|
|
283
|
+
this.client = client;
|
|
284
|
+
}
|
|
285
|
+
async call(tabId, method, commandParams = {}, options = {}) {
|
|
286
|
+
await this.ensureAttached(tabId);
|
|
287
|
+
return this.client.request("executeCdp", {
|
|
288
|
+
target: { tabId },
|
|
289
|
+
method,
|
|
290
|
+
commandParams,
|
|
291
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs })
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async evaluate(tabId, expression, options = {}) {
|
|
295
|
+
const result = await this.call(tabId, "Runtime.evaluate", {
|
|
296
|
+
expression,
|
|
297
|
+
returnByValue: true,
|
|
298
|
+
...(options.awaitPromise === undefined ? {} : { awaitPromise: options.awaitPromise })
|
|
299
|
+
});
|
|
300
|
+
if (isObject(result) && isObject(result.exceptionDetails)) {
|
|
301
|
+
throw new Error(String(result.exceptionDetails.text ?? "Open Browser Use evaluation failed"));
|
|
302
|
+
}
|
|
303
|
+
return isObject(result) && isObject(result.result) ? (result.result.value ?? null) : null;
|
|
304
|
+
}
|
|
305
|
+
async navigate(tabId, url, options = {}) {
|
|
306
|
+
if (!url) {
|
|
307
|
+
throw new Error("goto requires a URL");
|
|
308
|
+
}
|
|
309
|
+
const waitUntil = options.waitUntil ?? "load";
|
|
310
|
+
assertSupportedLoadState(waitUntil);
|
|
311
|
+
const timeoutMs = options.timeoutMs ?? defaultNavigationTimeoutMs;
|
|
312
|
+
await this.call(tabId, "Page.enable");
|
|
313
|
+
const wait = this.waitForLoadEvent(tabId, waitUntil, timeoutMs);
|
|
314
|
+
const result = await this.call(tabId, "Page.navigate", { url }, { timeoutMs });
|
|
315
|
+
if (isObject(result) && typeof result.errorText === "string" && result.errorText) {
|
|
316
|
+
throw new Error(`Browser failed to navigate tab ${tabId}: ${result.errorText}`);
|
|
317
|
+
}
|
|
318
|
+
await wait.catch(async (error) => {
|
|
319
|
+
const state = await this.readDocumentState(tabId);
|
|
320
|
+
if (documentStateMatches(state, waitUntil)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
});
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
async waitForLoadState(tabId, options = {}) {
|
|
328
|
+
const state = options.state ?? "load";
|
|
329
|
+
assertSupportedLoadState(state);
|
|
330
|
+
await this.call(tabId, "Page.enable");
|
|
331
|
+
const documentState = await this.readDocumentState(tabId);
|
|
332
|
+
if (documentStateMatches(documentState, state)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await this.waitForLoadEvent(tabId, state, options.timeoutMs ?? defaultNavigationTimeoutMs);
|
|
336
|
+
}
|
|
337
|
+
async readDocumentState(tabId) {
|
|
338
|
+
try {
|
|
339
|
+
const value = await this.evaluate(tabId, "({ href: window.location.href, readyState: document.readyState })");
|
|
340
|
+
return isObject(value) ? { href: stringValue(value.href), readyState: stringValue(value.readyState) } : undefined;
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
waitForEvent(tabId, predicate, options = {}) {
|
|
347
|
+
const timeoutMs = options.timeoutMs ?? defaultNavigationTimeoutMs;
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
let settled = false;
|
|
350
|
+
let removeHandler = null;
|
|
351
|
+
let timer;
|
|
352
|
+
const finish = () => {
|
|
353
|
+
if (settled) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
settled = true;
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
removeHandler?.();
|
|
359
|
+
return true;
|
|
360
|
+
};
|
|
361
|
+
timer = setTimeout(() => {
|
|
362
|
+
if (finish()) {
|
|
363
|
+
reject(new Error(options.timeoutMessage ?? `Timed out waiting for tab ${tabId} event`));
|
|
364
|
+
}
|
|
365
|
+
}, timeoutMs);
|
|
366
|
+
removeHandler = this.client.onNotification((notification) => {
|
|
367
|
+
try {
|
|
368
|
+
if (predicate(notification)) {
|
|
369
|
+
if (finish()) {
|
|
370
|
+
resolve(notification);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
if (finish()) {
|
|
376
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
waitForLoadEvent(tabId, state, timeoutMs) {
|
|
383
|
+
return this.waitForEvent(tabId, (notification) => {
|
|
384
|
+
const event = cdpEventForTab(notification, tabId);
|
|
385
|
+
if (!event) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (event.method === "Page.navigationBlocked") {
|
|
389
|
+
throw new Error(`Navigation was blocked in tab ${tabId}`);
|
|
390
|
+
}
|
|
391
|
+
return state === "domcontentloaded"
|
|
392
|
+
? event.method === "Page.domContentEventFired"
|
|
393
|
+
: event.method === "Page.loadEventFired";
|
|
394
|
+
}, {
|
|
395
|
+
timeoutMs,
|
|
396
|
+
timeoutMessage: `Timed out waiting for ${state} in tab ${tabId}`
|
|
397
|
+
}).then(() => undefined);
|
|
398
|
+
}
|
|
399
|
+
async ensureAttached(tabId) {
|
|
400
|
+
if (this.#attachedTabIds.has(tabId)) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
await this.client.attach(tabId);
|
|
404
|
+
this.#attachedTabIds.add(tabId);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
export function encodeFrame(value) {
|
|
408
|
+
const payload = Buffer.from(JSON.stringify(value), "utf8");
|
|
409
|
+
const frame = Buffer.alloc(headerBytes + payload.length);
|
|
410
|
+
if (endianness() === "LE") {
|
|
411
|
+
frame.writeUInt32LE(payload.length, 0);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
frame.writeUInt32BE(payload.length, 0);
|
|
415
|
+
}
|
|
416
|
+
payload.copy(frame, headerBytes);
|
|
417
|
+
return frame;
|
|
418
|
+
}
|
|
419
|
+
function isObject(value) {
|
|
420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
421
|
+
}
|
|
422
|
+
function tabIdFromValue(value, label) {
|
|
423
|
+
if (!isObject(value)) {
|
|
424
|
+
throw new Error(`${label} did not include a tab object`);
|
|
425
|
+
}
|
|
426
|
+
const id = value.id;
|
|
427
|
+
if (typeof id === "number" && Number.isInteger(id) && id > 0) {
|
|
428
|
+
return id;
|
|
429
|
+
}
|
|
430
|
+
if (typeof id === "string") {
|
|
431
|
+
const parsed = Number(id);
|
|
432
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
433
|
+
return parsed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
throw new Error(`${label} did not include a numeric tab id`);
|
|
437
|
+
}
|
|
438
|
+
function assertSupportedLoadState(state) {
|
|
439
|
+
if (state !== "domcontentloaded" && state !== "load") {
|
|
440
|
+
throw new Error(`Unsupported load state "${state}". Use "domcontentloaded" or "load".`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function documentStateMatches(documentState, state) {
|
|
444
|
+
if (documentState?.readyState === "complete") {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
return state === "domcontentloaded" && documentState?.readyState === "interactive";
|
|
448
|
+
}
|
|
449
|
+
function cdpEventForTab(notification, tabId) {
|
|
450
|
+
if (notification.method !== "onCDPEvent" || !isObject(notification.params)) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const source = notification.params.source;
|
|
454
|
+
if (!isObject(source) || source.tabId !== tabId) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
method: stringValue(notification.params.method)
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function stringValue(value) {
|
|
462
|
+
return typeof value === "string" ? value : undefined;
|
|
463
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-browser-use-sdk",
|
|
3
|
+
"version": "0.1.23",
|
|
4
|
+
"description": "JavaScript/TypeScript SDK for Open Browser Use.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
18
|
+
"test": "pnpm build && node --test test/*.test.mjs"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/iFurySt/open-codex-browser-use.git",
|
|
27
|
+
"directory": "packages/open-browser-use-js"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/iFurySt/open-codex-browser-use/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/iFurySt/open-codex-browser-use/tree/main/packages/open-browser-use-js#readme",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.10.2",
|
|
38
|
+
"typescript": "^5.7.2"
|
|
39
|
+
}
|
|
40
|
+
}
|