agent-browser 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/bin/agent-browser-win32-x64.exe +0 -0
- package/dist/actions.d.ts +11 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +87 -8
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +107 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +328 -29
- package/dist/browser.js.map +1 -1
- package/dist/daemon.d.ts +8 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +49 -1
- package/dist/daemon.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +48 -0
- package/dist/protocol.js.map +1 -1
- package/dist/stream-server.d.ts +111 -0
- package/dist/stream-server.d.ts.map +1 -0
- package/dist/stream-server.js +266 -0
- package/dist/stream-server.js.map +1 -0
- package/dist/types.d.ts +54 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
package/dist/browser.d.ts
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { devices, type Browser, type Page, type Frame, type Locator } from 'playwright-core';
|
|
1
|
+
import { devices, type Browser, type Page, type Frame, type Locator, type CDPSession } from 'playwright-core';
|
|
2
2
|
import type { LaunchCommand } from './types.js';
|
|
3
3
|
import { type RefMap, type EnhancedSnapshot } from './snapshot.js';
|
|
4
|
+
export interface ScreencastFrame {
|
|
5
|
+
data: string;
|
|
6
|
+
metadata: {
|
|
7
|
+
offsetTop: number;
|
|
8
|
+
pageScaleFactor: number;
|
|
9
|
+
deviceWidth: number;
|
|
10
|
+
deviceHeight: number;
|
|
11
|
+
scrollOffsetX: number;
|
|
12
|
+
scrollOffsetY: number;
|
|
13
|
+
timestamp?: number;
|
|
14
|
+
};
|
|
15
|
+
sessionId: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ScreencastOptions {
|
|
18
|
+
format?: 'jpeg' | 'png';
|
|
19
|
+
quality?: number;
|
|
20
|
+
maxWidth?: number;
|
|
21
|
+
maxHeight?: number;
|
|
22
|
+
everyNthFrame?: number;
|
|
23
|
+
}
|
|
4
24
|
interface TrackedRequest {
|
|
5
25
|
url: string;
|
|
6
26
|
method: string;
|
|
@@ -22,6 +42,8 @@ interface PageError {
|
|
|
22
42
|
*/
|
|
23
43
|
export declare class BrowserManager {
|
|
24
44
|
private browser;
|
|
45
|
+
private cdpPort;
|
|
46
|
+
private isPersistentContext;
|
|
25
47
|
private contexts;
|
|
26
48
|
private pages;
|
|
27
49
|
private activePageIndex;
|
|
@@ -35,6 +57,11 @@ export declare class BrowserManager {
|
|
|
35
57
|
private refMap;
|
|
36
58
|
private lastSnapshot;
|
|
37
59
|
private scopedHeaderRoutes;
|
|
60
|
+
private cdpSession;
|
|
61
|
+
private screencastActive;
|
|
62
|
+
private screencastSessionId;
|
|
63
|
+
private frameCallback;
|
|
64
|
+
private screencastFrameHandler;
|
|
38
65
|
/**
|
|
39
66
|
* Check if browser is launched
|
|
40
67
|
*/
|
|
@@ -217,15 +244,32 @@ export declare class BrowserManager {
|
|
|
217
244
|
* Get the current browser instance
|
|
218
245
|
*/
|
|
219
246
|
getBrowser(): Browser | null;
|
|
247
|
+
/**
|
|
248
|
+
* Check if an existing CDP connection is still alive
|
|
249
|
+
* by verifying we can access browser contexts and that at least one has pages
|
|
250
|
+
*/
|
|
251
|
+
private isCdpConnectionAlive;
|
|
252
|
+
/**
|
|
253
|
+
* Check if CDP connection needs to be re-established
|
|
254
|
+
*/
|
|
255
|
+
private needsCdpReconnect;
|
|
220
256
|
/**
|
|
221
257
|
* Launch the browser with the specified options
|
|
222
258
|
* If already launched, this is a no-op (browser stays open)
|
|
223
259
|
*/
|
|
224
260
|
launch(options: LaunchCommand): Promise<void>;
|
|
225
261
|
/**
|
|
226
|
-
*
|
|
262
|
+
* Connect to a running browser via CDP (Chrome DevTools Protocol)
|
|
263
|
+
*/
|
|
264
|
+
private connectViaCDP;
|
|
265
|
+
/**
|
|
266
|
+
* Set up console, error, and close tracking for a page
|
|
227
267
|
*/
|
|
228
268
|
private setupPageTracking;
|
|
269
|
+
/**
|
|
270
|
+
* Set up tracking for new pages in a context (for CDP connections)
|
|
271
|
+
*/
|
|
272
|
+
private setupContextTracking;
|
|
229
273
|
/**
|
|
230
274
|
* Create a new tab in the current context
|
|
231
275
|
*/
|
|
@@ -243,14 +287,19 @@ export declare class BrowserManager {
|
|
|
243
287
|
index: number;
|
|
244
288
|
total: number;
|
|
245
289
|
}>;
|
|
290
|
+
/**
|
|
291
|
+
* Invalidate the current CDP session (must be called before switching pages)
|
|
292
|
+
* This ensures screencast and input injection work correctly after tab switch
|
|
293
|
+
*/
|
|
294
|
+
private invalidateCDPSession;
|
|
246
295
|
/**
|
|
247
296
|
* Switch to a specific tab/page by index
|
|
248
297
|
*/
|
|
249
|
-
switchTo(index: number): {
|
|
298
|
+
switchTo(index: number): Promise<{
|
|
250
299
|
index: number;
|
|
251
300
|
url: string;
|
|
252
301
|
title: string;
|
|
253
|
-
}
|
|
302
|
+
}>;
|
|
254
303
|
/**
|
|
255
304
|
* Close a specific tab/page
|
|
256
305
|
*/
|
|
@@ -267,6 +316,60 @@ export declare class BrowserManager {
|
|
|
267
316
|
title: string;
|
|
268
317
|
active: boolean;
|
|
269
318
|
}>>;
|
|
319
|
+
/**
|
|
320
|
+
* Get or create a CDP session for the current page
|
|
321
|
+
* Only works with Chromium-based browsers
|
|
322
|
+
*/
|
|
323
|
+
getCDPSession(): Promise<CDPSession>;
|
|
324
|
+
/**
|
|
325
|
+
* Check if screencast is currently active
|
|
326
|
+
*/
|
|
327
|
+
isScreencasting(): boolean;
|
|
328
|
+
/**
|
|
329
|
+
* Start screencast - streams viewport frames via CDP
|
|
330
|
+
* @param callback Function called for each frame
|
|
331
|
+
* @param options Screencast options
|
|
332
|
+
*/
|
|
333
|
+
startScreencast(callback: (frame: ScreencastFrame) => void, options?: ScreencastOptions): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* Stop screencast
|
|
336
|
+
*/
|
|
337
|
+
stopScreencast(): Promise<void>;
|
|
338
|
+
/**
|
|
339
|
+
* Inject a mouse event via CDP
|
|
340
|
+
*/
|
|
341
|
+
injectMouseEvent(params: {
|
|
342
|
+
type: 'mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel';
|
|
343
|
+
x: number;
|
|
344
|
+
y: number;
|
|
345
|
+
button?: 'left' | 'right' | 'middle' | 'none';
|
|
346
|
+
clickCount?: number;
|
|
347
|
+
deltaX?: number;
|
|
348
|
+
deltaY?: number;
|
|
349
|
+
modifiers?: number;
|
|
350
|
+
}): Promise<void>;
|
|
351
|
+
/**
|
|
352
|
+
* Inject a keyboard event via CDP
|
|
353
|
+
*/
|
|
354
|
+
injectKeyboardEvent(params: {
|
|
355
|
+
type: 'keyDown' | 'keyUp' | 'char';
|
|
356
|
+
key?: string;
|
|
357
|
+
code?: string;
|
|
358
|
+
text?: string;
|
|
359
|
+
modifiers?: number;
|
|
360
|
+
}): Promise<void>;
|
|
361
|
+
/**
|
|
362
|
+
* Inject touch event via CDP (for mobile emulation)
|
|
363
|
+
*/
|
|
364
|
+
injectTouchEvent(params: {
|
|
365
|
+
type: 'touchStart' | 'touchEnd' | 'touchMove' | 'touchCancel';
|
|
366
|
+
touchPoints: Array<{
|
|
367
|
+
x: number;
|
|
368
|
+
y: number;
|
|
369
|
+
id?: number;
|
|
370
|
+
}>;
|
|
371
|
+
modifiers?: number;
|
|
372
|
+
}): Promise<void>;
|
|
270
373
|
/**
|
|
271
374
|
* Close the browser and clean up
|
|
272
375
|
*/
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,OAAO,EACP,KAAK,OAAO,EAEZ,KAAK,IAAI,EACT,KAAK,KAAK,EAIV,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
package/dist/browser.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { chromium, firefox, webkit, devices, } from 'playwright-core';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
2
4
|
import { getEnhancedSnapshot, parseRef } from './snapshot.js';
|
|
3
5
|
/**
|
|
4
6
|
* Manages the Playwright browser lifecycle with multiple tabs/windows
|
|
5
7
|
*/
|
|
6
8
|
export class BrowserManager {
|
|
7
9
|
browser = null;
|
|
10
|
+
cdpPort = null;
|
|
11
|
+
isPersistentContext = false;
|
|
8
12
|
contexts = [];
|
|
9
13
|
pages = [];
|
|
10
14
|
activePageIndex = 0;
|
|
@@ -18,11 +22,17 @@ export class BrowserManager {
|
|
|
18
22
|
refMap = {};
|
|
19
23
|
lastSnapshot = '';
|
|
20
24
|
scopedHeaderRoutes = new Map();
|
|
25
|
+
// CDP session for screencast and input injection
|
|
26
|
+
cdpSession = null;
|
|
27
|
+
screencastActive = false;
|
|
28
|
+
screencastSessionId = 0;
|
|
29
|
+
frameCallback = null;
|
|
30
|
+
screencastFrameHandler = null;
|
|
21
31
|
/**
|
|
22
32
|
* Check if browser is launched
|
|
23
33
|
*/
|
|
24
34
|
isLaunched() {
|
|
25
|
-
return this.browser !== null;
|
|
35
|
+
return this.browser !== null || this.isPersistentContext;
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
38
|
* Get enhanced snapshot with refs and cache the ref map
|
|
@@ -477,40 +487,134 @@ export class BrowserManager {
|
|
|
477
487
|
getBrowser() {
|
|
478
488
|
return this.browser;
|
|
479
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Check if an existing CDP connection is still alive
|
|
492
|
+
* by verifying we can access browser contexts and that at least one has pages
|
|
493
|
+
*/
|
|
494
|
+
isCdpConnectionAlive() {
|
|
495
|
+
if (!this.browser)
|
|
496
|
+
return false;
|
|
497
|
+
try {
|
|
498
|
+
const contexts = this.browser.contexts();
|
|
499
|
+
if (contexts.length === 0)
|
|
500
|
+
return false;
|
|
501
|
+
return contexts.some((context) => context.pages().length > 0);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Check if CDP connection needs to be re-established
|
|
509
|
+
*/
|
|
510
|
+
needsCdpReconnect(cdpPort) {
|
|
511
|
+
if (!this.browser?.isConnected())
|
|
512
|
+
return true;
|
|
513
|
+
if (this.cdpPort !== cdpPort)
|
|
514
|
+
return true;
|
|
515
|
+
if (!this.isCdpConnectionAlive())
|
|
516
|
+
return true;
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
480
519
|
/**
|
|
481
520
|
* Launch the browser with the specified options
|
|
482
521
|
* If already launched, this is a no-op (browser stays open)
|
|
483
522
|
*/
|
|
484
523
|
async launch(options) {
|
|
485
|
-
|
|
486
|
-
|
|
524
|
+
const cdpPort = options.cdpPort;
|
|
525
|
+
const hasExtensions = !!options.extensions?.length;
|
|
526
|
+
if (hasExtensions && cdpPort) {
|
|
527
|
+
throw new Error('Extensions cannot be used with CDP connection');
|
|
528
|
+
}
|
|
529
|
+
if (this.isLaunched()) {
|
|
530
|
+
const needsRelaunch = (!cdpPort && this.cdpPort !== null) || (!!cdpPort && this.needsCdpReconnect(cdpPort));
|
|
531
|
+
if (needsRelaunch) {
|
|
532
|
+
await this.close();
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (cdpPort) {
|
|
539
|
+
await this.connectViaCDP(cdpPort);
|
|
487
540
|
return;
|
|
488
541
|
}
|
|
489
|
-
// Select browser type
|
|
490
542
|
const browserType = options.browser ?? 'chromium';
|
|
543
|
+
if (hasExtensions && browserType !== 'chromium') {
|
|
544
|
+
throw new Error('Extensions are only supported in Chromium');
|
|
545
|
+
}
|
|
491
546
|
const launcher = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
547
|
+
const viewport = options.viewport ?? { width: 1280, height: 720 };
|
|
548
|
+
let context;
|
|
549
|
+
if (hasExtensions) {
|
|
550
|
+
const extPaths = options.extensions.join(',');
|
|
551
|
+
const session = process.env.AGENT_BROWSER_SESSION || 'default';
|
|
552
|
+
context = await launcher.launchPersistentContext(path.join(os.tmpdir(), `agent-browser-ext-${session}`), {
|
|
553
|
+
headless: false,
|
|
554
|
+
executablePath: options.executablePath,
|
|
555
|
+
args: [`--disable-extensions-except=${extPaths}`, `--load-extension=${extPaths}`],
|
|
556
|
+
viewport,
|
|
557
|
+
extraHTTPHeaders: options.headers,
|
|
558
|
+
});
|
|
559
|
+
this.isPersistentContext = true;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
this.browser = await launcher.launch({
|
|
563
|
+
headless: options.headless ?? true,
|
|
564
|
+
executablePath: options.executablePath,
|
|
565
|
+
});
|
|
566
|
+
this.cdpPort = null;
|
|
567
|
+
context = await this.browser.newContext({ viewport, extraHTTPHeaders: options.headers });
|
|
568
|
+
}
|
|
503
569
|
context.setDefaultTimeout(10000);
|
|
504
570
|
this.contexts.push(context);
|
|
505
|
-
|
|
506
|
-
const page = await context.newPage();
|
|
571
|
+
const page = context.pages()[0] ?? (await context.newPage());
|
|
507
572
|
this.pages.push(page);
|
|
508
573
|
this.activePageIndex = 0;
|
|
509
|
-
// Automatically start console and error tracking
|
|
510
574
|
this.setupPageTracking(page);
|
|
511
575
|
}
|
|
512
576
|
/**
|
|
513
|
-
*
|
|
577
|
+
* Connect to a running browser via CDP (Chrome DevTools Protocol)
|
|
578
|
+
*/
|
|
579
|
+
async connectViaCDP(cdpPort) {
|
|
580
|
+
if (!cdpPort) {
|
|
581
|
+
throw new Error('cdpPort is required for CDP connection');
|
|
582
|
+
}
|
|
583
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${cdpPort}`).catch(() => {
|
|
584
|
+
throw new Error(`Failed to connect via CDP on port ${cdpPort}. ` +
|
|
585
|
+
`Make sure the app is running with --remote-debugging-port=${cdpPort}`);
|
|
586
|
+
});
|
|
587
|
+
// Validate and set up state, cleaning up browser connection if anything fails
|
|
588
|
+
try {
|
|
589
|
+
const contexts = browser.contexts();
|
|
590
|
+
if (contexts.length === 0) {
|
|
591
|
+
throw new Error('No browser context found. Make sure the app has an open window.');
|
|
592
|
+
}
|
|
593
|
+
const allPages = contexts.flatMap((context) => context.pages());
|
|
594
|
+
if (allPages.length === 0) {
|
|
595
|
+
throw new Error('No page found. Make sure the app has loaded content.');
|
|
596
|
+
}
|
|
597
|
+
// All validation passed - commit state
|
|
598
|
+
this.browser = browser;
|
|
599
|
+
this.cdpPort = cdpPort;
|
|
600
|
+
for (const context of contexts) {
|
|
601
|
+
this.contexts.push(context);
|
|
602
|
+
this.setupContextTracking(context);
|
|
603
|
+
}
|
|
604
|
+
for (const page of allPages) {
|
|
605
|
+
this.pages.push(page);
|
|
606
|
+
this.setupPageTracking(page);
|
|
607
|
+
}
|
|
608
|
+
this.activePageIndex = 0;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
// Clean up browser connection if validation or setup failed
|
|
612
|
+
await browser.close().catch(() => { });
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Set up console, error, and close tracking for a page
|
|
514
618
|
*/
|
|
515
619
|
setupPageTracking(page) {
|
|
516
620
|
page.on('console', (msg) => {
|
|
@@ -526,6 +630,24 @@ export class BrowserManager {
|
|
|
526
630
|
timestamp: Date.now(),
|
|
527
631
|
});
|
|
528
632
|
});
|
|
633
|
+
page.on('close', () => {
|
|
634
|
+
const index = this.pages.indexOf(page);
|
|
635
|
+
if (index !== -1) {
|
|
636
|
+
this.pages.splice(index, 1);
|
|
637
|
+
if (this.activePageIndex >= this.pages.length) {
|
|
638
|
+
this.activePageIndex = Math.max(0, this.pages.length - 1);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Set up tracking for new pages in a context (for CDP connections)
|
|
645
|
+
*/
|
|
646
|
+
setupContextTracking(context) {
|
|
647
|
+
context.on('page', (page) => {
|
|
648
|
+
this.pages.push(page);
|
|
649
|
+
this.setupPageTracking(page);
|
|
650
|
+
});
|
|
529
651
|
}
|
|
530
652
|
/**
|
|
531
653
|
* Create a new tab in the current context
|
|
@@ -534,6 +656,8 @@ export class BrowserManager {
|
|
|
534
656
|
if (!this.browser || this.contexts.length === 0) {
|
|
535
657
|
throw new Error('Browser not launched');
|
|
536
658
|
}
|
|
659
|
+
// Invalidate CDP session since we're switching to a new page
|
|
660
|
+
await this.invalidateCDPSession();
|
|
537
661
|
const context = this.contexts[0]; // Use first context for tabs
|
|
538
662
|
const page = await context.newPage();
|
|
539
663
|
this.pages.push(page);
|
|
@@ -561,13 +685,32 @@ export class BrowserManager {
|
|
|
561
685
|
this.setupPageTracking(page);
|
|
562
686
|
return { index: this.activePageIndex, total: this.pages.length };
|
|
563
687
|
}
|
|
688
|
+
/**
|
|
689
|
+
* Invalidate the current CDP session (must be called before switching pages)
|
|
690
|
+
* This ensures screencast and input injection work correctly after tab switch
|
|
691
|
+
*/
|
|
692
|
+
async invalidateCDPSession() {
|
|
693
|
+
// Stop screencast if active (it's tied to the current page's CDP session)
|
|
694
|
+
if (this.screencastActive) {
|
|
695
|
+
await this.stopScreencast();
|
|
696
|
+
}
|
|
697
|
+
// Detach and clear the CDP session
|
|
698
|
+
if (this.cdpSession) {
|
|
699
|
+
await this.cdpSession.detach().catch(() => { });
|
|
700
|
+
this.cdpSession = null;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
564
703
|
/**
|
|
565
704
|
* Switch to a specific tab/page by index
|
|
566
705
|
*/
|
|
567
|
-
switchTo(index) {
|
|
706
|
+
async switchTo(index) {
|
|
568
707
|
if (index < 0 || index >= this.pages.length) {
|
|
569
708
|
throw new Error(`Invalid tab index: ${index}. Available: 0-${this.pages.length - 1}`);
|
|
570
709
|
}
|
|
710
|
+
// Invalidate CDP session before switching (it's page-specific)
|
|
711
|
+
if (index !== this.activePageIndex) {
|
|
712
|
+
await this.invalidateCDPSession();
|
|
713
|
+
}
|
|
571
714
|
this.activePageIndex = index;
|
|
572
715
|
const page = this.pages[index];
|
|
573
716
|
return {
|
|
@@ -587,6 +730,10 @@ export class BrowserManager {
|
|
|
587
730
|
if (this.pages.length === 1) {
|
|
588
731
|
throw new Error('Cannot close the last tab. Use "close" to close the browser.');
|
|
589
732
|
}
|
|
733
|
+
// If closing the active tab, invalidate CDP session first
|
|
734
|
+
if (targetIndex === this.activePageIndex) {
|
|
735
|
+
await this.invalidateCDPSession();
|
|
736
|
+
}
|
|
590
737
|
const page = this.pages[targetIndex];
|
|
591
738
|
await page.close();
|
|
592
739
|
this.pages.splice(targetIndex, 1);
|
|
@@ -611,25 +758,177 @@ export class BrowserManager {
|
|
|
611
758
|
})));
|
|
612
759
|
return tabs;
|
|
613
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Get or create a CDP session for the current page
|
|
763
|
+
* Only works with Chromium-based browsers
|
|
764
|
+
*/
|
|
765
|
+
async getCDPSession() {
|
|
766
|
+
if (this.cdpSession) {
|
|
767
|
+
return this.cdpSession;
|
|
768
|
+
}
|
|
769
|
+
const page = this.getPage();
|
|
770
|
+
const context = page.context();
|
|
771
|
+
// Create a new CDP session attached to the page
|
|
772
|
+
this.cdpSession = await context.newCDPSession(page);
|
|
773
|
+
return this.cdpSession;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Check if screencast is currently active
|
|
777
|
+
*/
|
|
778
|
+
isScreencasting() {
|
|
779
|
+
return this.screencastActive;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Start screencast - streams viewport frames via CDP
|
|
783
|
+
* @param callback Function called for each frame
|
|
784
|
+
* @param options Screencast options
|
|
785
|
+
*/
|
|
786
|
+
async startScreencast(callback, options) {
|
|
787
|
+
if (this.screencastActive) {
|
|
788
|
+
throw new Error('Screencast already active');
|
|
789
|
+
}
|
|
790
|
+
const cdp = await this.getCDPSession();
|
|
791
|
+
this.frameCallback = callback;
|
|
792
|
+
this.screencastActive = true;
|
|
793
|
+
// Create and store the frame handler so we can remove it later
|
|
794
|
+
this.screencastFrameHandler = async (params) => {
|
|
795
|
+
const frame = {
|
|
796
|
+
data: params.data,
|
|
797
|
+
metadata: params.metadata,
|
|
798
|
+
sessionId: params.sessionId,
|
|
799
|
+
};
|
|
800
|
+
// Acknowledge the frame to receive the next one
|
|
801
|
+
await cdp.send('Page.screencastFrameAck', { sessionId: params.sessionId });
|
|
802
|
+
// Call the callback with the frame
|
|
803
|
+
if (this.frameCallback) {
|
|
804
|
+
this.frameCallback(frame);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
// Listen for screencast frames
|
|
808
|
+
cdp.on('Page.screencastFrame', this.screencastFrameHandler);
|
|
809
|
+
// Start the screencast
|
|
810
|
+
await cdp.send('Page.startScreencast', {
|
|
811
|
+
format: options?.format ?? 'jpeg',
|
|
812
|
+
quality: options?.quality ?? 80,
|
|
813
|
+
maxWidth: options?.maxWidth ?? 1280,
|
|
814
|
+
maxHeight: options?.maxHeight ?? 720,
|
|
815
|
+
everyNthFrame: options?.everyNthFrame ?? 1,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Stop screencast
|
|
820
|
+
*/
|
|
821
|
+
async stopScreencast() {
|
|
822
|
+
if (!this.screencastActive) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const cdp = await this.getCDPSession();
|
|
827
|
+
await cdp.send('Page.stopScreencast');
|
|
828
|
+
// Remove the event listener to prevent accumulation
|
|
829
|
+
if (this.screencastFrameHandler) {
|
|
830
|
+
cdp.off('Page.screencastFrame', this.screencastFrameHandler);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// Ignore errors when stopping
|
|
835
|
+
}
|
|
836
|
+
this.screencastActive = false;
|
|
837
|
+
this.frameCallback = null;
|
|
838
|
+
this.screencastFrameHandler = null;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Inject a mouse event via CDP
|
|
842
|
+
*/
|
|
843
|
+
async injectMouseEvent(params) {
|
|
844
|
+
const cdp = await this.getCDPSession();
|
|
845
|
+
const cdpButton = params.button === 'left'
|
|
846
|
+
? 'left'
|
|
847
|
+
: params.button === 'right'
|
|
848
|
+
? 'right'
|
|
849
|
+
: params.button === 'middle'
|
|
850
|
+
? 'middle'
|
|
851
|
+
: 'none';
|
|
852
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
853
|
+
type: params.type,
|
|
854
|
+
x: params.x,
|
|
855
|
+
y: params.y,
|
|
856
|
+
button: cdpButton,
|
|
857
|
+
clickCount: params.clickCount ?? 1,
|
|
858
|
+
deltaX: params.deltaX ?? 0,
|
|
859
|
+
deltaY: params.deltaY ?? 0,
|
|
860
|
+
modifiers: params.modifiers ?? 0,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Inject a keyboard event via CDP
|
|
865
|
+
*/
|
|
866
|
+
async injectKeyboardEvent(params) {
|
|
867
|
+
const cdp = await this.getCDPSession();
|
|
868
|
+
await cdp.send('Input.dispatchKeyEvent', {
|
|
869
|
+
type: params.type,
|
|
870
|
+
key: params.key,
|
|
871
|
+
code: params.code,
|
|
872
|
+
text: params.text,
|
|
873
|
+
modifiers: params.modifiers ?? 0,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Inject touch event via CDP (for mobile emulation)
|
|
878
|
+
*/
|
|
879
|
+
async injectTouchEvent(params) {
|
|
880
|
+
const cdp = await this.getCDPSession();
|
|
881
|
+
await cdp.send('Input.dispatchTouchEvent', {
|
|
882
|
+
type: params.type,
|
|
883
|
+
touchPoints: params.touchPoints.map((tp, i) => ({
|
|
884
|
+
x: tp.x,
|
|
885
|
+
y: tp.y,
|
|
886
|
+
id: tp.id ?? i,
|
|
887
|
+
})),
|
|
888
|
+
modifiers: params.modifiers ?? 0,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
614
891
|
/**
|
|
615
892
|
* Close the browser and clean up
|
|
616
893
|
*/
|
|
617
894
|
async close() {
|
|
618
|
-
|
|
619
|
-
|
|
895
|
+
// Stop screencast if active
|
|
896
|
+
if (this.screencastActive) {
|
|
897
|
+
await this.stopScreencast();
|
|
898
|
+
}
|
|
899
|
+
// Clean up CDP session
|
|
900
|
+
if (this.cdpSession) {
|
|
901
|
+
await this.cdpSession.detach().catch(() => { });
|
|
902
|
+
this.cdpSession = null;
|
|
903
|
+
}
|
|
904
|
+
// CDP: only disconnect, don't close external app's pages
|
|
905
|
+
if (this.cdpPort !== null) {
|
|
906
|
+
if (this.browser) {
|
|
907
|
+
await this.browser.close().catch(() => { });
|
|
908
|
+
this.browser = null;
|
|
909
|
+
}
|
|
620
910
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
911
|
+
else {
|
|
912
|
+
// Regular browser: close everything
|
|
913
|
+
for (const page of this.pages) {
|
|
914
|
+
await page.close().catch(() => { });
|
|
915
|
+
}
|
|
916
|
+
for (const context of this.contexts) {
|
|
917
|
+
await context.close().catch(() => { });
|
|
918
|
+
}
|
|
919
|
+
if (this.browser) {
|
|
920
|
+
await this.browser.close().catch(() => { });
|
|
921
|
+
this.browser = null;
|
|
922
|
+
}
|
|
624
923
|
}
|
|
924
|
+
this.pages = [];
|
|
625
925
|
this.contexts = [];
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.browser = null;
|
|
629
|
-
}
|
|
926
|
+
this.cdpPort = null;
|
|
927
|
+
this.isPersistentContext = false;
|
|
630
928
|
this.activePageIndex = 0;
|
|
631
929
|
this.refMap = {};
|
|
632
930
|
this.lastSnapshot = '';
|
|
931
|
+
this.frameCallback = null;
|
|
633
932
|
}
|
|
634
933
|
}
|
|
635
934
|
//# sourceMappingURL=browser.js.map
|