@brightdata/cli 0.1.3 → 0.1.5
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 +410 -1
- package/dist/__tests__/browser/connection.test.d.ts +2 -0
- package/dist/__tests__/browser/connection.test.d.ts.map +1 -0
- package/dist/__tests__/browser/connection.test.js +168 -0
- package/dist/__tests__/browser/connection.test.js.map +1 -0
- package/dist/__tests__/browser/daemon.test.d.ts +2 -0
- package/dist/__tests__/browser/daemon.test.d.ts.map +1 -0
- package/dist/__tests__/browser/daemon.test.js +586 -0
- package/dist/__tests__/browser/daemon.test.js.map +1 -0
- package/dist/__tests__/browser/entrypoint.test.d.ts +2 -0
- package/dist/__tests__/browser/entrypoint.test.d.ts.map +1 -0
- package/dist/__tests__/browser/entrypoint.test.js +53 -0
- package/dist/__tests__/browser/entrypoint.test.js.map +1 -0
- package/dist/__tests__/browser/ipc.test.d.ts +2 -0
- package/dist/__tests__/browser/ipc.test.d.ts.map +1 -0
- package/dist/__tests__/browser/ipc.test.js +199 -0
- package/dist/__tests__/browser/ipc.test.js.map +1 -0
- package/dist/__tests__/browser/lifecycle.test.d.ts +2 -0
- package/dist/__tests__/browser/lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/browser/lifecycle.test.js +176 -0
- package/dist/__tests__/browser/lifecycle.test.js.map +1 -0
- package/dist/__tests__/browser/snapshot.test.d.ts +2 -0
- package/dist/__tests__/browser/snapshot.test.d.ts.map +1 -0
- package/dist/__tests__/browser/snapshot.test.js +202 -0
- package/dist/__tests__/browser/snapshot.test.js.map +1 -0
- package/dist/__tests__/commands/add-mcp.test.d.ts +2 -0
- package/dist/__tests__/commands/add-mcp.test.d.ts.map +1 -0
- package/dist/__tests__/commands/add-mcp.test.js +138 -0
- package/dist/__tests__/commands/add-mcp.test.js.map +1 -0
- package/dist/__tests__/commands/browser.test.d.ts +2 -0
- package/dist/__tests__/commands/browser.test.d.ts.map +1 -0
- package/dist/__tests__/commands/browser.test.js +431 -0
- package/dist/__tests__/commands/browser.test.js.map +1 -0
- package/dist/__tests__/utils/browser-credentials.test.d.ts +2 -0
- package/dist/__tests__/utils/browser-credentials.test.d.ts.map +1 -0
- package/dist/__tests__/utils/browser-credentials.test.js +81 -0
- package/dist/__tests__/utils/browser-credentials.test.js.map +1 -0
- package/dist/__tests__/utils/mcp-config.test.d.ts +2 -0
- package/dist/__tests__/utils/mcp-config.test.d.ts.map +1 -0
- package/dist/__tests__/utils/mcp-config.test.js +154 -0
- package/dist/__tests__/utils/mcp-config.test.js.map +1 -0
- package/dist/browser/connection.d.ts +24 -0
- package/dist/browser/connection.d.ts.map +1 -0
- package/dist/browser/connection.js +55 -0
- package/dist/browser/connection.js.map +1 -0
- package/dist/browser/daemon.d.ts +130 -0
- package/dist/browser/daemon.d.ts.map +1 -0
- package/dist/browser/daemon.js +645 -0
- package/dist/browser/daemon.js.map +1 -0
- package/dist/browser/entrypoint.d.ts +20 -0
- package/dist/browser/entrypoint.d.ts.map +1 -0
- package/dist/browser/entrypoint.js +46 -0
- package/dist/browser/entrypoint.js.map +1 -0
- package/dist/browser/interaction.d.ts +32 -0
- package/dist/browser/interaction.d.ts.map +1 -0
- package/dist/browser/interaction.js +215 -0
- package/dist/browser/interaction.js.map +1 -0
- package/dist/browser/ipc.d.ts +52 -0
- package/dist/browser/ipc.d.ts.map +1 -0
- package/dist/browser/ipc.js +254 -0
- package/dist/browser/ipc.js.map +1 -0
- package/dist/browser/lifecycle.d.ts +31 -0
- package/dist/browser/lifecycle.d.ts.map +1 -0
- package/dist/browser/lifecycle.js +202 -0
- package/dist/browser/lifecycle.js.map +1 -0
- package/dist/browser/screenshot.d.ts +19 -0
- package/dist/browser/screenshot.d.ts.map +1 -0
- package/dist/browser/screenshot.js +44 -0
- package/dist/browser/screenshot.js.map +1 -0
- package/dist/browser/snapshot.d.ts +48 -0
- package/dist/browser/snapshot.d.ts.map +1 -0
- package/dist/browser/snapshot.js +490 -0
- package/dist/browser/snapshot.js.map +1 -0
- package/dist/commands/add-mcp.d.ts +18 -0
- package/dist/commands/add-mcp.d.ts.map +1 -0
- package/dist/commands/add-mcp.js +196 -0
- package/dist/commands/add-mcp.js.map +1 -0
- package/dist/commands/browser.d.ts +62 -0
- package/dist/commands/browser.d.ts.map +1 -0
- package/dist/commands/browser.js +615 -0
- package/dist/commands/browser.js.map +1 -0
- package/dist/index.js +40 -21
- package/dist/index.js.map +1 -1
- package/dist/utils/browser-credentials.d.ts +5 -0
- package/dist/utils/browser-credentials.d.ts.map +1 -0
- package/dist/utils/browser-credentials.js +53 -0
- package/dist/utils/browser-credentials.js.map +1 -0
- package/dist/utils/mcp-config.d.ts +15 -0
- package/dist/utils/mcp-config.d.ts.map +1 -0
- package/dist/utils/mcp-config.js +87 -0
- package/dist/utils/mcp-config.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,645 @@
|
|
|
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.start_daemon_from_env = exports.parse_idle_timeout = exports.normalize_session_name = exports.get_port_for_session = exports.get_daemon_transport = exports.get_daemon_base_dir = exports.create_daemon_from_env = exports.DEFAULT_SESSION_NAME = exports.KEEPALIVE_INTERVAL = exports.DEFAULT_DAEMON_IDLE_TIMEOUT_MS = exports.BrowserDaemon = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const net_1 = __importDefault(require("net"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const playwright_core_1 = require("playwright-core");
|
|
12
|
+
const connection_1 = require("./connection");
|
|
13
|
+
const interaction_1 = require("./interaction");
|
|
14
|
+
const ipc_1 = require("./ipc");
|
|
15
|
+
const screenshot_1 = require("./screenshot");
|
|
16
|
+
const snapshot_1 = require("./snapshot");
|
|
17
|
+
const DEFAULT_SESSION_NAME = 'default';
|
|
18
|
+
exports.DEFAULT_SESSION_NAME = DEFAULT_SESSION_NAME;
|
|
19
|
+
const DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 600_000;
|
|
20
|
+
exports.DEFAULT_DAEMON_IDLE_TIMEOUT_MS = DEFAULT_DAEMON_IDLE_TIMEOUT_MS;
|
|
21
|
+
const KEEPALIVE_INTERVAL = 30_000;
|
|
22
|
+
exports.KEEPALIVE_INTERVAL = KEEPALIVE_INTERVAL;
|
|
23
|
+
const MAX_TRACKED_REQUESTS = 200;
|
|
24
|
+
const WINDOWS_PORT_BASE = 49_152;
|
|
25
|
+
const WINDOWS_PORT_SPAN = 16_383;
|
|
26
|
+
const is_object = (value) => {
|
|
27
|
+
return !!value && typeof value == 'object' && !Array.isArray(value);
|
|
28
|
+
};
|
|
29
|
+
const get_path_api = (platform) => platform == 'win32' ? path_1.default.win32 : path_1.default.posix;
|
|
30
|
+
const format_error = (error) => {
|
|
31
|
+
if (error instanceof Error)
|
|
32
|
+
return error.message;
|
|
33
|
+
return String(error);
|
|
34
|
+
};
|
|
35
|
+
const normalize_session_name = (session_name) => {
|
|
36
|
+
const normalized = (session_name ?? DEFAULT_SESSION_NAME).trim();
|
|
37
|
+
if (!normalized)
|
|
38
|
+
throw new Error('Browser session name cannot be empty.');
|
|
39
|
+
if (!/^[A-Za-z0-9._-]+$/.test(normalized)) {
|
|
40
|
+
throw new Error('Browser session name may contain only letters, numbers, dots, '
|
|
41
|
+
+ 'underscores, and hyphens.');
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
};
|
|
45
|
+
exports.normalize_session_name = normalize_session_name;
|
|
46
|
+
const normalize_idle_timeout = (idle_timeout_ms) => {
|
|
47
|
+
if (idle_timeout_ms === undefined)
|
|
48
|
+
return DEFAULT_DAEMON_IDLE_TIMEOUT_MS;
|
|
49
|
+
if (!Number.isFinite(idle_timeout_ms) || idle_timeout_ms < 0) {
|
|
50
|
+
throw new Error('Browser idle timeout must be a non-negative number of '
|
|
51
|
+
+ 'milliseconds.');
|
|
52
|
+
}
|
|
53
|
+
return Math.floor(idle_timeout_ms);
|
|
54
|
+
};
|
|
55
|
+
const parse_idle_timeout = (raw) => {
|
|
56
|
+
const normalized = raw?.trim();
|
|
57
|
+
if (!normalized)
|
|
58
|
+
return undefined;
|
|
59
|
+
return normalize_idle_timeout(Number(normalized));
|
|
60
|
+
};
|
|
61
|
+
exports.parse_idle_timeout = parse_idle_timeout;
|
|
62
|
+
const get_daemon_base_dir = (opts = {}) => {
|
|
63
|
+
const platform = opts.platform ?? process.platform;
|
|
64
|
+
const env = opts.env ?? process.env;
|
|
65
|
+
const home_dir = opts.home_dir ?? os_1.default.homedir();
|
|
66
|
+
const path_api = get_path_api(platform);
|
|
67
|
+
const daemon_dir = opts.daemon_dir?.trim();
|
|
68
|
+
const env_daemon_dir = env['BRIGHTDATA_DAEMON_DIR']?.trim();
|
|
69
|
+
if (daemon_dir)
|
|
70
|
+
return daemon_dir;
|
|
71
|
+
if (env_daemon_dir)
|
|
72
|
+
return env_daemon_dir;
|
|
73
|
+
if (platform == 'darwin') {
|
|
74
|
+
return path_api.join(home_dir, 'Library', 'Application Support', 'brightdata-cli');
|
|
75
|
+
}
|
|
76
|
+
if (platform == 'win32')
|
|
77
|
+
return path_api.join(home_dir, 'AppData', 'Roaming', 'brightdata-cli');
|
|
78
|
+
const runtime_dir = env['XDG_RUNTIME_DIR']?.trim();
|
|
79
|
+
if (runtime_dir)
|
|
80
|
+
return path_api.join(runtime_dir, 'brightdata-cli');
|
|
81
|
+
return path_api.join(home_dir, '.brightdata-cli');
|
|
82
|
+
};
|
|
83
|
+
exports.get_daemon_base_dir = get_daemon_base_dir;
|
|
84
|
+
const get_port_for_session = (session_name) => {
|
|
85
|
+
let hash = 0;
|
|
86
|
+
for (let i = 0; i < session_name.length; i++) {
|
|
87
|
+
hash = ((hash << 5) - hash + session_name.charCodeAt(i)) | 0;
|
|
88
|
+
}
|
|
89
|
+
return WINDOWS_PORT_BASE + ((hash >>> 0) % WINDOWS_PORT_SPAN);
|
|
90
|
+
};
|
|
91
|
+
exports.get_port_for_session = get_port_for_session;
|
|
92
|
+
const get_daemon_transport = (session_name, opts = {}) => {
|
|
93
|
+
const normalized_session = normalize_session_name(session_name);
|
|
94
|
+
const platform = opts.platform ?? process.platform;
|
|
95
|
+
const base_dir = get_daemon_base_dir({ ...opts, platform });
|
|
96
|
+
const path_api = get_path_api(platform);
|
|
97
|
+
const pid_path = path_api.join(base_dir, `${normalized_session}.pid`);
|
|
98
|
+
if (platform == 'win32') {
|
|
99
|
+
const port = get_port_for_session(normalized_session);
|
|
100
|
+
return {
|
|
101
|
+
kind: 'tcp',
|
|
102
|
+
base_dir,
|
|
103
|
+
host: '127.0.0.1',
|
|
104
|
+
pid_path,
|
|
105
|
+
port,
|
|
106
|
+
port_path: path_api.join(base_dir, `${normalized_session}.port`),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
kind: 'unix',
|
|
111
|
+
base_dir,
|
|
112
|
+
pid_path,
|
|
113
|
+
socket_path: path_api.join(base_dir, `${normalized_session}.sock`),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
exports.get_daemon_transport = get_daemon_transport;
|
|
117
|
+
class BrowserDaemon {
|
|
118
|
+
state;
|
|
119
|
+
active_contexts = new WeakSet();
|
|
120
|
+
active_pages = new WeakSet();
|
|
121
|
+
deps;
|
|
122
|
+
idle_timeout_ms;
|
|
123
|
+
request_ids = new WeakMap();
|
|
124
|
+
transport;
|
|
125
|
+
active_requests = 0;
|
|
126
|
+
request_counter = 0;
|
|
127
|
+
server = null;
|
|
128
|
+
stop_promise = null;
|
|
129
|
+
constructor(opts, deps = {}) {
|
|
130
|
+
const session_name = normalize_session_name(opts.session_name);
|
|
131
|
+
const cdp_endpoint = opts.cdp_endpoint.trim();
|
|
132
|
+
if (!cdp_endpoint)
|
|
133
|
+
throw new Error('Browser daemon CDP endpoint cannot be empty.');
|
|
134
|
+
this.idle_timeout_ms = normalize_idle_timeout(opts.idle_timeout_ms);
|
|
135
|
+
this.transport = get_daemon_transport(session_name, {
|
|
136
|
+
daemon_dir: opts.daemon_dir,
|
|
137
|
+
env: opts.env,
|
|
138
|
+
home_dir: opts.home_dir,
|
|
139
|
+
});
|
|
140
|
+
this.state = {
|
|
141
|
+
browser: null,
|
|
142
|
+
page: null,
|
|
143
|
+
connected: false,
|
|
144
|
+
dom_refs: new Map(),
|
|
145
|
+
requests: new Map(),
|
|
146
|
+
cdp_endpoint,
|
|
147
|
+
session_name,
|
|
148
|
+
idle_timer: null,
|
|
149
|
+
keepalive_timer: null,
|
|
150
|
+
};
|
|
151
|
+
this.deps = {
|
|
152
|
+
clear_interval: clearInterval,
|
|
153
|
+
clear_timeout: clearTimeout,
|
|
154
|
+
connect_over_cdp: (endpoint) => playwright_core_1.chromium.connectOverCDP(endpoint),
|
|
155
|
+
create_server: net_1.default.createServer,
|
|
156
|
+
pid: () => process.pid,
|
|
157
|
+
set_interval: setInterval,
|
|
158
|
+
set_timeout: setTimeout,
|
|
159
|
+
...deps,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
get_transport() {
|
|
163
|
+
return this.transport;
|
|
164
|
+
}
|
|
165
|
+
is_running() {
|
|
166
|
+
return this.server?.listening === true;
|
|
167
|
+
}
|
|
168
|
+
async start() {
|
|
169
|
+
if (this.server?.listening)
|
|
170
|
+
return;
|
|
171
|
+
fs_1.default.mkdirSync(this.transport.base_dir, { recursive: true });
|
|
172
|
+
if (this.transport.kind == 'unix' &&
|
|
173
|
+
fs_1.default.existsSync(this.transport.socket_path)) {
|
|
174
|
+
fs_1.default.rmSync(this.transport.socket_path, { force: true });
|
|
175
|
+
}
|
|
176
|
+
const server = this.deps.create_server(socket => this.handle_socket(socket));
|
|
177
|
+
this.server = server;
|
|
178
|
+
try {
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
const on_error = (error) => {
|
|
181
|
+
server.off('listening', on_listening);
|
|
182
|
+
reject(error);
|
|
183
|
+
};
|
|
184
|
+
const on_listening = () => {
|
|
185
|
+
server.off('error', on_error);
|
|
186
|
+
resolve();
|
|
187
|
+
};
|
|
188
|
+
server.once('error', on_error);
|
|
189
|
+
server.once('listening', on_listening);
|
|
190
|
+
if (this.transport.kind == 'unix')
|
|
191
|
+
server.listen(this.transport.socket_path);
|
|
192
|
+
else
|
|
193
|
+
server.listen(this.transport.port, this.transport.host);
|
|
194
|
+
});
|
|
195
|
+
fs_1.default.writeFileSync(this.transport.pid_path, `${this.deps.pid()}`);
|
|
196
|
+
if (this.transport.kind == 'tcp') {
|
|
197
|
+
fs_1.default.writeFileSync(this.transport.port_path, `${this.transport.port}`);
|
|
198
|
+
}
|
|
199
|
+
this.schedule_idle_timer();
|
|
200
|
+
this.start_keepalive();
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
this.server = null;
|
|
204
|
+
this.cleanup_transport_files();
|
|
205
|
+
try {
|
|
206
|
+
server.close();
|
|
207
|
+
}
|
|
208
|
+
catch (_error) { }
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async stop() {
|
|
213
|
+
if (this.stop_promise)
|
|
214
|
+
return this.stop_promise;
|
|
215
|
+
this.stop_promise = this.stop_internal();
|
|
216
|
+
try {
|
|
217
|
+
await this.stop_promise;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
this.stop_promise = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async handle_request(request) {
|
|
224
|
+
this.begin_activity();
|
|
225
|
+
try {
|
|
226
|
+
const data = await this.execute_request(request);
|
|
227
|
+
return { id: request.id, success: true, data };
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
id: request.id,
|
|
232
|
+
success: false,
|
|
233
|
+
error: format_error(error),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
this.end_activity();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
begin_activity() {
|
|
241
|
+
this.active_requests++;
|
|
242
|
+
this.clear_idle_timer();
|
|
243
|
+
}
|
|
244
|
+
end_activity() {
|
|
245
|
+
this.active_requests = Math.max(0, this.active_requests - 1);
|
|
246
|
+
if (this.active_requests == 0 && !this.stop_promise)
|
|
247
|
+
this.schedule_idle_timer();
|
|
248
|
+
}
|
|
249
|
+
clear_idle_timer() {
|
|
250
|
+
if (this.state.idle_timer) {
|
|
251
|
+
this.deps.clear_timeout(this.state.idle_timer);
|
|
252
|
+
this.state.idle_timer = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
schedule_idle_timer() {
|
|
256
|
+
this.clear_idle_timer();
|
|
257
|
+
if (this.idle_timeout_ms == 0)
|
|
258
|
+
return;
|
|
259
|
+
this.state.idle_timer = this.deps.set_timeout(() => {
|
|
260
|
+
void this.stop();
|
|
261
|
+
}, this.idle_timeout_ms);
|
|
262
|
+
}
|
|
263
|
+
clear_keepalive() {
|
|
264
|
+
if (this.state.keepalive_timer) {
|
|
265
|
+
this.deps.clear_interval(this.state.keepalive_timer);
|
|
266
|
+
this.state.keepalive_timer = null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
start_keepalive() {
|
|
270
|
+
this.clear_keepalive();
|
|
271
|
+
this.state.keepalive_timer = this.deps.set_interval(() => {
|
|
272
|
+
const browser = this.state.browser;
|
|
273
|
+
if (!browser)
|
|
274
|
+
return;
|
|
275
|
+
try {
|
|
276
|
+
browser.contexts();
|
|
277
|
+
this.state.connected = true;
|
|
278
|
+
}
|
|
279
|
+
catch (_error) {
|
|
280
|
+
if (this.state.browser == browser)
|
|
281
|
+
(0, connection_1.clear_connection_state)(this.state);
|
|
282
|
+
}
|
|
283
|
+
}, KEEPALIVE_INTERVAL);
|
|
284
|
+
}
|
|
285
|
+
async stop_internal() {
|
|
286
|
+
this.clear_idle_timer();
|
|
287
|
+
this.clear_keepalive();
|
|
288
|
+
await this.close_browser();
|
|
289
|
+
const server = this.server;
|
|
290
|
+
this.server = null;
|
|
291
|
+
if (server) {
|
|
292
|
+
await new Promise(resolve => {
|
|
293
|
+
server.close(() => resolve());
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
this.cleanup_transport_files();
|
|
297
|
+
}
|
|
298
|
+
cleanup_transport_files() {
|
|
299
|
+
fs_1.default.rmSync(this.transport.pid_path, { force: true });
|
|
300
|
+
if (this.transport.kind == 'unix')
|
|
301
|
+
fs_1.default.rmSync(this.transport.socket_path, { force: true });
|
|
302
|
+
else
|
|
303
|
+
fs_1.default.rmSync(this.transport.port_path, { force: true });
|
|
304
|
+
}
|
|
305
|
+
handle_socket(socket) {
|
|
306
|
+
socket.setEncoding('utf8');
|
|
307
|
+
let buffer = '';
|
|
308
|
+
let queue = Promise.resolve();
|
|
309
|
+
socket.on('data', chunk => {
|
|
310
|
+
buffer += chunk;
|
|
311
|
+
let newline_index = buffer.indexOf('\n');
|
|
312
|
+
while (newline_index >= 0) {
|
|
313
|
+
const line = buffer.slice(0, newline_index).trim();
|
|
314
|
+
buffer = buffer.slice(newline_index + 1);
|
|
315
|
+
if (line) {
|
|
316
|
+
queue = queue
|
|
317
|
+
.then(() => this.process_line(socket, line))
|
|
318
|
+
.catch(() => undefined);
|
|
319
|
+
}
|
|
320
|
+
newline_index = buffer.indexOf('\n');
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
socket.on('error', () => undefined);
|
|
324
|
+
}
|
|
325
|
+
async process_line(socket, line) {
|
|
326
|
+
let request = null;
|
|
327
|
+
let response;
|
|
328
|
+
try {
|
|
329
|
+
request = (0, ipc_1.parse_daemon_request)(JSON.parse(line));
|
|
330
|
+
response = await this.handle_request(request);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
response = {
|
|
334
|
+
id: request?.id ?? 'invalid',
|
|
335
|
+
success: false,
|
|
336
|
+
error: format_error(error),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (!socket.destroyed)
|
|
340
|
+
socket.write(JSON.stringify(response) + '\n');
|
|
341
|
+
if (request?.action == 'close' && response.success) {
|
|
342
|
+
socket.end();
|
|
343
|
+
void this.stop();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async execute_request(request) {
|
|
347
|
+
switch (request.action) {
|
|
348
|
+
case 'back':
|
|
349
|
+
return this.handle_history_navigation('back');
|
|
350
|
+
case 'check':
|
|
351
|
+
return (0, interaction_1.handle_check)(await this.ensure_connected(), request.params, true);
|
|
352
|
+
case 'click':
|
|
353
|
+
return (0, interaction_1.handle_click)(await this.ensure_connected(), request.params);
|
|
354
|
+
case 'close':
|
|
355
|
+
await this.close_browser();
|
|
356
|
+
return { closed: true };
|
|
357
|
+
case 'cookies':
|
|
358
|
+
return this.handle_cookies();
|
|
359
|
+
case 'fill':
|
|
360
|
+
return (0, interaction_1.handle_fill)(await this.ensure_connected(), request.params);
|
|
361
|
+
case 'forward':
|
|
362
|
+
return this.handle_history_navigation('forward');
|
|
363
|
+
case 'get_html':
|
|
364
|
+
return (0, interaction_1.handle_get_html)(await this.ensure_connected(), request.params);
|
|
365
|
+
case 'get_text':
|
|
366
|
+
return (0, interaction_1.handle_get_text)(await this.ensure_connected(), request.params);
|
|
367
|
+
case 'hover':
|
|
368
|
+
return (0, interaction_1.handle_hover)(await this.ensure_connected(), request.params);
|
|
369
|
+
case 'navigate':
|
|
370
|
+
return this.handle_navigate(request.params);
|
|
371
|
+
case 'network':
|
|
372
|
+
return this.handle_network();
|
|
373
|
+
case 'ping':
|
|
374
|
+
return { alive: true, connected: this.state.connected };
|
|
375
|
+
case 'reload':
|
|
376
|
+
return this.handle_reload();
|
|
377
|
+
case 'screenshot':
|
|
378
|
+
return this.handle_screenshot(request.params);
|
|
379
|
+
case 'scroll':
|
|
380
|
+
return (0, interaction_1.handle_scroll)(await this.ensure_connected(), request.params);
|
|
381
|
+
case 'select':
|
|
382
|
+
return (0, interaction_1.handle_select)(await this.ensure_connected(), request.params);
|
|
383
|
+
case 'snapshot':
|
|
384
|
+
return this.handle_snapshot(request.params);
|
|
385
|
+
case 'status':
|
|
386
|
+
return this.handle_status();
|
|
387
|
+
case 'type':
|
|
388
|
+
return (0, interaction_1.handle_type)(await this.ensure_connected(), request.params);
|
|
389
|
+
case 'uncheck':
|
|
390
|
+
return (0, interaction_1.handle_check)(await this.ensure_connected(), request.params, false);
|
|
391
|
+
default:
|
|
392
|
+
throw new Error(`Unknown daemon action "${request.action}".`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async handle_navigate(params) {
|
|
396
|
+
const url = params?.['url'];
|
|
397
|
+
if (typeof url != 'string' || !url.trim()) {
|
|
398
|
+
throw new Error('Navigate requires a non-empty "url" parameter.');
|
|
399
|
+
}
|
|
400
|
+
const new_endpoint = params?.['cdp_endpoint'];
|
|
401
|
+
if (new_endpoint !== undefined) {
|
|
402
|
+
if (typeof new_endpoint != 'string' || !new_endpoint.trim())
|
|
403
|
+
throw new Error('Navigate "cdp_endpoint" must be a non-empty string.');
|
|
404
|
+
if (new_endpoint.trim() !== this.state.cdp_endpoint)
|
|
405
|
+
await this.switch_cdp_endpoint(new_endpoint.trim());
|
|
406
|
+
}
|
|
407
|
+
const page = await this.ensure_connected();
|
|
408
|
+
this.state.dom_refs.clear();
|
|
409
|
+
this.state.requests.clear();
|
|
410
|
+
const response = await page.goto(url, { waitUntil: 'load' });
|
|
411
|
+
return this.create_navigation_result(page, response);
|
|
412
|
+
}
|
|
413
|
+
async switch_cdp_endpoint(new_endpoint) {
|
|
414
|
+
await this.close_browser();
|
|
415
|
+
this.state.cdp_endpoint = new_endpoint;
|
|
416
|
+
this.state.requests.clear();
|
|
417
|
+
}
|
|
418
|
+
async handle_history_navigation(direction) {
|
|
419
|
+
const page = await this.ensure_connected();
|
|
420
|
+
this.state.dom_refs.clear();
|
|
421
|
+
const response = direction == 'back'
|
|
422
|
+
? await page.goBack({ waitUntil: 'load' })
|
|
423
|
+
: await page.goForward({ waitUntil: 'load' });
|
|
424
|
+
return this.create_navigation_result(page, response);
|
|
425
|
+
}
|
|
426
|
+
async handle_reload() {
|
|
427
|
+
const page = await this.ensure_connected();
|
|
428
|
+
this.state.dom_refs.clear();
|
|
429
|
+
const response = await page.reload({ waitUntil: 'load' });
|
|
430
|
+
return this.create_navigation_result(page, response);
|
|
431
|
+
}
|
|
432
|
+
async handle_cookies() {
|
|
433
|
+
const page = await this.ensure_connected();
|
|
434
|
+
return {
|
|
435
|
+
cookies: await page.context().cookies(),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async create_navigation_result(page, response) {
|
|
439
|
+
return {
|
|
440
|
+
status: response?.status() ?? null,
|
|
441
|
+
title: await page.title(),
|
|
442
|
+
url: page.url(),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async handle_snapshot(params) {
|
|
446
|
+
const compact = params?.['compact'];
|
|
447
|
+
const depth = params?.['depth'];
|
|
448
|
+
const interactive = params?.['interactive'];
|
|
449
|
+
const selector = params?.['selector'];
|
|
450
|
+
if (compact !== undefined && typeof compact != 'boolean') {
|
|
451
|
+
throw new Error('Snapshot "compact" parameter must be a boolean when provided.');
|
|
452
|
+
}
|
|
453
|
+
if (interactive !== undefined && typeof interactive != 'boolean') {
|
|
454
|
+
throw new Error('Snapshot "interactive" parameter must be a boolean when provided.');
|
|
455
|
+
}
|
|
456
|
+
if (depth !== undefined
|
|
457
|
+
&& (typeof depth != 'number' || !Number.isInteger(depth) || depth < 0)) {
|
|
458
|
+
throw new Error('Snapshot "depth" parameter must be a non-negative integer when provided.');
|
|
459
|
+
}
|
|
460
|
+
if (selector !== undefined
|
|
461
|
+
&& (typeof selector != 'string' || !selector.trim())) {
|
|
462
|
+
throw new Error('Snapshot "selector" parameter must be a non-empty string when provided.');
|
|
463
|
+
}
|
|
464
|
+
const wrap = params?.['wrap'];
|
|
465
|
+
if (wrap !== undefined && typeof wrap != 'boolean') {
|
|
466
|
+
throw new Error('Snapshot "wrap" parameter must be a boolean when provided.');
|
|
467
|
+
}
|
|
468
|
+
const page = await this.ensure_connected();
|
|
469
|
+
const result = await (0, snapshot_1.capture_snapshot)(page, {
|
|
470
|
+
compact: compact === true,
|
|
471
|
+
depth: depth,
|
|
472
|
+
interactive: interactive === true,
|
|
473
|
+
selector: typeof selector == 'string' ? selector.trim() : undefined,
|
|
474
|
+
wrap: wrap === true,
|
|
475
|
+
});
|
|
476
|
+
this.state.dom_refs.clear();
|
|
477
|
+
for (const entry of result.refs)
|
|
478
|
+
this.state.dom_refs.set(entry.ref, entry.selector);
|
|
479
|
+
return {
|
|
480
|
+
compact: result.compact,
|
|
481
|
+
depth: result.depth,
|
|
482
|
+
interactive: result.interactive,
|
|
483
|
+
ref_count: result.refs.length,
|
|
484
|
+
selector: result.selector,
|
|
485
|
+
snapshot: result.snapshot,
|
|
486
|
+
title: result.title,
|
|
487
|
+
url: result.url,
|
|
488
|
+
wrap: result.wrap,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
async handle_screenshot(params) {
|
|
492
|
+
const base64 = params?.['base64'];
|
|
493
|
+
const full_page = params?.['full_page'];
|
|
494
|
+
const file_path = params?.['path'];
|
|
495
|
+
if (base64 !== undefined && typeof base64 != 'boolean') {
|
|
496
|
+
throw new Error('Screenshot "base64" parameter must be a boolean when provided.');
|
|
497
|
+
}
|
|
498
|
+
if (full_page !== undefined && typeof full_page != 'boolean') {
|
|
499
|
+
throw new Error('Screenshot "full_page" parameter must be a boolean when provided.');
|
|
500
|
+
}
|
|
501
|
+
if (file_path !== undefined
|
|
502
|
+
&& (typeof file_path != 'string' || !file_path.trim())) {
|
|
503
|
+
throw new Error('Screenshot "path" parameter must be a non-empty string when provided.');
|
|
504
|
+
}
|
|
505
|
+
const page = await this.ensure_connected();
|
|
506
|
+
return await (0, screenshot_1.take_screenshot)(page, {
|
|
507
|
+
base64: base64 === true,
|
|
508
|
+
full_page: full_page === true,
|
|
509
|
+
path: typeof file_path == 'string' ? file_path.trim() : undefined,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
handle_network() {
|
|
513
|
+
return {
|
|
514
|
+
requests: Array.from(this.state.requests.entries()).map(([id, tracked_request]) => ({ id, ...tracked_request })),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
async handle_status() {
|
|
518
|
+
const page = this.state.page && !this.state.page.isClosed()
|
|
519
|
+
? this.state.page
|
|
520
|
+
: null;
|
|
521
|
+
return {
|
|
522
|
+
connected: this.state.connected,
|
|
523
|
+
current_title: page ? await this.safe_page_title(page) : null,
|
|
524
|
+
current_url: page ? page.url() : null,
|
|
525
|
+
dom_refs: this.state.dom_refs.size,
|
|
526
|
+
idle_timeout_ms: this.idle_timeout_ms,
|
|
527
|
+
listener: this.transport.kind == 'unix'
|
|
528
|
+
? {
|
|
529
|
+
kind: 'unix',
|
|
530
|
+
socket_path: this.transport.socket_path,
|
|
531
|
+
}
|
|
532
|
+
: {
|
|
533
|
+
host: this.transport.host,
|
|
534
|
+
kind: 'tcp',
|
|
535
|
+
port: this.transport.port,
|
|
536
|
+
},
|
|
537
|
+
session_name: this.state.session_name,
|
|
538
|
+
tracked_requests: this.state.requests.size,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
async safe_page_title(page) {
|
|
542
|
+
try {
|
|
543
|
+
return await page.title();
|
|
544
|
+
}
|
|
545
|
+
catch (_error) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async ensure_connected() {
|
|
550
|
+
return (0, connection_1.ensure_connected)(this.state, {
|
|
551
|
+
connect_over_cdp: this.deps.connect_over_cdp,
|
|
552
|
+
}, {
|
|
553
|
+
on_context: context => this.track_context(context),
|
|
554
|
+
on_page: page => {
|
|
555
|
+
this.track_page(page);
|
|
556
|
+
this.state.page = page;
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
track_context(context) {
|
|
561
|
+
if (this.active_contexts.has(context))
|
|
562
|
+
return;
|
|
563
|
+
this.active_contexts.add(context);
|
|
564
|
+
context.on('page', page => {
|
|
565
|
+
this.track_page(page);
|
|
566
|
+
this.state.page = page;
|
|
567
|
+
this.state.dom_refs.clear();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
track_page(page) {
|
|
571
|
+
if (this.active_pages.has(page))
|
|
572
|
+
return;
|
|
573
|
+
this.active_pages.add(page);
|
|
574
|
+
page.on('close', () => {
|
|
575
|
+
if (this.state.page == page) {
|
|
576
|
+
this.state.page = null;
|
|
577
|
+
this.state.dom_refs.clear();
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
page.on('request', request => this.track_request(request));
|
|
581
|
+
page.on('response', response => this.track_response(response));
|
|
582
|
+
}
|
|
583
|
+
track_request(request) {
|
|
584
|
+
const request_id = `r${++this.request_counter}`;
|
|
585
|
+
this.request_ids.set(request, request_id);
|
|
586
|
+
this.state.requests.set(request_id, {
|
|
587
|
+
method: request.method(),
|
|
588
|
+
url: request.url(),
|
|
589
|
+
resource_type: request.resourceType(),
|
|
590
|
+
});
|
|
591
|
+
this.trim_tracked_requests();
|
|
592
|
+
}
|
|
593
|
+
track_response(response) {
|
|
594
|
+
const request = response.request();
|
|
595
|
+
const existing_id = this.request_ids.get(request)
|
|
596
|
+
?? `r${++this.request_counter}`;
|
|
597
|
+
if (!this.request_ids.has(request))
|
|
598
|
+
this.request_ids.set(request, existing_id);
|
|
599
|
+
const tracked_request = this.state.requests.get(existing_id) ?? {
|
|
600
|
+
method: request.method(),
|
|
601
|
+
url: request.url(),
|
|
602
|
+
};
|
|
603
|
+
tracked_request.status = response.status();
|
|
604
|
+
this.state.requests.set(existing_id, tracked_request);
|
|
605
|
+
this.trim_tracked_requests();
|
|
606
|
+
}
|
|
607
|
+
trim_tracked_requests() {
|
|
608
|
+
while (this.state.requests.size > MAX_TRACKED_REQUESTS) {
|
|
609
|
+
const oldest_id = this.state.requests.keys().next().value;
|
|
610
|
+
if (!oldest_id)
|
|
611
|
+
return;
|
|
612
|
+
this.state.requests.delete(oldest_id);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async close_browser() {
|
|
616
|
+
const browser = this.state.browser;
|
|
617
|
+
(0, connection_1.clear_connection_state)(this.state);
|
|
618
|
+
if (!browser)
|
|
619
|
+
return;
|
|
620
|
+
try {
|
|
621
|
+
await browser.close();
|
|
622
|
+
}
|
|
623
|
+
catch (_error) { }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
exports.BrowserDaemon = BrowserDaemon;
|
|
627
|
+
const create_daemon_from_env = (env = process.env) => {
|
|
628
|
+
const cdp_endpoint = env['BRIGHTDATA_CDP_ENDPOINT']?.trim();
|
|
629
|
+
if (!cdp_endpoint) {
|
|
630
|
+
throw new Error('BRIGHTDATA_CDP_ENDPOINT is required to start the browser daemon.');
|
|
631
|
+
}
|
|
632
|
+
return new BrowserDaemon({
|
|
633
|
+
cdp_endpoint,
|
|
634
|
+
idle_timeout_ms: parse_idle_timeout(env['BRIGHTDATA_IDLE_TIMEOUT_MS']),
|
|
635
|
+
session_name: env['BRIGHTDATA_SESSION'] ?? DEFAULT_SESSION_NAME,
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
exports.create_daemon_from_env = create_daemon_from_env;
|
|
639
|
+
const start_daemon_from_env = async (env = process.env) => {
|
|
640
|
+
const daemon = create_daemon_from_env(env);
|
|
641
|
+
await daemon.start();
|
|
642
|
+
return daemon;
|
|
643
|
+
};
|
|
644
|
+
exports.start_daemon_from_env = start_daemon_from_env;
|
|
645
|
+
//# sourceMappingURL=daemon.js.map
|