cloakbrowser 0.3.22 → 0.3.23

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.
@@ -0,0 +1,780 @@
1
+ /**
2
+ * Human-like behavioral layer for cloakbrowser — Puppeteer edition.
3
+ *
4
+ * Mirrors Playwright humanize architecture, adapted for Puppeteer API.
5
+ *
6
+ * Patches ALL native Puppeteer interaction surfaces:
7
+ *
8
+ * PAGE-LEVEL:
9
+ * click (with clickCount support for dblclick), hover, type,
10
+ * select, focus, tap, goto
11
+ *
12
+ * MOUSE:
13
+ * move, click (with clickCount support for dblclick), wheel,
14
+ * dragAndDrop
15
+ *
16
+ * KEYBOARD:
17
+ * type, down, up, press, sendCharacter
18
+ *
19
+ * FRAME-LEVEL:
20
+ * click, hover, type, select, focus, tap
21
+ * + $, $$, waitForSelector (return patched ElementHandles)
22
+ *
23
+ * ELEMENTHANDLE-LEVEL (Puppeteer-specific, no Playwright equivalent):
24
+ * click (with clickCount), hover, type, press, tap, select,
25
+ * focus, drop, dragAndDrop
26
+ * + $, $$, waitForSelector (nested elements are also patched)
27
+ *
28
+ * BROWSER-LEVEL:
29
+ * newPage, createBrowserContext / createIncognitoBrowserContext,
30
+ * targetcreated event
31
+ *
32
+ * Stealth-aware:
33
+ * - isInputElement / isSelectorFocused use CDP Isolated Worlds
34
+ * - Shift symbol typing uses CDP Input.dispatchKeyEvent (isTrusted=true)
35
+ * - ElementHandle isInput check uses CDP DOM.describeNode (no JS execution)
36
+ * - Falls back to page.evaluate only when CDP session is unavailable
37
+ *
38
+ * Puppeteer-specific adaptations:
39
+ * - page.createCDPSession() instead of context.newCDPSession(page)
40
+ * - page.viewport() instead of page.viewportSize()
41
+ * - page.$(selector) instead of page.locator(selector)
42
+ * - keyboard.sendCharacter() mapped via RawKeyboard.insertText
43
+ * - mouse.wheel({deltaX, deltaY}) object form adapted to (dx, dy)
44
+ * - page.select() instead of page.selectOption()
45
+ * - ElementHandle prototype patching (Puppeteer-only)
46
+ * - No page.dblclick() — Puppeteer uses click({clickCount:2})
47
+ */
48
+ import { rand, randRange, sleep } from '../human/config.js';
49
+ import { humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js';
50
+ import { humanType } from './keyboard.js';
51
+ import { scrollToElement, smoothWheel } from './scroll.js';
52
+ export { resolveConfig } from '../human/config.js';
53
+ export { humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js';
54
+ export { humanType } from './keyboard.js';
55
+ export { scrollToElement } from './scroll.js';
56
+ // ============================================================================
57
+ // CDP Isolated World — stealth DOM evaluation (Puppeteer version)
58
+ // ============================================================================
59
+ class StealthEval {
60
+ cdp = null;
61
+ contextId = null;
62
+ page;
63
+ constructor(page) {
64
+ this.page = page;
65
+ }
66
+ async ensureCdp() {
67
+ if (!this.cdp) {
68
+ this.cdp = await this.page.createCDPSession();
69
+ }
70
+ return this.cdp;
71
+ }
72
+ async createWorld() {
73
+ const cdp = await this.ensureCdp();
74
+ const tree = await cdp.send('Page.getFrameTree');
75
+ const frameId = tree.frameTree.frame.id;
76
+ const result = await cdp.send('Page.createIsolatedWorld', {
77
+ frameId,
78
+ worldName: '',
79
+ grantUniveralAccess: true,
80
+ });
81
+ const ctxId = result.executionContextId;
82
+ this.contextId = ctxId;
83
+ return ctxId;
84
+ }
85
+ async evaluate(expression) {
86
+ if (this.contextId === null) {
87
+ await this.createWorld();
88
+ }
89
+ for (let attempt = 0; attempt < 2; attempt++) {
90
+ try {
91
+ const cdp = await this.ensureCdp();
92
+ const result = await cdp.send('Runtime.evaluate', {
93
+ expression,
94
+ contextId: this.contextId,
95
+ returnByValue: true,
96
+ });
97
+ if (result.exceptionDetails) {
98
+ if (attempt === 0) {
99
+ await this.createWorld();
100
+ continue;
101
+ }
102
+ return undefined;
103
+ }
104
+ return result.result?.value;
105
+ }
106
+ catch {
107
+ if (attempt === 0) {
108
+ this.contextId = null;
109
+ try {
110
+ await this.createWorld();
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ continue;
116
+ }
117
+ return undefined;
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ invalidate() {
123
+ this.contextId = null;
124
+ }
125
+ async getCdpSession() {
126
+ return this.ensureCdp();
127
+ }
128
+ }
129
+ // ============================================================================
130
+ // Cursor state
131
+ // ============================================================================
132
+ class CursorState {
133
+ x = 0;
134
+ y = 0;
135
+ initialized = false;
136
+ }
137
+ // ============================================================================
138
+ // Stealth DOM queries
139
+ // ============================================================================
140
+ async function isInputElement(stealth, page, selector) {
141
+ if (stealth) {
142
+ try {
143
+ const escaped = JSON.stringify(selector);
144
+ const result = await stealth.evaluate(`
145
+ (() => {
146
+ const el = document.querySelector(${escaped});
147
+ if (!el) return false;
148
+ const tag = el.tagName.toLowerCase();
149
+ return tag === 'input' || tag === 'textarea'
150
+ || el.getAttribute('contenteditable') === 'true';
151
+ })()
152
+ `);
153
+ return !!result;
154
+ }
155
+ catch { /* fallthrough */ }
156
+ }
157
+ return page.evaluate((sel) => {
158
+ const el = document.querySelector(sel);
159
+ if (!el)
160
+ return false;
161
+ const tag = el.tagName.toLowerCase();
162
+ return tag === 'input' || tag === 'textarea'
163
+ || el.getAttribute('contenteditable') === 'true';
164
+ }, selector).catch(() => false);
165
+ }
166
+ async function isSelectorFocused(stealth, page, selector) {
167
+ if (stealth) {
168
+ try {
169
+ const escaped = JSON.stringify(selector);
170
+ const result = await stealth.evaluate(`
171
+ (() => {
172
+ const el = document.querySelector(${escaped});
173
+ return el === document.activeElement;
174
+ })()
175
+ `);
176
+ return !!result;
177
+ }
178
+ catch { /* fallthrough */ }
179
+ }
180
+ return page.evaluate((sel) => {
181
+ const el = document.querySelector(sel);
182
+ return el === document.activeElement;
183
+ }, selector).catch(() => false);
184
+ }
185
+ // ============================================================================
186
+ // Stealth ElementHandle input check — uses CDP DOM.describeNode
187
+ // instead of el.evaluate() to avoid main-world JS execution.
188
+ // ============================================================================
189
+ async function isInputElementHandle(stealth, el) {
190
+ if (stealth) {
191
+ try {
192
+ const cdp = await stealth.getCdpSession();
193
+ const remoteObject = el.remoteObject?.();
194
+ if (remoteObject?.objectId) {
195
+ const { node } = await cdp.send('DOM.describeNode', {
196
+ objectId: remoteObject.objectId,
197
+ });
198
+ const tag = (node?.nodeName || '').toLowerCase();
199
+ if (tag === 'input' || tag === 'textarea')
200
+ return true;
201
+ const attrs = node?.attributes || [];
202
+ for (let i = 0; i < attrs.length; i += 2) {
203
+ if (attrs[i] === 'contenteditable' && attrs[i + 1] === 'true') {
204
+ return true;
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+ }
210
+ catch { /* fallthrough to el.evaluate */ }
211
+ }
212
+ return el.evaluate((node) => {
213
+ const tag = node.tagName?.toLowerCase();
214
+ return tag === 'input' || tag === 'textarea'
215
+ || node.getAttribute?.('contenteditable') === 'true';
216
+ }).catch(() => false);
217
+ }
218
+ // ============================================================================
219
+ // Page-level patching
220
+ // ============================================================================
221
+ function patchPage(page, cfg, cursor) {
222
+ const originals = {
223
+ click: page.click.bind(page),
224
+ hover: page.hover.bind(page),
225
+ type: page.type.bind(page),
226
+ select: page.select.bind(page),
227
+ focus: page.focus.bind(page),
228
+ goto: page.goto.bind(page),
229
+ tap: page.tap.bind(page),
230
+ mouseMove: page.mouse.move.bind(page.mouse),
231
+ mouseClick: page.mouse.click.bind(page.mouse),
232
+ mouseDown: page.mouse.down.bind(page.mouse),
233
+ mouseUp: page.mouse.up.bind(page.mouse),
234
+ mouseWheel: page.mouse.wheel?.bind(page.mouse),
235
+ mouseDragAndDrop: page.mouse.dragAndDrop?.bind(page.mouse),
236
+ keyboardType: page.keyboard.type.bind(page.keyboard),
237
+ keyboardDown: page.keyboard.down.bind(page.keyboard),
238
+ keyboardUp: page.keyboard.up.bind(page.keyboard),
239
+ keyboardPress: page.keyboard.press.bind(page.keyboard),
240
+ keyboardSendCharacter: page.keyboard.sendCharacter.bind(page.keyboard),
241
+ };
242
+ page._original = originals;
243
+ page._humanCfg = cfg;
244
+ const stealth = new StealthEval(page);
245
+ page._stealth = stealth;
246
+ let cdpSession = null;
247
+ const ensureCdp = async () => {
248
+ if (!cdpSession) {
249
+ try {
250
+ cdpSession = await stealth.getCdpSession();
251
+ }
252
+ catch { }
253
+ }
254
+ return cdpSession;
255
+ };
256
+ const raw = {
257
+ move: originals.mouseMove,
258
+ down: originals.mouseDown,
259
+ up: originals.mouseUp,
260
+ wheel: async (deltaX, deltaY) => {
261
+ if (originals.mouseWheel) {
262
+ await originals.mouseWheel({ deltaX, deltaY });
263
+ }
264
+ },
265
+ };
266
+ const rawKb = {
267
+ down: originals.keyboardDown,
268
+ up: originals.keyboardUp,
269
+ type: originals.keyboardType,
270
+ insertText: originals.keyboardSendCharacter,
271
+ };
272
+ async function ensureCursorInit() {
273
+ if (!cursor.initialized) {
274
+ cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]);
275
+ cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]);
276
+ await originals.mouseMove(cursor.x, cursor.y);
277
+ cursor.initialized = true;
278
+ }
279
+ }
280
+ // ==== goto ====
281
+ const humanGoto = async (url, options) => {
282
+ const response = await originals.goto(url, options);
283
+ stealth.invalidate();
284
+ patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth);
285
+ return response;
286
+ };
287
+ // ==== click (with clickCount support for dblclick) ====
288
+ const humanClickFn = async (selector, options) => {
289
+ await ensureCursorInit();
290
+ if (cfg.idle_between_actions) {
291
+ await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
292
+ }
293
+ const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg);
294
+ cursor.x = cursorX;
295
+ cursor.y = cursorY;
296
+ const isInput = await isInputElement(stealth, page, selector);
297
+ const target = clickTarget(box, isInput, cfg);
298
+ await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
299
+ cursor.x = target.x;
300
+ cursor.y = target.y;
301
+ const clickCount = options?.clickCount ?? options?.count ?? 1;
302
+ if (clickCount >= 2) {
303
+ await humanClick(raw, isInput, cfg);
304
+ await sleep(rand(40, 90));
305
+ await raw.down({ clickCount: 2 });
306
+ await sleep(rand(30, 60));
307
+ await raw.up({ clickCount: 2 });
308
+ }
309
+ else {
310
+ await humanClick(raw, isInput, cfg);
311
+ }
312
+ };
313
+ // ==== hover ====
314
+ const humanHoverFn = async (selector, options) => {
315
+ await ensureCursorInit();
316
+ if (cfg.idle_between_actions) {
317
+ await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
318
+ }
319
+ const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg);
320
+ cursor.x = cursorX;
321
+ cursor.y = cursorY;
322
+ const target = clickTarget(box, false, cfg);
323
+ await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
324
+ cursor.x = target.x;
325
+ cursor.y = target.y;
326
+ };
327
+ // ==== type ====
328
+ const humanTypeFn = async (selector, text, options) => {
329
+ await sleep(randRange(cfg.field_switch_delay));
330
+ await humanClickFn(selector);
331
+ await sleep(rand(100, 250));
332
+ const cdp = await ensureCdp();
333
+ await humanType(page, rawKb, text, cfg, cdp);
334
+ };
335
+ // ==== select ====
336
+ const humanSelectFn = async (selector, ...values) => {
337
+ await humanHoverFn(selector);
338
+ await sleep(rand(100, 300));
339
+ return originals.select(selector, ...values);
340
+ };
341
+ // ==== focus ====
342
+ const humanFocusFn = async (selector) => {
343
+ if (!await isSelectorFocused(stealth, page, selector)) {
344
+ await humanClickFn(selector);
345
+ }
346
+ };
347
+ // ==== tap ====
348
+ const humanTapFn = async (selector, options) => {
349
+ await humanClickFn(selector, options);
350
+ };
351
+ // ============================================================
352
+ // Assign page-level patches
353
+ // ============================================================
354
+ page.goto = humanGoto;
355
+ page.click = humanClickFn;
356
+ page.hover = humanHoverFn;
357
+ page.type = humanTypeFn;
358
+ page.select = humanSelectFn;
359
+ page.focus = humanFocusFn;
360
+ page.tap = humanTapFn;
361
+ // ============================================================
362
+ // Mouse patches
363
+ // ============================================================
364
+ page.mouse.move = async (x, y, options) => {
365
+ await ensureCursorInit();
366
+ await humanMove(raw, cursor.x, cursor.y, x, y, cfg);
367
+ cursor.x = x;
368
+ cursor.y = y;
369
+ };
370
+ page.mouse.click = async (x, y, options) => {
371
+ await ensureCursorInit();
372
+ await humanMove(raw, cursor.x, cursor.y, x, y, cfg);
373
+ cursor.x = x;
374
+ cursor.y = y;
375
+ const clickCount = options?.clickCount ?? options?.count ?? 1;
376
+ if (clickCount >= 2) {
377
+ await humanClick(raw, false, cfg);
378
+ await sleep(rand(40, 90));
379
+ await raw.down({ clickCount: 2 });
380
+ await sleep(rand(30, 60));
381
+ await raw.up({ clickCount: 2 });
382
+ }
383
+ else {
384
+ await humanClick(raw, false, cfg);
385
+ }
386
+ };
387
+ if (originals.mouseWheel) {
388
+ page.mouse.wheel = async (options) => {
389
+ const dx = options?.deltaX ?? 0;
390
+ const dy = options?.deltaY ?? 0;
391
+ if (Math.abs(dy) > 0) {
392
+ await smoothWheel(raw, dy, cfg, 'y');
393
+ }
394
+ if (Math.abs(dx) > 0) {
395
+ await smoothWheel(raw, dx, cfg, 'x');
396
+ }
397
+ };
398
+ }
399
+ if (originals.mouseDragAndDrop) {
400
+ page.mouse.dragAndDrop = async (start, target, options) => {
401
+ await ensureCursorInit();
402
+ await humanMove(raw, cursor.x, cursor.y, start.x, start.y, cfg);
403
+ cursor.x = start.x;
404
+ cursor.y = start.y;
405
+ await sleep(rand(100, 200));
406
+ await originals.mouseDown();
407
+ await sleep(rand(80, 150));
408
+ await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
409
+ cursor.x = target.x;
410
+ cursor.y = target.y;
411
+ await sleep(rand(80, 150));
412
+ await originals.mouseUp();
413
+ };
414
+ }
415
+ // ============================================================
416
+ // Keyboard patches
417
+ // ============================================================
418
+ page.keyboard.type = async (text, options) => {
419
+ const cdp = await ensureCdp();
420
+ await humanType(page, rawKb, text, cfg, cdp);
421
+ };
422
+ page.keyboard.press = async (key, options) => {
423
+ await sleep(rand(20, 60));
424
+ await originals.keyboardDown(key);
425
+ await sleep(randRange(cfg.key_hold));
426
+ await originals.keyboardUp(key);
427
+ };
428
+ page.keyboard.down = async (key) => {
429
+ await sleep(rand(10, 30));
430
+ await originals.keyboardDown(key);
431
+ };
432
+ page.keyboard.up = async (key) => {
433
+ await sleep(rand(10, 30));
434
+ await originals.keyboardUp(key);
435
+ };
436
+ // ============================================================
437
+ // Store helpers for frame/element patching
438
+ // ============================================================
439
+ page._humanCursor = cursor;
440
+ page._humanRaw = raw;
441
+ page._humanRawKb = rawKb;
442
+ page._ensureCursorInit = ensureCursorInit;
443
+ // Initialize cursor
444
+ cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]);
445
+ cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]);
446
+ originals.mouseMove(cursor.x, cursor.y).then(() => {
447
+ cursor.initialized = true;
448
+ }).catch(() => { });
449
+ // Patch frames
450
+ patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth);
451
+ // Patch ElementHandle selectors
452
+ patchElementHandle(page, cfg, cursor, raw, rawKb, originals, stealth);
453
+ }
454
+ // ============================================================================
455
+ // ElementHandle patching — PUPPETEER-SPECIFIC
456
+ // ============================================================================
457
+ function patchElementHandle(page, cfg, cursor, raw, rawKb, originals, stealth) {
458
+ const orig$ = page.$.bind(page);
459
+ const orig$$ = page.$$.bind(page);
460
+ const origWaitForSelector = page.waitForSelector.bind(page);
461
+ page.$ = async (selector) => {
462
+ const el = await orig$(selector);
463
+ if (el)
464
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
465
+ return el;
466
+ };
467
+ page.$$ = async (selector) => {
468
+ const els = await orig$$(selector);
469
+ for (const el of els) {
470
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
471
+ }
472
+ return els;
473
+ };
474
+ page.waitForSelector = async (selector, options) => {
475
+ const el = await origWaitForSelector(selector, options);
476
+ if (el)
477
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
478
+ return el;
479
+ };
480
+ }
481
+ function patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth) {
482
+ if (el._humanPatched)
483
+ return;
484
+ el._humanPatched = true;
485
+ const origElClick = el.click.bind(el);
486
+ const origElHover = el.hover.bind(el);
487
+ const origElType = el.type.bind(el);
488
+ const origElPress = el.press?.bind(el);
489
+ const origElTap = el.tap?.bind(el);
490
+ const origElFocus = el.focus?.bind(el);
491
+ const origElDragAndDrop = el.dragAndDrop?.bind(el);
492
+ const origElSelect = el.select?.bind(el);
493
+ const origElDrop = el.drop?.bind(el);
494
+ // --- Nested selectors ---
495
+ const origEl$ = el.$.bind(el);
496
+ const origEl$$ = el.$$.bind(el);
497
+ const origElWaitForSelector = el.waitForSelector.bind(el);
498
+ el.$ = async (selector) => {
499
+ const child = await origEl$(selector);
500
+ if (child)
501
+ patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
502
+ return child;
503
+ };
504
+ el.$$ = async (selector) => {
505
+ const children = await origEl$$(selector);
506
+ for (const child of children) {
507
+ patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
508
+ }
509
+ return children;
510
+ };
511
+ el.waitForSelector = async (selector, options) => {
512
+ const child = await origElWaitForSelector(selector, options);
513
+ if (child)
514
+ patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
515
+ return child;
516
+ };
517
+ // --- Helper: get box and move cursor ---
518
+ const moveToElement = async () => {
519
+ await page._ensureCursorInit();
520
+ const box = await el.boundingBox();
521
+ if (!box)
522
+ return null;
523
+ const isInp = await isInputElementHandle(stealth, el);
524
+ const target = clickTarget(box, isInp, cfg);
525
+ if (cfg.idle_between_actions) {
526
+ await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
527
+ }
528
+ await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
529
+ cursor.x = target.x;
530
+ cursor.y = target.y;
531
+ return { box, isInp };
532
+ };
533
+ // --- el.click() ---
534
+ el.click = async (options) => {
535
+ const info = await moveToElement();
536
+ if (!info)
537
+ return origElClick(options);
538
+ const clickCount = options?.clickCount ?? options?.count ?? 1;
539
+ if (clickCount >= 2) {
540
+ await humanClick(raw, info.isInp, cfg);
541
+ await sleep(rand(40, 90));
542
+ await raw.down({ clickCount: 2 });
543
+ await sleep(rand(30, 60));
544
+ await raw.up({ clickCount: 2 });
545
+ }
546
+ else {
547
+ await humanClick(raw, info.isInp, cfg);
548
+ }
549
+ };
550
+ // --- el.hover() ---
551
+ el.hover = async () => {
552
+ const info = await moveToElement();
553
+ if (!info)
554
+ return origElHover();
555
+ };
556
+ // --- el.type() ---
557
+ el.type = async (text, options) => {
558
+ const info = await moveToElement();
559
+ if (!info)
560
+ return origElType(text, options);
561
+ await humanClick(raw, info.isInp, cfg);
562
+ await sleep(rand(100, 250));
563
+ const cdp = await stealth.getCdpSession().catch(() => null);
564
+ await humanType(page, rawKb, text, cfg, cdp);
565
+ };
566
+ // --- el.press() ---
567
+ if (origElPress) {
568
+ el.press = async (key, options) => {
569
+ await sleep(rand(20, 60));
570
+ await originals.keyboardDown(key);
571
+ await sleep(randRange(cfg.key_hold));
572
+ await originals.keyboardUp(key);
573
+ };
574
+ }
575
+ // --- el.tap() ---
576
+ if (origElTap) {
577
+ el.tap = async () => {
578
+ const info = await moveToElement();
579
+ if (!info)
580
+ return origElTap();
581
+ await humanClick(raw, info.isInp, cfg);
582
+ };
583
+ }
584
+ // --- el.focus() ---
585
+ if (origElFocus) {
586
+ el.focus = async () => {
587
+ const info = await moveToElement();
588
+ if (!info)
589
+ return origElFocus();
590
+ await humanClick(raw, info.isInp, cfg);
591
+ };
592
+ }
593
+ // --- el.select() ---
594
+ if (origElSelect) {
595
+ el.select = async (...values) => {
596
+ const info = await moveToElement();
597
+ if (!info)
598
+ return origElSelect(...values);
599
+ await humanClick(raw, false, cfg);
600
+ await sleep(rand(100, 300));
601
+ return origElSelect(...values);
602
+ };
603
+ }
604
+ // --- el.drop() ---
605
+ if (origElDrop) {
606
+ el.drop = async (draggable, options) => {
607
+ const srcBox = await draggable.boundingBox();
608
+ const tgtBox = await el.boundingBox();
609
+ if (srcBox && tgtBox) {
610
+ const sx = srcBox.x + srcBox.width / 2;
611
+ const sy = srcBox.y + srcBox.height / 2;
612
+ const tx = tgtBox.x + tgtBox.width / 2;
613
+ const ty = tgtBox.y + tgtBox.height / 2;
614
+ await page._ensureCursorInit();
615
+ await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg);
616
+ cursor.x = sx;
617
+ cursor.y = sy;
618
+ await sleep(rand(100, 200));
619
+ await originals.mouseDown();
620
+ await sleep(rand(80, 150));
621
+ await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg);
622
+ cursor.x = tx;
623
+ cursor.y = ty;
624
+ await sleep(rand(80, 150));
625
+ await originals.mouseUp();
626
+ }
627
+ else {
628
+ return origElDrop(draggable, options);
629
+ }
630
+ };
631
+ }
632
+ // --- el.dragAndDrop() ---
633
+ if (origElDragAndDrop) {
634
+ el.dragAndDrop = async (targetEl, options) => {
635
+ const srcBox = await el.boundingBox();
636
+ const tgtBox = await targetEl.boundingBox();
637
+ if (srcBox && tgtBox) {
638
+ const sx = srcBox.x + srcBox.width / 2;
639
+ const sy = srcBox.y + srcBox.height / 2;
640
+ const tx = tgtBox.x + tgtBox.width / 2;
641
+ const ty = tgtBox.y + tgtBox.height / 2;
642
+ await page._ensureCursorInit();
643
+ await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg);
644
+ cursor.x = sx;
645
+ cursor.y = sy;
646
+ await sleep(rand(100, 200));
647
+ await originals.mouseDown();
648
+ await sleep(rand(80, 150));
649
+ await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg);
650
+ cursor.x = tx;
651
+ cursor.y = ty;
652
+ await sleep(rand(80, 150));
653
+ await originals.mouseUp();
654
+ }
655
+ else {
656
+ return origElDragAndDrop(targetEl, options);
657
+ }
658
+ };
659
+ }
660
+ }
661
+ // ============================================================================
662
+ // Frame-level patching — native Puppeteer Frame methods only
663
+ // Puppeteer Frame has: click, hover, type, select, focus, tap
664
+ // ============================================================================
665
+ function patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth) {
666
+ for (const frame of iterFrames(page)) {
667
+ patchSingleFrame(frame, page, cfg, cursor, raw, rawKb, originals, stealth);
668
+ }
669
+ }
670
+ function patchSingleFrame(frame, page, cfg, cursor, raw, rawKb, originals, stealth) {
671
+ if (frame._humanPatched)
672
+ return;
673
+ frame._humanPatched = true;
674
+ const origFrameSelect = frame.select.bind(frame);
675
+ frame.click = async (selector, options) => {
676
+ await page.click(selector, options);
677
+ };
678
+ frame.hover = async (selector, options) => {
679
+ await page.hover(selector, options);
680
+ };
681
+ frame.type = async (selector, text, options) => {
682
+ await page.type(selector, text, options);
683
+ };
684
+ frame.select = async (selector, ...values) => {
685
+ await page.hover(selector);
686
+ await sleep(rand(100, 300));
687
+ return origFrameSelect(selector, ...values);
688
+ };
689
+ frame.focus = async (selector) => {
690
+ await page.focus(selector);
691
+ };
692
+ frame.tap = async (selector, options) => {
693
+ await page.click(selector, options);
694
+ };
695
+ // Patch frame.$() to return patched ElementHandles
696
+ const origFrame$ = frame.$.bind(frame);
697
+ const origFrame$$ = frame.$$.bind(frame);
698
+ const origFrameWaitForSelector = frame.waitForSelector.bind(frame);
699
+ frame.$ = async (selector) => {
700
+ const el = await origFrame$(selector);
701
+ if (el)
702
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
703
+ return el;
704
+ };
705
+ frame.$$ = async (selector) => {
706
+ const els = await origFrame$$(selector);
707
+ for (const el of els) {
708
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
709
+ }
710
+ return els;
711
+ };
712
+ frame.waitForSelector = async (selector, options) => {
713
+ const el = await origFrameWaitForSelector(selector, options);
714
+ if (el)
715
+ patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
716
+ return el;
717
+ };
718
+ }
719
+ function* iterFrames(page) {
720
+ try {
721
+ const mainFrame = page.mainFrame();
722
+ yield mainFrame;
723
+ for (const child of mainFrame.childFrames()) {
724
+ yield child;
725
+ }
726
+ }
727
+ catch { }
728
+ }
729
+ // ============================================================================
730
+ // Browser-level patching
731
+ // ============================================================================
732
+ export function patchBrowser(browser, cfg) {
733
+ browser.pages().then(pages => {
734
+ for (const page of pages) {
735
+ if (!page._original) {
736
+ patchPage(page, cfg, new CursorState());
737
+ }
738
+ }
739
+ }).catch(() => { });
740
+ const origNewPage = browser.newPage.bind(browser);
741
+ browser.newPage = async () => {
742
+ const page = await origNewPage();
743
+ if (!page._original) {
744
+ patchPage(page, cfg, new CursorState());
745
+ }
746
+ return page;
747
+ };
748
+ // v21: createIncognitoBrowserContext
749
+ // v22+: createBrowserContext (renamed in puppeteer/puppeteer#11834)
750
+ for (const methodName of ['createBrowserContext', 'createIncognitoBrowserContext']) {
751
+ if (typeof browser[methodName] === 'function') {
752
+ const origCreateContext = browser[methodName].bind(browser);
753
+ browser[methodName] = async (options) => {
754
+ const context = await origCreateContext(options);
755
+ const origCtxNewPage = context.newPage.bind(context);
756
+ context.newPage = async () => {
757
+ const page = await origCtxNewPage();
758
+ if (!page._original) {
759
+ patchPage(page, cfg, new CursorState());
760
+ }
761
+ return page;
762
+ };
763
+ return context;
764
+ };
765
+ }
766
+ }
767
+ browser.on('targetcreated', async (target) => {
768
+ try {
769
+ if (target.type() === 'page') {
770
+ const page = await target.page();
771
+ if (page && !page._original) {
772
+ patchPage(page, cfg, new CursorState());
773
+ }
774
+ }
775
+ }
776
+ catch { }
777
+ });
778
+ }
779
+ export { patchPage };
780
+ //# sourceMappingURL=index.js.map