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