/** * Human-like behavioral layer for cloakbrowser — Puppeteer edition. * * Mirrors Playwright humanize architecture, adapted for Puppeteer API. * * Patches ALL native Puppeteer interaction surfaces: * * PAGE-LEVEL: * click (with clickCount support for dblclick), hover, type, * select, focus, tap, goto * * MOUSE: * move, click (with clickCount support for dblclick), wheel, * dragAndDrop * * KEYBOARD: * type, down, up, press, sendCharacter * * FRAME-LEVEL: * click, hover, type, select, focus, tap * + $, $$, waitForSelector (return patched ElementHandles) * * ELEMENTHANDLE-LEVEL (Puppeteer-specific, no Playwright equivalent): * click (with clickCount), hover, type, press, tap, select, * focus, drop, dragAndDrop * + $, $$, waitForSelector (nested elements are also patched) * * BROWSER-LEVEL: * newPage, createBrowserContext / createIncognitoBrowserContext, * targetcreated event * * Stealth-aware: * - isInputElement / isSelectorFocused use CDP Isolated Worlds * - Shift symbol typing uses CDP Input.dispatchKeyEvent (isTrusted=true) * - ElementHandle isInput check uses CDP DOM.describeNode (no JS execution) * - Falls back to page.evaluate only when CDP session is unavailable * * Puppeteer-specific adaptations: * - page.createCDPSession() instead of context.newCDPSession(page) * - page.viewport() instead of page.viewportSize() * - page.$(selector) instead of page.locator(selector) * - keyboard.sendCharacter() mapped via RawKeyboard.insertText * - mouse.wheel({deltaX, deltaY}) object form adapted to (dx, dy) * - page.select() instead of page.selectOption() * - ElementHandle prototype patching (Puppeteer-only) * - No page.dblclick() — Puppeteer uses click({clickCount:2}) */ import type { Browser, Page, Frame, CDPSession, ElementHandle, BrowserContext } from 'puppeteer-core'; import type { HumanConfig } from '../human/config.js'; import { resolveConfig, rand, randRange, sleep } from '../human/config.js'; import { RawMouse, RawKeyboard, humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js'; import { humanType } from './keyboard.js'; import { scrollToElement, smoothWheel } from './scroll.js'; export type { HumanConfig } from '../human/config.js'; export { resolveConfig } from '../human/config.js'; export { humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js'; export { humanType } from './keyboard.js'; export { scrollToElement } from './scroll.js'; // ============================================================================ // CDP Isolated World — stealth DOM evaluation (Puppeteer version) // ============================================================================ class StealthEval { private cdp: CDPSession | null = null; private contextId: number | null = null; private page: Page; constructor(page: Page) { this.page = page; } private async ensureCdp(): Promise { if (!this.cdp) { this.cdp = await this.page.createCDPSession(); } return this.cdp; } private async createWorld(): Promise { const cdp = await this.ensureCdp(); const tree = await cdp.send('Page.getFrameTree'); const frameId = (tree as any).frameTree.frame.id; const result = await cdp.send('Page.createIsolatedWorld', { frameId, worldName: '', grantUniveralAccess: true, }); const ctxId = (result as any).executionContextId; this.contextId = ctxId; return ctxId; } async evaluate(expression: string): Promise { if (this.contextId === null) { await this.createWorld(); } for (let attempt = 0; attempt < 2; attempt++) { try { const cdp = await this.ensureCdp(); const result = await cdp.send('Runtime.evaluate', { expression, contextId: this.contextId!, returnByValue: true, }); if ((result as any).exceptionDetails) { if (attempt === 0) { await this.createWorld(); continue; } return undefined; } return (result as any).result?.value; } catch { if (attempt === 0) { this.contextId = null; try { await this.createWorld(); } catch { return undefined; } continue; } return undefined; } } return undefined; } invalidate(): void { this.contextId = null; } async getCdpSession(): Promise { return this.ensureCdp(); } } // ============================================================================ // Cursor state // ============================================================================ class CursorState { x = 0; y = 0; initialized = false; } // ============================================================================ // Stealth DOM queries // ============================================================================ async function isInputElement( stealth: StealthEval | null, page: Page, selector: string, ): Promise { if (stealth) { try { const escaped = JSON.stringify(selector); const result = await stealth.evaluate(` (() => { const el = document.querySelector(${escaped}); if (!el) return false; const tag = el.tagName.toLowerCase(); return tag === 'input' || tag === 'textarea' || el.getAttribute('contenteditable') === 'true'; })() `); return !!result; } catch { /* fallthrough */ } } return page.evaluate((sel: string) => { const el = document.querySelector(sel); if (!el) return false; const tag = el.tagName.toLowerCase(); return tag === 'input' || tag === 'textarea' || el.getAttribute('contenteditable') === 'true'; }, selector).catch(() => false); } async function isSelectorFocused( stealth: StealthEval | null, page: Page, selector: string, ): Promise { if (stealth) { try { const escaped = JSON.stringify(selector); const result = await stealth.evaluate(` (() => { const el = document.querySelector(${escaped}); return el === document.activeElement; })() `); return !!result; } catch { /* fallthrough */ } } return page.evaluate((sel: string) => { const el = document.querySelector(sel); return el === document.activeElement; }, selector).catch(() => false); } // ============================================================================ // Stealth ElementHandle input check — uses CDP DOM.describeNode // instead of el.evaluate() to avoid main-world JS execution. // ============================================================================ async function isInputElementHandle( stealth: StealthEval | null, el: ElementHandle, ): Promise { if (stealth) { try { const cdp = await stealth.getCdpSession(); const remoteObject = (el as any).remoteObject?.(); if (remoteObject?.objectId) { const { node } = await cdp.send('DOM.describeNode', { objectId: remoteObject.objectId, }) as any; const tag = (node?.nodeName || '').toLowerCase(); if (tag === 'input' || tag === 'textarea') return true; const attrs: string[] = node?.attributes || []; for (let i = 0; i < attrs.length; i += 2) { if (attrs[i] === 'contenteditable' && attrs[i + 1] === 'true') { return true; } } return false; } } catch { /* fallthrough to el.evaluate */ } } return el.evaluate((node: any) => { const tag = node.tagName?.toLowerCase(); return tag === 'input' || tag === 'textarea' || node.getAttribute?.('contenteditable') === 'true'; }).catch(() => false); } // ============================================================================ // Page-level patching // ============================================================================ function patchPage(page: Page, cfg: HumanConfig, cursor: CursorState): void { const originals = { click: page.click.bind(page), hover: page.hover.bind(page), type: page.type.bind(page), select: page.select.bind(page), focus: page.focus.bind(page), goto: page.goto.bind(page), tap: page.tap.bind(page), mouseMove: page.mouse.move.bind(page.mouse), mouseClick: page.mouse.click.bind(page.mouse), mouseDown: page.mouse.down.bind(page.mouse), mouseUp: page.mouse.up.bind(page.mouse), mouseWheel: (page.mouse as any).wheel?.bind(page.mouse), mouseDragAndDrop: (page.mouse as any).dragAndDrop?.bind(page.mouse), keyboardType: page.keyboard.type.bind(page.keyboard), keyboardDown: page.keyboard.down.bind(page.keyboard) as (key: string) => Promise, keyboardUp: page.keyboard.up.bind(page.keyboard) as (key: string) => Promise, keyboardPress: page.keyboard.press.bind(page.keyboard), keyboardSendCharacter: page.keyboard.sendCharacter.bind(page.keyboard), }; (page as any)._original = originals; (page as any)._humanCfg = cfg; const stealth = new StealthEval(page); (page as any)._stealth = stealth; let cdpSession: CDPSession | null = null; const ensureCdp = async (): Promise => { if (!cdpSession) { try { cdpSession = await stealth.getCdpSession(); } catch {} } return cdpSession; }; const raw: RawMouse = { move: originals.mouseMove, down: originals.mouseDown, up: originals.mouseUp, wheel: async (deltaX: number, deltaY: number) => { if (originals.mouseWheel) { await originals.mouseWheel({ deltaX, deltaY }); } }, }; const rawKb: RawKeyboard = { down: originals.keyboardDown, up: originals.keyboardUp, type: originals.keyboardType, insertText: originals.keyboardSendCharacter, }; async function ensureCursorInit(): Promise { if (!cursor.initialized) { cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]); cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]); await originals.mouseMove(cursor.x, cursor.y); cursor.initialized = true; } } // ==== goto ==== const humanGoto = async (url: string, options?: any) => { const response = await originals.goto(url, options); stealth.invalidate(); patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth); return response; }; // ==== click (with clickCount support for dblclick) ==== const humanClickFn = async (selector: string, options?: any) => { await ensureCursorInit(); if (cfg.idle_between_actions) { await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg); } const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg); cursor.x = cursorX; cursor.y = cursorY; const isInput = await isInputElement(stealth, page, selector); const target = clickTarget(box, isInput, cfg); await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg); cursor.x = target.x; cursor.y = target.y; const clickCount = options?.clickCount ?? options?.count ?? 1; if (clickCount >= 2) { await humanClick(raw, isInput, cfg); await sleep(rand(40, 90)); await raw.down({ clickCount: 2 }); await sleep(rand(30, 60)); await raw.up({ clickCount: 2 }); } else { await humanClick(raw, isInput, cfg); } }; // ==== hover ==== const humanHoverFn = async (selector: string, options?: any) => { await ensureCursorInit(); if (cfg.idle_between_actions) { await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg); } const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg); cursor.x = cursorX; cursor.y = cursorY; const target = clickTarget(box, false, cfg); await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg); cursor.x = target.x; cursor.y = target.y; }; // ==== type ==== const humanTypeFn = async (selector: string, text: string, options?: any) => { await sleep(randRange(cfg.field_switch_delay)); await humanClickFn(selector); await sleep(rand(100, 250)); const cdp = await ensureCdp(); await humanType(page, rawKb, text, cfg, cdp); }; // ==== select ==== const humanSelectFn = async (selector: string, ...values: string[]) => { await humanHoverFn(selector); await sleep(rand(100, 300)); return originals.select(selector, ...values); }; // ==== focus ==== const humanFocusFn = async (selector: string) => { if (!await isSelectorFocused(stealth, page, selector)) { await humanClickFn(selector); } }; // ==== tap ==== const humanTapFn = async (selector: string, options?: any) => { await humanClickFn(selector, options); }; // ============================================================ // Assign page-level patches // ============================================================ (page as any).goto = humanGoto; (page as any).click = humanClickFn; (page as any).hover = humanHoverFn; (page as any).type = humanTypeFn; (page as any).select = humanSelectFn; (page as any).focus = humanFocusFn; (page as any).tap = humanTapFn; // ============================================================ // Mouse patches // ============================================================ page.mouse.move = async (x: number, y: number, options?: any) => { await ensureCursorInit(); await humanMove(raw, cursor.x, cursor.y, x, y, cfg); cursor.x = x; cursor.y = y; }; page.mouse.click = async (x: number, y: number, options?: any) => { await ensureCursorInit(); await humanMove(raw, cursor.x, cursor.y, x, y, cfg); cursor.x = x; cursor.y = y; const clickCount = options?.clickCount ?? options?.count ?? 1; if (clickCount >= 2) { await humanClick(raw, false, cfg); await sleep(rand(40, 90)); await raw.down({ clickCount: 2 }); await sleep(rand(30, 60)); await raw.up({ clickCount: 2 }); } else { await humanClick(raw, false, cfg); } }; if (originals.mouseWheel) { (page.mouse as any).wheel = async (options?: { deltaX?: number; deltaY?: number }) => { const dx = options?.deltaX ?? 0; const dy = options?.deltaY ?? 0; if (Math.abs(dy) > 0) { await smoothWheel(raw, dy, cfg, 'y'); } if (Math.abs(dx) > 0) { await smoothWheel(raw, dx, cfg, 'x'); } }; } if (originals.mouseDragAndDrop) { (page.mouse as any).dragAndDrop = async ( start: { x: number; y: number }, target: { x: number; y: number }, options?: any, ) => { await ensureCursorInit(); await humanMove(raw, cursor.x, cursor.y, start.x, start.y, cfg); cursor.x = start.x; cursor.y = start.y; await sleep(rand(100, 200)); await originals.mouseDown(); await sleep(rand(80, 150)); await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg); cursor.x = target.x; cursor.y = target.y; await sleep(rand(80, 150)); await originals.mouseUp(); }; } // ============================================================ // Keyboard patches // ============================================================ page.keyboard.type = async (text: string, options?: any) => { const cdp = await ensureCdp(); await humanType(page, rawKb, text, cfg, cdp); }; page.keyboard.press = async (key: any, options?: any) => { await sleep(rand(20, 60)); await originals.keyboardDown(key as any); await sleep(randRange(cfg.key_hold)); await originals.keyboardUp(key as any); }; page.keyboard.down = async (key: any) => { await sleep(rand(10, 30)); await originals.keyboardDown(key as any); }; page.keyboard.up = async (key: any) => { await sleep(rand(10, 30)); await originals.keyboardUp(key as any); }; // ============================================================ // Store helpers for frame/element patching // ============================================================ (page as any)._humanCursor = cursor; (page as any)._humanRaw = raw; (page as any)._humanRawKb = rawKb; (page as any)._ensureCursorInit = ensureCursorInit; // Initialize cursor cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]); cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]); originals.mouseMove(cursor.x, cursor.y).then(() => { cursor.initialized = true; }).catch(() => {}); // Patch frames patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth); // Patch ElementHandle selectors patchElementHandle(page, cfg, cursor, raw, rawKb, originals, stealth); } // ============================================================================ // ElementHandle patching — PUPPETEER-SPECIFIC // ============================================================================ function patchElementHandle( page: Page, cfg: HumanConfig, cursor: CursorState, raw: RawMouse, rawKb: RawKeyboard, originals: any, stealth: StealthEval, ): void { const orig$ = page.$.bind(page); const orig$$ = page.$$.bind(page); const origWaitForSelector = page.waitForSelector.bind(page); (page as any).$ = async (selector: string) => { const el = await orig$(selector); if (el) patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); return el; }; (page as any).$$ = async (selector: string) => { const els = await orig$$(selector); for (const el of els) { patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); } return els; }; (page as any).waitForSelector = async (selector: string, options?: any) => { const el = await origWaitForSelector(selector, options); if (el) patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); return el; }; } function patchSingleElementHandle( el: ElementHandle, page: Page, cfg: HumanConfig, cursor: CursorState, raw: RawMouse, rawKb: RawKeyboard, originals: any, stealth: StealthEval, ): void { if ((el as any)._humanPatched) return; (el as any)._humanPatched = true; const origElClick = el.click.bind(el); const origElHover = el.hover.bind(el); const origElType = el.type.bind(el); const origElPress = (el as any).press?.bind(el); const origElTap = (el as any).tap?.bind(el); const origElFocus = (el as any).focus?.bind(el); const origElDragAndDrop = (el as any).dragAndDrop?.bind(el); const origElSelect = (el as any).select?.bind(el); const origElDrop = (el as any).drop?.bind(el); // --- Nested selectors --- const origEl$ = el.$.bind(el); const origEl$$ = el.$$.bind(el); const origElWaitForSelector = el.waitForSelector.bind(el); (el as any).$ = async (selector: string) => { const child = await origEl$(selector); if (child) patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth); return child; }; (el as any).$$ = async (selector: string) => { const children = await origEl$$(selector); for (const child of children) { patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth); } return children; }; (el as any).waitForSelector = async (selector: string, options?: any) => { const child = await origElWaitForSelector(selector, options); if (child) patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth); return child; }; // --- Helper: get box and move cursor --- const moveToElement = async () => { await (page as any)._ensureCursorInit(); const box = await el.boundingBox(); if (!box) return null; const isInp = await isInputElementHandle(stealth, el); const target = clickTarget(box, isInp, cfg); if (cfg.idle_between_actions) { await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg); } await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg); cursor.x = target.x; cursor.y = target.y; return { box, isInp }; }; // --- el.click() --- (el as any).click = async (options?: any) => { const info = await moveToElement(); if (!info) return origElClick(options); const clickCount = options?.clickCount ?? options?.count ?? 1; if (clickCount >= 2) { await humanClick(raw, info.isInp, cfg); await sleep(rand(40, 90)); await raw.down({ clickCount: 2 }); await sleep(rand(30, 60)); await raw.up({ clickCount: 2 }); } else { await humanClick(raw, info.isInp, cfg); } }; // --- el.hover() --- (el as any).hover = async () => { const info = await moveToElement(); if (!info) return origElHover(); }; // --- el.type() --- (el as any).type = async (text: string, options?: any) => { const info = await moveToElement(); if (!info) return origElType(text, options); await humanClick(raw, info.isInp, cfg); await sleep(rand(100, 250)); const cdp = await stealth.getCdpSession().catch(() => null); await humanType(page, rawKb, text, cfg, cdp); }; // --- el.press() --- if (origElPress) { (el as any).press = async (key: string, options?: any) => { await sleep(rand(20, 60)); await originals.keyboardDown(key as any); await sleep(randRange(cfg.key_hold)); await originals.keyboardUp(key as any); }; } // --- el.tap() --- if (origElTap) { (el as any).tap = async () => { const info = await moveToElement(); if (!info) return origElTap(); await humanClick(raw, info.isInp, cfg); }; } // --- el.focus() --- if (origElFocus) { (el as any).focus = async () => { const info = await moveToElement(); if (!info) return origElFocus(); await humanClick(raw, info.isInp, cfg); }; } // --- el.select() --- if (origElSelect) { (el as any).select = async (...values: string[]) => { const info = await moveToElement(); if (!info) return origElSelect(...values); await humanClick(raw, false, cfg); await sleep(rand(100, 300)); return origElSelect(...values); }; } // --- el.drop() --- if (origElDrop) { (el as any).drop = async (draggable: ElementHandle, options?: any) => { const srcBox = await draggable.boundingBox(); const tgtBox = await el.boundingBox(); if (srcBox && tgtBox) { const sx = srcBox.x + srcBox.width / 2; const sy = srcBox.y + srcBox.height / 2; const tx = tgtBox.x + tgtBox.width / 2; const ty = tgtBox.y + tgtBox.height / 2; await (page as any)._ensureCursorInit(); await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg); cursor.x = sx; cursor.y = sy; await sleep(rand(100, 200)); await originals.mouseDown(); await sleep(rand(80, 150)); await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg); cursor.x = tx; cursor.y = ty; await sleep(rand(80, 150)); await originals.mouseUp(); } else { return origElDrop(draggable, options); } }; } // --- el.dragAndDrop() --- if (origElDragAndDrop) { (el as any).dragAndDrop = async (targetEl: ElementHandle, options?: any) => { const srcBox = await el.boundingBox(); const tgtBox = await targetEl.boundingBox(); if (srcBox && tgtBox) { const sx = srcBox.x + srcBox.width / 2; const sy = srcBox.y + srcBox.height / 2; const tx = tgtBox.x + tgtBox.width / 2; const ty = tgtBox.y + tgtBox.height / 2; await (page as any)._ensureCursorInit(); await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg); cursor.x = sx; cursor.y = sy; await sleep(rand(100, 200)); await originals.mouseDown(); await sleep(rand(80, 150)); await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg); cursor.x = tx; cursor.y = ty; await sleep(rand(80, 150)); await originals.mouseUp(); } else { return origElDragAndDrop(targetEl, options); } }; } } // ============================================================================ // Frame-level patching — native Puppeteer Frame methods only // Puppeteer Frame has: click, hover, type, select, focus, tap // ============================================================================ function patchFrames( page: Page, cfg: HumanConfig, cursor: CursorState, raw: RawMouse, rawKb: RawKeyboard, originals: any, stealth: StealthEval, ): void { for (const frame of iterFrames(page)) { patchSingleFrame(frame, page, cfg, cursor, raw, rawKb, originals, stealth); } } function patchSingleFrame( frame: Frame, page: Page, cfg: HumanConfig, cursor: CursorState, raw: RawMouse, rawKb: RawKeyboard, originals: any, stealth: StealthEval, ): void { if ((frame as any)._humanPatched) return; (frame as any)._humanPatched = true; const origFrameSelect = frame.select.bind(frame); (frame as any).click = async (selector: string, options?: any) => { await (page as any).click(selector, options); }; (frame as any).hover = async (selector: string, options?: any) => { await (page as any).hover(selector, options); }; (frame as any).type = async (selector: string, text: string, options?: any) => { await (page as any).type(selector, text, options); }; (frame as any).select = async (selector: string, ...values: string[]) => { await (page as any).hover(selector); await sleep(rand(100, 300)); return origFrameSelect(selector, ...values); }; (frame as any).focus = async (selector: string) => { await (page as any).focus(selector); }; (frame as any).tap = async (selector: string, options?: any) => { await (page as any).click(selector, options); }; // Patch frame.$() to return patched ElementHandles const origFrame$ = frame.$.bind(frame); const origFrame$$ = frame.$$.bind(frame); const origFrameWaitForSelector = frame.waitForSelector.bind(frame); (frame as any).$ = async (selector: string) => { const el = await origFrame$(selector); if (el) patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); return el; }; (frame as any).$$ = async (selector: string) => { const els = await origFrame$$(selector); for (const el of els) { patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); } return els; }; (frame as any).waitForSelector = async (selector: string, options?: any) => { const el = await origFrameWaitForSelector(selector, options); if (el) patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth); return el; }; } function* iterFrames(page: Page): Generator { try { const mainFrame = page.mainFrame(); yield mainFrame; for (const child of mainFrame.childFrames()) { yield child; } } catch {} } // ============================================================================ // Browser-level patching // ============================================================================ export function patchBrowser(browser: Browser, cfg: HumanConfig): void { browser.pages().then(pages => { for (const page of pages) { if (!(page as any)._original) { patchPage(page, cfg, new CursorState()); } } }).catch(() => {}); const origNewPage = browser.newPage.bind(browser); (browser as any).newPage = async () => { const page = await origNewPage(); if (!(page as any)._original) { patchPage(page, cfg, new CursorState()); } return page; }; // v21: createIncognitoBrowserContext // v22+: createBrowserContext (renamed in puppeteer/puppeteer#11834) for (const methodName of ['createBrowserContext', 'createIncognitoBrowserContext'] as const) { if (typeof (browser as any)[methodName] === 'function') { const origCreateContext = (browser as any)[methodName].bind(browser); (browser as any)[methodName] = async (options?: any) => { const context: BrowserContext = await origCreateContext(options); const origCtxNewPage = context.newPage.bind(context); (context as any).newPage = async () => { const page = await origCtxNewPage(); if (!(page as any)._original) { patchPage(page, cfg, new CursorState()); } return page; }; return context; }; } } browser.on('targetcreated', async (target: any) => { try { if (target.type() === 'page') { const page = await target.page(); if (page && !(page as any)._original) { patchPage(page, cfg, new CursorState()); } } } catch {} }); } export { patchPage };