@reconcrap/boss-recommend-mcp 2.0.47 → 2.0.48
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/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +27 -27
- package/package.json +1 -1
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +39 -39
- package/skills/boss-chat/SKILL.md +93 -93
- package/skills/boss-recommend-pipeline/README.md +12 -12
- package/skills/boss-recommend-pipeline/SKILL.md +180 -180
- package/skills/boss-recruit-pipeline/README.md +17 -17
- package/skills/boss-recruit-pipeline/SKILL.md +58 -58
- package/src/chat-mcp.js +1780 -1780
- package/src/chat-runtime-config.js +749 -749
- package/src/cli.js +3054 -3054
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +1586 -1453
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +54 -54
- package/src/core/infinite-list/index.js +1326 -1326
- package/src/core/reporting/legacy-csv.js +341 -341
- package/src/core/run/timing.js +33 -33
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +221 -221
- package/src/domains/chat/detail.js +1668 -1668
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +592 -592
- package/src/domains/chat/page-guard.js +98 -98
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1977 -1977
- package/src/domains/recommend/actions.js +457 -457
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/constants.js +165 -165
- package/src/domains/recommend/filters.js +610 -610
- package/src/domains/recommend/index.js +10 -10
- package/src/domains/recommend/jobs.js +316 -316
- package/src/domains/recommend/refresh.js +472 -472
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +167 -167
- package/src/domains/recruit/detail.js +461 -461
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +451 -451
- package/src/domains/recruit/refresh.js +44 -44
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1207 -1207
- package/src/domains/recruit/search.js +1202 -1202
- package/src/recommend-mcp.js +22 -22
- package/src/recruit-mcp.js +1338 -1338
|
@@ -1,1453 +1,1586 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import CDP from "chrome-remote-interface";
|
|
6
|
-
|
|
7
|
-
export const DEFAULT_CHROME_HOST = "127.0.0.1";
|
|
8
|
-
export const DEFAULT_CHROME_PORT = 9222;
|
|
9
|
-
export const BOSS_LOGIN_URL = "https://www.zhipin.com/web/user/?ka=bticket";
|
|
10
|
-
export const LID_CLOSED_SAFE_CHROME_ARGS = [
|
|
11
|
-
"--disable-backgrounding-occluded-windows",
|
|
12
|
-
"--disable-background-timer-throttling",
|
|
13
|
-
"--disable-renderer-backgrounding",
|
|
14
|
-
"--disable-features=CalculateNativeWinOcclusion"
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export const ALLOWED_CDP_DOMAINS = new Set([
|
|
18
|
-
"Accessibility",
|
|
19
|
-
"Browser",
|
|
20
|
-
"DOM",
|
|
21
|
-
"Input",
|
|
22
|
-
"Network",
|
|
23
|
-
"Page",
|
|
24
|
-
"Target"
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
export const FORBIDDEN_CDP_DOMAINS = new Set(["Runtime"]);
|
|
28
|
-
|
|
29
|
-
const BOSS_LOGIN_URL_PATTERN = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com|login\.zhipin\.com)/i;
|
|
30
|
-
const BOSS_LOGIN_TEXT_PATTERN = /扫码登录|验证码登录|密码登录|登录后|请登录|登录BOSS直聘|Boss登录|BOSS登录/i;
|
|
31
|
-
const CHROME_DEBUG_UNAVAILABLE_PATTERN = /ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|connect|socket hang up/i;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
".login-
|
|
35
|
-
".login-
|
|
36
|
-
".
|
|
37
|
-
".
|
|
38
|
-
".
|
|
39
|
-
"
|
|
40
|
-
"input[
|
|
41
|
-
"input[placeholder*='
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (typeof raw === "
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
if (["
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
let
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
state.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
error
|
|
496
|
-
error.
|
|
497
|
-
error.
|
|
498
|
-
error.
|
|
499
|
-
error.
|
|
500
|
-
error.
|
|
501
|
-
error.
|
|
502
|
-
error.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
process.env.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
path.join(process.env.
|
|
590
|
-
path.join(process.env
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
"/usr/bin/google-chrome
|
|
602
|
-
"/usr/bin/
|
|
603
|
-
"/usr/bin/chromium",
|
|
604
|
-
"/
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
.
|
|
624
|
-
.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
`--
|
|
636
|
-
|
|
637
|
-
"--no-
|
|
638
|
-
|
|
639
|
-
...
|
|
640
|
-
...
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
||
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
methodLog,
|
|
940
|
-
|
|
941
|
-
await
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
return
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
};
|
|
1253
|
-
const
|
|
1254
|
-
||
|
|
1255
|
-
||
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
:
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1401
|
-
return {
|
|
1402
|
-
mode: "
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
await
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import CDP from "chrome-remote-interface";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_CHROME_HOST = "127.0.0.1";
|
|
8
|
+
export const DEFAULT_CHROME_PORT = 9222;
|
|
9
|
+
export const BOSS_LOGIN_URL = "https://www.zhipin.com/web/user/?ka=bticket";
|
|
10
|
+
export const LID_CLOSED_SAFE_CHROME_ARGS = [
|
|
11
|
+
"--disable-backgrounding-occluded-windows",
|
|
12
|
+
"--disable-background-timer-throttling",
|
|
13
|
+
"--disable-renderer-backgrounding",
|
|
14
|
+
"--disable-features=CalculateNativeWinOcclusion"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const ALLOWED_CDP_DOMAINS = new Set([
|
|
18
|
+
"Accessibility",
|
|
19
|
+
"Browser",
|
|
20
|
+
"DOM",
|
|
21
|
+
"Input",
|
|
22
|
+
"Network",
|
|
23
|
+
"Page",
|
|
24
|
+
"Target"
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const FORBIDDEN_CDP_DOMAINS = new Set(["Runtime"]);
|
|
28
|
+
|
|
29
|
+
const BOSS_LOGIN_URL_PATTERN = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com|login\.zhipin\.com)/i;
|
|
30
|
+
const BOSS_LOGIN_TEXT_PATTERN = /扫码登录|验证码登录|密码登录|登录后|请登录|登录BOSS直聘|Boss登录|BOSS登录/i;
|
|
31
|
+
const CHROME_DEBUG_UNAVAILABLE_PATTERN = /ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|connect|socket hang up/i;
|
|
32
|
+
const CDP_CLOSED_TRANSPORT_PATTERN = /WebSocket is not open|readyState\s+\d+\s+\(CLOSED\)|ECONNRESET|socket hang up|Target closed|Session closed|Connection closed/i;
|
|
33
|
+
const BOSS_LOGIN_DOM_SELECTORS = [
|
|
34
|
+
".login-box",
|
|
35
|
+
".login-form",
|
|
36
|
+
".login-dialog",
|
|
37
|
+
".sign-form",
|
|
38
|
+
".qrcode-box",
|
|
39
|
+
".user-login",
|
|
40
|
+
"input[name='phone']",
|
|
41
|
+
"input[placeholder*='手机号']",
|
|
42
|
+
"input[placeholder*='验证码']"
|
|
43
|
+
];
|
|
44
|
+
const HUMAN_INTERACTION_CONFIG = new WeakMap();
|
|
45
|
+
const DEFAULT_HUMAN_BEHAVIOR_PROFILE = "paced_with_rests";
|
|
46
|
+
export const DETERMINISTIC_CLICK_OPTIONS = Object.freeze({
|
|
47
|
+
humanRestEnabled: false
|
|
48
|
+
});
|
|
49
|
+
const HUMAN_BEHAVIOR_PROFILES = Object.freeze({
|
|
50
|
+
baseline: Object.freeze({
|
|
51
|
+
enabled: false,
|
|
52
|
+
clickMovement: false,
|
|
53
|
+
textEntry: false,
|
|
54
|
+
listScrollJitter: false,
|
|
55
|
+
shortRest: false,
|
|
56
|
+
batchRest: false,
|
|
57
|
+
actionCooldown: false
|
|
58
|
+
}),
|
|
59
|
+
paced: Object.freeze({
|
|
60
|
+
enabled: true,
|
|
61
|
+
clickMovement: true,
|
|
62
|
+
textEntry: true,
|
|
63
|
+
listScrollJitter: true,
|
|
64
|
+
shortRest: false,
|
|
65
|
+
batchRest: false,
|
|
66
|
+
actionCooldown: true
|
|
67
|
+
}),
|
|
68
|
+
paced_with_rests: Object.freeze({
|
|
69
|
+
enabled: true,
|
|
70
|
+
clickMovement: true,
|
|
71
|
+
textEntry: true,
|
|
72
|
+
listScrollJitter: true,
|
|
73
|
+
shortRest: true,
|
|
74
|
+
batchRest: true,
|
|
75
|
+
actionCooldown: true
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
const HUMAN_BEHAVIOR_PROFILE_ALIASES = Object.freeze({
|
|
79
|
+
off: "baseline",
|
|
80
|
+
disabled: "baseline",
|
|
81
|
+
deterministic: "baseline",
|
|
82
|
+
safe: "paced",
|
|
83
|
+
safe_pacing: "paced",
|
|
84
|
+
paced_with_rest: "paced_with_rests",
|
|
85
|
+
rests: "paced_with_rests",
|
|
86
|
+
rest: "paced_with_rests"
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function clampNumber(value, min, max) {
|
|
90
|
+
const number = Number(value);
|
|
91
|
+
if (!Number.isFinite(number)) return min;
|
|
92
|
+
return Math.min(max, Math.max(min, number));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function randomBetween(random, min, max) {
|
|
96
|
+
const lower = Number(min) || 0;
|
|
97
|
+
const upper = Number(max) || lower;
|
|
98
|
+
if (upper <= lower) return lower;
|
|
99
|
+
return lower + random() * (upper - lower);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function randomIntegerBetween(random, min, max) {
|
|
103
|
+
return Math.floor(randomBetween(random, min, max + 1));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizePoint(point) {
|
|
107
|
+
const x = Number(point?.x);
|
|
108
|
+
const y = Number(point?.y);
|
|
109
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
110
|
+
return { x, y };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeRandom(random) {
|
|
114
|
+
return typeof random === "function" ? random : Math.random;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getHumanInteractionConfig(client) {
|
|
118
|
+
return HUMAN_INTERACTION_CONFIG.get(client) || null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeBooleanOption(raw, fallback = null) {
|
|
122
|
+
if (typeof raw === "boolean") return raw;
|
|
123
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw !== 0;
|
|
124
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
125
|
+
if (!normalized) return fallback;
|
|
126
|
+
if (["true", "1", "yes", "y", "on", "enabled"].includes(normalized)) return true;
|
|
127
|
+
if (["false", "0", "no", "n", "off", "disabled"].includes(normalized)) return false;
|
|
128
|
+
return fallback;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readFirstOption(source, keys = []) {
|
|
132
|
+
if (!source || typeof source !== "object") return undefined;
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeFeatureBoolean(raw, fallback) {
|
|
140
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
141
|
+
return normalizeBooleanOption(readFirstOption(raw, ["enabled", "enable"]), fallback);
|
|
142
|
+
}
|
|
143
|
+
return normalizeBooleanOption(raw, fallback);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeHumanBehaviorProfile(raw, fallback = "baseline") {
|
|
147
|
+
const normalized = String(raw || "").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
148
|
+
const profile = HUMAN_BEHAVIOR_PROFILE_ALIASES[normalized] || normalized;
|
|
149
|
+
return Object.prototype.hasOwnProperty.call(HUMAN_BEHAVIOR_PROFILES, profile)
|
|
150
|
+
? profile
|
|
151
|
+
: fallback;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function normalizeHumanBehaviorOptions(raw = null, {
|
|
155
|
+
legacyEnabled = false,
|
|
156
|
+
safePacing = null,
|
|
157
|
+
batchRestEnabled = null
|
|
158
|
+
} = {}) {
|
|
159
|
+
const safePacingFlag = normalizeBooleanOption(safePacing, null);
|
|
160
|
+
const batchRestFlag = normalizeBooleanOption(batchRestEnabled, null);
|
|
161
|
+
let source = "default";
|
|
162
|
+
let rawObject = {};
|
|
163
|
+
if (typeof raw === "boolean") {
|
|
164
|
+
rawObject = { enabled: raw };
|
|
165
|
+
source = "boolean";
|
|
166
|
+
} else if (typeof raw === "string") {
|
|
167
|
+
rawObject = { profile: raw };
|
|
168
|
+
source = "profile";
|
|
169
|
+
} else if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
170
|
+
rawObject = raw;
|
|
171
|
+
source = "object";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const explicitProfile = readFirstOption(rawObject, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
175
|
+
const enabledRaw = readFirstOption(rawObject, ["enabled", "enable", "human_behavior_enabled"]);
|
|
176
|
+
const explicitEnabled = normalizeBooleanOption(enabledRaw, null);
|
|
177
|
+
const inferredProfile = (raw === true || explicitEnabled === true) && legacyEnabled !== true && batchRestFlag !== true
|
|
178
|
+
? "paced"
|
|
179
|
+
: legacyEnabled === true || batchRestFlag === true
|
|
180
|
+
? "paced_with_rests"
|
|
181
|
+
: safePacingFlag === true
|
|
182
|
+
? "paced"
|
|
183
|
+
: DEFAULT_HUMAN_BEHAVIOR_PROFILE;
|
|
184
|
+
const profile = normalizeHumanBehaviorProfile(explicitProfile, inferredProfile);
|
|
185
|
+
const profileDefaults = {
|
|
186
|
+
...HUMAN_BEHAVIOR_PROFILES[profile]
|
|
187
|
+
};
|
|
188
|
+
if (legacyEnabled === true && !explicitProfile) {
|
|
189
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced_with_rests);
|
|
190
|
+
} else if (safePacingFlag === true && !explicitProfile) {
|
|
191
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced);
|
|
192
|
+
}
|
|
193
|
+
if (batchRestFlag === true && !explicitProfile) {
|
|
194
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced_with_rests);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const hasExplicitEnabled = enabledRaw !== undefined;
|
|
198
|
+
if (hasExplicitEnabled) {
|
|
199
|
+
profileDefaults.enabled = normalizeBooleanOption(enabledRaw, profileDefaults.enabled);
|
|
200
|
+
}
|
|
201
|
+
if (!hasExplicitEnabled && (safePacingFlag === false || batchRestFlag === false) && !explicitProfile && legacyEnabled !== true) {
|
|
202
|
+
profileDefaults.enabled = false;
|
|
203
|
+
}
|
|
204
|
+
if (!hasExplicitEnabled && (safePacingFlag === true || batchRestFlag === true || legacyEnabled === true)) {
|
|
205
|
+
profileDefaults.enabled = true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const enabled = profileDefaults.enabled === true;
|
|
209
|
+
const clickMovement = normalizeFeatureBoolean(
|
|
210
|
+
readFirstOption(rawObject, ["clickMovement", "click_movement", "click_movement_enabled"]),
|
|
211
|
+
profileDefaults.clickMovement
|
|
212
|
+
);
|
|
213
|
+
const textEntry = normalizeFeatureBoolean(
|
|
214
|
+
readFirstOption(rawObject, ["textEntry", "text_entry", "text_entry_enabled"]),
|
|
215
|
+
profileDefaults.textEntry
|
|
216
|
+
);
|
|
217
|
+
const listScrollJitter = normalizeFeatureBoolean(
|
|
218
|
+
readFirstOption(rawObject, ["listScrollJitter", "list_scroll_jitter", "scrollJitter", "scroll_jitter"]),
|
|
219
|
+
profileDefaults.listScrollJitter
|
|
220
|
+
);
|
|
221
|
+
const actionCooldown = normalizeFeatureBoolean(
|
|
222
|
+
readFirstOption(rawObject, ["actionCooldown", "action_cooldown", "readPause", "read_pause"]),
|
|
223
|
+
profileDefaults.actionCooldown
|
|
224
|
+
);
|
|
225
|
+
let shortRest = normalizeFeatureBoolean(
|
|
226
|
+
readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]),
|
|
227
|
+
profileDefaults.shortRest
|
|
228
|
+
);
|
|
229
|
+
let batchRest = normalizeFeatureBoolean(
|
|
230
|
+
readFirstOption(rawObject, ["batchRest", "batch_rest", "batchRestEnabled", "batch_rest_enabled"]),
|
|
231
|
+
profileDefaults.batchRest
|
|
232
|
+
);
|
|
233
|
+
if (batchRestFlag !== null) {
|
|
234
|
+
batchRest = batchRestFlag;
|
|
235
|
+
if (batchRestFlag === true && readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]) === undefined) {
|
|
236
|
+
shortRest = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
enabled,
|
|
242
|
+
profile,
|
|
243
|
+
source,
|
|
244
|
+
clickMovement: enabled && clickMovement === true,
|
|
245
|
+
textEntry: enabled && textEntry === true,
|
|
246
|
+
listScrollJitter: enabled && listScrollJitter === true,
|
|
247
|
+
shortRest: enabled && shortRest === true,
|
|
248
|
+
batchRest: enabled && batchRest === true,
|
|
249
|
+
actionCooldown: enabled && actionCooldown === true,
|
|
250
|
+
restEnabled: enabled && (shortRest === true || batchRest === true)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function nowIso() {
|
|
255
|
+
return new Date().toISOString();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizeTargetMatcher({ targetUrlIncludes, targetPredicate } = {}) {
|
|
259
|
+
if (typeof targetPredicate === "function") return targetPredicate;
|
|
260
|
+
if (targetUrlIncludes) {
|
|
261
|
+
return (target) => String(target?.url || "").includes(targetUrlIncludes);
|
|
262
|
+
}
|
|
263
|
+
return (target) => target?.type === "page";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isForbiddenMethod(methodName) {
|
|
267
|
+
const [domain] = String(methodName || "").split(".");
|
|
268
|
+
return FORBIDDEN_CDP_DOMAINS.has(domain);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function methodName(domain, method) {
|
|
272
|
+
return `${String(domain)}.${String(method)}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function recordMethod(methodLog, method) {
|
|
276
|
+
if (Array.isArray(methodLog)) {
|
|
277
|
+
methodLog.push({ method, at: nowIso() });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function assertNoForbiddenCdpCalls(methodLog = []) {
|
|
282
|
+
const forbidden = methodLog.filter((entry) => isForbiddenMethod(entry?.method));
|
|
283
|
+
if (forbidden.length > 0) {
|
|
284
|
+
const methods = forbidden.map((entry) => entry.method).join(", ");
|
|
285
|
+
throw new Error(`Forbidden CDP methods were used: ${methods}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function humanDelay(baseMs, varianceMs, {
|
|
290
|
+
minMs = 100,
|
|
291
|
+
maxMs = 60000,
|
|
292
|
+
random = Math.random
|
|
293
|
+
} = {}) {
|
|
294
|
+
const nextRandom = normalizeRandom(random);
|
|
295
|
+
const base = Math.max(0, Number(baseMs) || 0);
|
|
296
|
+
const variance = Math.max(0, Number(varianceMs) || 0);
|
|
297
|
+
const lower = Math.max(0, Number(minMs) || 0);
|
|
298
|
+
const upper = Math.max(lower, Number(maxMs) || lower);
|
|
299
|
+
if (variance <= 0) return Math.round(clampNumber(base, lower, upper));
|
|
300
|
+
const u1 = Math.max(Number.EPSILON, Math.min(1 - Number.EPSILON, nextRandom()));
|
|
301
|
+
const u2 = Math.max(Number.EPSILON, Math.min(1 - Number.EPSILON, nextRandom()));
|
|
302
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
303
|
+
return Math.round(clampNumber(base + z * variance, lower, upper));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function generateBezierPath(start, end, {
|
|
307
|
+
steps = 18,
|
|
308
|
+
random = Math.random,
|
|
309
|
+
controlJitterX = 100,
|
|
310
|
+
controlJitterY = 60
|
|
311
|
+
} = {}) {
|
|
312
|
+
const startPoint = normalizePoint(start);
|
|
313
|
+
const endPoint = normalizePoint(end);
|
|
314
|
+
if (!startPoint || !endPoint) {
|
|
315
|
+
throw new Error("generateBezierPath requires finite start and end points");
|
|
316
|
+
}
|
|
317
|
+
const nextRandom = normalizeRandom(random);
|
|
318
|
+
const safeSteps = Math.max(1, Math.floor(Number(steps) || 18));
|
|
319
|
+
const midX = (startPoint.x + endPoint.x) / 2 + (nextRandom() - 0.5) * Math.max(0, Number(controlJitterX) || 0);
|
|
320
|
+
const midY = (startPoint.y + endPoint.y) / 2 + (nextRandom() - 0.5) * Math.max(0, Number(controlJitterY) || 0);
|
|
321
|
+
const path = [];
|
|
322
|
+
for (let index = 0; index <= safeSteps; index += 1) {
|
|
323
|
+
const t = index / safeSteps;
|
|
324
|
+
const inverse = 1 - t;
|
|
325
|
+
path.push({
|
|
326
|
+
x: inverse * inverse * startPoint.x + 2 * inverse * t * midX + t * t * endPoint.x,
|
|
327
|
+
y: inverse * inverse * startPoint.y + 2 * inverse * t * midY + t * t * endPoint.y
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return path;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function configureHumanInteraction(client, {
|
|
334
|
+
enabled = false,
|
|
335
|
+
clickMovementEnabled = null,
|
|
336
|
+
textEntryEnabled = null,
|
|
337
|
+
safeClickPointEnabled = null,
|
|
338
|
+
actionCooldownEnabled = null,
|
|
339
|
+
random = Math.random,
|
|
340
|
+
sleepFn = null,
|
|
341
|
+
moveSteps = 18,
|
|
342
|
+
moveJitterPx = 3,
|
|
343
|
+
hoverJitterPx = 5,
|
|
344
|
+
moveDelayMinMs = 5,
|
|
345
|
+
moveDelayMaxMs = 23,
|
|
346
|
+
hoverDelayMinMs = 10,
|
|
347
|
+
hoverDelayMaxMs = 30,
|
|
348
|
+
prePressBaseMs = 260,
|
|
349
|
+
prePressVarianceMs = 80,
|
|
350
|
+
holdVarianceMs = 30,
|
|
351
|
+
safeClickMinWidth = 44,
|
|
352
|
+
safeClickMinHeight = 28,
|
|
353
|
+
safeClickInsetRatio = 0.22,
|
|
354
|
+
safeClickMinInsetPx = 4,
|
|
355
|
+
safeClickMaxInsetPx = 18,
|
|
356
|
+
textChunkMinLength = 1,
|
|
357
|
+
textChunkMaxLength = 5,
|
|
358
|
+
textChunkDelayBaseMs = 55,
|
|
359
|
+
textChunkDelayVarianceMs = 30
|
|
360
|
+
} = {}) {
|
|
361
|
+
const previous = getHumanInteractionConfig(client);
|
|
362
|
+
const normalizedEnabled = enabled === true;
|
|
363
|
+
HUMAN_INTERACTION_CONFIG.set(client, {
|
|
364
|
+
enabled: normalizedEnabled,
|
|
365
|
+
clickMovementEnabled: normalizedEnabled && clickMovementEnabled !== false,
|
|
366
|
+
textEntryEnabled: normalizedEnabled && textEntryEnabled !== false,
|
|
367
|
+
safeClickPointEnabled: normalizedEnabled && safeClickPointEnabled !== false,
|
|
368
|
+
actionCooldownEnabled: normalizedEnabled && actionCooldownEnabled !== false,
|
|
369
|
+
random: normalizeRandom(random),
|
|
370
|
+
sleepFn: typeof sleepFn === "function" ? sleepFn : sleep,
|
|
371
|
+
moveSteps: Math.max(1, Math.floor(Number(moveSteps) || 18)),
|
|
372
|
+
moveJitterPx: Math.max(0, Number(moveJitterPx) || 0),
|
|
373
|
+
hoverJitterPx: Math.max(0, Number(hoverJitterPx) || 0),
|
|
374
|
+
moveDelayMinMs: Math.max(0, Number(moveDelayMinMs) || 0),
|
|
375
|
+
moveDelayMaxMs: Math.max(0, Number(moveDelayMaxMs) || 0),
|
|
376
|
+
hoverDelayMinMs: Math.max(0, Number(hoverDelayMinMs) || 0),
|
|
377
|
+
hoverDelayMaxMs: Math.max(0, Number(hoverDelayMaxMs) || 0),
|
|
378
|
+
prePressBaseMs: Math.max(0, Number(prePressBaseMs) || 0),
|
|
379
|
+
prePressVarianceMs: Math.max(0, Number(prePressVarianceMs) || 0),
|
|
380
|
+
holdVarianceMs: Math.max(0, Number(holdVarianceMs) || 0),
|
|
381
|
+
safeClickMinWidth: Math.max(1, Number(safeClickMinWidth) || 44),
|
|
382
|
+
safeClickMinHeight: Math.max(1, Number(safeClickMinHeight) || 28),
|
|
383
|
+
safeClickInsetRatio: clampNumber(safeClickInsetRatio, 0.05, 0.45),
|
|
384
|
+
safeClickMinInsetPx: Math.max(0, Number(safeClickMinInsetPx) || 0),
|
|
385
|
+
safeClickMaxInsetPx: Math.max(0, Number(safeClickMaxInsetPx) || 0),
|
|
386
|
+
textChunkMinLength: Math.max(1, Math.floor(Number(textChunkMinLength) || 1)),
|
|
387
|
+
textChunkMaxLength: Math.max(1, Math.floor(Number(textChunkMaxLength) || 5)),
|
|
388
|
+
textChunkDelayBaseMs: Math.max(0, Number(textChunkDelayBaseMs) || 0),
|
|
389
|
+
textChunkDelayVarianceMs: Math.max(0, Number(textChunkDelayVarianceMs) || 0),
|
|
390
|
+
lastMousePoint: previous?.lastMousePoint || null
|
|
391
|
+
});
|
|
392
|
+
return () => {
|
|
393
|
+
if (previous) {
|
|
394
|
+
HUMAN_INTERACTION_CONFIG.set(client, previous);
|
|
395
|
+
} else {
|
|
396
|
+
HUMAN_INTERACTION_CONFIG.delete(client);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function createHumanRestController({
|
|
402
|
+
enabled = false,
|
|
403
|
+
shortRestEnabled = true,
|
|
404
|
+
batchRestEnabled = true,
|
|
405
|
+
random = Math.random,
|
|
406
|
+
shortRestProbability = 0.08,
|
|
407
|
+
shortRestMinMs = 3000,
|
|
408
|
+
shortRestMaxMs = 7000,
|
|
409
|
+
batchThresholdBase = 25,
|
|
410
|
+
batchThresholdJitter = 8,
|
|
411
|
+
batchRestMinMs = 15000,
|
|
412
|
+
batchRestMaxMs = 30000
|
|
413
|
+
} = {}) {
|
|
414
|
+
const nextRandom = normalizeRandom(random);
|
|
415
|
+
const state = {
|
|
416
|
+
enabled: enabled === true,
|
|
417
|
+
short_rest_enabled: enabled === true && shortRestEnabled !== false,
|
|
418
|
+
batch_rest_enabled: enabled === true && batchRestEnabled !== false,
|
|
419
|
+
rest_counter: 0,
|
|
420
|
+
rest_threshold: Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1))),
|
|
421
|
+
rest_count: 0,
|
|
422
|
+
total_rest_ms: 0
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
function resetThreshold() {
|
|
426
|
+
state.rest_threshold = Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1)));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function takeBreakIfNeeded({ sleepFn = sleep } = {}) {
|
|
430
|
+
if (!state.enabled) {
|
|
431
|
+
return {
|
|
432
|
+
enabled: false,
|
|
433
|
+
rested: false,
|
|
434
|
+
rest_counter: state.rest_counter,
|
|
435
|
+
rest_threshold: state.rest_threshold,
|
|
436
|
+
events: []
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
|
|
440
|
+
state.rest_counter += 1;
|
|
441
|
+
const events = [];
|
|
442
|
+
if (state.short_rest_enabled && nextRandom() < Math.max(0, Number(shortRestProbability) || 0)) {
|
|
443
|
+
const pauseMs = Math.round(randomBetween(nextRandom, shortRestMinMs, shortRestMaxMs));
|
|
444
|
+
await sleeper(pauseMs);
|
|
445
|
+
events.push({ kind: "random_rest", pause_ms: pauseMs });
|
|
446
|
+
}
|
|
447
|
+
if (state.batch_rest_enabled && state.rest_counter >= state.rest_threshold) {
|
|
448
|
+
const pauseMs = Math.round(randomBetween(nextRandom, batchRestMinMs, batchRestMaxMs));
|
|
449
|
+
await sleeper(pauseMs);
|
|
450
|
+
events.push({
|
|
451
|
+
kind: "batch_rest",
|
|
452
|
+
pause_ms: pauseMs,
|
|
453
|
+
processed_since_last_batch_rest: state.rest_counter
|
|
454
|
+
});
|
|
455
|
+
state.rest_counter = 0;
|
|
456
|
+
resetThreshold();
|
|
457
|
+
}
|
|
458
|
+
const pauseMs = events.reduce((sum, event) => sum + event.pause_ms, 0);
|
|
459
|
+
if (pauseMs > 0) {
|
|
460
|
+
state.rest_count += events.length;
|
|
461
|
+
state.total_rest_ms += pauseMs;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
enabled: true,
|
|
465
|
+
rested: events.length > 0,
|
|
466
|
+
pause_ms: pauseMs,
|
|
467
|
+
rest_counter: state.rest_counter,
|
|
468
|
+
rest_threshold: state.rest_threshold,
|
|
469
|
+
rest_count: state.rest_count,
|
|
470
|
+
total_rest_ms: state.total_rest_ms,
|
|
471
|
+
events
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
takeBreakIfNeeded,
|
|
477
|
+
getState() {
|
|
478
|
+
return { ...state };
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function isBossLoginUrl(url) {
|
|
484
|
+
return BOSS_LOGIN_URL_PATTERN.test(String(url || ""));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export function createBossLoginRequiredError({
|
|
488
|
+
domain = "boss",
|
|
489
|
+
currentUrl = "",
|
|
490
|
+
targetUrl = "",
|
|
491
|
+
loginUrl = BOSS_LOGIN_URL,
|
|
492
|
+
loginDetection = null,
|
|
493
|
+
chrome = null
|
|
494
|
+
} = {}) {
|
|
495
|
+
const error = new Error(`Boss login is required before starting the ${domain} run.`);
|
|
496
|
+
error.code = "BOSS_LOGIN_REQUIRED";
|
|
497
|
+
error.requires_login = true;
|
|
498
|
+
error.current_url = currentUrl || null;
|
|
499
|
+
error.target_url = targetUrl || null;
|
|
500
|
+
error.login_url = loginUrl;
|
|
501
|
+
error.login_detection = loginDetection || null;
|
|
502
|
+
error.chrome = chrome || null;
|
|
503
|
+
error.retryable = true;
|
|
504
|
+
return error;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export async function detectBossLoginState(client, { currentUrl = "" } = {}) {
|
|
508
|
+
const inspectedUrl = currentUrl || await getMainFrameUrl(client).catch(() => "");
|
|
509
|
+
if (isBossLoginUrl(inspectedUrl)) {
|
|
510
|
+
return {
|
|
511
|
+
requires_login: true,
|
|
512
|
+
reason: "url",
|
|
513
|
+
current_url: inspectedUrl,
|
|
514
|
+
matched_selectors: []
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let root = null;
|
|
519
|
+
try {
|
|
520
|
+
root = await getDocumentRoot(client, { depth: 1, pierce: true });
|
|
521
|
+
} catch (error) {
|
|
522
|
+
return {
|
|
523
|
+
requires_login: false,
|
|
524
|
+
reason: "dom_unavailable",
|
|
525
|
+
current_url: inspectedUrl,
|
|
526
|
+
error: error?.message || String(error || "")
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const matchedSelectors = [];
|
|
531
|
+
for (const selector of BOSS_LOGIN_DOM_SELECTORS) {
|
|
532
|
+
const nodeId = await querySelector(client, root.nodeId, selector).catch(() => 0);
|
|
533
|
+
if (nodeId) matchedSelectors.push(selector);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (matchedSelectors.length === 0) {
|
|
537
|
+
return {
|
|
538
|
+
requires_login: false,
|
|
539
|
+
reason: "no_login_dom",
|
|
540
|
+
current_url: inspectedUrl,
|
|
541
|
+
matched_selectors: []
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const html = await getOuterHTML(client, root.nodeId).catch(() => "");
|
|
546
|
+
const looksLikeLogin = BOSS_LOGIN_TEXT_PATTERN.test(html);
|
|
547
|
+
return {
|
|
548
|
+
requires_login: looksLikeLogin,
|
|
549
|
+
reason: looksLikeLogin ? "dom" : "login_selector_without_login_text",
|
|
550
|
+
current_url: inspectedUrl,
|
|
551
|
+
matched_selectors: matchedSelectors
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function isChromeDebugUnavailableError(error) {
|
|
556
|
+
return CHROME_DEBUG_UNAVAILABLE_PATTERN.test(String(error?.message || error || ""));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function pathExists(targetPath) {
|
|
560
|
+
try {
|
|
561
|
+
return Boolean(targetPath) && fs.existsSync(targetPath);
|
|
562
|
+
} catch {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function ensureDir(targetPath) {
|
|
568
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function isLocalChromeHost(host) {
|
|
572
|
+
const normalized = String(host || "").trim().toLowerCase();
|
|
573
|
+
return !normalized || normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function getCodexHome() {
|
|
577
|
+
return process.env.CODEX_HOME
|
|
578
|
+
? path.resolve(process.env.CODEX_HOME)
|
|
579
|
+
: path.join(os.homedir(), ".codex");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function getDefaultChromeExecutableCandidates() {
|
|
583
|
+
const candidates = [
|
|
584
|
+
process.env.BOSS_MCP_CHROME_PATH,
|
|
585
|
+
process.env.BOSS_RECOMMEND_CHROME_PATH
|
|
586
|
+
].filter(Boolean);
|
|
587
|
+
if (process.platform === "win32") {
|
|
588
|
+
candidates.push(
|
|
589
|
+
path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
|
|
590
|
+
path.join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
|
|
591
|
+
path.join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe")
|
|
592
|
+
);
|
|
593
|
+
} else if (process.platform === "darwin") {
|
|
594
|
+
candidates.push(
|
|
595
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
596
|
+
path.join(os.homedir(), "Applications", "Google Chrome.app", "Contents", "MacOS", "Google Chrome"),
|
|
597
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
598
|
+
);
|
|
599
|
+
} else {
|
|
600
|
+
candidates.push(
|
|
601
|
+
"/usr/bin/google-chrome",
|
|
602
|
+
"/usr/bin/google-chrome-stable",
|
|
603
|
+
"/usr/bin/chromium-browser",
|
|
604
|
+
"/usr/bin/chromium",
|
|
605
|
+
"/snap/bin/chromium"
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
return Array.from(new Set(candidates.filter(Boolean)));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function getChromeExecutable() {
|
|
612
|
+
return getDefaultChromeExecutableCandidates().find((candidate) => pathExists(candidate)) || null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function getBossChromeUserDataDir(port = DEFAULT_CHROME_PORT) {
|
|
616
|
+
const sharedPath = path.join(getCodexHome(), "boss-mcp", `chrome-profile-${port}`);
|
|
617
|
+
ensureDir(sharedPath);
|
|
618
|
+
return sharedPath;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function parseExtraChromeArgs(value = "") {
|
|
622
|
+
return String(value || "")
|
|
623
|
+
.split(/\s+/)
|
|
624
|
+
.map((item) => item.trim())
|
|
625
|
+
.filter(Boolean);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function buildBossChromeLaunchArgs({
|
|
629
|
+
port = DEFAULT_CHROME_PORT,
|
|
630
|
+
userDataDir = "",
|
|
631
|
+
url = "about:blank",
|
|
632
|
+
extraArgs = []
|
|
633
|
+
} = {}) {
|
|
634
|
+
const args = [
|
|
635
|
+
`--remote-debugging-port=${port}`,
|
|
636
|
+
`--user-data-dir=${userDataDir}`,
|
|
637
|
+
"--no-first-run",
|
|
638
|
+
"--no-default-browser-check",
|
|
639
|
+
...LID_CLOSED_SAFE_CHROME_ARGS,
|
|
640
|
+
...parseExtraChromeArgs(process.env.BOSS_MCP_EXTRA_CHROME_ARGS),
|
|
641
|
+
...extraArgs,
|
|
642
|
+
"--new-window",
|
|
643
|
+
url
|
|
644
|
+
];
|
|
645
|
+
return Array.from(new Set(args.filter(Boolean)));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export async function waitForChromeDebugPort({
|
|
649
|
+
host = DEFAULT_CHROME_HOST,
|
|
650
|
+
port = DEFAULT_CHROME_PORT,
|
|
651
|
+
timeoutMs = 8000,
|
|
652
|
+
intervalMs = 300
|
|
653
|
+
} = {}) {
|
|
654
|
+
const started = Date.now();
|
|
655
|
+
let lastError = null;
|
|
656
|
+
while (Date.now() - started <= timeoutMs) {
|
|
657
|
+
try {
|
|
658
|
+
const targets = await listChromeTargets({ host, port });
|
|
659
|
+
return {
|
|
660
|
+
ok: true,
|
|
661
|
+
elapsed_ms: Date.now() - started,
|
|
662
|
+
targets
|
|
663
|
+
};
|
|
664
|
+
} catch (error) {
|
|
665
|
+
lastError = error;
|
|
666
|
+
await sleep(intervalMs);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
ok: false,
|
|
671
|
+
elapsed_ms: Date.now() - started,
|
|
672
|
+
error: lastError?.message || String(lastError || "Chrome debug port did not become ready")
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export async function launchChromeDebugInstance({
|
|
677
|
+
host = DEFAULT_CHROME_HOST,
|
|
678
|
+
port = DEFAULT_CHROME_PORT,
|
|
679
|
+
url = "about:blank",
|
|
680
|
+
slowLive = false
|
|
681
|
+
} = {}) {
|
|
682
|
+
if (!isLocalChromeHost(host)) {
|
|
683
|
+
throw new Error(`Cannot auto-launch Chrome for non-local debug host: ${host}`);
|
|
684
|
+
}
|
|
685
|
+
const chromePath = getChromeExecutable();
|
|
686
|
+
if (!chromePath) {
|
|
687
|
+
throw new Error("Chrome executable not found. Set BOSS_MCP_CHROME_PATH or BOSS_RECOMMEND_CHROME_PATH.");
|
|
688
|
+
}
|
|
689
|
+
const userDataDir = getBossChromeUserDataDir(port);
|
|
690
|
+
const args = buildBossChromeLaunchArgs({ port, userDataDir, url });
|
|
691
|
+
const child = spawn(chromePath, args, {
|
|
692
|
+
detached: true,
|
|
693
|
+
stdio: "ignore",
|
|
694
|
+
windowsHide: false
|
|
695
|
+
});
|
|
696
|
+
child.unref();
|
|
697
|
+
const readiness = await waitForChromeDebugPort({
|
|
698
|
+
host,
|
|
699
|
+
port,
|
|
700
|
+
timeoutMs: slowLive ? 30000 : 12000,
|
|
701
|
+
intervalMs: slowLive ? 700 : 300
|
|
702
|
+
});
|
|
703
|
+
if (!readiness.ok) {
|
|
704
|
+
throw new Error(`Chrome launched but DevTools port ${port} did not become reachable: ${readiness.error}`);
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
launched: true,
|
|
708
|
+
chrome_path: chromePath,
|
|
709
|
+
user_data_dir: userDataDir,
|
|
710
|
+
launch_args: args,
|
|
711
|
+
port,
|
|
712
|
+
url,
|
|
713
|
+
readiness: {
|
|
714
|
+
elapsed_ms: readiness.elapsed_ms,
|
|
715
|
+
target_count: readiness.targets.length
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export async function ensureChromeDebugPort({
|
|
721
|
+
host = DEFAULT_CHROME_HOST,
|
|
722
|
+
port = DEFAULT_CHROME_PORT,
|
|
723
|
+
url = "about:blank",
|
|
724
|
+
slowLive = false,
|
|
725
|
+
launchIfMissing = true
|
|
726
|
+
} = {}) {
|
|
727
|
+
try {
|
|
728
|
+
const targets = await listChromeTargets({ host, port });
|
|
729
|
+
return {
|
|
730
|
+
launched: false,
|
|
731
|
+
reused: true,
|
|
732
|
+
port,
|
|
733
|
+
target_count: targets.length
|
|
734
|
+
};
|
|
735
|
+
} catch (error) {
|
|
736
|
+
if (!launchIfMissing || !isChromeDebugUnavailableError(error)) {
|
|
737
|
+
throw error;
|
|
738
|
+
}
|
|
739
|
+
return launchChromeDebugInstance({ host, port, url, slowLive });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export async function openChromeTarget({
|
|
744
|
+
host = DEFAULT_CHROME_HOST,
|
|
745
|
+
port = DEFAULT_CHROME_PORT,
|
|
746
|
+
url
|
|
747
|
+
} = {}) {
|
|
748
|
+
const encodedUrl = encodeURIComponent(url || "about:blank");
|
|
749
|
+
const endpoint = `http://${host}:${port}/json/new?${encodedUrl}`;
|
|
750
|
+
const methods = ["PUT", "GET"];
|
|
751
|
+
let lastError = null;
|
|
752
|
+
for (const method of methods) {
|
|
753
|
+
try {
|
|
754
|
+
const response = await fetch(endpoint, { method });
|
|
755
|
+
if (response.ok) {
|
|
756
|
+
let payload = null;
|
|
757
|
+
try {
|
|
758
|
+
payload = await response.json();
|
|
759
|
+
} catch {}
|
|
760
|
+
return {
|
|
761
|
+
ok: true,
|
|
762
|
+
method,
|
|
763
|
+
target_id: payload?.id || null,
|
|
764
|
+
url: payload?.url || url || null
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
lastError = new Error(`DevTools /json/new returned ${response.status}`);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
lastError = error;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
error: lastError?.message || "Failed to open Chrome target"
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export async function connectToChromeTargetOrOpen({
|
|
779
|
+
host = DEFAULT_CHROME_HOST,
|
|
780
|
+
port = DEFAULT_CHROME_PORT,
|
|
781
|
+
targetUrlIncludes,
|
|
782
|
+
targetPredicate,
|
|
783
|
+
fallbackTargetPredicate,
|
|
784
|
+
targetUrl,
|
|
785
|
+
allowNavigate = true,
|
|
786
|
+
slowLive = false,
|
|
787
|
+
launchIfMissing = true
|
|
788
|
+
} = {}) {
|
|
789
|
+
let chrome = null;
|
|
790
|
+
if (allowNavigate && targetUrl) {
|
|
791
|
+
chrome = await ensureChromeDebugPort({
|
|
792
|
+
host,
|
|
793
|
+
port,
|
|
794
|
+
url: targetUrl,
|
|
795
|
+
slowLive,
|
|
796
|
+
launchIfMissing
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
try {
|
|
801
|
+
const session = await connectToChromeTarget({
|
|
802
|
+
host,
|
|
803
|
+
port,
|
|
804
|
+
targetUrlIncludes,
|
|
805
|
+
targetPredicate
|
|
806
|
+
});
|
|
807
|
+
return {
|
|
808
|
+
...session,
|
|
809
|
+
chrome: {
|
|
810
|
+
...(chrome || { launched: false, reused: true, port }),
|
|
811
|
+
target_created: false
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
} catch (primaryError) {
|
|
815
|
+
if (!allowNavigate) throw primaryError;
|
|
816
|
+
|
|
817
|
+
if (typeof fallbackTargetPredicate === "function") {
|
|
818
|
+
try {
|
|
819
|
+
const session = await connectToChromeTarget({
|
|
820
|
+
host,
|
|
821
|
+
port,
|
|
822
|
+
targetPredicate: fallbackTargetPredicate
|
|
823
|
+
});
|
|
824
|
+
return {
|
|
825
|
+
...session,
|
|
826
|
+
chrome: {
|
|
827
|
+
...(chrome || { launched: false, reused: true, port }),
|
|
828
|
+
target_created: false,
|
|
829
|
+
fallback_target: true
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
} catch {}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let openAttempt = null;
|
|
836
|
+
if (targetUrl) {
|
|
837
|
+
openAttempt = await openChromeTarget({ host, port, url: targetUrl });
|
|
838
|
+
if (openAttempt.ok) {
|
|
839
|
+
const session = await connectToChromeTarget({
|
|
840
|
+
host,
|
|
841
|
+
port,
|
|
842
|
+
targetPredicate: (target) => (
|
|
843
|
+
(openAttempt.target_id && target?.id === openAttempt.target_id)
|
|
844
|
+
|| String(target?.url || "").includes(targetUrlIncludes || targetUrl)
|
|
845
|
+
|| (targetUrl.includes("zhipin.com") && String(target?.url || "").includes("zhipin.com"))
|
|
846
|
+
)
|
|
847
|
+
});
|
|
848
|
+
return {
|
|
849
|
+
...session,
|
|
850
|
+
chrome: {
|
|
851
|
+
...(chrome || { launched: false, reused: true, port }),
|
|
852
|
+
target_created: true,
|
|
853
|
+
open_attempt: openAttempt
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const session = await connectToChromeTarget({
|
|
860
|
+
host,
|
|
861
|
+
port,
|
|
862
|
+
targetPredicate: (target) => target?.type === "page"
|
|
863
|
+
});
|
|
864
|
+
return {
|
|
865
|
+
...session,
|
|
866
|
+
chrome: {
|
|
867
|
+
...(chrome || { launched: false, reused: true, port }),
|
|
868
|
+
target_created: false,
|
|
869
|
+
open_attempt: openAttempt,
|
|
870
|
+
fallback_any_page: true
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function isClosedCdpTransportError(error) {
|
|
877
|
+
return CDP_CLOSED_TRANSPORT_PATTERN.test(String(error?.message || error || ""));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function cloneCdpParams(params = {}) {
|
|
881
|
+
if (!params || typeof params !== "object" || typeof params === "function") return params;
|
|
882
|
+
try {
|
|
883
|
+
return JSON.parse(JSON.stringify(params));
|
|
884
|
+
} catch {
|
|
885
|
+
return { ...params };
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function shouldReplayCdpSetupCall(domain, method) {
|
|
890
|
+
return method === "enable"
|
|
891
|
+
|| (domain === "Network" && method === "setCacheDisabled")
|
|
892
|
+
|| (domain === "Page" && method === "bringToFront");
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export function createGuardedCdpClient(client, { methodLog = [], reconnect = null } = {}) {
|
|
896
|
+
let currentClient = client;
|
|
897
|
+
let reconnectInFlight = null;
|
|
898
|
+
const setupCalls = [];
|
|
899
|
+
const eventSubscriptions = [];
|
|
900
|
+
|
|
901
|
+
async function replaySessionSetup(nextClient) {
|
|
902
|
+
for (const call of setupCalls) {
|
|
903
|
+
const fn = nextClient?.[call.domain]?.[call.method];
|
|
904
|
+
if (typeof fn === "function") {
|
|
905
|
+
await fn.call(nextClient[call.domain], cloneCdpParams(call.params));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
for (const subscription of eventSubscriptions) {
|
|
909
|
+
const fn = nextClient?.[subscription.domain]?.[subscription.event];
|
|
910
|
+
if (typeof fn === "function") {
|
|
911
|
+
fn.call(nextClient[subscription.domain], subscription.listener);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function reconnectClient() {
|
|
917
|
+
if (typeof reconnect !== "function") return null;
|
|
918
|
+
if (!reconnectInFlight) {
|
|
919
|
+
reconnectInFlight = Promise.resolve()
|
|
920
|
+
.then(() => reconnect())
|
|
921
|
+
.then(async (nextClient) => {
|
|
922
|
+
if (!nextClient) throw new Error("CDP reconnect returned no client");
|
|
923
|
+
currentClient = nextClient;
|
|
924
|
+
await replaySessionSetup(nextClient);
|
|
925
|
+
return nextClient;
|
|
926
|
+
})
|
|
927
|
+
.finally(() => {
|
|
928
|
+
reconnectInFlight = null;
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
return reconnectInFlight;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
async function invokeWithReconnect({
|
|
935
|
+
methodNameForLog,
|
|
936
|
+
invoke,
|
|
937
|
+
retryable = true
|
|
938
|
+
}) {
|
|
939
|
+
recordMethod(methodLog, methodNameForLog);
|
|
940
|
+
try {
|
|
941
|
+
return await invoke(currentClient);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (!retryable || !isClosedCdpTransportError(error) || typeof reconnect !== "function") {
|
|
944
|
+
throw error;
|
|
945
|
+
}
|
|
946
|
+
await reconnectClient();
|
|
947
|
+
recordMethod(methodLog, `${methodNameForLog}:retry_after_reconnect`);
|
|
948
|
+
return invoke(currentClient);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return new Proxy({}, {
|
|
953
|
+
get(_target, property, receiver) {
|
|
954
|
+
if (property === "send") {
|
|
955
|
+
return async (method, params = {}) => {
|
|
956
|
+
if (isForbiddenMethod(method)) {
|
|
957
|
+
throw new Error(`Forbidden CDP method blocked: ${method}`);
|
|
958
|
+
}
|
|
959
|
+
return invokeWithReconnect({
|
|
960
|
+
methodNameForLog: method,
|
|
961
|
+
invoke: (activeClient) => activeClient.send(method, params)
|
|
962
|
+
});
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (property === "close") {
|
|
967
|
+
return async () => currentClient?.close?.();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (property === "__rawClient") return currentClient;
|
|
971
|
+
|
|
972
|
+
const value = Reflect.get(currentClient, property, receiver);
|
|
973
|
+
if (!value || typeof value !== "object") return value;
|
|
974
|
+
|
|
975
|
+
return new Proxy({}, {
|
|
976
|
+
get(_domainTarget, method, domainReceiver) {
|
|
977
|
+
const domainTarget = Reflect.get(currentClient, property, receiver);
|
|
978
|
+
const domainValue = Reflect.get(domainTarget, method, domainReceiver);
|
|
979
|
+
if (typeof domainValue !== "function") return domainValue;
|
|
980
|
+
|
|
981
|
+
return (params = {}) => {
|
|
982
|
+
const fullMethod = methodName(property, method);
|
|
983
|
+
if (isForbiddenMethod(fullMethod)) {
|
|
984
|
+
throw new Error(`Forbidden CDP method blocked: ${fullMethod}`);
|
|
985
|
+
}
|
|
986
|
+
if (typeof params === "function") {
|
|
987
|
+
eventSubscriptions.push({
|
|
988
|
+
domain: property,
|
|
989
|
+
event: method,
|
|
990
|
+
listener: params
|
|
991
|
+
});
|
|
992
|
+
recordMethod(methodLog, fullMethod);
|
|
993
|
+
return domainValue.call(domainTarget, params);
|
|
994
|
+
}
|
|
995
|
+
if (shouldReplayCdpSetupCall(property, method)) {
|
|
996
|
+
setupCalls.push({
|
|
997
|
+
domain: property,
|
|
998
|
+
method,
|
|
999
|
+
params: cloneCdpParams(params)
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
return invokeWithReconnect({
|
|
1003
|
+
methodNameForLog: fullMethod,
|
|
1004
|
+
invoke: (activeClient) => {
|
|
1005
|
+
const activeDomain = activeClient?.[property];
|
|
1006
|
+
const activeMethod = activeDomain?.[method];
|
|
1007
|
+
if (typeof activeMethod !== "function") {
|
|
1008
|
+
throw new Error(`CDP method is unavailable after reconnect: ${fullMethod}`);
|
|
1009
|
+
}
|
|
1010
|
+
return activeMethod.call(activeDomain, params);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
export async function listChromeTargets({
|
|
1021
|
+
host = DEFAULT_CHROME_HOST,
|
|
1022
|
+
port = DEFAULT_CHROME_PORT
|
|
1023
|
+
} = {}) {
|
|
1024
|
+
return CDP.List({ host, port });
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export async function connectToChromeTarget({
|
|
1028
|
+
host = DEFAULT_CHROME_HOST,
|
|
1029
|
+
port = DEFAULT_CHROME_PORT,
|
|
1030
|
+
targetUrlIncludes,
|
|
1031
|
+
targetPredicate
|
|
1032
|
+
} = {}) {
|
|
1033
|
+
const targets = await listChromeTargets({ host, port });
|
|
1034
|
+
const matcher = normalizeTargetMatcher({ targetUrlIncludes, targetPredicate });
|
|
1035
|
+
const target = targets.find(matcher);
|
|
1036
|
+
if (!target) {
|
|
1037
|
+
const urls = targets.map((item) => item.url).filter(Boolean).join("\n");
|
|
1038
|
+
throw new Error(`No matching Chrome target found on ${host}:${port}.\nAvailable targets:\n${urls}`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
let rawClient = await CDP({ host, port, target });
|
|
1042
|
+
let activeTarget = target;
|
|
1043
|
+
const methodLog = [];
|
|
1044
|
+
const client = createGuardedCdpClient(rawClient, {
|
|
1045
|
+
methodLog,
|
|
1046
|
+
reconnect: async () => {
|
|
1047
|
+
const latestTargets = await listChromeTargets({ host, port });
|
|
1048
|
+
const nextTarget = activeTarget?.id
|
|
1049
|
+
? latestTargets.find((item) => item?.id === activeTarget.id)
|
|
1050
|
+
: latestTargets.find(matcher);
|
|
1051
|
+
if (!nextTarget) {
|
|
1052
|
+
const urls = latestTargets.map((item) => item.url).filter(Boolean).join("\n");
|
|
1053
|
+
throw new Error(`No matching Chrome target found while reconnecting to ${host}:${port}.\nAvailable targets:\n${urls}`);
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
await rawClient.close();
|
|
1057
|
+
} catch {}
|
|
1058
|
+
rawClient = await CDP({ host, port, target: nextTarget });
|
|
1059
|
+
activeTarget = nextTarget;
|
|
1060
|
+
return rawClient;
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
return {
|
|
1065
|
+
client,
|
|
1066
|
+
get rawClient() {
|
|
1067
|
+
return rawClient;
|
|
1068
|
+
},
|
|
1069
|
+
get target() {
|
|
1070
|
+
return activeTarget;
|
|
1071
|
+
},
|
|
1072
|
+
methodLog,
|
|
1073
|
+
async close() {
|
|
1074
|
+
await rawClient.close();
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export async function assertRuntimeEvaluateBlocked(client) {
|
|
1080
|
+
try {
|
|
1081
|
+
await client.Runtime.evaluate({ expression: "1" });
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
if (/Forbidden CDP method blocked: Runtime\.evaluate/.test(String(error?.message || ""))) {
|
|
1084
|
+
return { blocked: true, message: error.message };
|
|
1085
|
+
}
|
|
1086
|
+
throw error;
|
|
1087
|
+
}
|
|
1088
|
+
throw new Error("Runtime.evaluate was not blocked by the CDP guard");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
export async function enableDomains(client, domains = ["Page", "DOM", "Input"]) {
|
|
1092
|
+
for (const domain of domains) {
|
|
1093
|
+
if (!ALLOWED_CDP_DOMAINS.has(domain)) {
|
|
1094
|
+
throw new Error(`CDP domain is not allowed by the CDP-only contract: ${domain}`);
|
|
1095
|
+
}
|
|
1096
|
+
if (typeof client?.[domain]?.enable === "function") {
|
|
1097
|
+
await client[domain].enable();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
export async function bringPageToFront(client) {
|
|
1103
|
+
if (typeof client?.Page?.bringToFront === "function") {
|
|
1104
|
+
await client.Page.bringToFront();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export async function getPageFrameTree(client) {
|
|
1109
|
+
const result = await client.Page.getFrameTree();
|
|
1110
|
+
return result.frameTree || null;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
export async function getMainFrame(client) {
|
|
1114
|
+
const frameTree = await getPageFrameTree(client);
|
|
1115
|
+
return frameTree?.frame || null;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
export async function getMainFrameUrl(client) {
|
|
1119
|
+
const frame = await getMainFrame(client);
|
|
1120
|
+
return frame?.url || "";
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
export async function waitForMainFrameUrl(client, predicate, {
|
|
1124
|
+
timeoutMs = 10000,
|
|
1125
|
+
intervalMs = 250
|
|
1126
|
+
} = {}) {
|
|
1127
|
+
const started = Date.now();
|
|
1128
|
+
let lastUrl = "";
|
|
1129
|
+
while (Date.now() - started <= timeoutMs) {
|
|
1130
|
+
lastUrl = await getMainFrameUrl(client);
|
|
1131
|
+
if (predicate(lastUrl)) {
|
|
1132
|
+
return {
|
|
1133
|
+
ok: true,
|
|
1134
|
+
elapsed_ms: Date.now() - started,
|
|
1135
|
+
url: lastUrl
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
await sleep(intervalMs);
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
ok: false,
|
|
1142
|
+
elapsed_ms: Date.now() - started,
|
|
1143
|
+
url: lastUrl
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
export async function getDocumentRoot(client, { depth = 1, pierce = true } = {}) {
|
|
1148
|
+
const result = await client.DOM.getDocument({ depth, pierce });
|
|
1149
|
+
return result.root;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
export async function querySelector(client, nodeId, selector) {
|
|
1153
|
+
const result = await client.DOM.querySelector({ nodeId, selector });
|
|
1154
|
+
return result.nodeId || 0;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export async function querySelectorAll(client, nodeId, selector) {
|
|
1158
|
+
const result = await client.DOM.querySelectorAll({ nodeId, selector });
|
|
1159
|
+
return result.nodeIds || [];
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
export async function findFirstNode(client, rootNodeId, selectors = []) {
|
|
1163
|
+
for (const selector of selectors) {
|
|
1164
|
+
const nodeId = await querySelector(client, rootNodeId, selector);
|
|
1165
|
+
if (nodeId) return { selector, nodeId };
|
|
1166
|
+
}
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export async function describeNode(client, nodeId, { depth = 1, pierce = true } = {}) {
|
|
1171
|
+
const result = await client.DOM.describeNode({ nodeId, depth, pierce });
|
|
1172
|
+
return result.node;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export async function getFrameDocumentNodeId(client, iframeNodeId) {
|
|
1176
|
+
const node = await describeNode(client, iframeNodeId, { depth: 1, pierce: true });
|
|
1177
|
+
const documentNodeId = node?.contentDocument?.nodeId;
|
|
1178
|
+
if (!documentNodeId) {
|
|
1179
|
+
throw new Error(`Node ${iframeNodeId} does not expose a contentDocument node`);
|
|
1180
|
+
}
|
|
1181
|
+
return documentNodeId;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export async function findIframeDocument(client, rootNodeId, selectors = []) {
|
|
1185
|
+
const iframe = await findFirstNode(client, rootNodeId, selectors);
|
|
1186
|
+
if (!iframe) return null;
|
|
1187
|
+
const documentNodeId = await getFrameDocumentNodeId(client, iframe.nodeId);
|
|
1188
|
+
return { ...iframe, documentNodeId };
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
export async function getAttributesMap(client, nodeId) {
|
|
1192
|
+
const result = await client.DOM.getAttributes({ nodeId });
|
|
1193
|
+
const attributes = {};
|
|
1194
|
+
const raw = result.attributes || [];
|
|
1195
|
+
for (let index = 0; index < raw.length; index += 2) {
|
|
1196
|
+
attributes[raw[index]] = raw[index + 1] || "";
|
|
1197
|
+
}
|
|
1198
|
+
return attributes;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
export async function getOuterHTML(client, nodeId) {
|
|
1202
|
+
const result = await client.DOM.getOuterHTML({ nodeId });
|
|
1203
|
+
return result.outerHTML || "";
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export async function getNodeBox(client, nodeId) {
|
|
1207
|
+
const result = await client.DOM.getBoxModel({ nodeId });
|
|
1208
|
+
const model = result.model;
|
|
1209
|
+
const quad = model.border?.length ? model.border : model.content;
|
|
1210
|
+
const xs = [quad[0], quad[2], quad[4], quad[6]];
|
|
1211
|
+
const ys = [quad[1], quad[3], quad[5], quad[7]];
|
|
1212
|
+
const minX = Math.min(...xs);
|
|
1213
|
+
const maxX = Math.max(...xs);
|
|
1214
|
+
const minY = Math.min(...ys);
|
|
1215
|
+
const maxY = Math.max(...ys);
|
|
1216
|
+
return {
|
|
1217
|
+
model,
|
|
1218
|
+
center: {
|
|
1219
|
+
x: (minX + maxX) / 2,
|
|
1220
|
+
y: (minY + maxY) / 2
|
|
1221
|
+
},
|
|
1222
|
+
rect: {
|
|
1223
|
+
x: minX,
|
|
1224
|
+
y: minY,
|
|
1225
|
+
width: maxX - minX,
|
|
1226
|
+
height: maxY - minY
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
export async function simulateHumanClick(client, targetX, targetY, {
|
|
1232
|
+
button = "left",
|
|
1233
|
+
clickCount = 1,
|
|
1234
|
+
delayMs = 80,
|
|
1235
|
+
random = Math.random,
|
|
1236
|
+
sleepFn = sleep,
|
|
1237
|
+
moveSteps = 18,
|
|
1238
|
+
moveJitterPx = 3,
|
|
1239
|
+
hoverJitterPx = 5,
|
|
1240
|
+
moveDelayMinMs = 5,
|
|
1241
|
+
moveDelayMaxMs = 23,
|
|
1242
|
+
hoverDelayMinMs = 10,
|
|
1243
|
+
hoverDelayMaxMs = 30,
|
|
1244
|
+
prePressBaseMs = 260,
|
|
1245
|
+
prePressVarianceMs = 80,
|
|
1246
|
+
holdVarianceMs = 30,
|
|
1247
|
+
startPoint = null
|
|
1248
|
+
} = {}) {
|
|
1249
|
+
const target = normalizePoint({ x: targetX, y: targetY });
|
|
1250
|
+
if (!target) throw new Error("simulateHumanClick requires finite target coordinates");
|
|
1251
|
+
const nextRandom = normalizeRandom(random);
|
|
1252
|
+
const interactionConfig = getHumanInteractionConfig(client) || {};
|
|
1253
|
+
const start = normalizePoint(startPoint)
|
|
1254
|
+
|| normalizePoint(interactionConfig.lastMousePoint)
|
|
1255
|
+
|| {
|
|
1256
|
+
x: Math.max(0, target.x + randomBetween(nextRandom, -140, 140)),
|
|
1257
|
+
y: Math.max(0, target.y + randomBetween(nextRandom, -90, 90))
|
|
1258
|
+
};
|
|
1259
|
+
const path = generateBezierPath(start, target, {
|
|
1260
|
+
steps: moveSteps,
|
|
1261
|
+
random: nextRandom
|
|
1262
|
+
});
|
|
1263
|
+
const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
|
|
1264
|
+
const moveDelayMin = Math.min(moveDelayMinMs, moveDelayMaxMs);
|
|
1265
|
+
const moveDelayMax = Math.max(moveDelayMinMs, moveDelayMaxMs);
|
|
1266
|
+
const hoverDelayMin = Math.min(hoverDelayMinMs, hoverDelayMaxMs);
|
|
1267
|
+
const hoverDelayMax = Math.max(hoverDelayMinMs, hoverDelayMaxMs);
|
|
1268
|
+
for (const point of path) {
|
|
1269
|
+
await client.Input.dispatchMouseEvent({
|
|
1270
|
+
type: "mouseMoved",
|
|
1271
|
+
x: Math.round(point.x + randomBetween(nextRandom, -moveJitterPx / 2, moveJitterPx / 2)),
|
|
1272
|
+
y: Math.round(point.y + randomBetween(nextRandom, -moveJitterPx / 2, moveJitterPx / 2)),
|
|
1273
|
+
button: "none"
|
|
1274
|
+
});
|
|
1275
|
+
const pauseMs = Math.round(randomBetween(nextRandom, moveDelayMin, moveDelayMax));
|
|
1276
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1277
|
+
}
|
|
1278
|
+
const hoverSteps = randomIntegerBetween(nextRandom, 3, 6);
|
|
1279
|
+
for (let index = 0; index < hoverSteps; index += 1) {
|
|
1280
|
+
await client.Input.dispatchMouseEvent({
|
|
1281
|
+
type: "mouseMoved",
|
|
1282
|
+
x: Math.round(target.x + randomBetween(nextRandom, -hoverJitterPx / 2, hoverJitterPx / 2)),
|
|
1283
|
+
y: Math.round(target.y + randomBetween(nextRandom, -hoverJitterPx / 2, hoverJitterPx / 2)),
|
|
1284
|
+
button: "none"
|
|
1285
|
+
});
|
|
1286
|
+
const pauseMs = Math.round(randomBetween(nextRandom, hoverDelayMin, hoverDelayMax));
|
|
1287
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1288
|
+
}
|
|
1289
|
+
const prePressMs = humanDelay(prePressBaseMs, prePressVarianceMs, {
|
|
1290
|
+
minMs: 0,
|
|
1291
|
+
maxMs: Math.max(prePressBaseMs + prePressVarianceMs * 4, prePressBaseMs),
|
|
1292
|
+
random: nextRandom
|
|
1293
|
+
});
|
|
1294
|
+
if (prePressMs > 0) await sleeper(prePressMs);
|
|
1295
|
+
await client.Input.dispatchMouseEvent({ type: "mousePressed", x: target.x, y: target.y, button, clickCount });
|
|
1296
|
+
const holdMs = humanDelay(delayMs, holdVarianceMs, {
|
|
1297
|
+
minMs: 0,
|
|
1298
|
+
maxMs: Math.max(delayMs + holdVarianceMs * 4, delayMs),
|
|
1299
|
+
random: nextRandom
|
|
1300
|
+
});
|
|
1301
|
+
if (holdMs > 0) await sleeper(holdMs);
|
|
1302
|
+
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x: target.x, y: target.y, button, clickCount });
|
|
1303
|
+
const latestConfig = getHumanInteractionConfig(client);
|
|
1304
|
+
if (latestConfig) latestConfig.lastMousePoint = target;
|
|
1305
|
+
return {
|
|
1306
|
+
mode: "human",
|
|
1307
|
+
path_points: path.length,
|
|
1308
|
+
hover_steps: hoverSteps,
|
|
1309
|
+
pre_press_ms: prePressMs,
|
|
1310
|
+
hold_ms: holdMs
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
export function resolveHumanClickPointForBox(box, {
|
|
1315
|
+
enabled = true,
|
|
1316
|
+
safeClickPointEnabled = true,
|
|
1317
|
+
random = Math.random,
|
|
1318
|
+
safeClickMinWidth = 44,
|
|
1319
|
+
safeClickMinHeight = 28,
|
|
1320
|
+
safeClickInsetRatio = 0.22,
|
|
1321
|
+
safeClickMinInsetPx = 4,
|
|
1322
|
+
safeClickMaxInsetPx = 18
|
|
1323
|
+
} = {}) {
|
|
1324
|
+
const center = normalizePoint(box?.center);
|
|
1325
|
+
if (!center) throw new Error("resolveHumanClickPointForBox requires a box center");
|
|
1326
|
+
const rect = box?.rect || {};
|
|
1327
|
+
const width = Number(rect.width);
|
|
1328
|
+
const height = Number(rect.height);
|
|
1329
|
+
const originX = Number(rect.x);
|
|
1330
|
+
const originY = Number(rect.y);
|
|
1331
|
+
if (
|
|
1332
|
+
enabled !== true
|
|
1333
|
+
|| safeClickPointEnabled === false
|
|
1334
|
+
|| !Number.isFinite(width)
|
|
1335
|
+
|| !Number.isFinite(height)
|
|
1336
|
+
|| !Number.isFinite(originX)
|
|
1337
|
+
|| !Number.isFinite(originY)
|
|
1338
|
+
|| width < Math.max(1, Number(safeClickMinWidth) || 44)
|
|
1339
|
+
|| height < Math.max(1, Number(safeClickMinHeight) || 28)
|
|
1340
|
+
) {
|
|
1341
|
+
return {
|
|
1342
|
+
x: center.x,
|
|
1343
|
+
y: center.y,
|
|
1344
|
+
mode: "center",
|
|
1345
|
+
reason: "small_or_disabled"
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const nextRandom = normalizeRandom(random);
|
|
1350
|
+
const insetRatio = clampNumber(safeClickInsetRatio, 0.05, 0.45);
|
|
1351
|
+
const minInset = Math.max(0, Number(safeClickMinInsetPx) || 0);
|
|
1352
|
+
const maxInset = Math.max(minInset, Number(safeClickMaxInsetPx) || minInset);
|
|
1353
|
+
const insetX = Math.min(width / 2 - 1, Math.max(minInset, Math.min(maxInset, width * insetRatio)));
|
|
1354
|
+
const insetY = Math.min(height / 2 - 1, Math.max(minInset, Math.min(maxInset, height * insetRatio)));
|
|
1355
|
+
const usableWidth = Math.max(0, width - insetX * 2);
|
|
1356
|
+
const usableHeight = Math.max(0, height - insetY * 2);
|
|
1357
|
+
if (usableWidth <= 0 || usableHeight <= 0) {
|
|
1358
|
+
return {
|
|
1359
|
+
x: center.x,
|
|
1360
|
+
y: center.y,
|
|
1361
|
+
mode: "center",
|
|
1362
|
+
reason: "insufficient_safe_area"
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
x: originX + insetX + nextRandom() * usableWidth,
|
|
1367
|
+
y: originY + insetY + nextRandom() * usableHeight,
|
|
1368
|
+
mode: "safe_inset",
|
|
1369
|
+
inset_x: insetX,
|
|
1370
|
+
inset_y: insetY
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
export async function clickPoint(client, x, y, {
|
|
1375
|
+
button = "left",
|
|
1376
|
+
clickCount = 1,
|
|
1377
|
+
delayMs = 80,
|
|
1378
|
+
humanRestEnabled = null,
|
|
1379
|
+
humanInteraction = null
|
|
1380
|
+
} = {}) {
|
|
1381
|
+
const configured = getHumanInteractionConfig(client);
|
|
1382
|
+
const mergedHumanInteraction = {
|
|
1383
|
+
...(configured || {}),
|
|
1384
|
+
...(humanInteraction || {})
|
|
1385
|
+
};
|
|
1386
|
+
const humanEnabled = humanRestEnabled === true
|
|
1387
|
+
|| humanInteraction?.enabled === true
|
|
1388
|
+
|| (humanRestEnabled !== false && configured?.enabled === true);
|
|
1389
|
+
if (humanEnabled && mergedHumanInteraction.clickMovementEnabled !== false) {
|
|
1390
|
+
return simulateHumanClick(client, x, y, {
|
|
1391
|
+
...mergedHumanInteraction,
|
|
1392
|
+
button,
|
|
1393
|
+
clickCount,
|
|
1394
|
+
delayMs
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
1398
|
+
await client.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button, clickCount });
|
|
1399
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
1400
|
+
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button, clickCount });
|
|
1401
|
+
return {
|
|
1402
|
+
mode: "direct"
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
export async function scrollNodeIntoView(client, nodeId) {
|
|
1407
|
+
await client.DOM.scrollIntoViewIfNeeded({ nodeId });
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
export async function clickNodeCenter(client, nodeId, {
|
|
1411
|
+
scrollIntoView = false,
|
|
1412
|
+
...clickOptions
|
|
1413
|
+
} = {}) {
|
|
1414
|
+
if (scrollIntoView) {
|
|
1415
|
+
await scrollNodeIntoView(client, nodeId);
|
|
1416
|
+
await sleep(150);
|
|
1417
|
+
}
|
|
1418
|
+
const box = await getNodeBox(client, nodeId);
|
|
1419
|
+
const configured = getHumanInteractionConfig(client);
|
|
1420
|
+
const mergedHumanInteraction = {
|
|
1421
|
+
...(configured || {}),
|
|
1422
|
+
...(clickOptions.humanInteraction || {})
|
|
1423
|
+
};
|
|
1424
|
+
const humanClickPointEnabled = (
|
|
1425
|
+
clickOptions.humanRestEnabled === true
|
|
1426
|
+
|| clickOptions.humanInteraction?.enabled === true
|
|
1427
|
+
|| (clickOptions.humanRestEnabled !== false && configured?.enabled === true)
|
|
1428
|
+
) && mergedHumanInteraction.safeClickPointEnabled !== false;
|
|
1429
|
+
const clickPointTarget = humanClickPointEnabled
|
|
1430
|
+
? resolveHumanClickPointForBox(box, mergedHumanInteraction)
|
|
1431
|
+
: { ...box.center, mode: "center" };
|
|
1432
|
+
const clickResult = await clickPoint(client, clickPointTarget.x, clickPointTarget.y, clickOptions);
|
|
1433
|
+
return {
|
|
1434
|
+
...box,
|
|
1435
|
+
click_target: clickPointTarget,
|
|
1436
|
+
click_result: clickResult
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
export async function pressKey(client, key, {
|
|
1441
|
+
code = key,
|
|
1442
|
+
windowsVirtualKeyCode,
|
|
1443
|
+
nativeVirtualKeyCode = windowsVirtualKeyCode,
|
|
1444
|
+
text = "",
|
|
1445
|
+
modifiers = 0
|
|
1446
|
+
} = {}) {
|
|
1447
|
+
await client.Input.dispatchKeyEvent({
|
|
1448
|
+
type: "keyDown",
|
|
1449
|
+
key,
|
|
1450
|
+
code,
|
|
1451
|
+
windowsVirtualKeyCode,
|
|
1452
|
+
nativeVirtualKeyCode,
|
|
1453
|
+
text,
|
|
1454
|
+
modifiers
|
|
1455
|
+
});
|
|
1456
|
+
await client.Input.dispatchKeyEvent({
|
|
1457
|
+
type: "keyUp",
|
|
1458
|
+
key,
|
|
1459
|
+
code,
|
|
1460
|
+
windowsVirtualKeyCode,
|
|
1461
|
+
nativeVirtualKeyCode,
|
|
1462
|
+
modifiers
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
export function chunkHumanText(text, {
|
|
1467
|
+
random = Math.random,
|
|
1468
|
+
minLength = 1,
|
|
1469
|
+
maxLength = 5
|
|
1470
|
+
} = {}) {
|
|
1471
|
+
const chars = Array.from(String(text || ""));
|
|
1472
|
+
const min = Math.max(1, Math.floor(Number(minLength) || 1));
|
|
1473
|
+
const max = Math.max(min, Math.floor(Number(maxLength) || min));
|
|
1474
|
+
const nextRandom = normalizeRandom(random);
|
|
1475
|
+
const chunks = [];
|
|
1476
|
+
let index = 0;
|
|
1477
|
+
while (index < chars.length) {
|
|
1478
|
+
const remaining = chars.length - index;
|
|
1479
|
+
const size = Math.min(remaining, randomIntegerBetween(nextRandom, min, max));
|
|
1480
|
+
chunks.push(chars.slice(index, index + size).join(""));
|
|
1481
|
+
index += size;
|
|
1482
|
+
}
|
|
1483
|
+
return chunks;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
export async function insertText(client, text, {
|
|
1487
|
+
humanTextEntryEnabled = null,
|
|
1488
|
+
humanInteraction = null
|
|
1489
|
+
} = {}) {
|
|
1490
|
+
const value = String(text || "");
|
|
1491
|
+
const configured = getHumanInteractionConfig(client);
|
|
1492
|
+
const mergedHumanInteraction = {
|
|
1493
|
+
...(configured || {}),
|
|
1494
|
+
...(humanInteraction || {})
|
|
1495
|
+
};
|
|
1496
|
+
const textEntryEnabled = humanTextEntryEnabled === true
|
|
1497
|
+
|| humanInteraction?.textEntryEnabled === true
|
|
1498
|
+
|| (humanTextEntryEnabled !== false
|
|
1499
|
+
&& configured?.enabled === true
|
|
1500
|
+
&& configured?.textEntryEnabled !== false);
|
|
1501
|
+
if (!textEntryEnabled || value.length <= 1) {
|
|
1502
|
+
await client.Input.insertText({ text: value });
|
|
1503
|
+
return {
|
|
1504
|
+
mode: "direct",
|
|
1505
|
+
chunk_count: value ? 1 : 0
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
const chunks = chunkHumanText(value, {
|
|
1509
|
+
random: mergedHumanInteraction.random,
|
|
1510
|
+
minLength: mergedHumanInteraction.textChunkMinLength,
|
|
1511
|
+
maxLength: mergedHumanInteraction.textChunkMaxLength
|
|
1512
|
+
});
|
|
1513
|
+
const sleeper = typeof mergedHumanInteraction.sleepFn === "function"
|
|
1514
|
+
? mergedHumanInteraction.sleepFn
|
|
1515
|
+
: sleep;
|
|
1516
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
1517
|
+
await client.Input.insertText({ text: chunks[index] });
|
|
1518
|
+
if (index < chunks.length - 1) {
|
|
1519
|
+
const pauseMs = humanDelay(
|
|
1520
|
+
mergedHumanInteraction.textChunkDelayBaseMs,
|
|
1521
|
+
mergedHumanInteraction.textChunkDelayVarianceMs,
|
|
1522
|
+
{
|
|
1523
|
+
minMs: 0,
|
|
1524
|
+
maxMs: Math.max(
|
|
1525
|
+
mergedHumanInteraction.textChunkDelayBaseMs + mergedHumanInteraction.textChunkDelayVarianceMs * 4,
|
|
1526
|
+
mergedHumanInteraction.textChunkDelayBaseMs
|
|
1527
|
+
),
|
|
1528
|
+
random: mergedHumanInteraction.random
|
|
1529
|
+
}
|
|
1530
|
+
);
|
|
1531
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
mode: "chunked",
|
|
1536
|
+
chunk_count: chunks.length,
|
|
1537
|
+
chunks
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
export async function selectAllFocusedText(client) {
|
|
1542
|
+
await pressKey(client, "a", {
|
|
1543
|
+
code: "KeyA",
|
|
1544
|
+
windowsVirtualKeyCode: 65,
|
|
1545
|
+
nativeVirtualKeyCode: 65,
|
|
1546
|
+
modifiers: 2
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
export async function clearFocusedInput(client) {
|
|
1551
|
+
await selectAllFocusedText(client);
|
|
1552
|
+
await pressKey(client, "Backspace", {
|
|
1553
|
+
code: "Backspace",
|
|
1554
|
+
windowsVirtualKeyCode: 8,
|
|
1555
|
+
nativeVirtualKeyCode: 8
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
export async function waitForSelector(client, nodeId, selector, {
|
|
1560
|
+
timeoutMs = 5000,
|
|
1561
|
+
intervalMs = 150
|
|
1562
|
+
} = {}) {
|
|
1563
|
+
const started = Date.now();
|
|
1564
|
+
while (Date.now() - started <= timeoutMs) {
|
|
1565
|
+
const foundNodeId = await querySelector(client, nodeId, selector);
|
|
1566
|
+
if (foundNodeId) return foundNodeId;
|
|
1567
|
+
await sleep(intervalMs);
|
|
1568
|
+
}
|
|
1569
|
+
return 0;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
export async function countSelectors(client, nodeId, selectors = {}) {
|
|
1573
|
+
const counts = {};
|
|
1574
|
+
for (const [name, selector] of Object.entries(selectors)) {
|
|
1575
|
+
counts[name] = (await querySelectorAll(client, nodeId, selector)).length;
|
|
1576
|
+
}
|
|
1577
|
+
return counts;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
export async function getAccessibilityTree(client, options = {}) {
|
|
1581
|
+
return client.Accessibility.getFullAXTree(options);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
export async function sleep(ms) {
|
|
1585
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1586
|
+
}
|