@ohos-ports/vibium 26.5.31-beta.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/dist/worker.js ADDED
@@ -0,0 +1,3639 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/sync/worker.ts
26
+ var import_worker_threads = require("worker_threads");
27
+
28
+ // src/clicker/platform.ts
29
+ var import_os = __toESM(require("os"));
30
+ function getPlatform() {
31
+ const platform = import_os.default.platform();
32
+ if (platform === "linux" || platform === "darwin" || platform === "win32") {
33
+ return platform;
34
+ }
35
+ if (platform === "openharmony") {
36
+ return "linux";
37
+ }
38
+ throw new Error(`Unsupported platform: ${platform}`);
39
+ }
40
+ function getArch() {
41
+ const arch = import_os.default.arch();
42
+ if (arch === "x64" || arch === "arm64") {
43
+ return arch;
44
+ }
45
+ throw new Error(`Unsupported architecture: ${arch}`);
46
+ }
47
+
48
+ // src/clicker/binary.ts
49
+ var import_fs = __toESM(require("fs"));
50
+ var import_path = __toESM(require("path"));
51
+ var import_module = require("module");
52
+ var moduleRequire = (0, import_module.createRequire)(__filename);
53
+ function getVibiumBinPath() {
54
+ const envPath = process.env.VIBIUM_BIN_PATH;
55
+ if (envPath && import_fs.default.existsSync(envPath)) {
56
+ return envPath;
57
+ }
58
+ const platform = getPlatform();
59
+ const arch = getArch();
60
+ const packageName = `@ohos-ports/vibium-${platform}-${arch}`;
61
+ const binaryName = platform === "win32" ? "vibium.exe" : "vibium";
62
+ try {
63
+ const packagePath = moduleRequire.resolve(`${packageName}/package.json`);
64
+ const packageDir = import_path.default.dirname(packagePath);
65
+ const binaryPath = import_path.default.join(packageDir, "bin", binaryName);
66
+ if (import_fs.default.existsSync(binaryPath)) {
67
+ return binaryPath;
68
+ }
69
+ } catch(e) {
70
+ }
71
+ const localPaths = [
72
+ // From vibium/ root
73
+ import_path.default.resolve(process.cwd(), "clicker", "bin", binaryName),
74
+ // From clients/javascript/
75
+ import_path.default.resolve(process.cwd(), "..", "..", "clicker", "bin", binaryName)
76
+ ];
77
+ for (const localPath of localPaths) {
78
+ if (import_fs.default.existsSync(localPath)) {
79
+ return localPath;
80
+ }
81
+ }
82
+ throw new Error(
83
+ `Could not find vibium binary. Set VIBIUM_BIN_PATH environment variable or install ${packageName}. On HarmonyOS, the vibium binary is a static Go binary that runs after signing with binary-sign-tool, but browser automation also requires Chrome/Chromium which is not available on HarmonyOS.`
84
+ );
85
+ }
86
+
87
+ // src/clicker/process.ts
88
+ var import_child_process = require("child_process");
89
+
90
+ // src/utils/errors.ts
91
+ var TimeoutError = class extends Error {
92
+ constructor(selector, timeout, reason) {
93
+ const msg = reason ? `Timeout after ${timeout}ms waiting for '${selector}': ${reason}` : `Timeout after ${timeout}ms waiting for '${selector}'`;
94
+ super(msg);
95
+ this.selector = selector;
96
+ this.timeout = timeout;
97
+ this.reason = reason;
98
+ this.name = "TimeoutError";
99
+ }
100
+ };
101
+ var BrowserCrashedError = class extends Error {
102
+ constructor(exitCode, output) {
103
+ const msg = output ? `Browser crashed with exit code ${exitCode}: ${output}` : `Browser crashed with exit code ${exitCode}`;
104
+ super(msg);
105
+ this.exitCode = exitCode;
106
+ this.output = output;
107
+ this.name = "BrowserCrashedError";
108
+ }
109
+ };
110
+
111
+ // src/clicker/process.ts
112
+ var VibiumProcess = class _VibiumProcess {
113
+ constructor(process2, preReadyLines) {
114
+ this._stopped = false;
115
+ this._preReadyLines = [];
116
+ this._cleanupListeners = null;
117
+ this._process = process2;
118
+ this._preReadyLines = preReadyLines;
119
+ }
120
+ /** The child process stdin stream (for sending commands). */
121
+ get stdin() {
122
+ return this._process.stdin;
123
+ }
124
+ /** The child process stdout stream (for receiving responses/events). */
125
+ get stdout() {
126
+ return this._process.stdout;
127
+ }
128
+ /** Lines received before the vibium:lifecycle.ready signal (buffered events). */
129
+ get preReadyLines() {
130
+ return this._preReadyLines;
131
+ }
132
+ static async start(options = {}) {
133
+ const binaryPath = options.executablePath || getVibiumBinPath();
134
+ const args = ["pipe"];
135
+ if (options.headless === true) {
136
+ args.push("--headless=old");
137
+ }
138
+ if (options.connectURL) {
139
+ args.push("--connect", options.connectURL);
140
+ }
141
+ if (options.connectHeaders) {
142
+ for (const [key, value] of Object.entries(options.connectHeaders)) {
143
+ args.push("--connect-header", `${key}: ${value}`);
144
+ }
145
+ }
146
+ const proc = (0, import_child_process.spawn)(binaryPath, args, {
147
+ stdio: ["pipe", "pipe", "pipe"]
148
+ });
149
+ const killSpawnedChild = () => {
150
+ try {
151
+ if (proc.exitCode === null) proc.kill("SIGKILL");
152
+ } catch(e) {
153
+ }
154
+ };
155
+ const preReadyLines = [];
156
+ let readyOK = false;
157
+ try {
158
+ await new Promise((resolve, reject) => {
159
+ let buffer = "";
160
+ let stderrBuffer = "";
161
+ let resolved = false;
162
+ const timeout = setTimeout(() => {
163
+ if (!resolved) {
164
+ resolved = true;
165
+ reject(new TimeoutError("vibium", 6e4, "waiting for vibium ready signal"));
166
+ }
167
+ }, 6e4);
168
+ const handleData = (data) => {
169
+ buffer += data.toString();
170
+ let newlineIdx;
171
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
172
+ const line = buffer.slice(0, newlineIdx).trim();
173
+ buffer = buffer.slice(newlineIdx + 1);
174
+ if (!line) continue;
175
+ try {
176
+ const msg = JSON.parse(line);
177
+ if (msg.method === "vibium:lifecycle.ready") {
178
+ if (!resolved) {
179
+ resolved = true;
180
+ clearTimeout(timeout);
181
+ proc.stdout?.removeListener("data", handleData);
182
+ resolve();
183
+ }
184
+ return;
185
+ }
186
+ } catch(e) {
187
+ }
188
+ preReadyLines.push(line);
189
+ }
190
+ };
191
+ proc.stdout?.on("data", handleData);
192
+ proc.stderr?.on("data", (data) => {
193
+ stderrBuffer += data.toString();
194
+ });
195
+ proc.on("error", (err) => {
196
+ if (!resolved) {
197
+ resolved = true;
198
+ clearTimeout(timeout);
199
+ reject(err);
200
+ }
201
+ });
202
+ proc.on("exit", (code) => {
203
+ if (!resolved) {
204
+ resolved = true;
205
+ clearTimeout(timeout);
206
+ let combinedOutput = (buffer + (stderrBuffer ? "\n" + stderrBuffer : "")).trim();
207
+ if (process.platform === "openharmony" && /chromedriver|chrome|browser/i.test(combinedOutput)) {
208
+ combinedOutput += "\n\n[HarmonyOS] Local browser launch failed. Chromium is available via Alpine musl + LD_PRELOAD. Ensure Alpine Chromium and shared libraries are installed.\n export VIBIUM_CONNECT_URL=ws://<remote-host>:9222/session\n browser.start() will auto-detect VIBIUM_CONNECT_URL";
209
+ }
210
+ reject(new BrowserCrashedError(code ?? 1, combinedOutput));
211
+ }
212
+ });
213
+ });
214
+ readyOK = true;
215
+ } finally {
216
+ if (!readyOK) killSpawnedChild();
217
+ }
218
+ const vp = new _VibiumProcess(proc, preReadyLines);
219
+ const cleanup = () => vp.stop();
220
+ process.on("exit", cleanup);
221
+ process.on("SIGINT", cleanup);
222
+ process.on("SIGTERM", cleanup);
223
+ vp._cleanupListeners = cleanup;
224
+ return vp;
225
+ }
226
+ async stop() {
227
+ if (this._stopped) return;
228
+ this._stopped = true;
229
+ if (this._cleanupListeners) {
230
+ process.removeListener("exit", this._cleanupListeners);
231
+ process.removeListener("SIGINT", this._cleanupListeners);
232
+ process.removeListener("SIGTERM", this._cleanupListeners);
233
+ this._cleanupListeners = null;
234
+ }
235
+ return new Promise((resolve) => {
236
+ let resolved = false;
237
+ const done = () => {
238
+ if (!resolved) {
239
+ resolved = true;
240
+ resolve();
241
+ }
242
+ };
243
+ this._process.on("exit", done);
244
+ try {
245
+ this._process.stdin?.end();
246
+ } catch(e) {
247
+ }
248
+ if (process.platform === "win32") {
249
+ try {
250
+ (0, import_child_process.execFileSync)("taskkill", ["/T", "/F", "/PID", this._process.pid.toString()], { stdio: "ignore" });
251
+ } catch(e) {
252
+ }
253
+ done();
254
+ } else {
255
+ setTimeout(() => {
256
+ if (!resolved) {
257
+ try {
258
+ this._process.kill("SIGTERM");
259
+ } catch(e) {
260
+ }
261
+ }
262
+ }, 1e3);
263
+ setTimeout(() => {
264
+ if (!resolved) {
265
+ try {
266
+ this._process.kill("SIGKILL");
267
+ } catch(e) {
268
+ }
269
+ }
270
+ }, 4e3);
271
+ setTimeout(done, 5e3);
272
+ }
273
+ });
274
+ }
275
+ };
276
+
277
+ // src/bidi/types.ts
278
+ function isResponse(msg) {
279
+ return "id" in msg;
280
+ }
281
+ function isEvent(msg) {
282
+ return !("id" in msg) && "method" in msg;
283
+ }
284
+
285
+ // src/bidi/client.ts
286
+ var import_readline = require("readline");
287
+ var DEFAULT_COMMAND_TIMEOUT = 6e4;
288
+ var BiDiClient = class _BiDiClient {
289
+ constructor(stdin, stdout) {
290
+ this.nextId = 1;
291
+ this.pendingCommands = /* @__PURE__ */ new Map();
292
+ this.eventHandlers = [];
293
+ this._closed = false;
294
+ this.stdin = stdin;
295
+ this.rl = (0, import_readline.createInterface)({ input: stdout, crlfDelay: Infinity });
296
+ this.rl.on("line", (line) => {
297
+ const trimmed = line.trim();
298
+ if (!trimmed) return;
299
+ try {
300
+ const msg = JSON.parse(trimmed);
301
+ if (isResponse(msg)) {
302
+ this.handleResponse(msg);
303
+ } else if (isEvent(msg)) {
304
+ this.handleEvent(msg);
305
+ }
306
+ } catch (err) {
307
+ }
308
+ });
309
+ this.rl.on("close", () => {
310
+ this._closed = true;
311
+ for (const [id, pending] of this.pendingCommands) {
312
+ clearTimeout(pending.timer);
313
+ pending.reject(new Error("Connection closed unexpectedly"));
314
+ this.pendingCommands.delete(id);
315
+ }
316
+ });
317
+ }
318
+ /**
319
+ * Create a BiDiClient from stdin/stdout streams.
320
+ * Optionally replay pre-ready lines (events received before the ready signal).
321
+ */
322
+ static fromStreams(stdin, stdout, preReadyLines = []) {
323
+ const client = new _BiDiClient(stdin, stdout);
324
+ for (const line of preReadyLines) {
325
+ try {
326
+ const msg = JSON.parse(line);
327
+ if (isEvent(msg)) {
328
+ client.handleEvent(msg);
329
+ }
330
+ } catch(e) {
331
+ }
332
+ }
333
+ return client;
334
+ }
335
+ handleResponse(response) {
336
+ const pending = this.pendingCommands.get(response.id);
337
+ if (!pending) {
338
+ return;
339
+ }
340
+ clearTimeout(pending.timer);
341
+ this.pendingCommands.delete(response.id);
342
+ if (response.type === "error" && response.error) {
343
+ pending.reject(new Error(`${response.error}: ${response.message}`));
344
+ } else {
345
+ pending.resolve(response.result);
346
+ }
347
+ }
348
+ handleEvent(event) {
349
+ for (const handler of this.eventHandlers) {
350
+ handler(event);
351
+ }
352
+ }
353
+ onEvent(handler) {
354
+ this.eventHandlers.push(handler);
355
+ }
356
+ offEvent(handler) {
357
+ this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
358
+ }
359
+ send(method, params = {}, timeout = DEFAULT_COMMAND_TIMEOUT) {
360
+ return new Promise((resolve, reject) => {
361
+ const id = this.nextId++;
362
+ const command = { id, method, params };
363
+ const timer = setTimeout(() => {
364
+ this.pendingCommands.delete(id);
365
+ reject(new Error(`Command '${method}' timed out after ${timeout}ms`));
366
+ }, timeout);
367
+ this.pendingCommands.set(id, {
368
+ resolve,
369
+ reject,
370
+ timer
371
+ });
372
+ try {
373
+ if (this._closed) {
374
+ throw new Error("Connection closed");
375
+ }
376
+ this.stdin.write(JSON.stringify(command) + "\n");
377
+ } catch (err) {
378
+ clearTimeout(timer);
379
+ this.pendingCommands.delete(id);
380
+ reject(err);
381
+ }
382
+ });
383
+ }
384
+ async close() {
385
+ if (this._closed) {
386
+ return;
387
+ }
388
+ this._closed = true;
389
+ for (const [id, pending] of this.pendingCommands) {
390
+ clearTimeout(pending.timer);
391
+ pending.reject(new Error("Connection closed"));
392
+ this.pendingCommands.delete(id);
393
+ }
394
+ this.rl.close();
395
+ try {
396
+ this.stdin.end();
397
+ } catch(e) {
398
+ }
399
+ }
400
+ };
401
+
402
+ // src/element.ts
403
+ var customInspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
404
+ var Element = class _Element {
405
+ constructor(client, context, selector, info2, index, params) {
406
+ this.client = client;
407
+ this.context = context;
408
+ this.selector = selector;
409
+ this.info = info2;
410
+ this._index = index;
411
+ this._params = params || {};
412
+ }
413
+ /** Build the common params sent to vibium: commands for element resolution. */
414
+ commandParams(extra) {
415
+ return {
416
+ ...this._params,
417
+ context: this.context,
418
+ selector: this.selector,
419
+ index: this._index,
420
+ ...extra
421
+ };
422
+ }
423
+ /** Return params that can identify this element for use as a target (e.g. dragTo). */
424
+ toParams() {
425
+ return {
426
+ ...this._params,
427
+ selector: this.selector,
428
+ index: this._index
429
+ };
430
+ }
431
+ /**
432
+ * Click the element.
433
+ * Waits for element to be visible, stable, receive events, and enabled.
434
+ */
435
+ async click(options) {
436
+ await this.client.send("vibium:element.click", this.commandParams({
437
+ timeout: options?.timeout
438
+ }));
439
+ }
440
+ /** Double-click the element. */
441
+ async dblclick(options) {
442
+ await this.client.send("vibium:element.dblclick", this.commandParams({
443
+ timeout: options?.timeout
444
+ }));
445
+ }
446
+ /**
447
+ * Fill the element with text (clears existing content first).
448
+ * For inputs and textareas.
449
+ */
450
+ async fill(value, options) {
451
+ await this.client.send("vibium:element.fill", this.commandParams({
452
+ value,
453
+ timeout: options?.timeout
454
+ }));
455
+ }
456
+ /**
457
+ * Type text into the element (appends to existing content).
458
+ * Waits for element to be visible, stable, receive events, enabled, and editable.
459
+ */
460
+ async type(text, options) {
461
+ await this.client.send("vibium:element.type", this.commandParams({
462
+ text,
463
+ timeout: options?.timeout
464
+ }));
465
+ }
466
+ /**
467
+ * Press a key while the element is focused.
468
+ * Supports key names ("Enter", "Tab") and combos ("Control+a").
469
+ */
470
+ async press(key, options) {
471
+ await this.client.send("vibium:element.press", this.commandParams({
472
+ key,
473
+ timeout: options?.timeout
474
+ }));
475
+ }
476
+ /** Clear the element's content (select all + delete). */
477
+ async clear(options) {
478
+ await this.client.send("vibium:element.clear", this.commandParams({
479
+ timeout: options?.timeout
480
+ }));
481
+ }
482
+ /** Check a checkbox (no-op if already checked). */
483
+ async check(options) {
484
+ await this.client.send("vibium:element.check", this.commandParams({
485
+ timeout: options?.timeout
486
+ }));
487
+ }
488
+ /** Uncheck a checkbox (no-op if already unchecked). */
489
+ async uncheck(options) {
490
+ await this.client.send("vibium:element.uncheck", this.commandParams({
491
+ timeout: options?.timeout
492
+ }));
493
+ }
494
+ /** Select an option in a <select> element by value. */
495
+ async selectOption(value, options) {
496
+ await this.client.send("vibium:element.selectOption", this.commandParams({
497
+ value,
498
+ timeout: options?.timeout
499
+ }));
500
+ }
501
+ /** Hover over the element (move mouse to center, no click). */
502
+ async hover(options) {
503
+ await this.client.send("vibium:element.hover", this.commandParams({
504
+ timeout: options?.timeout
505
+ }));
506
+ }
507
+ /** Focus the element. */
508
+ async focus(options) {
509
+ await this.client.send("vibium:element.focus", this.commandParams({
510
+ timeout: options?.timeout
511
+ }));
512
+ }
513
+ /** Drag this element to a target element. */
514
+ async dragTo(target, options) {
515
+ await this.client.send("vibium:element.dragTo", this.commandParams({
516
+ target: target.toParams(),
517
+ timeout: options?.timeout
518
+ }));
519
+ }
520
+ /** Tap the element (touch action). */
521
+ async tap(options) {
522
+ await this.client.send("vibium:element.tap", this.commandParams({
523
+ timeout: options?.timeout
524
+ }));
525
+ }
526
+ /** Scroll the element into view. */
527
+ async scrollIntoView(options) {
528
+ await this.client.send("vibium:element.scrollIntoView", this.commandParams({
529
+ timeout: options?.timeout
530
+ }));
531
+ }
532
+ /** Dispatch a DOM event on the element. */
533
+ async dispatchEvent(eventType, eventInit, options) {
534
+ await this.client.send("vibium:element.dispatchEvent", this.commandParams({
535
+ eventType,
536
+ eventInit,
537
+ timeout: options?.timeout
538
+ }));
539
+ }
540
+ /** Set files on an <input type="file"> element. */
541
+ async setFiles(files, options) {
542
+ await this.client.send("vibium:element.setFiles", this.commandParams({
543
+ files,
544
+ timeout: options?.timeout
545
+ }));
546
+ }
547
+ // --- Element State ---
548
+ /** Get the element's textContent (trimmed). */
549
+ async text() {
550
+ const result = await this.client.send("vibium:element.text", this.commandParams());
551
+ return result.text;
552
+ }
553
+ /** Get the element's innerText (rendered text only). */
554
+ async innerText() {
555
+ const result = await this.client.send("vibium:element.innerText", this.commandParams());
556
+ return result.text;
557
+ }
558
+ /** Get the element's innerHTML. */
559
+ async html() {
560
+ const result = await this.client.send("vibium:element.html", this.commandParams());
561
+ return result.html;
562
+ }
563
+ /** Get the element's value (for inputs, textareas, selects). */
564
+ async value() {
565
+ const result = await this.client.send("vibium:element.value", this.commandParams());
566
+ return result.value;
567
+ }
568
+ /** Get an attribute value. Short name for getAttribute. */
569
+ async attr(name) {
570
+ const result = await this.client.send("vibium:element.attr", this.commandParams({ name }));
571
+ return result.value;
572
+ }
573
+ /** Get an attribute value. Alias for attr(). */
574
+ async getAttribute(name) {
575
+ return this.attr(name);
576
+ }
577
+ /** Get the element's bounding box. Short name for boundingBox. */
578
+ async bounds() {
579
+ const result = await this.client.send("vibium:element.bounds", this.commandParams());
580
+ return result;
581
+ }
582
+ /** Get the element's bounding box. Alias for bounds(). */
583
+ async boundingBox() {
584
+ return this.bounds();
585
+ }
586
+ /** Check if the element is visible (has dimensions, not display:none/visibility:hidden/opacity:0). */
587
+ async isVisible() {
588
+ const result = await this.client.send("vibium:element.isVisible", this.commandParams());
589
+ return result.visible;
590
+ }
591
+ /** Check if the element is hidden (inverse of isVisible). */
592
+ async isHidden() {
593
+ const result = await this.client.send("vibium:element.isHidden", this.commandParams());
594
+ return result.hidden;
595
+ }
596
+ /** Check if the element is enabled (not disabled). */
597
+ async isEnabled() {
598
+ const result = await this.client.send("vibium:element.isEnabled", this.commandParams());
599
+ return result.enabled;
600
+ }
601
+ /** Check if the element is checked (for checkboxes/radios). */
602
+ async isChecked() {
603
+ const result = await this.client.send("vibium:element.isChecked", this.commandParams());
604
+ return result.checked;
605
+ }
606
+ /** Check if the element is editable (not disabled and not readonly). */
607
+ async isEditable() {
608
+ const result = await this.client.send("vibium:element.isEditable", this.commandParams());
609
+ return result.editable;
610
+ }
611
+ /** Get the element's computed ARIA role. */
612
+ async role() {
613
+ const result = await this.client.send("vibium:element.role", this.commandParams());
614
+ return result.role;
615
+ }
616
+ /** Get the element's accessible name (label). */
617
+ async label() {
618
+ const result = await this.client.send("vibium:element.label", this.commandParams());
619
+ return result.label;
620
+ }
621
+ /** Take a screenshot of just this element. Returns a PNG buffer. */
622
+ async screenshot() {
623
+ const result = await this.client.send("vibium:element.screenshot", this.commandParams());
624
+ return Buffer.from(result.data, "base64");
625
+ }
626
+ /** Wait until the element reaches a state: "visible", "hidden", "attached", or "detached". */
627
+ async waitUntil(state, options) {
628
+ await this.client.send("vibium:element.waitFor", this.commandParams({
629
+ state,
630
+ timeout: options?.timeout
631
+ }));
632
+ }
633
+ /** Find a child element by CSS selector or semantic options. Scoped to this element. */
634
+ find(selector, options) {
635
+ const promise = (async () => {
636
+ const params = {
637
+ context: this.context,
638
+ scope: this.selector,
639
+ timeout: options?.timeout
640
+ };
641
+ if (typeof selector === "string") {
642
+ params.selector = selector;
643
+ } else {
644
+ Object.assign(params, selector);
645
+ if (selector.timeout) params.timeout = selector.timeout;
646
+ }
647
+ const result = await this.client.send("vibium:element.find", params);
648
+ const info2 = { tag: result.tag, text: result.text, box: result.box };
649
+ const childSelector = typeof selector === "string" ? selector : "";
650
+ const childParams = typeof selector === "string" ? { selector } : { ...selector };
651
+ return new _Element(this.client, this.context, childSelector, info2, void 0, childParams);
652
+ })();
653
+ return fluent(promise);
654
+ }
655
+ /** Find all child elements by CSS selector or semantic options. Scoped to this element. */
656
+ async findAll(selector, options) {
657
+ const params = {
658
+ context: this.context,
659
+ scope: this.selector,
660
+ timeout: options?.timeout
661
+ };
662
+ if (typeof selector === "string") {
663
+ params.selector = selector;
664
+ } else {
665
+ Object.assign(params, selector);
666
+ if (selector.timeout) params.timeout = selector.timeout;
667
+ }
668
+ const result = await this.client.send("vibium:element.findAll", params);
669
+ const selectorStr = typeof selector === "string" ? selector : "";
670
+ const selectorParams = typeof selector === "string" ? { selector } : { ...selector };
671
+ return result.elements.map((el) => {
672
+ const info2 = { tag: el.tag, text: el.text, box: el.box };
673
+ return new _Element(this.client, this.context, selectorStr, info2, el.index, selectorParams);
674
+ });
675
+ }
676
+ [customInspect]() {
677
+ const text = this.info.text.length > 50 ? this.info.text.slice(0, 50) + "..." : this.info.text;
678
+ return `Element { tag: '${this.info.tag}', text: '${text}' }`;
679
+ }
680
+ getCenter() {
681
+ return {
682
+ x: this.info.box.x + this.info.box.width / 2,
683
+ y: this.info.box.y + this.info.box.height / 2
684
+ };
685
+ }
686
+ };
687
+ function fluent(promise) {
688
+ const p = promise;
689
+ p.click = (opts) => promise.then((el) => el.click(opts));
690
+ p.dblclick = (opts) => promise.then((el) => el.dblclick(opts));
691
+ p.fill = (value, opts) => promise.then((el) => el.fill(value, opts));
692
+ p.type = (text, opts) => promise.then((el) => el.type(text, opts));
693
+ p.press = (key, opts) => promise.then((el) => el.press(key, opts));
694
+ p.clear = (opts) => promise.then((el) => el.clear(opts));
695
+ p.check = (opts) => promise.then((el) => el.check(opts));
696
+ p.uncheck = (opts) => promise.then((el) => el.uncheck(opts));
697
+ p.selectOption = (value, opts) => promise.then((el) => el.selectOption(value, opts));
698
+ p.hover = (opts) => promise.then((el) => el.hover(opts));
699
+ p.focus = (opts) => promise.then((el) => el.focus(opts));
700
+ p.dragTo = (target, opts) => promise.then((el) => el.dragTo(target, opts));
701
+ p.tap = (opts) => promise.then((el) => el.tap(opts));
702
+ p.scrollIntoView = (opts) => promise.then((el) => el.scrollIntoView(opts));
703
+ p.dispatchEvent = (type, init, opts) => promise.then((el) => el.dispatchEvent(type, init, opts));
704
+ p.setFiles = (files, opts) => promise.then((el) => el.setFiles(files, opts));
705
+ p.text = () => promise.then((el) => el.text());
706
+ p.innerText = () => promise.then((el) => el.innerText());
707
+ p.html = () => promise.then((el) => el.html());
708
+ p.value = () => promise.then((el) => el.value());
709
+ p.attr = (name) => promise.then((el) => el.attr(name));
710
+ p.getAttribute = (name) => promise.then((el) => el.getAttribute(name));
711
+ p.bounds = () => promise.then((el) => el.bounds());
712
+ p.boundingBox = () => promise.then((el) => el.boundingBox());
713
+ p.isVisible = () => promise.then((el) => el.isVisible());
714
+ p.isHidden = () => promise.then((el) => el.isHidden());
715
+ p.isEnabled = () => promise.then((el) => el.isEnabled());
716
+ p.isChecked = () => promise.then((el) => el.isChecked());
717
+ p.isEditable = () => promise.then((el) => el.isEditable());
718
+ p.role = () => promise.then((el) => el.role());
719
+ p.label = () => promise.then((el) => el.label());
720
+ p.screenshot = () => promise.then((el) => el.screenshot());
721
+ p.waitUntil = (state, opts) => promise.then((el) => el.waitUntil(state, opts));
722
+ p.find = (sel, opts) => fluent(promise.then((el) => el.find(sel, opts)));
723
+ p.findAll = (sel, opts) => promise.then((el) => el.findAll(sel, opts));
724
+ return p;
725
+ }
726
+
727
+ // src/recording.ts
728
+ var Recording = class {
729
+ constructor(client, userContextId) {
730
+ this.client = client;
731
+ this.userContextId = userContextId;
732
+ }
733
+ /** Start recording. */
734
+ async start(options = {}) {
735
+ await this.client.send("vibium:recording.start", {
736
+ userContext: this.userContextId,
737
+ ...options
738
+ });
739
+ }
740
+ /** Stop recording and return the recording zip as a Buffer. */
741
+ async stop(options = {}) {
742
+ const result = await this.client.send("vibium:recording.stop", {
743
+ userContext: this.userContextId,
744
+ ...options
745
+ });
746
+ if (options.path) {
747
+ const fs2 = await import("fs");
748
+ return fs2.readFileSync(result.path);
749
+ }
750
+ return Buffer.from(result.data, "base64");
751
+ }
752
+ /** Start a new recording chunk (resets event buffer, keeps resources). */
753
+ async startChunk(options = {}) {
754
+ await this.client.send("vibium:recording.startChunk", {
755
+ userContext: this.userContextId,
756
+ ...options
757
+ });
758
+ }
759
+ /** Stop the current chunk and return the recording zip as a Buffer. */
760
+ async stopChunk(options = {}) {
761
+ const result = await this.client.send("vibium:recording.stopChunk", {
762
+ userContext: this.userContextId,
763
+ ...options
764
+ });
765
+ if (options.path) {
766
+ const fs2 = await import("fs");
767
+ return fs2.readFileSync(result.path);
768
+ }
769
+ return Buffer.from(result.data, "base64");
770
+ }
771
+ /** Start a named group of actions in the recording. */
772
+ async startGroup(name, options = {}) {
773
+ await this.client.send("vibium:recording.startGroup", {
774
+ userContext: this.userContextId,
775
+ name,
776
+ ...options
777
+ });
778
+ }
779
+ /** End the current group. */
780
+ async stopGroup() {
781
+ await this.client.send("vibium:recording.stopGroup", {
782
+ userContext: this.userContextId
783
+ });
784
+ }
785
+ };
786
+
787
+ // src/context.ts
788
+ var BrowserContext = class {
789
+ constructor(client, userContextId) {
790
+ this.client = client;
791
+ this.userContextId = userContextId;
792
+ this.recording = new Recording(client, userContextId);
793
+ }
794
+ /** The user context ID for this browser context. */
795
+ get id() {
796
+ return this.userContextId;
797
+ }
798
+ /** Create a new page (tab) in this context. */
799
+ async newPage() {
800
+ const result = await this.client.send("vibium:context.newPage", {
801
+ userContext: this.userContextId
802
+ });
803
+ return new Page(this.client, result.context, this.userContextId);
804
+ }
805
+ /** Close this context and all its pages. */
806
+ async close() {
807
+ await this.client.send("browser.removeUserContext", {
808
+ userContext: this.userContextId
809
+ });
810
+ }
811
+ /** Get cookies for this context. Optionally filter by URLs. */
812
+ async cookies(urls) {
813
+ const params = { userContext: this.userContextId };
814
+ if (urls && urls.length > 0) {
815
+ params.urls = urls;
816
+ }
817
+ const result = await this.client.send("vibium:context.cookies", params);
818
+ return result.cookies;
819
+ }
820
+ /** Set cookies in this context. */
821
+ async setCookies(cookies) {
822
+ await this.client.send("vibium:context.setCookies", {
823
+ userContext: this.userContextId,
824
+ cookies
825
+ });
826
+ }
827
+ /** Clear all cookies in this context. */
828
+ async clearCookies() {
829
+ await this.client.send("vibium:context.clearCookies", {
830
+ userContext: this.userContextId
831
+ });
832
+ }
833
+ /** Get the storage state (cookies + localStorage + sessionStorage). */
834
+ async storage() {
835
+ const result = await this.client.send("vibium:context.storage", {
836
+ userContext: this.userContextId
837
+ });
838
+ return result;
839
+ }
840
+ /** Set the storage state (cookies + localStorage + sessionStorage). */
841
+ async setStorage(state) {
842
+ await this.client.send("vibium:context.setStorage", {
843
+ userContext: this.userContextId,
844
+ state
845
+ });
846
+ }
847
+ /** Clear all storage (cookies + localStorage + sessionStorage). */
848
+ async clearStorage() {
849
+ await this.client.send("vibium:context.clearStorage", {
850
+ userContext: this.userContextId
851
+ });
852
+ }
853
+ /** Add an init script that runs before page scripts in this context. */
854
+ async addInitScript(script) {
855
+ const result = await this.client.send("vibium:context.addInitScript", {
856
+ userContext: this.userContextId,
857
+ script
858
+ });
859
+ return result.script;
860
+ }
861
+ };
862
+
863
+ // src/route.ts
864
+ function isRaceConditionError(e) {
865
+ if (!(e instanceof Error)) return false;
866
+ const msg = e.message;
867
+ return msg === "Connection closed" || msg.includes("Invalid state") || msg.includes("No blocked request") || msg.includes("no such request");
868
+ }
869
+ var Route = class {
870
+ constructor(client, requestId, request) {
871
+ this.client = client;
872
+ this.requestId = requestId;
873
+ this.request = request;
874
+ }
875
+ /** Fulfill the request with a custom response (mock response). */
876
+ async fulfill(response = {}) {
877
+ try {
878
+ const params = { request: this.requestId };
879
+ if (response.status !== void 0) params.statusCode = response.status;
880
+ if (response.headers) params.headers = response.headers;
881
+ if (response.contentType) params.contentType = response.contentType;
882
+ if (response.body !== void 0) params.body = response.body;
883
+ await this.client.send("vibium:network.fulfill", params);
884
+ } catch (e) {
885
+ if (isRaceConditionError(e)) return;
886
+ throw e;
887
+ }
888
+ }
889
+ /** Continue the request with optional overrides. */
890
+ async continue(overrides) {
891
+ try {
892
+ const params = { request: this.requestId };
893
+ if (overrides?.url) params.url = overrides.url;
894
+ if (overrides?.method) params.method = overrides.method;
895
+ if (overrides?.headers) params.headers = overrides.headers;
896
+ if (overrides?.postData) params.postData = overrides.postData;
897
+ await this.client.send("vibium:network.continue", params);
898
+ } catch (e) {
899
+ if (isRaceConditionError(e)) return;
900
+ throw e;
901
+ }
902
+ }
903
+ /** Abort the request. */
904
+ async abort() {
905
+ try {
906
+ await this.client.send("vibium:network.abort", {
907
+ request: this.requestId
908
+ });
909
+ } catch (e) {
910
+ if (isRaceConditionError(e)) return;
911
+ throw e;
912
+ }
913
+ }
914
+ };
915
+
916
+ // src/network.ts
917
+ var Request = class {
918
+ constructor(data, client) {
919
+ this.data = data;
920
+ this.client = client ?? null;
921
+ }
922
+ /** The request URL. */
923
+ url() {
924
+ const req = this.data.request;
925
+ return req?.url ?? "";
926
+ }
927
+ /** The HTTP method (GET, POST, etc.). */
928
+ method() {
929
+ const req = this.data.request;
930
+ return req?.method ?? "";
931
+ }
932
+ /** Request headers as a simple key-value object. */
933
+ headers() {
934
+ const req = this.data.request;
935
+ const entries = req?.headers ?? [];
936
+ return convertHeaders(entries);
937
+ }
938
+ /** The request ID (BiDi network request identifier). */
939
+ requestId() {
940
+ const req = this.data.request;
941
+ return req?.request ?? "";
942
+ }
943
+ /** Request body / post data. Uses BiDi network.getData when a data collector is active. */
944
+ async postData() {
945
+ if (!this.client) return null;
946
+ const reqId = this.requestId();
947
+ if (!reqId) return null;
948
+ try {
949
+ const result = await this.client.send(
950
+ "network.getData",
951
+ { dataType: "request", request: reqId }
952
+ );
953
+ return result?.bytes?.value ?? null;
954
+ } catch(e) {
955
+ return null;
956
+ }
957
+ }
958
+ };
959
+ var Response = class {
960
+ constructor(data, client) {
961
+ this.data = data;
962
+ this.client = client ?? null;
963
+ }
964
+ /** The response URL. */
965
+ url() {
966
+ const resp = this.data.response;
967
+ return resp?.url ?? this.data.url ?? "";
968
+ }
969
+ /** The HTTP status code. */
970
+ status() {
971
+ const resp = this.data.response;
972
+ return resp?.status ?? 0;
973
+ }
974
+ /** Response headers as a simple key-value object. */
975
+ headers() {
976
+ const resp = this.data.response;
977
+ const entries = resp?.headers ?? [];
978
+ return convertHeaders(entries);
979
+ }
980
+ /** The request ID associated with this response. */
981
+ requestId() {
982
+ const req = this.data.request;
983
+ return req?.request ?? "";
984
+ }
985
+ /** Response body as a string. Requires a data collector (set up by onResponse/expect.response). */
986
+ async body() {
987
+ if (!this.client) return null;
988
+ const reqId = this.requestId();
989
+ if (!reqId) return null;
990
+ try {
991
+ const result = await this.client.send(
992
+ "network.getData",
993
+ { dataType: "response", request: reqId }
994
+ );
995
+ if (!result?.bytes?.value) return null;
996
+ if (result.bytes.type === "base64") {
997
+ return Buffer.from(result.bytes.value, "base64").toString("utf-8");
998
+ }
999
+ return result.bytes.value;
1000
+ } catch(e) {
1001
+ return null;
1002
+ }
1003
+ }
1004
+ /** Response body parsed as JSON. Requires a data collector (set up by onResponse/expect.response). */
1005
+ async json() {
1006
+ const text = await this.body();
1007
+ if (text === null) return null;
1008
+ return JSON.parse(text);
1009
+ }
1010
+ };
1011
+ function convertHeaders(entries) {
1012
+ const result = {};
1013
+ for (const entry of entries) {
1014
+ result[entry.name] = entry.value?.value ?? "";
1015
+ }
1016
+ return result;
1017
+ }
1018
+
1019
+ // src/dialog.ts
1020
+ function isDialogRaceError(e) {
1021
+ if (!(e instanceof Error)) return false;
1022
+ const msg = e.message;
1023
+ return msg === "Connection closed" || msg.includes("no such alert") || msg.includes("No dialog");
1024
+ }
1025
+ var Dialog = class {
1026
+ constructor(client, contextId, data) {
1027
+ this.client = client;
1028
+ this.contextId = contextId;
1029
+ this.data = data;
1030
+ }
1031
+ /** The dialog message text. */
1032
+ message() {
1033
+ return this.data.message ?? "";
1034
+ }
1035
+ /** The dialog type: 'alert', 'confirm', 'prompt', or 'beforeunload'. */
1036
+ type() {
1037
+ return this.data.type ?? "alert";
1038
+ }
1039
+ /** The default value for prompt dialogs. */
1040
+ defaultValue() {
1041
+ return this.data.defaultValue ?? "";
1042
+ }
1043
+ /** Accept the dialog. For prompt dialogs, optionally provide text. */
1044
+ async accept(promptText) {
1045
+ try {
1046
+ const params = {
1047
+ context: this.contextId,
1048
+ accept: true
1049
+ };
1050
+ if (promptText !== void 0) params.userText = promptText;
1051
+ await this.client.send("browsingContext.handleUserPrompt", params);
1052
+ } catch (e) {
1053
+ if (isDialogRaceError(e)) return;
1054
+ throw e;
1055
+ }
1056
+ }
1057
+ /** Dismiss the dialog (cancel/close). */
1058
+ async dismiss() {
1059
+ try {
1060
+ await this.client.send("browsingContext.handleUserPrompt", {
1061
+ context: this.contextId,
1062
+ accept: false
1063
+ });
1064
+ } catch (e) {
1065
+ if (isDialogRaceError(e)) return;
1066
+ throw e;
1067
+ }
1068
+ }
1069
+ };
1070
+
1071
+ // src/console.ts
1072
+ var ConsoleMessage = class {
1073
+ constructor(data) {
1074
+ this.data = data;
1075
+ }
1076
+ /** The console method: 'log', 'warn', 'error', 'debug', 'info', etc. */
1077
+ type() {
1078
+ return this.data.method ?? "log";
1079
+ }
1080
+ /** The message text. */
1081
+ text() {
1082
+ return this.data.text ?? "";
1083
+ }
1084
+ /** The serialized arguments passed to the console call. */
1085
+ args() {
1086
+ return this.data.args ?? [];
1087
+ }
1088
+ /** The source location of the console call, if available. */
1089
+ location() {
1090
+ const stack = this.data.stackTrace;
1091
+ const frame = stack?.callFrames?.[0];
1092
+ if (!frame) return null;
1093
+ return { url: frame.url, lineNumber: frame.lineNumber, columnNumber: frame.columnNumber };
1094
+ }
1095
+ };
1096
+
1097
+ // src/download.ts
1098
+ var DOWNLOAD_TIMEOUT_MS = 3e5;
1099
+ var Download = class {
1100
+ constructor(client, url, suggestedFilename) {
1101
+ this.client = client;
1102
+ this._url = url;
1103
+ this._suggestedFilename = suggestedFilename;
1104
+ this._completionPromise = new Promise((resolve) => {
1105
+ this._resolve = resolve;
1106
+ });
1107
+ }
1108
+ /** The URL of the download. */
1109
+ url() {
1110
+ return this._url;
1111
+ }
1112
+ /** The filename suggested by the server (from Content-Disposition). */
1113
+ suggestedFilename() {
1114
+ return this._suggestedFilename;
1115
+ }
1116
+ /** Wait for the download completion promise with a timeout. */
1117
+ _waitForCompletion() {
1118
+ let timer;
1119
+ const timeoutPromise = new Promise((_, reject) => {
1120
+ timer = setTimeout(
1121
+ () => reject(new Error(`Download timed out after ${DOWNLOAD_TIMEOUT_MS / 1e3}s`)),
1122
+ DOWNLOAD_TIMEOUT_MS
1123
+ );
1124
+ });
1125
+ return Promise.race([this._completionPromise, timeoutPromise]).finally(() => {
1126
+ clearTimeout(timer);
1127
+ });
1128
+ }
1129
+ /** Wait for the download to complete, then save to the specified path. */
1130
+ async saveAs(path2) {
1131
+ const result = await this._waitForCompletion();
1132
+ if (result.status !== "complete" || !result.filepath) {
1133
+ throw new Error(`Download failed with status: ${result.status}`);
1134
+ }
1135
+ await this.client.send("vibium:download.saveAs", {
1136
+ sourcePath: result.filepath,
1137
+ destPath: path2
1138
+ });
1139
+ }
1140
+ /** Wait for the download to complete and return the temp file path, or null if failed. */
1141
+ async path() {
1142
+ const result = await this._waitForCompletion();
1143
+ return result.filepath;
1144
+ }
1145
+ /** Internal: called by Page when downloadCompleted fires. */
1146
+ _complete(status, filepath) {
1147
+ this._resolve({ status, filepath });
1148
+ }
1149
+ };
1150
+
1151
+ // src/websocket.ts
1152
+ var WebSocketInfo = class {
1153
+ constructor(url) {
1154
+ this._isClosed = false;
1155
+ this.messageHandlers = [];
1156
+ this.closeHandlers = [];
1157
+ this._url = url;
1158
+ }
1159
+ url() {
1160
+ return this._url;
1161
+ }
1162
+ onMessage(fn) {
1163
+ this.messageHandlers.push(fn);
1164
+ }
1165
+ onClose(fn) {
1166
+ this.closeHandlers.push(fn);
1167
+ }
1168
+ isClosed() {
1169
+ return this._isClosed;
1170
+ }
1171
+ /** @internal */
1172
+ _emitMessage(data, direction) {
1173
+ for (const fn of this.messageHandlers) fn(data, { direction });
1174
+ }
1175
+ /** @internal */
1176
+ _emitClose(code, reason) {
1177
+ this._isClosed = true;
1178
+ for (const fn of this.closeHandlers) fn(code, reason);
1179
+ }
1180
+ };
1181
+
1182
+ // src/clock.ts
1183
+ function normalizeTime(time) {
1184
+ if (typeof time === "number") return time;
1185
+ if (time instanceof Date) return time.getTime();
1186
+ return new Date(time).getTime();
1187
+ }
1188
+ var Clock = class {
1189
+ constructor(client, contextId) {
1190
+ this.client = client;
1191
+ this.contextId = contextId;
1192
+ }
1193
+ /** Install fake clock, overriding Date, setTimeout, setInterval, etc. */
1194
+ async install(options) {
1195
+ const params = { context: this.contextId };
1196
+ if (options?.time !== void 0) {
1197
+ params.time = normalizeTime(options.time);
1198
+ }
1199
+ if (options?.timezone !== void 0) {
1200
+ params.timezone = options.timezone;
1201
+ }
1202
+ await this.client.send("vibium:clock.install", params);
1203
+ }
1204
+ /** Jump forward by ticks ms, fire each due timer at most once. */
1205
+ async fastForward(ticks) {
1206
+ await this.client.send("vibium:clock.fastForward", {
1207
+ context: this.contextId,
1208
+ ticks
1209
+ });
1210
+ }
1211
+ /** Advance ticks ms, firing all callbacks systematically (including interval reschedules). */
1212
+ async runFor(ticks) {
1213
+ await this.client.send("vibium:clock.runFor", {
1214
+ context: this.contextId,
1215
+ ticks
1216
+ });
1217
+ }
1218
+ /** Jump to a specific time and pause — no timers fire until resumed/advanced. */
1219
+ async pauseAt(time) {
1220
+ await this.client.send("vibium:clock.pauseAt", {
1221
+ context: this.contextId,
1222
+ time: normalizeTime(time)
1223
+ });
1224
+ }
1225
+ /** Resume real-time progression from current fake time. */
1226
+ async resume() {
1227
+ await this.client.send("vibium:clock.resume", {
1228
+ context: this.contextId
1229
+ });
1230
+ }
1231
+ /** Freeze Date.now() at a value permanently. Timers still run. */
1232
+ async setFixedTime(time) {
1233
+ await this.client.send("vibium:clock.setFixedTime", {
1234
+ context: this.contextId,
1235
+ time: normalizeTime(time)
1236
+ });
1237
+ }
1238
+ /** Set Date.now() without triggering timers. */
1239
+ async setSystemTime(time) {
1240
+ await this.client.send("vibium:clock.setSystemTime", {
1241
+ context: this.contextId,
1242
+ time: normalizeTime(time)
1243
+ });
1244
+ }
1245
+ /** Override the browser timezone. Pass an IANA timezone ID, or empty string to reset to system default. */
1246
+ async setTimezone(timezone) {
1247
+ await this.client.send("vibium:clock.setTimezone", {
1248
+ context: this.contextId,
1249
+ timezone
1250
+ });
1251
+ }
1252
+ };
1253
+
1254
+ // src/utils/match.ts
1255
+ function matchPattern(pattern, url) {
1256
+ if (!pattern.includes("*") && !pattern.includes("{")) {
1257
+ return url === pattern;
1258
+ }
1259
+ const braces = [];
1260
+ let withPlaceholders = pattern.replace(/\{([^}]+)\}/g, (_match, inner) => {
1261
+ braces.push(inner);
1262
+ return `{{BRACE${braces.length - 1}}}`;
1263
+ });
1264
+ let regex = withPlaceholders.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1265
+ regex = regex.replace(/\*\*/g, "{{GLOBSTAR}}");
1266
+ regex = regex.replace(/\*/g, "[^/]*");
1267
+ regex = regex.replace(/\{\{GLOBSTAR\}\}/g, ".*");
1268
+ for (let i = 0; i < braces.length; i++) {
1269
+ const alternatives = braces[i].split(",").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
1270
+ regex = regex.replace(`\\{\\{BRACE${i}\\}\\}`, `(?:${alternatives})`);
1271
+ }
1272
+ return new RegExp(`^${regex}$`).test(url);
1273
+ }
1274
+
1275
+ // src/utils/debug.ts
1276
+ var isDebugEnabled = process.env.VIBIUM_DEBUG === "1" || process.env.VIBIUM_DEBUG === "true";
1277
+ function debug(message, data) {
1278
+ if (!isDebugEnabled) return;
1279
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1280
+ const logObj = {
1281
+ time: timestamp,
1282
+ level: "debug",
1283
+ msg: message,
1284
+ ...data
1285
+ };
1286
+ console.error(JSON.stringify(logObj));
1287
+ }
1288
+ function info(message, data) {
1289
+ if (!isDebugEnabled) return;
1290
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1291
+ const logObj = {
1292
+ time: timestamp,
1293
+ level: "info",
1294
+ msg: message,
1295
+ ...data
1296
+ };
1297
+ console.error(JSON.stringify(logObj));
1298
+ }
1299
+
1300
+ // src/page.ts
1301
+ var customInspect2 = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
1302
+ var Keyboard = class {
1303
+ constructor(client, contextId) {
1304
+ this.client = client;
1305
+ this.contextId = contextId;
1306
+ }
1307
+ /** Press and release a key. Supports combos like "Control+a". */
1308
+ async press(key) {
1309
+ await this.client.send("vibium:keyboard.press", {
1310
+ context: this.contextId,
1311
+ key
1312
+ });
1313
+ }
1314
+ /** Press a key down (without releasing). */
1315
+ async down(key) {
1316
+ await this.client.send("vibium:keyboard.down", {
1317
+ context: this.contextId,
1318
+ key
1319
+ });
1320
+ }
1321
+ /** Release a key. */
1322
+ async up(key) {
1323
+ await this.client.send("vibium:keyboard.up", {
1324
+ context: this.contextId,
1325
+ key
1326
+ });
1327
+ }
1328
+ /** Type a string of text character by character. */
1329
+ async type(text) {
1330
+ await this.client.send("vibium:keyboard.type", {
1331
+ context: this.contextId,
1332
+ text
1333
+ });
1334
+ }
1335
+ };
1336
+ var Mouse = class {
1337
+ constructor(client, contextId) {
1338
+ this.client = client;
1339
+ this.contextId = contextId;
1340
+ }
1341
+ /** Click at (x, y) coordinates. */
1342
+ async click(x, y) {
1343
+ await this.client.send("vibium:mouse.click", {
1344
+ context: this.contextId,
1345
+ x,
1346
+ y
1347
+ });
1348
+ }
1349
+ /** Move mouse to (x, y) coordinates. */
1350
+ async move(x, y) {
1351
+ await this.client.send("vibium:mouse.move", {
1352
+ context: this.contextId,
1353
+ x,
1354
+ y
1355
+ });
1356
+ }
1357
+ /** Press mouse button down. */
1358
+ async down() {
1359
+ await this.client.send("vibium:mouse.down", {
1360
+ context: this.contextId
1361
+ });
1362
+ }
1363
+ /** Release mouse button. */
1364
+ async up() {
1365
+ await this.client.send("vibium:mouse.up", {
1366
+ context: this.contextId
1367
+ });
1368
+ }
1369
+ /** Scroll the mouse wheel. */
1370
+ async wheel(deltaX, deltaY) {
1371
+ await this.client.send("vibium:mouse.wheel", {
1372
+ context: this.contextId,
1373
+ x: 0,
1374
+ y: 0,
1375
+ deltaX,
1376
+ deltaY
1377
+ });
1378
+ }
1379
+ };
1380
+ var Touch = class {
1381
+ constructor(client, contextId) {
1382
+ this.client = client;
1383
+ this.contextId = contextId;
1384
+ }
1385
+ /** Tap at (x, y) coordinates. */
1386
+ async tap(x, y) {
1387
+ await this.client.send("vibium:touch.tap", {
1388
+ context: this.contextId,
1389
+ x,
1390
+ y
1391
+ });
1392
+ }
1393
+ };
1394
+ var Page = class _Page {
1395
+ constructor(client, contextId, userContextId = "default") {
1396
+ // Network interception state
1397
+ this.routes = [];
1398
+ this.requestCallbacks = [];
1399
+ this.responseCallbacks = [];
1400
+ this.dialogCallbacks = [];
1401
+ this.consoleCallbacks = [];
1402
+ this.errorCallbacks = [];
1403
+ this.downloadCallbacks = [];
1404
+ this.navigationCallbacks = [];
1405
+ this.pendingDownloads = /* @__PURE__ */ new Map();
1406
+ this.wsCallbacks = [];
1407
+ this.wsConnections = /* @__PURE__ */ new Map();
1408
+ this.eventHandler = null;
1409
+ this.interceptId = null;
1410
+ this.dataCollectorId = null;
1411
+ // Console/error collect-mode buffers (null = not collecting)
1412
+ this._consoleBuffer = null;
1413
+ this._errorBuffer = null;
1414
+ this.client = client;
1415
+ this.contextId = contextId;
1416
+ this._context = new BrowserContext(client, userContextId);
1417
+ this.keyboard = new Keyboard(client, contextId);
1418
+ this.mouse = new Mouse(client, contextId);
1419
+ this.touch = new Touch(client, contextId);
1420
+ this.clock = new Clock(client, contextId);
1421
+ const self = this;
1422
+ this.capture = {
1423
+ response(pattern, fn, options) {
1424
+ const promise = self._captureResponse(pattern, options);
1425
+ if (fn) return fn().then(() => promise);
1426
+ return promise;
1427
+ },
1428
+ request(pattern, fn, options) {
1429
+ const promise = self._captureRequest(pattern, options);
1430
+ if (fn) return fn().then(() => promise);
1431
+ return promise;
1432
+ },
1433
+ navigation(fn, options) {
1434
+ const promise = self._captureNavigation(options);
1435
+ if (fn) return fn().then(() => promise);
1436
+ return promise;
1437
+ },
1438
+ download(fn, options) {
1439
+ const promise = self._captureDownload(options);
1440
+ if (fn) return fn().then(() => promise);
1441
+ return promise;
1442
+ },
1443
+ dialog(fn, options) {
1444
+ const promise = self._captureDialog(options);
1445
+ if (fn) return fn().then(() => promise);
1446
+ return promise;
1447
+ },
1448
+ event(name, fn, options) {
1449
+ const promise = self._captureEvent(name, options);
1450
+ if (fn) return fn().then(() => promise);
1451
+ return promise;
1452
+ }
1453
+ };
1454
+ this.waitUntil = Object.assign(
1455
+ (fn, options) => self._waitForFunction(fn, options),
1456
+ {
1457
+ url: (pattern, options) => self._waitForURL(pattern, options),
1458
+ loaded: (state, options) => self._waitForLoad(state, options)
1459
+ }
1460
+ );
1461
+ this.eventHandler = (event) => {
1462
+ const params = event.params;
1463
+ const eventContext = params.context;
1464
+ if (eventContext && eventContext !== this.contextId) return;
1465
+ if (event.method === "network.beforeRequestSent") {
1466
+ this.handleBeforeRequestSent(params);
1467
+ } else if (event.method === "network.responseCompleted") {
1468
+ this.handleResponseCompleted(params);
1469
+ } else if (event.method === "browsingContext.userPromptOpened") {
1470
+ this.handleUserPromptOpened(params);
1471
+ } else if (event.method === "browsingContext.downloadWillBegin") {
1472
+ this.handleDownloadWillBegin(params);
1473
+ } else if (event.method === "browsingContext.downloadEnd") {
1474
+ this.handleDownloadCompleted(params);
1475
+ } else if (event.method === "log.entryAdded") {
1476
+ const source = params.source;
1477
+ const logContext = source?.context;
1478
+ if (logContext && logContext !== this.contextId) return;
1479
+ this.handleLogEntryAdded(params);
1480
+ } else if (event.method === "browsingContext.load") {
1481
+ const url = params.url;
1482
+ if (url) {
1483
+ for (const cb of this.navigationCallbacks) {
1484
+ cb(url);
1485
+ }
1486
+ }
1487
+ } else if (event.method === "browsingContext.fragmentNavigated") {
1488
+ const url = params.url;
1489
+ if (url) {
1490
+ for (const cb of this.navigationCallbacks) {
1491
+ cb(url);
1492
+ }
1493
+ }
1494
+ } else if (event.method === "vibium:ws.created") {
1495
+ this.handleWsCreated(params);
1496
+ } else if (event.method === "vibium:ws.message") {
1497
+ this.handleWsMessage(params);
1498
+ } else if (event.method === "vibium:ws.closed") {
1499
+ this.handleWsClosed(params);
1500
+ }
1501
+ };
1502
+ this.client.onEvent(this.eventHandler);
1503
+ }
1504
+ /** The browsing context ID for this page. */
1505
+ get id() {
1506
+ return this.contextId;
1507
+ }
1508
+ [customInspect2]() {
1509
+ return `Page { contextId: '${this.contextId}' }`;
1510
+ }
1511
+ /** The parent BrowserContext that owns this page. */
1512
+ get context() {
1513
+ return this._context;
1514
+ }
1515
+ /** Navigate to a URL. */
1516
+ async go(url) {
1517
+ debug("page.go", { url, context: this.contextId });
1518
+ await this.client.send("vibium:page.navigate", {
1519
+ context: this.contextId,
1520
+ url
1521
+ });
1522
+ }
1523
+ /** Navigate back in history. */
1524
+ async back() {
1525
+ await this.client.send("vibium:page.back", { context: this.contextId });
1526
+ }
1527
+ /** Navigate forward in history. */
1528
+ async forward() {
1529
+ await this.client.send("vibium:page.forward", { context: this.contextId });
1530
+ }
1531
+ /** Reload the page. */
1532
+ async reload() {
1533
+ await this.client.send("vibium:page.reload", { context: this.contextId });
1534
+ }
1535
+ /** Get the current page URL. */
1536
+ async url() {
1537
+ const result = await this.client.send("vibium:page.url", {
1538
+ context: this.contextId
1539
+ });
1540
+ return result.url;
1541
+ }
1542
+ /** Get the current page title. */
1543
+ async title() {
1544
+ const result = await this.client.send("vibium:page.title", {
1545
+ context: this.contextId
1546
+ });
1547
+ return result.title;
1548
+ }
1549
+ /** Get the full HTML content of the page. */
1550
+ async content() {
1551
+ const result = await this.client.send("vibium:page.content", {
1552
+ context: this.contextId
1553
+ });
1554
+ return result.content;
1555
+ }
1556
+ /** @internal Wait until the page URL matches a pattern. */
1557
+ async _waitForURL(pattern, options) {
1558
+ await this.client.send("vibium:page.waitForURL", {
1559
+ context: this.contextId,
1560
+ pattern,
1561
+ timeout: options?.timeout
1562
+ });
1563
+ }
1564
+ /** @internal Wait until the page reaches a load state. */
1565
+ async _waitForLoad(state, options) {
1566
+ await this.client.send("vibium:page.waitForLoad", {
1567
+ context: this.contextId,
1568
+ state,
1569
+ timeout: options?.timeout
1570
+ });
1571
+ }
1572
+ // --- Frames ---
1573
+ /** Get all child frames of this page (recursive, flattened). */
1574
+ async frames() {
1575
+ const result = await this.client.send("vibium:page.frames", {
1576
+ context: this.contextId
1577
+ });
1578
+ return result.frames.map((f) => new _Page(this.client, f.context));
1579
+ }
1580
+ /** Find a frame by name attribute or URL substring. Returns null if not found. */
1581
+ async frame(nameOrUrl) {
1582
+ const result = await this.client.send("vibium:page.frame", {
1583
+ context: this.contextId,
1584
+ nameOrUrl
1585
+ });
1586
+ if (!result || !result.context) return null;
1587
+ return new _Page(this.client, result.context);
1588
+ }
1589
+ /** Returns this page — the page IS its own main frame. */
1590
+ mainFrame() {
1591
+ return this;
1592
+ }
1593
+ // --- Emulation ---
1594
+ /** Set the viewport size. */
1595
+ async setViewport(size) {
1596
+ await this.client.send("vibium:page.setViewport", {
1597
+ context: this.contextId,
1598
+ width: size.width,
1599
+ height: size.height
1600
+ });
1601
+ }
1602
+ /** Get the current viewport size. */
1603
+ async viewport() {
1604
+ return await this.client.send("vibium:page.viewport", {
1605
+ context: this.contextId
1606
+ });
1607
+ }
1608
+ /** Override CSS media features (colorScheme, reducedMotion, forcedColors, contrast, media type). */
1609
+ async emulateMedia(opts) {
1610
+ await this.client.send("vibium:page.emulateMedia", {
1611
+ context: this.contextId,
1612
+ ...opts
1613
+ });
1614
+ }
1615
+ /** Replace the page HTML content. */
1616
+ async setContent(html) {
1617
+ await this.client.send("vibium:page.setContent", {
1618
+ context: this.contextId,
1619
+ html
1620
+ });
1621
+ }
1622
+ /** Override the browser's geolocation. */
1623
+ async setGeolocation(coords) {
1624
+ await this.client.send("vibium:page.setGeolocation", {
1625
+ context: this.contextId,
1626
+ ...coords
1627
+ });
1628
+ }
1629
+ /** Set the OS browser window size, position, or state. */
1630
+ async setWindow(options) {
1631
+ await this.client.send("vibium:page.setWindow", {
1632
+ ...options
1633
+ });
1634
+ }
1635
+ /** Get the current OS browser window state and dimensions. */
1636
+ async window() {
1637
+ return await this.client.send("vibium:page.window", {});
1638
+ }
1639
+ // --- Accessibility ---
1640
+ /** Get the accessibility tree for the page. */
1641
+ async a11yTree(options) {
1642
+ const result = await this.client.send("vibium:page.a11yTree", {
1643
+ context: this.contextId,
1644
+ ...options
1645
+ });
1646
+ return result.tree;
1647
+ }
1648
+ /** Bring this page/tab to the foreground. */
1649
+ async bringToFront() {
1650
+ await this.client.send("browsingContext.activate", { context: this.contextId });
1651
+ }
1652
+ /** Close this page/tab. */
1653
+ async close() {
1654
+ if (this.eventHandler) {
1655
+ this.client.offEvent(this.eventHandler);
1656
+ }
1657
+ await this.client.send("browsingContext.close", { context: this.contextId });
1658
+ }
1659
+ /** Scroll the page in a direction. */
1660
+ async scroll(direction, amount, selector) {
1661
+ await this.client.send("vibium:page.scroll", {
1662
+ context: this.contextId,
1663
+ direction,
1664
+ amount,
1665
+ selector
1666
+ });
1667
+ }
1668
+ // --- Screenshots & PDF ---
1669
+ /** Take a screenshot of the page. Returns a PNG buffer. */
1670
+ async screenshot(options) {
1671
+ const result = await this.client.send("vibium:page.screenshot", {
1672
+ context: this.contextId,
1673
+ fullPage: options?.fullPage,
1674
+ clip: options?.clip
1675
+ });
1676
+ return Buffer.from(result.data, "base64");
1677
+ }
1678
+ /** Print the page to PDF. Returns a PDF buffer. Only works in headless mode. */
1679
+ async pdf() {
1680
+ const result = await this.client.send("vibium:page.pdf", {
1681
+ context: this.contextId
1682
+ });
1683
+ return Buffer.from(result.data, "base64");
1684
+ }
1685
+ // --- Evaluation ---
1686
+ /** Evaluate a JS expression and return the deserialized value. */
1687
+ async evaluate(expression) {
1688
+ const result = await this.client.send("vibium:page.eval", {
1689
+ context: this.contextId,
1690
+ expression
1691
+ });
1692
+ return result.value;
1693
+ }
1694
+ /** Inject a script into the page. Pass a URL or inline JavaScript. */
1695
+ async addScript(source) {
1696
+ const isURL = source.startsWith("http://") || source.startsWith("https://") || source.startsWith("//");
1697
+ await this.client.send("vibium:page.addScript", {
1698
+ context: this.contextId,
1699
+ ...isURL ? { url: source } : { content: source }
1700
+ });
1701
+ }
1702
+ /** Inject a stylesheet into the page. Pass a URL or inline CSS. */
1703
+ async addStyle(source) {
1704
+ const isURL = source.startsWith("http://") || source.startsWith("https://") || source.startsWith("//");
1705
+ await this.client.send("vibium:page.addStyle", {
1706
+ context: this.contextId,
1707
+ ...isURL ? { url: source } : { content: source }
1708
+ });
1709
+ }
1710
+ /** Expose a function on window. The function body is injected as a string. */
1711
+ async expose(name, fn) {
1712
+ await this.client.send("vibium:page.expose", {
1713
+ context: this.contextId,
1714
+ name,
1715
+ fn
1716
+ });
1717
+ }
1718
+ /** Wait for a fixed amount of time (milliseconds). Discouraged but useful for debugging. */
1719
+ async wait(ms) {
1720
+ await this.client.send("vibium:page.wait", {
1721
+ context: this.contextId,
1722
+ ms
1723
+ });
1724
+ }
1725
+ /** @internal Wait until a function returns a truthy value. */
1726
+ async _waitForFunction(fn, options) {
1727
+ const result = await this.client.send("vibium:page.waitForFunction", {
1728
+ context: this.contextId,
1729
+ fn,
1730
+ timeout: options?.timeout
1731
+ });
1732
+ return result.value;
1733
+ }
1734
+ /** Find an element by CSS selector or semantic options. Waits for element to exist. */
1735
+ find(selector, options) {
1736
+ const promise = (async () => {
1737
+ const params = {
1738
+ context: this.contextId,
1739
+ timeout: options?.timeout
1740
+ };
1741
+ if (typeof selector === "string") {
1742
+ debug("page.find", { selector, timeout: options?.timeout });
1743
+ params.selector = selector;
1744
+ } else {
1745
+ debug("page.find", { ...selector, timeout: options?.timeout });
1746
+ Object.assign(params, selector);
1747
+ if (selector.timeout && !options?.timeout) params.timeout = selector.timeout;
1748
+ }
1749
+ const result = await this.client.send("vibium:page.find", params);
1750
+ const info2 = {
1751
+ tag: result.tag,
1752
+ text: result.text,
1753
+ box: result.box
1754
+ };
1755
+ const selectorStr = typeof selector === "string" ? selector : "";
1756
+ const selectorParams = typeof selector === "string" ? { selector } : { ...selector };
1757
+ return new Element(this.client, this.contextId, selectorStr, info2, void 0, selectorParams);
1758
+ })();
1759
+ return fluent(promise);
1760
+ }
1761
+ /** Find all elements matching a CSS selector or semantic options. Waits for at least one. */
1762
+ async findAll(selector, options) {
1763
+ const params = {
1764
+ context: this.contextId,
1765
+ timeout: options?.timeout
1766
+ };
1767
+ if (typeof selector === "string") {
1768
+ debug("page.findAll", { selector, timeout: options?.timeout });
1769
+ params.selector = selector;
1770
+ } else {
1771
+ debug("page.findAll", { ...selector, timeout: options?.timeout });
1772
+ Object.assign(params, selector);
1773
+ if (selector.timeout && !options?.timeout) params.timeout = selector.timeout;
1774
+ }
1775
+ const result = await this.client.send("vibium:page.findAll", params);
1776
+ const selectorStr = typeof selector === "string" ? selector : "";
1777
+ const selectorParams = typeof selector === "string" ? { selector } : { ...selector };
1778
+ return result.elements.map((el) => {
1779
+ const info2 = { tag: el.tag, text: el.text, box: el.box };
1780
+ return new Element(this.client, this.contextId, selectorStr, info2, el.index, selectorParams);
1781
+ });
1782
+ }
1783
+ // --- Network Interception ---
1784
+ /**
1785
+ * Intercept network requests matching a URL pattern.
1786
+ * The handler receives a Route object that can fulfill, continue, or abort the request.
1787
+ */
1788
+ async route(pattern, handler) {
1789
+ if (this.interceptId === null) {
1790
+ const result = await this.client.send("vibium:page.route", {
1791
+ context: this.contextId
1792
+ });
1793
+ this.interceptId = result.intercept;
1794
+ }
1795
+ this.ensureDataCollector();
1796
+ this.routes.push({ pattern, handler, interceptId: this.interceptId ?? void 0 });
1797
+ }
1798
+ /** Remove a previously registered route. If no handler given, removes all routes for the pattern. */
1799
+ async unroute(pattern) {
1800
+ this.routes = this.routes.filter((r) => r.pattern !== pattern);
1801
+ if (this.routes.length === 0 && this.interceptId) {
1802
+ await this.client.send("network.removeIntercept", {
1803
+ intercept: this.interceptId
1804
+ });
1805
+ this.interceptId = null;
1806
+ }
1807
+ }
1808
+ /** Register a callback for every outgoing request. */
1809
+ onRequest(fn) {
1810
+ this.ensureDataCollector();
1811
+ this.requestCallbacks.push(fn);
1812
+ }
1813
+ /** Register a callback for every completed response. */
1814
+ onResponse(fn) {
1815
+ this.ensureDataCollector();
1816
+ this.responseCallbacks.push(fn);
1817
+ }
1818
+ /**
1819
+ * Remove all listeners for a given event, or all events if no event specified.
1820
+ * Supported events: 'request', 'response', 'dialog', 'console', 'error', 'download', 'websocket'.
1821
+ */
1822
+ removeAllListeners(event) {
1823
+ if (!event || event === "request") {
1824
+ this.requestCallbacks = [];
1825
+ }
1826
+ if (!event || event === "response") {
1827
+ this.responseCallbacks = [];
1828
+ }
1829
+ if (!event || event === "dialog") {
1830
+ this.dialogCallbacks = [];
1831
+ }
1832
+ if (!event || event === "console") {
1833
+ this.consoleCallbacks = [];
1834
+ this._consoleBuffer = null;
1835
+ }
1836
+ if (!event || event === "error") {
1837
+ this.errorCallbacks = [];
1838
+ this._errorBuffer = null;
1839
+ }
1840
+ if (!event || event === "download") {
1841
+ this.downloadCallbacks = [];
1842
+ }
1843
+ if (!event || event === "navigation") {
1844
+ this.navigationCallbacks = [];
1845
+ }
1846
+ if (!event || event === "websocket") {
1847
+ this.wsCallbacks = [];
1848
+ }
1849
+ if (this.requestCallbacks.length === 0 && this.responseCallbacks.length === 0 && this.routes.length === 0) {
1850
+ this.teardownDataCollector();
1851
+ }
1852
+ }
1853
+ /** @internal Capture a request matching a URL pattern. */
1854
+ _captureRequest(pattern, options) {
1855
+ const timeout = options?.timeout ?? 1e4;
1856
+ return new Promise((resolve, reject) => {
1857
+ const timer = setTimeout(() => {
1858
+ this.requestCallbacks = this.requestCallbacks.filter((cb) => cb !== handler);
1859
+ reject(new Error(`Timeout waiting for request matching '${pattern}'`));
1860
+ }, timeout);
1861
+ const handler = (request) => {
1862
+ if (matchPattern(pattern, request.url())) {
1863
+ clearTimeout(timer);
1864
+ this.requestCallbacks = this.requestCallbacks.filter((cb) => cb !== handler);
1865
+ resolve(request);
1866
+ }
1867
+ };
1868
+ this.requestCallbacks.push(handler);
1869
+ });
1870
+ }
1871
+ /** @internal Capture a response matching a URL pattern. */
1872
+ _captureResponse(pattern, options) {
1873
+ this.ensureDataCollector();
1874
+ const timeout = options?.timeout ?? 1e4;
1875
+ return new Promise((resolve, reject) => {
1876
+ const timer = setTimeout(() => {
1877
+ this.responseCallbacks = this.responseCallbacks.filter((cb) => cb !== handler);
1878
+ reject(new Error(`Timeout waiting for response matching '${pattern}'`));
1879
+ }, timeout);
1880
+ const handler = (response) => {
1881
+ if (matchPattern(pattern, response.url())) {
1882
+ clearTimeout(timer);
1883
+ this.responseCallbacks = this.responseCallbacks.filter((cb) => cb !== handler);
1884
+ resolve(response);
1885
+ }
1886
+ };
1887
+ this.responseCallbacks.push(handler);
1888
+ });
1889
+ }
1890
+ /** @internal Capture a navigation event. Resolves with the URL. */
1891
+ _captureNavigation(options) {
1892
+ const timeout = options?.timeout ?? 1e4;
1893
+ return new Promise((resolve, reject) => {
1894
+ const timer = setTimeout(() => {
1895
+ this.navigationCallbacks = this.navigationCallbacks.filter((cb) => cb !== handler);
1896
+ reject(new Error(`Timeout waiting for navigation`));
1897
+ }, timeout);
1898
+ const handler = (url) => {
1899
+ clearTimeout(timer);
1900
+ this.navigationCallbacks = this.navigationCallbacks.filter((cb) => cb !== handler);
1901
+ resolve(url);
1902
+ };
1903
+ this.navigationCallbacks.push(handler);
1904
+ });
1905
+ }
1906
+ /** @internal Capture a download event. */
1907
+ _captureDownload(options) {
1908
+ const timeout = options?.timeout ?? 1e4;
1909
+ return new Promise((resolve, reject) => {
1910
+ const timer = setTimeout(() => {
1911
+ this.downloadCallbacks = this.downloadCallbacks.filter((cb) => cb !== handler);
1912
+ reject(new Error(`Timeout waiting for download`));
1913
+ }, timeout);
1914
+ const handler = (download) => {
1915
+ clearTimeout(timer);
1916
+ this.downloadCallbacks = this.downloadCallbacks.filter((cb) => cb !== handler);
1917
+ resolve(download);
1918
+ };
1919
+ this.downloadCallbacks.push(handler);
1920
+ });
1921
+ }
1922
+ /** @internal Capture a dialog event. The registered callback prevents auto-dismiss. */
1923
+ _captureDialog(options) {
1924
+ const timeout = options?.timeout ?? 1e4;
1925
+ return new Promise((resolve, reject) => {
1926
+ const timer = setTimeout(() => {
1927
+ this.dialogCallbacks = this.dialogCallbacks.filter((cb) => cb !== handler);
1928
+ reject(new Error(`Timeout waiting for dialog`));
1929
+ }, timeout);
1930
+ const handler = (dialog) => {
1931
+ clearTimeout(timer);
1932
+ this.dialogCallbacks = this.dialogCallbacks.filter((cb) => cb !== handler);
1933
+ resolve(dialog);
1934
+ };
1935
+ this.dialogCallbacks.push(handler);
1936
+ });
1937
+ }
1938
+ /** @internal Capture a named event. Maps event name to the appropriate callback array. */
1939
+ _captureEvent(name, options) {
1940
+ const timeout = options?.timeout ?? 1e4;
1941
+ return new Promise((resolve, reject) => {
1942
+ const timer = setTimeout(() => {
1943
+ cleanup();
1944
+ reject(new Error(`Timeout waiting for event '${name}'`));
1945
+ }, timeout);
1946
+ let cleanup;
1947
+ switch (name) {
1948
+ case "request": {
1949
+ const handler = (req) => {
1950
+ clearTimeout(timer);
1951
+ cleanup();
1952
+ resolve(req);
1953
+ };
1954
+ this.requestCallbacks.push(handler);
1955
+ cleanup = () => {
1956
+ this.requestCallbacks = this.requestCallbacks.filter((cb) => cb !== handler);
1957
+ };
1958
+ this.ensureDataCollector();
1959
+ break;
1960
+ }
1961
+ case "response": {
1962
+ const handler = (resp) => {
1963
+ clearTimeout(timer);
1964
+ cleanup();
1965
+ resolve(resp);
1966
+ };
1967
+ this.responseCallbacks.push(handler);
1968
+ cleanup = () => {
1969
+ this.responseCallbacks = this.responseCallbacks.filter((cb) => cb !== handler);
1970
+ };
1971
+ this.ensureDataCollector();
1972
+ break;
1973
+ }
1974
+ case "dialog": {
1975
+ const handler = (dialog) => {
1976
+ clearTimeout(timer);
1977
+ cleanup();
1978
+ resolve(dialog);
1979
+ };
1980
+ this.dialogCallbacks.push(handler);
1981
+ cleanup = () => {
1982
+ this.dialogCallbacks = this.dialogCallbacks.filter((cb) => cb !== handler);
1983
+ };
1984
+ break;
1985
+ }
1986
+ case "download": {
1987
+ const handler = (download) => {
1988
+ clearTimeout(timer);
1989
+ cleanup();
1990
+ resolve(download);
1991
+ };
1992
+ this.downloadCallbacks.push(handler);
1993
+ cleanup = () => {
1994
+ this.downloadCallbacks = this.downloadCallbacks.filter((cb) => cb !== handler);
1995
+ };
1996
+ break;
1997
+ }
1998
+ case "navigation": {
1999
+ const handler = (url) => {
2000
+ clearTimeout(timer);
2001
+ cleanup();
2002
+ resolve(url);
2003
+ };
2004
+ this.navigationCallbacks.push(handler);
2005
+ cleanup = () => {
2006
+ this.navigationCallbacks = this.navigationCallbacks.filter((cb) => cb !== handler);
2007
+ };
2008
+ break;
2009
+ }
2010
+ case "console": {
2011
+ const handler = (msg) => {
2012
+ clearTimeout(timer);
2013
+ cleanup();
2014
+ resolve(msg);
2015
+ };
2016
+ this.consoleCallbacks.push(handler);
2017
+ cleanup = () => {
2018
+ this.consoleCallbacks = this.consoleCallbacks.filter((cb) => cb !== handler);
2019
+ };
2020
+ break;
2021
+ }
2022
+ case "error": {
2023
+ const handler = (err) => {
2024
+ clearTimeout(timer);
2025
+ cleanup();
2026
+ resolve(err);
2027
+ };
2028
+ this.errorCallbacks.push(handler);
2029
+ cleanup = () => {
2030
+ this.errorCallbacks = this.errorCallbacks.filter((cb) => cb !== handler);
2031
+ };
2032
+ break;
2033
+ }
2034
+ default:
2035
+ clearTimeout(timer);
2036
+ reject(new Error(`Unknown event name: '${name}'`));
2037
+ }
2038
+ });
2039
+ }
2040
+ /** Set extra HTTP headers for all requests in this page. */
2041
+ async setHeaders(headers) {
2042
+ const result = await this.client.send("vibium:page.setHeaders", {
2043
+ context: this.contextId,
2044
+ headers
2045
+ });
2046
+ this.routes.push({
2047
+ pattern: "**",
2048
+ handler: (route) => {
2049
+ const merged = { ...route.request.headers(), ...headers };
2050
+ route.continue({ headers: merged });
2051
+ },
2052
+ interceptId: result.intercept
2053
+ });
2054
+ }
2055
+ /** Intercept WebSocket connections. Not supported by BiDi. */
2056
+ routeWebSocket(_pattern, _handler) {
2057
+ throw new Error("Not implemented: BiDi does not support WebSocket interception");
2058
+ }
2059
+ /** Listen for WebSocket connections opened by the page. */
2060
+ onWebSocket(fn) {
2061
+ const isFirst = this.wsCallbacks.length === 0;
2062
+ this.wsCallbacks.push(fn);
2063
+ if (isFirst) {
2064
+ this.client.send("vibium:page.onWebSocket", { context: this.contextId }).catch(() => {
2065
+ });
2066
+ }
2067
+ }
2068
+ // --- Dialog Handling ---
2069
+ /**
2070
+ * Register a handler for browser dialogs (alert, confirm, prompt).
2071
+ * If no handler is registered, dialogs are automatically dismissed.
2072
+ */
2073
+ onDialog(handler) {
2074
+ this.dialogCallbacks.push(handler);
2075
+ }
2076
+ /** Register a handler for console messages, or pass 'collect' to buffer them for consoleMessages(). */
2077
+ onConsole(handler) {
2078
+ if (handler === "collect") {
2079
+ if (this._consoleBuffer === null) {
2080
+ this._consoleBuffer = [];
2081
+ this.consoleCallbacks.push((msg) => {
2082
+ this._consoleBuffer?.push({ type: msg.type(), text: msg.text() });
2083
+ });
2084
+ }
2085
+ } else {
2086
+ this.consoleCallbacks.push(handler);
2087
+ }
2088
+ }
2089
+ /** Return collected console messages and clear the buffer. Returns [] if not collecting. */
2090
+ consoleMessages() {
2091
+ const msgs = this._consoleBuffer || [];
2092
+ if (this._consoleBuffer) this._consoleBuffer = [];
2093
+ return msgs;
2094
+ }
2095
+ /** Register a handler for uncaught page errors, or pass 'collect' to buffer them for errors(). */
2096
+ onError(handler) {
2097
+ if (handler === "collect") {
2098
+ if (this._errorBuffer === null) {
2099
+ this._errorBuffer = [];
2100
+ this.errorCallbacks.push((error) => {
2101
+ this._errorBuffer?.push({ message: error.message });
2102
+ });
2103
+ }
2104
+ } else {
2105
+ this.errorCallbacks.push(handler);
2106
+ }
2107
+ }
2108
+ /** Return collected errors and clear the buffer. Returns [] if not collecting. */
2109
+ errors() {
2110
+ const errs = this._errorBuffer || [];
2111
+ if (this._errorBuffer) this._errorBuffer = [];
2112
+ return errs;
2113
+ }
2114
+ /** Register a handler for file downloads. */
2115
+ onDownload(handler) {
2116
+ this.downloadCallbacks.push(handler);
2117
+ }
2118
+ // --- Event Handlers (internal) ---
2119
+ ensureDataCollector() {
2120
+ if (this.dataCollectorId !== null) return;
2121
+ this.dataCollectorId = "pending";
2122
+ this.client.send(
2123
+ "network.addDataCollector",
2124
+ { dataTypes: ["request", "response"], maxEncodedDataSize: 10 * 1024 * 1024 }
2125
+ ).then((result) => {
2126
+ this.dataCollectorId = result.collector;
2127
+ }).catch(() => {
2128
+ this.dataCollectorId = null;
2129
+ });
2130
+ }
2131
+ teardownDataCollector() {
2132
+ const id = this.dataCollectorId;
2133
+ if (!id || id === "pending") {
2134
+ this.dataCollectorId = null;
2135
+ return;
2136
+ }
2137
+ this.dataCollectorId = null;
2138
+ this.client.send("network.removeDataCollector", { collector: id }).catch(() => {
2139
+ });
2140
+ }
2141
+ handleBeforeRequestSent(params) {
2142
+ const isBlocked = params.isBlocked;
2143
+ const request = params.request;
2144
+ const requestId = request?.request;
2145
+ if (isBlocked && requestId) {
2146
+ const requestUrl = request?.url ?? "";
2147
+ const req = new Request(params, this.client);
2148
+ for (const routeEntry of this.routes) {
2149
+ if (matchPattern(routeEntry.pattern, requestUrl)) {
2150
+ const route = new Route(this.client, requestId, req);
2151
+ try {
2152
+ const result = routeEntry.handler(route);
2153
+ if (result && typeof result.catch === "function") {
2154
+ result.catch(() => {
2155
+ });
2156
+ }
2157
+ } catch (_) {
2158
+ }
2159
+ return;
2160
+ }
2161
+ }
2162
+ this.client.send("network.continueRequest", { request: requestId }).catch(() => {
2163
+ });
2164
+ } else {
2165
+ const req = new Request(params, this.client);
2166
+ for (const cb of this.requestCallbacks) {
2167
+ cb(req);
2168
+ }
2169
+ }
2170
+ }
2171
+ handleResponseCompleted(params) {
2172
+ const resp = new Response(params, this.client);
2173
+ for (const cb of this.responseCallbacks) {
2174
+ cb(resp);
2175
+ }
2176
+ }
2177
+ handleUserPromptOpened(params) {
2178
+ const dialog = new Dialog(this.client, this.contextId, params);
2179
+ if (this.dialogCallbacks.length > 0) {
2180
+ for (const cb of this.dialogCallbacks) {
2181
+ try {
2182
+ const result = cb(dialog);
2183
+ if (result && typeof result.catch === "function") {
2184
+ result.catch(() => {
2185
+ });
2186
+ }
2187
+ } catch (_) {
2188
+ }
2189
+ }
2190
+ } else {
2191
+ dialog.dismiss().catch(() => {
2192
+ });
2193
+ }
2194
+ }
2195
+ handleLogEntryAdded(params) {
2196
+ const entryType = params.type;
2197
+ if (entryType === "console") {
2198
+ const msg = new ConsoleMessage(params);
2199
+ for (const cb of this.consoleCallbacks) {
2200
+ cb(msg);
2201
+ }
2202
+ } else if (entryType === "javascript") {
2203
+ const text = params.text ?? "Unknown error";
2204
+ const error = new Error(text);
2205
+ for (const cb of this.errorCallbacks) {
2206
+ cb(error);
2207
+ }
2208
+ }
2209
+ }
2210
+ handleDownloadWillBegin(params) {
2211
+ const url = params.url ?? "";
2212
+ const suggestedFilename = params.suggestedFilename ?? "";
2213
+ const navigation = params.navigation ?? "";
2214
+ const download = new Download(this.client, url, suggestedFilename);
2215
+ if (navigation) {
2216
+ this.pendingDownloads.set(navigation, download);
2217
+ }
2218
+ for (const cb of this.downloadCallbacks) {
2219
+ cb(download);
2220
+ }
2221
+ }
2222
+ handleDownloadCompleted(params) {
2223
+ const navigation = params.navigation ?? "";
2224
+ const status = params.status ?? "complete";
2225
+ const filepath = params.filepath ?? null;
2226
+ const download = this.pendingDownloads.get(navigation);
2227
+ if (download) {
2228
+ download._complete(status, filepath);
2229
+ this.pendingDownloads.delete(navigation);
2230
+ }
2231
+ }
2232
+ handleWsCreated(params) {
2233
+ const id = params.id;
2234
+ const url = params.url;
2235
+ const ws = new WebSocketInfo(url);
2236
+ this.wsConnections.set(id, ws);
2237
+ for (const cb of this.wsCallbacks) {
2238
+ cb(ws);
2239
+ }
2240
+ }
2241
+ handleWsMessage(params) {
2242
+ const id = params.id;
2243
+ const data = params.data;
2244
+ const direction = params.direction;
2245
+ const ws = this.wsConnections.get(id);
2246
+ if (ws) {
2247
+ ws._emitMessage(data, direction);
2248
+ }
2249
+ }
2250
+ handleWsClosed(params) {
2251
+ const id = params.id;
2252
+ const code = params.code;
2253
+ const reason = params.reason;
2254
+ const ws = this.wsConnections.get(id);
2255
+ if (ws) {
2256
+ ws._emitClose(code, reason);
2257
+ this.wsConnections.delete(id);
2258
+ }
2259
+ }
2260
+ };
2261
+
2262
+ // src/browser.ts
2263
+ var customInspect3 = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
2264
+ var Browser = class {
2265
+ constructor(client, process2) {
2266
+ this.pageCallbacks = [];
2267
+ this.popupCallbacks = [];
2268
+ this.client = client;
2269
+ this.process = process2;
2270
+ this.client.onEvent((event) => {
2271
+ if (event.method === "browsingContext.contextCreated") {
2272
+ const params = event.params;
2273
+ const callbacks = params.originalOpener ? this.popupCallbacks : this.pageCallbacks;
2274
+ if (callbacks.length > 0) {
2275
+ const page = new Page(this.client, params.context, params.userContext || "default");
2276
+ for (const cb of callbacks) {
2277
+ cb(page);
2278
+ }
2279
+ }
2280
+ }
2281
+ });
2282
+ }
2283
+ [customInspect3]() {
2284
+ return "Browser { connected: true }";
2285
+ }
2286
+ /** Get the default page (first browsing context). */
2287
+ async page() {
2288
+ const result = await this.client.send("vibium:browser.page", {});
2289
+ return new Page(this.client, result.context, result.userContext);
2290
+ }
2291
+ /** Create a new page (tab) in the default context. */
2292
+ async newPage() {
2293
+ const result = await this.client.send("vibium:browser.newPage", {});
2294
+ return new Page(this.client, result.context, result.userContext);
2295
+ }
2296
+ /** Create a new browser context (isolated, incognito-like). */
2297
+ async newContext() {
2298
+ const result = await this.client.send("vibium:browser.newContext", {});
2299
+ return new BrowserContext(this.client, result.userContext);
2300
+ }
2301
+ /** Get all open pages. */
2302
+ async pages() {
2303
+ const result = await this.client.send("vibium:browser.pages", {});
2304
+ return result.pages.map((p) => new Page(this.client, p.context, p.userContext));
2305
+ }
2306
+ /** Register a callback for when a new page is created (e.g. new tab). */
2307
+ onPage(callback) {
2308
+ this.pageCallbacks.push(callback);
2309
+ }
2310
+ /** Register a callback for when a popup is opened (window.open or target=_blank). */
2311
+ onPopup(callback) {
2312
+ this.popupCallbacks.push(callback);
2313
+ }
2314
+ /**
2315
+ * Remove all listeners for a given event, or all events if no event specified.
2316
+ * Supported events: 'page', 'popup'.
2317
+ */
2318
+ removeAllListeners(event) {
2319
+ if (!event || event === "page") {
2320
+ this.pageCallbacks = [];
2321
+ }
2322
+ if (!event || event === "popup") {
2323
+ this.popupCallbacks = [];
2324
+ }
2325
+ }
2326
+ /** Stop the browser and clean up. */
2327
+ async stop() {
2328
+ try {
2329
+ await this.client.send("vibium:browser.stop", {});
2330
+ } catch(e) {
2331
+ }
2332
+ await this.client.close();
2333
+ if (this.process) {
2334
+ await this.process.stop();
2335
+ }
2336
+ }
2337
+ };
2338
+ function envHeaders() {
2339
+ const apiKey = process.env.VIBIUM_CONNECT_API_KEY;
2340
+ return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
2341
+ }
2342
+ var browser = {
2343
+ async start(urlOrOptions, options = {}) {
2344
+ let url;
2345
+ if (typeof urlOrOptions === "object") {
2346
+ options = urlOrOptions;
2347
+ url = void 0;
2348
+ } else {
2349
+ url = urlOrOptions;
2350
+ }
2351
+ const connectURL = url || process.env.VIBIUM_CONNECT_URL;
2352
+ if (connectURL) {
2353
+ const headers = { ...envHeaders(), ...options.headers };
2354
+ debug("connecting to remote browser", { url: connectURL });
2355
+ const proc2 = await VibiumProcess.start({
2356
+ connectURL,
2357
+ connectHeaders: Object.keys(headers).length ? headers : void 0,
2358
+ executablePath: options.executablePath
2359
+ });
2360
+ debug("vibium started (connect mode)");
2361
+ const client2 = BiDiClient.fromStreams(
2362
+ proc2.stdin,
2363
+ proc2.stdout,
2364
+ proc2.preReadyLines
2365
+ );
2366
+ info("browser connected (pipe \u2192 remote)");
2367
+ return new Browser(client2, proc2);
2368
+ }
2369
+ const { headless = false, executablePath } = options;
2370
+ if (process.platform === "openharmony" && !process.env.VIBIUM_CONNECT_URL) {
2371
+ process.env.VIBIUM_SKIP_BROWSER_DOWNLOAD = "1";
2372
+ const vibiumHome = process.env.HOME || "/storage/Users/currentUser";
2373
+ const hdcPath = "/data/service/hnp/bin/hdc";
2374
+ const bridgePath = require("path").join(__dirname, "bidi-bridge.js");
2375
+ const cdpPort = options.cdpPort || 9333;
2376
+ const bridgePort = options.bridgePort || 9517;
2377
+ const browserPkg = options.harmonyBundleName || "com.haitai.htbrowser";
2378
+ const browserAbility = options.harmonyAbility || "EntryAbility";
2379
+ debug("HarmonyOS: auto-starting native browser + BiDi bridge");
2380
+ try {
2381
+ const { execFileSync, spawn } = require("child_process");
2382
+ const fs = require("fs");
2383
+ const http = require("http");
2384
+ execFileSync(hdcPath, ["shell", "aa", "force-stop", browserPkg], { timeout: 1e4, stdio: "pipe" });
2385
+ const cmdArgs = `--remote-debugging-port=${cdpPort} --remote-allow-origins=http://127.0.0.1:${cdpPort} --disable-extensions --no-first-run --no-default-browser-check --disable-popup-blocking --disable-dev-shm-usage --password-store=basic --use-mock-keychain --force-color-profile=srgb`;
2386
+ execFileSync(hdcPath, ["shell", "aa", "start", "-b", browserPkg, "-a", browserAbility, "--ps", "cmdArgs", cmdArgs], { timeout: 1e4, stdio: "pipe" });
2387
+ for (let i = 0; i < 15; i++) {
2388
+ try {
2389
+ await new Promise((resolve2, reject2) => {
2390
+ const req2 = http.get(`http://127.0.0.1:${cdpPort}/json/version`, (res2) => {
2391
+ let d = ""; res2.on("data", (c) => d += c); res2.on("end", () => { try { JSON.parse(d); resolve2(); } catch { reject2(new Error("not ready")); } });
2392
+ });
2393
+ req2.on("error", reject2); req2.setTimeout(2e3, () => { req2.destroy(); reject2(new Error("timeout")); });
2394
+ });
2395
+ break;
2396
+ } catch { await new Promise((r) => setTimeout(r, 1e3)); }
2397
+ }
2398
+ debug("HarmonyOS: native browser CDP ready on port " + cdpPort);
2399
+ const bridgeProc = spawn(process.execPath, [bridgePath, `--cdp-port=${cdpPort}`, `--listen-port=${bridgePort}`], { stdio: ["pipe", "pipe", "pipe"], detached: false, env: { ...process.env, NODE_PATH: require("path").dirname(require.resolve("ws/package.json")) } });
2400
+ bridgeProc.unref();
2401
+ process.env.VIBIUM_CONNECT_URL = `ws://127.0.0.1:${bridgePort}/session`;
2402
+ for (let i = 0; i < 10; i++) {
2403
+ try {
2404
+ await new Promise((resolve3, reject3) => {
2405
+ const WebSocket2 = require("ws");
2406
+ const ws3 = new WebSocket2(`ws://127.0.0.1:${bridgePort}/session`);
2407
+ ws3.on("open", () => { ws3.close(); resolve3(); });
2408
+ ws3.on("error", reject3);
2409
+ setTimeout(() => { ws3.close(); reject3(new Error("timeout")); }, 2e3);
2410
+ });
2411
+ break;
2412
+ } catch { await new Promise((r) => setTimeout(r, 1e3)); }
2413
+ }
2414
+ debug("HarmonyOS: BiDi bridge ready, connecting vibium");
2415
+ const proc2 = await VibiumProcess.start({ connectURL: process.env.VIBIUM_CONNECT_URL, executablePath: options.executablePath });
2416
+ debug("vibium started (HarmonyOS native browser via bridge)");
2417
+ const client2 = BiDiClient.fromStreams(proc2.stdin, proc2.stdout, proc2.preReadyLines);
2418
+ info("browser launched (HarmonyOS native \u2192 bridge)");
2419
+ return new Browser(client2, proc2);
2420
+ } catch (harmonyErr) {
2421
+ throw new BrowserCrashedError(1, "[HarmonyOS] Failed to auto-start native browser + bridge: " + (harmonyErr.message || harmonyErr) + "\nTo use a remote browser instead:\n export VIBIUM_CONNECT_URL=ws://<remote-host>:9222/session");
2422
+ }
2423
+ }
2424
+ debug("launching browser", { headless, executablePath });
2425
+ let proc;
2426
+ try {
2427
+ proc = await VibiumProcess.start({
2428
+ headless,
2429
+ executablePath
2430
+ });
2431
+ } catch (err) {
2432
+ if (process.platform === "openharmony" && err instanceof BrowserCrashedError) {
2433
+ throw new BrowserCrashedError(err.exitCode, (err.output || "") + "\n\n[HarmonyOS] Browser launch failed. To use the native browser:\n Ensure hdc is available and the browser is installed.\nTo use a remote browser:\n export VIBIUM_CONNECT_URL=ws://<remote-host>:9222/session");
2434
+ }
2435
+ throw err;
2436
+ }
2437
+ debug("vibium started");
2438
+ const client = BiDiClient.fromStreams(
2439
+ proc.stdin,
2440
+ proc.stdout,
2441
+ proc.preReadyLines
2442
+ );
2443
+ info("browser launched (pipe)");
2444
+ return new Browser(client, proc);
2445
+ }
2446
+ };
2447
+
2448
+ // src/sync/worker.ts
2449
+ var { signal, callbackPort } = import_worker_threads.workerData;
2450
+ var callbackChain = Promise.resolve(void 0);
2451
+ function invokeMainThread(handlerId, data) {
2452
+ const call = callbackChain.then(async () => {
2453
+ const decisionPromise = new Promise((resolve) => {
2454
+ callbackPort.once("message", (msg) => {
2455
+ resolve(msg.decision);
2456
+ });
2457
+ });
2458
+ callbackPort.postMessage({ handlerId, data });
2459
+ Atomics.store(signal, 0, 2);
2460
+ Atomics.notify(signal, 0);
2461
+ return await decisionPromise;
2462
+ });
2463
+ callbackChain = call.then(() => {
2464
+ }, () => {
2465
+ });
2466
+ return call;
2467
+ }
2468
+ var browserInstance = null;
2469
+ var nextId = 1;
2470
+ var pages = /* @__PURE__ */ new Map();
2471
+ var contexts = /* @__PURE__ */ new Map();
2472
+ var elements = /* @__PURE__ */ new Map();
2473
+ var pageContextMap = /* @__PURE__ */ new Map();
2474
+ var defaultPageId = 0;
2475
+ function allocId() {
2476
+ return nextId++;
2477
+ }
2478
+ function getPage(id) {
2479
+ const p = pages.get(id);
2480
+ if (!p) throw new Error(`Page ${id} not found`);
2481
+ return p;
2482
+ }
2483
+ function getContext(id) {
2484
+ const c = contexts.get(id);
2485
+ if (!c) throw new Error(`Context ${id} not found`);
2486
+ return c;
2487
+ }
2488
+ function getElement(id) {
2489
+ const el = elements.get(id);
2490
+ if (!el) throw new Error(`Element ${id} not found`);
2491
+ return el;
2492
+ }
2493
+ function storePage(page) {
2494
+ const id = allocId();
2495
+ pages.set(id, page);
2496
+ const pageContext = page.context;
2497
+ let contextSyncId;
2498
+ for (const [cid, ctx] of contexts) {
2499
+ if (ctx.id === pageContext.id) {
2500
+ contextSyncId = cid;
2501
+ break;
2502
+ }
2503
+ }
2504
+ if (contextSyncId === void 0) {
2505
+ contextSyncId = storeContext(pageContext);
2506
+ }
2507
+ pageContextMap.set(id, contextSyncId);
2508
+ return id;
2509
+ }
2510
+ function storeContext(ctx) {
2511
+ const id = allocId();
2512
+ contexts.set(id, ctx);
2513
+ return id;
2514
+ }
2515
+ function storeElement(el) {
2516
+ const id = allocId();
2517
+ elements.set(id, el);
2518
+ return id;
2519
+ }
2520
+ var pageEventBuffer = [];
2521
+ var onPageHandlerId = null;
2522
+ var onPopupHandlerId = null;
2523
+ var networkEventBuffer = [];
2524
+ var onRequestHandlerIds = /* @__PURE__ */ new Map();
2525
+ var onResponseHandlerIds = /* @__PURE__ */ new Map();
2526
+ var onDownloadHandlerIds = /* @__PURE__ */ new Map();
2527
+ var onWebSocketHandlerIds = /* @__PURE__ */ new Map();
2528
+ var handlers = {
2529
+ // ========================
2530
+ // Browser commands
2531
+ // ========================
2532
+ "browser.start": async (args) => {
2533
+ const [url, options] = args;
2534
+ browserInstance = await browser.start(url, options);
2535
+ const page = await browserInstance.page();
2536
+ defaultPageId = storePage(page);
2537
+ return { pageId: defaultPageId };
2538
+ },
2539
+ "browser.stop": async () => {
2540
+ if (!browserInstance) throw new Error("Browser not launched");
2541
+ await browserInstance.stop();
2542
+ browserInstance = null;
2543
+ pages.clear();
2544
+ contexts.clear();
2545
+ elements.clear();
2546
+ return { success: true };
2547
+ },
2548
+ "browser.page": async () => {
2549
+ if (!browserInstance) throw new Error("Browser not launched");
2550
+ if (defaultPageId && pages.has(defaultPageId)) {
2551
+ return { pageId: defaultPageId };
2552
+ }
2553
+ const page = await browserInstance.page();
2554
+ defaultPageId = storePage(page);
2555
+ return { pageId: defaultPageId };
2556
+ },
2557
+ "browser.newPage": async () => {
2558
+ if (!browserInstance) throw new Error("Browser not launched");
2559
+ const page = await browserInstance.newPage();
2560
+ const id = storePage(page);
2561
+ return { pageId: id };
2562
+ },
2563
+ "browser.newContext": async () => {
2564
+ if (!browserInstance) throw new Error("Browser not launched");
2565
+ const ctx = await browserInstance.newContext();
2566
+ const id = storeContext(ctx);
2567
+ return { contextId: id };
2568
+ },
2569
+ "browser.pages": async () => {
2570
+ if (!browserInstance) throw new Error("Browser not launched");
2571
+ const allPages = await browserInstance.pages();
2572
+ const ids = [];
2573
+ for (const p of allPages) {
2574
+ let found = false;
2575
+ for (const [id, stored] of pages) {
2576
+ if (stored.id === p.id) {
2577
+ ids.push(id);
2578
+ found = true;
2579
+ break;
2580
+ }
2581
+ }
2582
+ if (!found) {
2583
+ ids.push(storePage(p));
2584
+ }
2585
+ }
2586
+ return { pageIds: ids };
2587
+ },
2588
+ "browser.onPage": async (args) => {
2589
+ if (!browserInstance) throw new Error("Browser not launched");
2590
+ const [handlerId] = args;
2591
+ onPageHandlerId = handlerId;
2592
+ const existingPages = await browserInstance.pages();
2593
+ const knownContextIds = new Set(existingPages.map((p) => p.id));
2594
+ browserInstance.removeAllListeners("page");
2595
+ await new Promise((r) => setImmediate(r));
2596
+ browserInstance.onPage((page) => {
2597
+ if (knownContextIds.has(page.id)) return;
2598
+ knownContextIds.add(page.id);
2599
+ const id = storePage(page);
2600
+ pageEventBuffer.push({ type: "page", pageId: id });
2601
+ });
2602
+ return { success: true };
2603
+ },
2604
+ "browser.onPopup": async (args) => {
2605
+ if (!browserInstance) throw new Error("Browser not launched");
2606
+ const [handlerId] = args;
2607
+ onPopupHandlerId = handlerId;
2608
+ const existingPages = await browserInstance.pages();
2609
+ const knownContextIds = new Set(existingPages.map((p) => p.id));
2610
+ browserInstance.removeAllListeners("popup");
2611
+ await new Promise((r) => setImmediate(r));
2612
+ browserInstance.onPopup((page) => {
2613
+ if (knownContextIds.has(page.id)) return;
2614
+ knownContextIds.add(page.id);
2615
+ const id = storePage(page);
2616
+ pageEventBuffer.push({ type: "popup", pageId: id });
2617
+ });
2618
+ return { success: true };
2619
+ },
2620
+ "browser.removeAllListeners": async (args) => {
2621
+ if (!browserInstance) throw new Error("Browser not launched");
2622
+ const [event] = args;
2623
+ browserInstance.removeAllListeners(event);
2624
+ if (!event || event === "page") onPageHandlerId = null;
2625
+ if (!event || event === "popup") onPopupHandlerId = null;
2626
+ return { success: true };
2627
+ },
2628
+ "browser.waitForPage": async (args) => {
2629
+ if (!browserInstance) throw new Error("Browser not launched");
2630
+ const [options] = args;
2631
+ const timeout = options?.timeout ?? 1e4;
2632
+ const page = await new Promise((resolve, reject) => {
2633
+ const timer = setTimeout(() => {
2634
+ browserInstance?.removeAllListeners("page");
2635
+ reject(new Error("Timeout waiting for new page"));
2636
+ }, timeout);
2637
+ browserInstance.onPage((p) => {
2638
+ clearTimeout(timer);
2639
+ resolve(p);
2640
+ });
2641
+ });
2642
+ const id = storePage(page);
2643
+ return { pageId: id };
2644
+ },
2645
+ "browser.waitForPopup": async (args) => {
2646
+ if (!browserInstance) throw new Error("Browser not launched");
2647
+ const [options] = args;
2648
+ const timeout = options?.timeout ?? 1e4;
2649
+ const page = await new Promise((resolve, reject) => {
2650
+ const timer = setTimeout(() => {
2651
+ browserInstance?.removeAllListeners("popup");
2652
+ reject(new Error("Timeout waiting for popup"));
2653
+ }, timeout);
2654
+ browserInstance.onPopup((p) => {
2655
+ clearTimeout(timer);
2656
+ resolve(p);
2657
+ });
2658
+ });
2659
+ const id = storePage(page);
2660
+ return { pageId: id };
2661
+ },
2662
+ // ========================
2663
+ // Page commands
2664
+ // ========================
2665
+ "page.go": async (args) => {
2666
+ const [pageId, url] = args;
2667
+ await getPage(pageId).go(url);
2668
+ return { success: true };
2669
+ },
2670
+ "page.back": async (args) => {
2671
+ const [pageId] = args;
2672
+ await getPage(pageId).back();
2673
+ return { success: true };
2674
+ },
2675
+ "page.forward": async (args) => {
2676
+ const [pageId] = args;
2677
+ await getPage(pageId).forward();
2678
+ return { success: true };
2679
+ },
2680
+ "page.reload": async (args) => {
2681
+ const [pageId] = args;
2682
+ await getPage(pageId).reload();
2683
+ return { success: true };
2684
+ },
2685
+ "page.url": async (args) => {
2686
+ const [pageId] = args;
2687
+ const url = await getPage(pageId).url();
2688
+ return { url };
2689
+ },
2690
+ "page.title": async (args) => {
2691
+ const [pageId] = args;
2692
+ const title = await getPage(pageId).title();
2693
+ return { title };
2694
+ },
2695
+ "page.content": async (args) => {
2696
+ const [pageId] = args;
2697
+ const content = await getPage(pageId).content();
2698
+ return { content };
2699
+ },
2700
+ "page.screenshot": async (args) => {
2701
+ const [pageId, options] = args;
2702
+ const buffer = await getPage(pageId).screenshot(options);
2703
+ return { data: buffer.toString("base64") };
2704
+ },
2705
+ "page.pdf": async (args) => {
2706
+ const [pageId] = args;
2707
+ const buffer = await getPage(pageId).pdf();
2708
+ return { data: buffer.toString("base64") };
2709
+ },
2710
+ "page.eval": async (args) => {
2711
+ const [pageId, expression] = args;
2712
+ const value = await getPage(pageId).evaluate(expression);
2713
+ return { value };
2714
+ },
2715
+ "page.addScript": async (args) => {
2716
+ const [pageId, source] = args;
2717
+ await getPage(pageId).addScript(source);
2718
+ return { success: true };
2719
+ },
2720
+ "page.addStyle": async (args) => {
2721
+ const [pageId, source] = args;
2722
+ await getPage(pageId).addStyle(source);
2723
+ return { success: true };
2724
+ },
2725
+ "page.expose": async (args) => {
2726
+ const [pageId, name, fn] = args;
2727
+ await getPage(pageId).expose(name, fn);
2728
+ return { success: true };
2729
+ },
2730
+ "page.find": async (args) => {
2731
+ const [pageId, selector, options] = args;
2732
+ const element = await getPage(pageId).find(selector, options);
2733
+ const elementId = storeElement(element);
2734
+ return { elementId, info: element.info };
2735
+ },
2736
+ "page.findAll": async (args) => {
2737
+ const [pageId, selector, options] = args;
2738
+ const arr = await getPage(pageId).findAll(selector, options);
2739
+ const elements2 = [];
2740
+ for (const el of arr) {
2741
+ elements2.push({ elementId: storeElement(el), info: el.info });
2742
+ }
2743
+ return { elements: elements2 };
2744
+ },
2745
+ "page.wait": async (args) => {
2746
+ const [pageId, ms] = args;
2747
+ await getPage(pageId).wait(ms);
2748
+ return { success: true };
2749
+ },
2750
+ "page.waitForURL": async (args) => {
2751
+ const [pageId, pattern, options] = args;
2752
+ await getPage(pageId).waitUntil.url(pattern, options);
2753
+ return { success: true };
2754
+ },
2755
+ "page.waitForLoad": async (args) => {
2756
+ const [pageId, state, options] = args;
2757
+ await getPage(pageId).waitUntil.loaded(state, options);
2758
+ return { success: true };
2759
+ },
2760
+ "page.waitForFunction": async (args) => {
2761
+ const [pageId, fn, options] = args;
2762
+ const value = await getPage(pageId).waitUntil(fn, options);
2763
+ return { value };
2764
+ },
2765
+ "page.bringToFront": async (args) => {
2766
+ const [pageId] = args;
2767
+ await getPage(pageId).bringToFront();
2768
+ return { success: true };
2769
+ },
2770
+ "page.close": async (args) => {
2771
+ const [pageId] = args;
2772
+ await getPage(pageId).close();
2773
+ pages.delete(pageId);
2774
+ return { success: true };
2775
+ },
2776
+ "page.scroll": async (args) => {
2777
+ const [pageId, direction, amount, selector] = args;
2778
+ await getPage(pageId).scroll(direction, amount, selector);
2779
+ return { success: true };
2780
+ },
2781
+ // --- Page emulation ---
2782
+ "page.setViewport": async (args) => {
2783
+ const [pageId, size] = args;
2784
+ await getPage(pageId).setViewport(size);
2785
+ return { success: true };
2786
+ },
2787
+ "page.viewport": async (args) => {
2788
+ const [pageId] = args;
2789
+ return await getPage(pageId).viewport();
2790
+ },
2791
+ "page.emulateMedia": async (args) => {
2792
+ const [pageId, opts] = args;
2793
+ await getPage(pageId).emulateMedia(opts);
2794
+ return { success: true };
2795
+ },
2796
+ "page.setContent": async (args) => {
2797
+ const [pageId, html] = args;
2798
+ await getPage(pageId).setContent(html);
2799
+ return { success: true };
2800
+ },
2801
+ "page.setGeolocation": async (args) => {
2802
+ const [pageId, coords] = args;
2803
+ await getPage(pageId).setGeolocation(coords);
2804
+ return { success: true };
2805
+ },
2806
+ "page.setWindow": async (args) => {
2807
+ const [pageId, options] = args;
2808
+ await getPage(pageId).setWindow(options);
2809
+ return { success: true };
2810
+ },
2811
+ "page.window": async (args) => {
2812
+ const [pageId] = args;
2813
+ return await getPage(pageId).window();
2814
+ },
2815
+ // --- Page frames ---
2816
+ "page.frames": async (args) => {
2817
+ const [pageId] = args;
2818
+ const frames = await getPage(pageId).frames();
2819
+ const frameIds = frames.map((f) => storePage(f));
2820
+ return { frameIds };
2821
+ },
2822
+ "page.frame": async (args) => {
2823
+ const [pageId, nameOrUrl] = args;
2824
+ const frame = await getPage(pageId).frame(nameOrUrl);
2825
+ if (!frame) return { frameId: null };
2826
+ const id = storePage(frame);
2827
+ return { frameId: id };
2828
+ },
2829
+ "page.mainFrame": async (args) => {
2830
+ const [pageId] = args;
2831
+ return { frameId: pageId };
2832
+ },
2833
+ // --- Page accessibility ---
2834
+ "page.a11yTree": async (args) => {
2835
+ const [pageId, options] = args;
2836
+ const tree = await getPage(pageId).a11yTree(options);
2837
+ return { tree };
2838
+ },
2839
+ "page.context": async (args) => {
2840
+ const [pageId] = args;
2841
+ const contextId = pageContextMap.get(pageId);
2842
+ if (contextId === void 0) throw new Error(`No context found for page ${pageId}`);
2843
+ return { contextId };
2844
+ },
2845
+ // --- Page network ---
2846
+ "page.route": async (args) => {
2847
+ const [pageId, pattern, action] = args;
2848
+ const page = getPage(pageId);
2849
+ await page.route(pattern, (route) => {
2850
+ if (action === "continue") {
2851
+ route.continue();
2852
+ } else if (action === "abort") {
2853
+ route.abort();
2854
+ } else if (typeof action === "object") {
2855
+ route.fulfill(action);
2856
+ }
2857
+ });
2858
+ return { success: true };
2859
+ },
2860
+ "page.routeWithCallback": async (args) => {
2861
+ const [pageId, pattern, handlerId] = args;
2862
+ const page = getPage(pageId);
2863
+ await page.route(pattern, async (route) => {
2864
+ let postData = null;
2865
+ try {
2866
+ postData = await Promise.race([
2867
+ route.request.postData(),
2868
+ new Promise((resolve) => setTimeout(() => resolve(null), 500))
2869
+ ]);
2870
+ } catch(e) {
2871
+ }
2872
+ const requestData = {
2873
+ url: route.request.url(),
2874
+ method: route.request.method(),
2875
+ headers: route.request.headers(),
2876
+ postData
2877
+ };
2878
+ const decision = await invokeMainThread(handlerId, requestData);
2879
+ if (!decision || decision.action === "continue") {
2880
+ const overrides = {};
2881
+ if (decision?.url) overrides.url = decision.url;
2882
+ if (decision?.method) overrides.method = decision.method;
2883
+ if (decision?.headers) overrides.headers = decision.headers;
2884
+ if (decision?.postData) overrides.postData = decision.postData;
2885
+ await route.continue(Object.keys(overrides).length > 0 ? overrides : void 0);
2886
+ } else if (decision.action === "fulfill") {
2887
+ await route.fulfill({
2888
+ status: decision.status,
2889
+ headers: decision.headers,
2890
+ contentType: decision.contentType,
2891
+ body: decision.body
2892
+ });
2893
+ } else if (decision.action === "abort") {
2894
+ await route.abort();
2895
+ }
2896
+ });
2897
+ return { success: true };
2898
+ },
2899
+ "page.unroute": async (args) => {
2900
+ const [pageId, pattern] = args;
2901
+ await getPage(pageId).unroute(pattern);
2902
+ return { success: true };
2903
+ },
2904
+ "page.setHeaders": async (args) => {
2905
+ const [pageId, headers] = args;
2906
+ await getPage(pageId).setHeaders(headers);
2907
+ return { success: true };
2908
+ },
2909
+ "page.waitForRequest": async (args) => {
2910
+ const [pageId, pattern, options] = args;
2911
+ const page = getPage(pageId);
2912
+ const request = await page.capture.request(pattern, void 0, options);
2913
+ return {
2914
+ url: request.url(),
2915
+ method: request.method(),
2916
+ headers: request.headers(),
2917
+ postData: await request.postData()
2918
+ };
2919
+ },
2920
+ "page.waitForResponse": async (args) => {
2921
+ const [pageId, pattern, options] = args;
2922
+ const page = getPage(pageId);
2923
+ const response = await page.capture.response(pattern, void 0, options);
2924
+ return {
2925
+ url: response.url(),
2926
+ status: response.status(),
2927
+ headers: response.headers(),
2928
+ body: await response.body()
2929
+ };
2930
+ },
2931
+ "page.captureResponseStart": async (args) => {
2932
+ const [pageId, pattern, options] = args;
2933
+ const page = getPage(pageId);
2934
+ page._pendingCaptureResponse = page.capture.response(pattern, void 0, options);
2935
+ return { success: true };
2936
+ },
2937
+ "page.captureResponseFinish": async (args) => {
2938
+ const [pageId] = args;
2939
+ const page = getPage(pageId);
2940
+ const response = await page._pendingCaptureResponse;
2941
+ delete page._pendingCaptureResponse;
2942
+ return {
2943
+ url: response.url(),
2944
+ status: response.status(),
2945
+ headers: response.headers(),
2946
+ body: await response.body()
2947
+ };
2948
+ },
2949
+ "page.captureRequestStart": async (args) => {
2950
+ const [pageId, pattern, options] = args;
2951
+ const page = getPage(pageId);
2952
+ page._pendingCaptureRequest = page.capture.request(pattern, void 0, options);
2953
+ return { success: true };
2954
+ },
2955
+ "page.captureRequestFinish": async (args) => {
2956
+ const [pageId] = args;
2957
+ const page = getPage(pageId);
2958
+ const request = await page._pendingCaptureRequest;
2959
+ delete page._pendingCaptureRequest;
2960
+ return {
2961
+ url: request.url(),
2962
+ method: request.method(),
2963
+ headers: request.headers(),
2964
+ postData: await request.postData()
2965
+ };
2966
+ },
2967
+ "page.captureNavigationStart": async (args) => {
2968
+ const [pageId, options] = args;
2969
+ const page = getPage(pageId);
2970
+ page._pendingCaptureNavigation = page.capture.navigation(void 0, options);
2971
+ return { success: true };
2972
+ },
2973
+ "page.captureNavigationFinish": async (args) => {
2974
+ const [pageId] = args;
2975
+ const page = getPage(pageId);
2976
+ const url = await page._pendingCaptureNavigation;
2977
+ delete page._pendingCaptureNavigation;
2978
+ return { url };
2979
+ },
2980
+ "page.captureDownloadStart": async (args) => {
2981
+ const [pageId, options] = args;
2982
+ const page = getPage(pageId);
2983
+ page._pendingCaptureDownload = page.capture.download(void 0, options);
2984
+ return { success: true };
2985
+ },
2986
+ "page.captureDownloadFinish": async (args) => {
2987
+ const [pageId] = args;
2988
+ const page = getPage(pageId);
2989
+ const download = await page._pendingCaptureDownload;
2990
+ delete page._pendingCaptureDownload;
2991
+ const filePath = await download.path();
2992
+ return {
2993
+ url: download.url(),
2994
+ suggestedFilename: download.suggestedFilename(),
2995
+ path: filePath
2996
+ };
2997
+ },
2998
+ "page.captureDialogStart": async (args) => {
2999
+ const [pageId, options] = args;
3000
+ const page = getPage(pageId);
3001
+ page._pendingCaptureDialog = page.capture.dialog(void 0, options);
3002
+ return { success: true };
3003
+ },
3004
+ "page.captureDialogFinish": async (args) => {
3005
+ const [pageId] = args;
3006
+ const page = getPage(pageId);
3007
+ const dialog = await page._pendingCaptureDialog;
3008
+ delete page._pendingCaptureDialog;
3009
+ return {
3010
+ type: dialog.type(),
3011
+ message: dialog.message(),
3012
+ defaultValue: dialog.defaultValue()
3013
+ };
3014
+ },
3015
+ "page.captureEventStart": async (args) => {
3016
+ const [pageId, name, options] = args;
3017
+ const page = getPage(pageId);
3018
+ page._pendingCaptureEvent = page.capture.event(name, void 0, options);
3019
+ return { success: true };
3020
+ },
3021
+ "page.captureEventFinish": async (args) => {
3022
+ const [pageId] = args;
3023
+ const page = getPage(pageId);
3024
+ const data = await page._pendingCaptureEvent;
3025
+ delete page._pendingCaptureEvent;
3026
+ if (data && typeof data === "object") {
3027
+ if ("url" in data && "status" in data) {
3028
+ return { url: data.url(), status: data.status(), headers: data.headers() };
3029
+ }
3030
+ if ("url" in data && "method" in data) {
3031
+ return { url: data.url(), method: data.method(), headers: data.headers() };
3032
+ }
3033
+ if ("type" in data && "message" in data) {
3034
+ return { type: data.type(), message: data.message(), defaultValue: data.defaultValue() };
3035
+ }
3036
+ }
3037
+ return { data };
3038
+ },
3039
+ // --- Page events (simplified for sync) ---
3040
+ "page.onDialog": async (args) => {
3041
+ const [pageId, action] = args;
3042
+ const page = getPage(pageId);
3043
+ page.onDialog((dialog) => {
3044
+ if (action === "accept") {
3045
+ dialog.accept().catch(() => {
3046
+ });
3047
+ } else {
3048
+ dialog.dismiss().catch(() => {
3049
+ });
3050
+ }
3051
+ });
3052
+ return { success: true };
3053
+ },
3054
+ "page.onDialogWithCallback": async (args) => {
3055
+ const [pageId, handlerId] = args;
3056
+ const page = getPage(pageId);
3057
+ page.onDialog(async (dialog) => {
3058
+ const dialogData = {
3059
+ type: dialog.type(),
3060
+ message: dialog.message(),
3061
+ defaultValue: dialog.defaultValue()
3062
+ };
3063
+ const decision = await invokeMainThread(handlerId, dialogData);
3064
+ if (!decision || decision.action === "dismiss") {
3065
+ await dialog.dismiss();
3066
+ } else if (decision.action === "accept") {
3067
+ await dialog.accept(decision.promptText);
3068
+ }
3069
+ });
3070
+ return { success: true };
3071
+ },
3072
+ "page.onConsole": async (args) => {
3073
+ const [pageId, mode] = args;
3074
+ if (mode !== "collect") throw new Error('Only "collect" mode supported');
3075
+ const page = getPage(pageId);
3076
+ if (!page._syncConsoleBuffer) {
3077
+ page._syncConsoleBuffer = [];
3078
+ page.onConsole((msg) => {
3079
+ page._syncConsoleBuffer.push({
3080
+ type: msg.type(),
3081
+ text: msg.text()
3082
+ });
3083
+ });
3084
+ }
3085
+ return { success: true };
3086
+ },
3087
+ "page.consoleMessages": async (args) => {
3088
+ const [pageId] = args;
3089
+ const page = getPage(pageId);
3090
+ const buffer = page._syncConsoleBuffer || [];
3091
+ page._syncConsoleBuffer = [];
3092
+ return { messages: buffer };
3093
+ },
3094
+ "page.onError": async (args) => {
3095
+ const [pageId, mode] = args;
3096
+ if (mode !== "collect") throw new Error('Only "collect" mode supported');
3097
+ const page = getPage(pageId);
3098
+ if (!page._syncErrorBuffer) {
3099
+ page._syncErrorBuffer = [];
3100
+ page.onError((error) => {
3101
+ page._syncErrorBuffer.push({
3102
+ message: error.message
3103
+ });
3104
+ });
3105
+ }
3106
+ return { success: true };
3107
+ },
3108
+ "page.errors": async (args) => {
3109
+ const [pageId] = args;
3110
+ const page = getPage(pageId);
3111
+ const buffer = page._syncErrorBuffer || [];
3112
+ page._syncErrorBuffer = [];
3113
+ return { errors: buffer };
3114
+ },
3115
+ "page.onRequestWithCallback": async (args) => {
3116
+ const [pageId, handlerId] = args;
3117
+ const page = getPage(pageId);
3118
+ onRequestHandlerIds.set(pageId, handlerId);
3119
+ page.onRequest(async (req) => {
3120
+ const hid = onRequestHandlerIds.get(pageId);
3121
+ if (!hid) return;
3122
+ networkEventBuffer.push({
3123
+ handlerId: hid,
3124
+ data: { url: req.url(), method: req.method(), headers: req.headers(), postData: await req.postData() }
3125
+ });
3126
+ });
3127
+ return { success: true };
3128
+ },
3129
+ "page.onResponseWithCallback": async (args) => {
3130
+ const [pageId, handlerId] = args;
3131
+ const page = getPage(pageId);
3132
+ onResponseHandlerIds.set(pageId, handlerId);
3133
+ page.onResponse(async (resp) => {
3134
+ const hid = onResponseHandlerIds.get(pageId);
3135
+ if (!hid) return;
3136
+ networkEventBuffer.push({
3137
+ handlerId: hid,
3138
+ data: { url: resp.url(), status: resp.status(), headers: resp.headers(), body: await resp.body() }
3139
+ });
3140
+ });
3141
+ return { success: true };
3142
+ },
3143
+ "page.onDownloadWithCallback": async (args) => {
3144
+ const [pageId, handlerId] = args;
3145
+ const page = getPage(pageId);
3146
+ onDownloadHandlerIds.set(pageId, handlerId);
3147
+ page.onDownload(async (dl) => {
3148
+ const hid = onDownloadHandlerIds.get(pageId);
3149
+ if (!hid) return;
3150
+ const filePath = await dl.path();
3151
+ networkEventBuffer.push({
3152
+ handlerId: hid,
3153
+ data: { url: dl.url(), suggestedFilename: dl.suggestedFilename(), path: filePath }
3154
+ });
3155
+ });
3156
+ return { success: true };
3157
+ },
3158
+ "page.onWebSocketWithCallback": async (args) => {
3159
+ const [pageId, handlerId] = args;
3160
+ const page = getPage(pageId);
3161
+ onWebSocketHandlerIds.set(pageId, handlerId);
3162
+ let nextWsId = 0;
3163
+ page.onWebSocket((ws) => {
3164
+ const hid = onWebSocketHandlerIds.get(pageId);
3165
+ if (!hid) return;
3166
+ const wsId = nextWsId++;
3167
+ networkEventBuffer.push({
3168
+ handlerId: hid,
3169
+ data: { type: "created", wsId, url: ws.url() }
3170
+ });
3171
+ ws.onMessage((data, info2) => {
3172
+ const hid2 = onWebSocketHandlerIds.get(pageId);
3173
+ if (!hid2) return;
3174
+ networkEventBuffer.push({
3175
+ handlerId: hid2,
3176
+ data: { type: "message", wsId, data, direction: info2.direction }
3177
+ });
3178
+ });
3179
+ ws.onClose((code, reason) => {
3180
+ const hid2 = onWebSocketHandlerIds.get(pageId);
3181
+ if (!hid2) return;
3182
+ networkEventBuffer.push({
3183
+ handlerId: hid2,
3184
+ data: { type: "close", wsId, code, reason }
3185
+ });
3186
+ });
3187
+ });
3188
+ return { success: true };
3189
+ },
3190
+ "page.removeAllListeners": async (args) => {
3191
+ const [pageId, event] = args;
3192
+ getPage(pageId).removeAllListeners(event);
3193
+ if (!event || event === "request") onRequestHandlerIds.delete(pageId);
3194
+ if (!event || event === "response") onResponseHandlerIds.delete(pageId);
3195
+ if (!event || event === "download") onDownloadHandlerIds.delete(pageId);
3196
+ if (!event || event === "websocket") onWebSocketHandlerIds.delete(pageId);
3197
+ return { success: true };
3198
+ },
3199
+ // ========================
3200
+ // Context commands
3201
+ // ========================
3202
+ "context.newPage": async (args) => {
3203
+ const [contextId] = args;
3204
+ const ctx = getContext(contextId);
3205
+ const page = await ctx.newPage();
3206
+ const id = storePage(page);
3207
+ return { pageId: id };
3208
+ },
3209
+ "context.close": async (args) => {
3210
+ const [contextId] = args;
3211
+ await getContext(contextId).close();
3212
+ contexts.delete(contextId);
3213
+ return { success: true };
3214
+ },
3215
+ "context.cookies": async (args) => {
3216
+ const [contextId, urls] = args;
3217
+ const cookies = await getContext(contextId).cookies(urls);
3218
+ return { cookies };
3219
+ },
3220
+ "context.setCookies": async (args) => {
3221
+ const [contextId, cookies] = args;
3222
+ await getContext(contextId).setCookies(cookies);
3223
+ return { success: true };
3224
+ },
3225
+ "context.clearCookies": async (args) => {
3226
+ const [contextId] = args;
3227
+ await getContext(contextId).clearCookies();
3228
+ return { success: true };
3229
+ },
3230
+ "context.storage": async (args) => {
3231
+ const [contextId] = args;
3232
+ return await getContext(contextId).storage();
3233
+ },
3234
+ "context.setStorage": async (args) => {
3235
+ const [contextId, state] = args;
3236
+ await getContext(contextId).setStorage(state);
3237
+ return { success: true };
3238
+ },
3239
+ "context.clearStorage": async (args) => {
3240
+ const [contextId] = args;
3241
+ await getContext(contextId).clearStorage();
3242
+ return { success: true };
3243
+ },
3244
+ "context.addInitScript": async (args) => {
3245
+ const [contextId, script] = args;
3246
+ const result = await getContext(contextId).addInitScript(script);
3247
+ return { script: result };
3248
+ },
3249
+ // ========================
3250
+ // Recording commands (context-scoped)
3251
+ // ========================
3252
+ "recording.start": async (args) => {
3253
+ const [contextId, options] = args;
3254
+ await getContext(contextId).recording.start(options);
3255
+ return { success: true };
3256
+ },
3257
+ "recording.stop": async (args) => {
3258
+ const [contextId, options] = args;
3259
+ const buffer = await getContext(contextId).recording.stop(options);
3260
+ return { data: buffer.toString("base64") };
3261
+ },
3262
+ "recording.startChunk": async (args) => {
3263
+ const [contextId, options] = args;
3264
+ await getContext(contextId).recording.startChunk(options);
3265
+ return { success: true };
3266
+ },
3267
+ "recording.stopChunk": async (args) => {
3268
+ const [contextId, options] = args;
3269
+ const buffer = await getContext(contextId).recording.stopChunk(options);
3270
+ return { data: buffer.toString("base64") };
3271
+ },
3272
+ "recording.startGroup": async (args) => {
3273
+ const [contextId, name, options] = args;
3274
+ await getContext(contextId).recording.startGroup(name, options);
3275
+ return { success: true };
3276
+ },
3277
+ "recording.stopGroup": async (args) => {
3278
+ const [contextId] = args;
3279
+ await getContext(contextId).recording.stopGroup();
3280
+ return { success: true };
3281
+ },
3282
+ // ========================
3283
+ // Clock commands (page-scoped)
3284
+ // ========================
3285
+ "clock.install": async (args) => {
3286
+ const [pageId, options] = args;
3287
+ await getPage(pageId).clock.install(options);
3288
+ return { success: true };
3289
+ },
3290
+ "clock.fastForward": async (args) => {
3291
+ const [pageId, ticks] = args;
3292
+ await getPage(pageId).clock.fastForward(ticks);
3293
+ return { success: true };
3294
+ },
3295
+ "clock.runFor": async (args) => {
3296
+ const [pageId, ticks] = args;
3297
+ await getPage(pageId).clock.runFor(ticks);
3298
+ return { success: true };
3299
+ },
3300
+ "clock.pauseAt": async (args) => {
3301
+ const [pageId, time] = args;
3302
+ await getPage(pageId).clock.pauseAt(time);
3303
+ return { success: true };
3304
+ },
3305
+ "clock.resume": async (args) => {
3306
+ const [pageId] = args;
3307
+ await getPage(pageId).clock.resume();
3308
+ return { success: true };
3309
+ },
3310
+ "clock.setFixedTime": async (args) => {
3311
+ const [pageId, time] = args;
3312
+ await getPage(pageId).clock.setFixedTime(time);
3313
+ return { success: true };
3314
+ },
3315
+ "clock.setSystemTime": async (args) => {
3316
+ const [pageId, time] = args;
3317
+ await getPage(pageId).clock.setSystemTime(time);
3318
+ return { success: true };
3319
+ },
3320
+ "clock.setTimezone": async (args) => {
3321
+ const [pageId, timezone] = args;
3322
+ await getPage(pageId).clock.setTimezone(timezone);
3323
+ return { success: true };
3324
+ },
3325
+ // ========================
3326
+ // Keyboard commands (page-scoped)
3327
+ // ========================
3328
+ "keyboard.press": async (args) => {
3329
+ const [pageId, key] = args;
3330
+ await getPage(pageId).keyboard.press(key);
3331
+ return { success: true };
3332
+ },
3333
+ "keyboard.down": async (args) => {
3334
+ const [pageId, key] = args;
3335
+ await getPage(pageId).keyboard.down(key);
3336
+ return { success: true };
3337
+ },
3338
+ "keyboard.up": async (args) => {
3339
+ const [pageId, key] = args;
3340
+ await getPage(pageId).keyboard.up(key);
3341
+ return { success: true };
3342
+ },
3343
+ "keyboard.type": async (args) => {
3344
+ const [pageId, text] = args;
3345
+ await getPage(pageId).keyboard.type(text);
3346
+ return { success: true };
3347
+ },
3348
+ // ========================
3349
+ // Mouse commands (page-scoped)
3350
+ // ========================
3351
+ "mouse.click": async (args) => {
3352
+ const [pageId, x, y] = args;
3353
+ await getPage(pageId).mouse.click(x, y);
3354
+ return { success: true };
3355
+ },
3356
+ "mouse.move": async (args) => {
3357
+ const [pageId, x, y] = args;
3358
+ await getPage(pageId).mouse.move(x, y);
3359
+ return { success: true };
3360
+ },
3361
+ "mouse.down": async (args) => {
3362
+ const [pageId] = args;
3363
+ await getPage(pageId).mouse.down();
3364
+ return { success: true };
3365
+ },
3366
+ "mouse.up": async (args) => {
3367
+ const [pageId] = args;
3368
+ await getPage(pageId).mouse.up();
3369
+ return { success: true };
3370
+ },
3371
+ "mouse.wheel": async (args) => {
3372
+ const [pageId, deltaX, deltaY] = args;
3373
+ await getPage(pageId).mouse.wheel(deltaX, deltaY);
3374
+ return { success: true };
3375
+ },
3376
+ // ========================
3377
+ // Touch commands (page-scoped)
3378
+ // ========================
3379
+ "touch.tap": async (args) => {
3380
+ const [pageId, x, y] = args;
3381
+ await getPage(pageId).touch.tap(x, y);
3382
+ return { success: true };
3383
+ },
3384
+ // ========================
3385
+ // Element commands
3386
+ // ========================
3387
+ "element.click": async (args) => {
3388
+ const [elementId, options] = args;
3389
+ await getElement(elementId).click(options);
3390
+ return { success: true };
3391
+ },
3392
+ "element.dblclick": async (args) => {
3393
+ const [elementId, options] = args;
3394
+ await getElement(elementId).dblclick(options);
3395
+ return { success: true };
3396
+ },
3397
+ "element.fill": async (args) => {
3398
+ const [elementId, value, options] = args;
3399
+ await getElement(elementId).fill(value, options);
3400
+ return { success: true };
3401
+ },
3402
+ "element.type": async (args) => {
3403
+ const [elementId, text, options] = args;
3404
+ await getElement(elementId).type(text, options);
3405
+ return { success: true };
3406
+ },
3407
+ "element.press": async (args) => {
3408
+ const [elementId, key, options] = args;
3409
+ await getElement(elementId).press(key, options);
3410
+ return { success: true };
3411
+ },
3412
+ "element.clear": async (args) => {
3413
+ const [elementId, options] = args;
3414
+ await getElement(elementId).clear(options);
3415
+ return { success: true };
3416
+ },
3417
+ "element.check": async (args) => {
3418
+ const [elementId, options] = args;
3419
+ await getElement(elementId).check(options);
3420
+ return { success: true };
3421
+ },
3422
+ "element.uncheck": async (args) => {
3423
+ const [elementId, options] = args;
3424
+ await getElement(elementId).uncheck(options);
3425
+ return { success: true };
3426
+ },
3427
+ "element.selectOption": async (args) => {
3428
+ const [elementId, value, options] = args;
3429
+ await getElement(elementId).selectOption(value, options);
3430
+ return { success: true };
3431
+ },
3432
+ "element.hover": async (args) => {
3433
+ const [elementId, options] = args;
3434
+ await getElement(elementId).hover(options);
3435
+ return { success: true };
3436
+ },
3437
+ "element.focus": async (args) => {
3438
+ const [elementId, options] = args;
3439
+ await getElement(elementId).focus(options);
3440
+ return { success: true };
3441
+ },
3442
+ "element.dragTo": async (args) => {
3443
+ const [elementId, targetId, options] = args;
3444
+ const target = getElement(targetId);
3445
+ await getElement(elementId).dragTo(target, options);
3446
+ return { success: true };
3447
+ },
3448
+ "element.tap": async (args) => {
3449
+ const [elementId, options] = args;
3450
+ await getElement(elementId).tap(options);
3451
+ return { success: true };
3452
+ },
3453
+ "element.scrollIntoView": async (args) => {
3454
+ const [elementId, options] = args;
3455
+ await getElement(elementId).scrollIntoView(options);
3456
+ return { success: true };
3457
+ },
3458
+ "element.dispatchEvent": async (args) => {
3459
+ const [elementId, eventType, eventInit, options] = args;
3460
+ await getElement(elementId).dispatchEvent(eventType, eventInit, options);
3461
+ return { success: true };
3462
+ },
3463
+ "element.setFiles": async (args) => {
3464
+ const [elementId, files, options] = args;
3465
+ await getElement(elementId).setFiles(files, options);
3466
+ return { success: true };
3467
+ },
3468
+ "element.text": async (args) => {
3469
+ const [elementId] = args;
3470
+ const text = await getElement(elementId).text();
3471
+ return { text };
3472
+ },
3473
+ "element.innerText": async (args) => {
3474
+ const [elementId] = args;
3475
+ const text = await getElement(elementId).innerText();
3476
+ return { text };
3477
+ },
3478
+ "element.html": async (args) => {
3479
+ const [elementId] = args;
3480
+ const html = await getElement(elementId).html();
3481
+ return { html };
3482
+ },
3483
+ "element.value": async (args) => {
3484
+ const [elementId] = args;
3485
+ const value = await getElement(elementId).value();
3486
+ return { value };
3487
+ },
3488
+ "element.attr": async (args) => {
3489
+ const [elementId, name] = args;
3490
+ const value = await getElement(elementId).attr(name);
3491
+ return { value };
3492
+ },
3493
+ "element.getAttribute": async (args) => {
3494
+ const [elementId, name] = args;
3495
+ const value = await getElement(elementId).getAttribute(name);
3496
+ return { value };
3497
+ },
3498
+ "element.bounds": async (args) => {
3499
+ const [elementId] = args;
3500
+ const box = await getElement(elementId).bounds();
3501
+ return { box };
3502
+ },
3503
+ "element.boundingBox": async (args) => {
3504
+ const [elementId] = args;
3505
+ const box = await getElement(elementId).boundingBox();
3506
+ return { box };
3507
+ },
3508
+ "element.isVisible": async (args) => {
3509
+ const [elementId] = args;
3510
+ const visible = await getElement(elementId).isVisible();
3511
+ return { visible };
3512
+ },
3513
+ "element.isHidden": async (args) => {
3514
+ const [elementId] = args;
3515
+ const hidden = await getElement(elementId).isHidden();
3516
+ return { hidden };
3517
+ },
3518
+ "element.isEnabled": async (args) => {
3519
+ const [elementId] = args;
3520
+ const enabled = await getElement(elementId).isEnabled();
3521
+ return { enabled };
3522
+ },
3523
+ "element.isChecked": async (args) => {
3524
+ const [elementId] = args;
3525
+ const checked = await getElement(elementId).isChecked();
3526
+ return { checked };
3527
+ },
3528
+ "element.isEditable": async (args) => {
3529
+ const [elementId] = args;
3530
+ const editable = await getElement(elementId).isEditable();
3531
+ return { editable };
3532
+ },
3533
+ "element.role": async (args) => {
3534
+ const [elementId] = args;
3535
+ const role = await getElement(elementId).role();
3536
+ return { role };
3537
+ },
3538
+ "element.label": async (args) => {
3539
+ const [elementId] = args;
3540
+ const label = await getElement(elementId).label();
3541
+ return { label };
3542
+ },
3543
+ "element.screenshot": async (args) => {
3544
+ const [elementId] = args;
3545
+ const buffer = await getElement(elementId).screenshot();
3546
+ return { data: buffer.toString("base64") };
3547
+ },
3548
+ "element.waitUntil": async (args) => {
3549
+ const [elementId, state, options] = args;
3550
+ await getElement(elementId).waitUntil(state, options);
3551
+ return { success: true };
3552
+ },
3553
+ "element.find": async (args) => {
3554
+ const [elementId, selector, options] = args;
3555
+ const child = await getElement(elementId).find(selector, options);
3556
+ const childId = storeElement(child);
3557
+ return { elementId: childId, info: child.info };
3558
+ },
3559
+ "element.findAll": async (args) => {
3560
+ const [elementId, selector, options] = args;
3561
+ const arr = await getElement(elementId).findAll(selector, options);
3562
+ const elements2 = [];
3563
+ for (const el of arr) {
3564
+ elements2.push({ elementId: storeElement(el), info: el.info });
3565
+ }
3566
+ return { elements: elements2 };
3567
+ },
3568
+ // ========================
3569
+ // Quit
3570
+ // ========================
3571
+ "quit": async () => {
3572
+ if (!browserInstance) throw new Error("Browser not launched");
3573
+ await browserInstance.stop();
3574
+ browserInstance = null;
3575
+ pages.clear();
3576
+ contexts.clear();
3577
+ elements.clear();
3578
+ return { success: true };
3579
+ }
3580
+ };
3581
+ async function handleCommand(cmd) {
3582
+ const handler = handlers[cmd.method];
3583
+ if (!handler) {
3584
+ throw new Error(`Unknown method: ${cmd.method}`);
3585
+ }
3586
+ return handler(cmd.args);
3587
+ }
3588
+ import_worker_threads.parentPort.on("message", async ({ cmd, port }) => {
3589
+ let result;
3590
+ let error = null;
3591
+ try {
3592
+ result = await handleCommand(cmd);
3593
+ } catch (err) {
3594
+ error = err instanceof Error ? err.message : String(err);
3595
+ }
3596
+ if (onPageHandlerId || onPopupHandlerId || networkEventBuffer.length > 0) {
3597
+ await new Promise((r) => setImmediate(r));
3598
+ }
3599
+ while (pageEventBuffer.length > 0) {
3600
+ const event = pageEventBuffer.shift();
3601
+ const handlerId = event.type === "page" ? onPageHandlerId : onPopupHandlerId;
3602
+ if (handlerId) {
3603
+ await invokeMainThread(handlerId, { pageId: event.pageId });
3604
+ }
3605
+ }
3606
+ while (networkEventBuffer.length > 0) {
3607
+ const event = networkEventBuffer.shift();
3608
+ await invokeMainThread(event.handlerId, event.data);
3609
+ }
3610
+ port.postMessage({ result, error });
3611
+ Atomics.store(signal, 0, 1);
3612
+ Atomics.notify(signal, 0);
3613
+ });
3614
+ import_worker_threads.parentPort.on("message", async ({ cmd, port }) => {
3615
+ let result;
3616
+ let error = null;
3617
+ try {
3618
+ result = await handleCommand(cmd);
3619
+ } catch (err) {
3620
+ error = err instanceof Error ? err.message : String(err);
3621
+ }
3622
+ if (onPageHandlerId || onPopupHandlerId || networkEventBuffer.length > 0) {
3623
+ await new Promise((r) => setImmediate(r));
3624
+ }
3625
+ while (pageEventBuffer.length > 0) {
3626
+ const event = pageEventBuffer.shift();
3627
+ const handlerId = event.type === "page" ? onPageHandlerId : onPopupHandlerId;
3628
+ if (handlerId) {
3629
+ await invokeMainThread(handlerId, { pageId: event.pageId });
3630
+ }
3631
+ }
3632
+ while (networkEventBuffer.length > 0) {
3633
+ const event = networkEventBuffer.shift();
3634
+ await invokeMainThread(event.handlerId, event.data);
3635
+ }
3636
+ port.postMessage({ result, error });
3637
+ Atomics.store(signal, 0, 1);
3638
+ Atomics.notify(signal, 0);
3639
+ });