@eko-ai/eko-nodejs 4.0.2 → 4.0.4

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/dist/index.esm.js CHANGED
@@ -1,8 +1,7 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
1
+ import 'fs';
2
+ import 'os';
3
+ import 'path';
4
4
  import { Log, BaseBrowserLabelsAgent, uuidv4 } from '@eko-ai/eko';
5
- import { chromium } from 'playwright';
6
5
  import { spawn } from 'child_process';
7
6
 
8
7
  async function getCdpWsEndpoint(port) {
@@ -12,105 +11,2403 @@ async function getCdpWsEndpoint(port) {
12
11
  Log.info("browserInfo: ", browserInfo);
13
12
  return browserInfo.webSocketDebuggerUrl;
14
13
  }
15
- function getDefaultChromeUserDataDir(copyToTempDir = false) {
16
- const platform = os.platform();
17
- const homeDir = os.homedir();
18
- let defaultPath;
19
- switch (platform) {
20
- case "win32":
21
- // Windows: %LOCALAPPDATA%\Google\Chrome\User Data
22
- const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, "AppData", "Local");
23
- defaultPath = path.join(localAppData, "Google", "Chrome", "User Data");
24
- break;
25
- case "darwin":
26
- // macOS: ~/Library/Application Support/Google/Chrome
27
- defaultPath = path.join(homeDir, "Library", "Application Support", "Google", "Chrome");
28
- break;
29
- case "linux":
30
- // Linux: ~/.config/google-chrome
31
- defaultPath = path.join(homeDir, ".config", "google-chrome");
32
- break;
33
- }
34
- if (defaultPath && fs.existsSync(defaultPath)) {
35
- if (copyToTempDir) {
36
- const tempDir = os.tmpdir();
37
- const tempPath = path.join(tempDir, "chrome-user-data");
38
- if (fs.existsSync(tempPath)) {
39
- Log.info(`Removing existing temp directory: ${tempPath}`);
40
- fs.rmSync(tempPath, { recursive: true, force: true });
41
- }
42
- fs.cpSync(defaultPath, tempPath, { recursive: true });
43
- // Delete all Chrome locked files and directories to prevent startup conflicts.
44
- removeLockFiles(tempPath);
45
- const defaultProfilePath = path.join(tempPath, "Default");
46
- if (fs.existsSync(defaultProfilePath)) {
47
- removeLockFiles(defaultProfilePath);
48
- }
49
- Log.info(`Created clean Chrome user data directory: ${tempPath}`);
50
- return tempPath;
51
- }
52
- else {
53
- return defaultPath;
54
- }
55
- }
56
- return undefined;
14
+
15
+ function getDefaultExportFromCjs (x) {
16
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
57
17
  }
58
- function removeLockFiles(dirPath) {
59
- try {
60
- const items = fs.readdirSync(dirPath);
61
- items.forEach((item) => {
62
- const itemPath = path.join(dirPath, item);
63
- try {
64
- const stat = fs.statSync(itemPath);
65
- if (stat.isDirectory()) {
66
- removeLockFiles(itemPath);
67
- }
68
- const shouldDelete = item === "SingletonLock" ||
69
- item === "lockfile" ||
70
- item === "RunningChromeVersion" ||
71
- item === "SingletonCookie" ||
72
- item === "SingletonSocket" ||
73
- item === "chrome_debug.log" ||
74
- item === "LOCK" ||
75
- item === "LOG" ||
76
- item === "LOG.old" ||
77
- item.includes(".lock") ||
78
- item.includes("Lock") ||
79
- item.includes("LOCK") ||
80
- item.includes(".tmp") ||
81
- item.includes("Temp") ||
82
- item.endsWith(".pid") ||
83
- item.endsWith(".log") ||
84
- item.includes("chrome_shutdown_ms.txt") ||
85
- item.includes("Crashpad") ||
86
- (stat.isDirectory() &&
87
- (item.includes("CrashReports") ||
88
- item.includes("ShaderCache") ||
89
- item.includes("crashpad_database")));
90
- if (shouldDelete) {
91
- fs.rmSync(itemPath, { recursive: true, force: true });
92
- }
93
- }
94
- catch (statError) {
95
- if (item.includes("Lock") ||
96
- item.includes("lock") ||
97
- item.includes("LOCK")) {
98
- try {
99
- Log.info(`Force deleting suspected lock file: ${itemPath}`);
100
- fs.rmSync(itemPath, { recursive: true, force: true });
101
- }
102
- catch (deleteError) {
103
- Log.warn(`Failed to force delete ${itemPath}:`, deleteError);
104
- }
105
- }
106
- }
107
- });
108
- }
109
- catch (error) {
110
- Log.warn(`Error while removing lock files from ${dirPath}:`, error);
111
- }
18
+
19
+ function getAugmentedNamespace(n) {
20
+ if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
21
+ var f = n.default;
22
+ if (typeof f == "function") {
23
+ var a = function a () {
24
+ if (this instanceof a) {
25
+ return Reflect.construct(f, arguments, this.constructor);
26
+ }
27
+ return f.apply(this, arguments);
28
+ };
29
+ a.prototype = f.prototype;
30
+ } else a = {};
31
+ Object.defineProperty(a, '__esModule', {value: true});
32
+ Object.keys(n).forEach(function (k) {
33
+ var d = Object.getOwnPropertyDescriptor(n, k);
34
+ Object.defineProperty(a, k, d.get ? d : {
35
+ enumerable: true,
36
+ get: function () {
37
+ return n[k];
38
+ }
39
+ });
40
+ });
41
+ return a;
42
+ }
43
+
44
+ var browser = {exports: {}};
45
+
46
+ /**
47
+ * Helpers.
48
+ */
49
+
50
+ var ms;
51
+ var hasRequiredMs;
52
+
53
+ function requireMs () {
54
+ if (hasRequiredMs) return ms;
55
+ hasRequiredMs = 1;
56
+ var s = 1000;
57
+ var m = s * 60;
58
+ var h = m * 60;
59
+ var d = h * 24;
60
+ var w = d * 7;
61
+ var y = d * 365.25;
62
+
63
+ /**
64
+ * Parse or format the given `val`.
65
+ *
66
+ * Options:
67
+ *
68
+ * - `long` verbose formatting [false]
69
+ *
70
+ * @param {String|Number} val
71
+ * @param {Object} [options]
72
+ * @throws {Error} throw an error if val is not a non-empty string or a number
73
+ * @return {String|Number}
74
+ * @api public
75
+ */
76
+
77
+ ms = function (val, options) {
78
+ options = options || {};
79
+ var type = typeof val;
80
+ if (type === 'string' && val.length > 0) {
81
+ return parse(val);
82
+ } else if (type === 'number' && isFinite(val)) {
83
+ return options.long ? fmtLong(val) : fmtShort(val);
84
+ }
85
+ throw new Error(
86
+ 'val is not a non-empty string or a valid number. val=' +
87
+ JSON.stringify(val)
88
+ );
89
+ };
90
+
91
+ /**
92
+ * Parse the given `str` and return milliseconds.
93
+ *
94
+ * @param {String} str
95
+ * @return {Number}
96
+ * @api private
97
+ */
98
+
99
+ function parse(str) {
100
+ str = String(str);
101
+ if (str.length > 100) {
102
+ return;
103
+ }
104
+ var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
105
+ str
106
+ );
107
+ if (!match) {
108
+ return;
109
+ }
110
+ var n = parseFloat(match[1]);
111
+ var type = (match[2] || 'ms').toLowerCase();
112
+ switch (type) {
113
+ case 'years':
114
+ case 'year':
115
+ case 'yrs':
116
+ case 'yr':
117
+ case 'y':
118
+ return n * y;
119
+ case 'weeks':
120
+ case 'week':
121
+ case 'w':
122
+ return n * w;
123
+ case 'days':
124
+ case 'day':
125
+ case 'd':
126
+ return n * d;
127
+ case 'hours':
128
+ case 'hour':
129
+ case 'hrs':
130
+ case 'hr':
131
+ case 'h':
132
+ return n * h;
133
+ case 'minutes':
134
+ case 'minute':
135
+ case 'mins':
136
+ case 'min':
137
+ case 'm':
138
+ return n * m;
139
+ case 'seconds':
140
+ case 'second':
141
+ case 'secs':
142
+ case 'sec':
143
+ case 's':
144
+ return n * s;
145
+ case 'milliseconds':
146
+ case 'millisecond':
147
+ case 'msecs':
148
+ case 'msec':
149
+ case 'ms':
150
+ return n;
151
+ default:
152
+ return undefined;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Short format for `ms`.
158
+ *
159
+ * @param {Number} ms
160
+ * @return {String}
161
+ * @api private
162
+ */
163
+
164
+ function fmtShort(ms) {
165
+ var msAbs = Math.abs(ms);
166
+ if (msAbs >= d) {
167
+ return Math.round(ms / d) + 'd';
168
+ }
169
+ if (msAbs >= h) {
170
+ return Math.round(ms / h) + 'h';
171
+ }
172
+ if (msAbs >= m) {
173
+ return Math.round(ms / m) + 'm';
174
+ }
175
+ if (msAbs >= s) {
176
+ return Math.round(ms / s) + 's';
177
+ }
178
+ return ms + 'ms';
179
+ }
180
+
181
+ /**
182
+ * Long format for `ms`.
183
+ *
184
+ * @param {Number} ms
185
+ * @return {String}
186
+ * @api private
187
+ */
188
+
189
+ function fmtLong(ms) {
190
+ var msAbs = Math.abs(ms);
191
+ if (msAbs >= d) {
192
+ return plural(ms, msAbs, d, 'day');
193
+ }
194
+ if (msAbs >= h) {
195
+ return plural(ms, msAbs, h, 'hour');
196
+ }
197
+ if (msAbs >= m) {
198
+ return plural(ms, msAbs, m, 'minute');
199
+ }
200
+ if (msAbs >= s) {
201
+ return plural(ms, msAbs, s, 'second');
202
+ }
203
+ return ms + ' ms';
204
+ }
205
+
206
+ /**
207
+ * Pluralization helper.
208
+ */
209
+
210
+ function plural(ms, msAbs, n, name) {
211
+ var isPlural = msAbs >= n * 1.5;
212
+ return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
213
+ }
214
+ return ms;
112
215
  }
113
216
 
217
+ var common;
218
+ var hasRequiredCommon;
219
+
220
+ function requireCommon () {
221
+ if (hasRequiredCommon) return common;
222
+ hasRequiredCommon = 1;
223
+ /**
224
+ * This is the common logic for both the Node.js and web browser
225
+ * implementations of `debug()`.
226
+ */
227
+
228
+ function setup(env) {
229
+ createDebug.debug = createDebug;
230
+ createDebug.default = createDebug;
231
+ createDebug.coerce = coerce;
232
+ createDebug.disable = disable;
233
+ createDebug.enable = enable;
234
+ createDebug.enabled = enabled;
235
+ createDebug.humanize = requireMs();
236
+ createDebug.destroy = destroy;
237
+
238
+ Object.keys(env).forEach(key => {
239
+ createDebug[key] = env[key];
240
+ });
241
+
242
+ /**
243
+ * The currently active debug mode names, and names to skip.
244
+ */
245
+
246
+ createDebug.names = [];
247
+ createDebug.skips = [];
248
+
249
+ /**
250
+ * Map of special "%n" handling functions, for the debug "format" argument.
251
+ *
252
+ * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
253
+ */
254
+ createDebug.formatters = {};
255
+
256
+ /**
257
+ * Selects a color for a debug namespace
258
+ * @param {String} namespace The namespace string for the debug instance to be colored
259
+ * @return {Number|String} An ANSI color code for the given namespace
260
+ * @api private
261
+ */
262
+ function selectColor(namespace) {
263
+ let hash = 0;
264
+
265
+ for (let i = 0; i < namespace.length; i++) {
266
+ hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
267
+ hash |= 0; // Convert to 32bit integer
268
+ }
269
+
270
+ return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
271
+ }
272
+ createDebug.selectColor = selectColor;
273
+
274
+ /**
275
+ * Create a debugger with the given `namespace`.
276
+ *
277
+ * @param {String} namespace
278
+ * @return {Function}
279
+ * @api public
280
+ */
281
+ function createDebug(namespace) {
282
+ let prevTime;
283
+ let enableOverride = null;
284
+ let namespacesCache;
285
+ let enabledCache;
286
+
287
+ function debug(...args) {
288
+ // Disabled?
289
+ if (!debug.enabled) {
290
+ return;
291
+ }
292
+
293
+ const self = debug;
294
+
295
+ // Set `diff` timestamp
296
+ const curr = Number(new Date());
297
+ const ms = curr - (prevTime || curr);
298
+ self.diff = ms;
299
+ self.prev = prevTime;
300
+ self.curr = curr;
301
+ prevTime = curr;
302
+
303
+ args[0] = createDebug.coerce(args[0]);
304
+
305
+ if (typeof args[0] !== 'string') {
306
+ // Anything else let's inspect with %O
307
+ args.unshift('%O');
308
+ }
309
+
310
+ // Apply any `formatters` transformations
311
+ let index = 0;
312
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
313
+ // If we encounter an escaped % then don't increase the array index
314
+ if (match === '%%') {
315
+ return '%';
316
+ }
317
+ index++;
318
+ const formatter = createDebug.formatters[format];
319
+ if (typeof formatter === 'function') {
320
+ const val = args[index];
321
+ match = formatter.call(self, val);
322
+
323
+ // Now we need to remove `args[index]` since it's inlined in the `format`
324
+ args.splice(index, 1);
325
+ index--;
326
+ }
327
+ return match;
328
+ });
329
+
330
+ // Apply env-specific formatting (colors, etc.)
331
+ createDebug.formatArgs.call(self, args);
332
+
333
+ const logFn = self.log || createDebug.log;
334
+ logFn.apply(self, args);
335
+ }
336
+
337
+ debug.namespace = namespace;
338
+ debug.useColors = createDebug.useColors();
339
+ debug.color = createDebug.selectColor(namespace);
340
+ debug.extend = extend;
341
+ debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
342
+
343
+ Object.defineProperty(debug, 'enabled', {
344
+ enumerable: true,
345
+ configurable: false,
346
+ get: () => {
347
+ if (enableOverride !== null) {
348
+ return enableOverride;
349
+ }
350
+ if (namespacesCache !== createDebug.namespaces) {
351
+ namespacesCache = createDebug.namespaces;
352
+ enabledCache = createDebug.enabled(namespace);
353
+ }
354
+
355
+ return enabledCache;
356
+ },
357
+ set: v => {
358
+ enableOverride = v;
359
+ }
360
+ });
361
+
362
+ // Env-specific initialization logic for debug instances
363
+ if (typeof createDebug.init === 'function') {
364
+ createDebug.init(debug);
365
+ }
366
+
367
+ return debug;
368
+ }
369
+
370
+ function extend(namespace, delimiter) {
371
+ const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
372
+ newDebug.log = this.log;
373
+ return newDebug;
374
+ }
375
+
376
+ /**
377
+ * Enables a debug mode by namespaces. This can include modes
378
+ * separated by a colon and wildcards.
379
+ *
380
+ * @param {String} namespaces
381
+ * @api public
382
+ */
383
+ function enable(namespaces) {
384
+ createDebug.save(namespaces);
385
+ createDebug.namespaces = namespaces;
386
+
387
+ createDebug.names = [];
388
+ createDebug.skips = [];
389
+
390
+ const split = (typeof namespaces === 'string' ? namespaces : '')
391
+ .trim()
392
+ .replace(/\s+/g, ',')
393
+ .split(',')
394
+ .filter(Boolean);
395
+
396
+ for (const ns of split) {
397
+ if (ns[0] === '-') {
398
+ createDebug.skips.push(ns.slice(1));
399
+ } else {
400
+ createDebug.names.push(ns);
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Checks if the given string matches a namespace template, honoring
407
+ * asterisks as wildcards.
408
+ *
409
+ * @param {String} search
410
+ * @param {String} template
411
+ * @return {Boolean}
412
+ */
413
+ function matchesTemplate(search, template) {
414
+ let searchIndex = 0;
415
+ let templateIndex = 0;
416
+ let starIndex = -1;
417
+ let matchIndex = 0;
418
+
419
+ while (searchIndex < search.length) {
420
+ if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) {
421
+ // Match character or proceed with wildcard
422
+ if (template[templateIndex] === '*') {
423
+ starIndex = templateIndex;
424
+ matchIndex = searchIndex;
425
+ templateIndex++; // Skip the '*'
426
+ } else {
427
+ searchIndex++;
428
+ templateIndex++;
429
+ }
430
+ } else if (starIndex !== -1) { // eslint-disable-line no-negated-condition
431
+ // Backtrack to the last '*' and try to match more characters
432
+ templateIndex = starIndex + 1;
433
+ matchIndex++;
434
+ searchIndex = matchIndex;
435
+ } else {
436
+ return false; // No match
437
+ }
438
+ }
439
+
440
+ // Handle trailing '*' in template
441
+ while (templateIndex < template.length && template[templateIndex] === '*') {
442
+ templateIndex++;
443
+ }
444
+
445
+ return templateIndex === template.length;
446
+ }
447
+
448
+ /**
449
+ * Disable debug output.
450
+ *
451
+ * @return {String} namespaces
452
+ * @api public
453
+ */
454
+ function disable() {
455
+ const namespaces = [
456
+ ...createDebug.names,
457
+ ...createDebug.skips.map(namespace => '-' + namespace)
458
+ ].join(',');
459
+ createDebug.enable('');
460
+ return namespaces;
461
+ }
462
+
463
+ /**
464
+ * Returns true if the given mode name is enabled, false otherwise.
465
+ *
466
+ * @param {String} name
467
+ * @return {Boolean}
468
+ * @api public
469
+ */
470
+ function enabled(name) {
471
+ for (const skip of createDebug.skips) {
472
+ if (matchesTemplate(name, skip)) {
473
+ return false;
474
+ }
475
+ }
476
+
477
+ for (const ns of createDebug.names) {
478
+ if (matchesTemplate(name, ns)) {
479
+ return true;
480
+ }
481
+ }
482
+
483
+ return false;
484
+ }
485
+
486
+ /**
487
+ * Coerce `val`.
488
+ *
489
+ * @param {Mixed} val
490
+ * @return {Mixed}
491
+ * @api private
492
+ */
493
+ function coerce(val) {
494
+ if (val instanceof Error) {
495
+ return val.stack || val.message;
496
+ }
497
+ return val;
498
+ }
499
+
500
+ /**
501
+ * XXX DO NOT USE. This is a temporary stub function.
502
+ * XXX It WILL be removed in the next major release.
503
+ */
504
+ function destroy() {
505
+ console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
506
+ }
507
+
508
+ createDebug.enable(createDebug.load());
509
+
510
+ return createDebug;
511
+ }
512
+
513
+ common = setup;
514
+ return common;
515
+ }
516
+
517
+ /* eslint-env browser */
518
+
519
+ var hasRequiredBrowser;
520
+
521
+ function requireBrowser () {
522
+ if (hasRequiredBrowser) return browser.exports;
523
+ hasRequiredBrowser = 1;
524
+ (function (module, exports) {
525
+ /**
526
+ * This is the web browser implementation of `debug()`.
527
+ */
528
+
529
+ exports.formatArgs = formatArgs;
530
+ exports.save = save;
531
+ exports.load = load;
532
+ exports.useColors = useColors;
533
+ exports.storage = localstorage();
534
+ exports.destroy = (() => {
535
+ let warned = false;
536
+
537
+ return () => {
538
+ if (!warned) {
539
+ warned = true;
540
+ console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
541
+ }
542
+ };
543
+ })();
544
+
545
+ /**
546
+ * Colors.
547
+ */
548
+
549
+ exports.colors = [
550
+ '#0000CC',
551
+ '#0000FF',
552
+ '#0033CC',
553
+ '#0033FF',
554
+ '#0066CC',
555
+ '#0066FF',
556
+ '#0099CC',
557
+ '#0099FF',
558
+ '#00CC00',
559
+ '#00CC33',
560
+ '#00CC66',
561
+ '#00CC99',
562
+ '#00CCCC',
563
+ '#00CCFF',
564
+ '#3300CC',
565
+ '#3300FF',
566
+ '#3333CC',
567
+ '#3333FF',
568
+ '#3366CC',
569
+ '#3366FF',
570
+ '#3399CC',
571
+ '#3399FF',
572
+ '#33CC00',
573
+ '#33CC33',
574
+ '#33CC66',
575
+ '#33CC99',
576
+ '#33CCCC',
577
+ '#33CCFF',
578
+ '#6600CC',
579
+ '#6600FF',
580
+ '#6633CC',
581
+ '#6633FF',
582
+ '#66CC00',
583
+ '#66CC33',
584
+ '#9900CC',
585
+ '#9900FF',
586
+ '#9933CC',
587
+ '#9933FF',
588
+ '#99CC00',
589
+ '#99CC33',
590
+ '#CC0000',
591
+ '#CC0033',
592
+ '#CC0066',
593
+ '#CC0099',
594
+ '#CC00CC',
595
+ '#CC00FF',
596
+ '#CC3300',
597
+ '#CC3333',
598
+ '#CC3366',
599
+ '#CC3399',
600
+ '#CC33CC',
601
+ '#CC33FF',
602
+ '#CC6600',
603
+ '#CC6633',
604
+ '#CC9900',
605
+ '#CC9933',
606
+ '#CCCC00',
607
+ '#CCCC33',
608
+ '#FF0000',
609
+ '#FF0033',
610
+ '#FF0066',
611
+ '#FF0099',
612
+ '#FF00CC',
613
+ '#FF00FF',
614
+ '#FF3300',
615
+ '#FF3333',
616
+ '#FF3366',
617
+ '#FF3399',
618
+ '#FF33CC',
619
+ '#FF33FF',
620
+ '#FF6600',
621
+ '#FF6633',
622
+ '#FF9900',
623
+ '#FF9933',
624
+ '#FFCC00',
625
+ '#FFCC33'
626
+ ];
627
+
628
+ /**
629
+ * Currently only WebKit-based Web Inspectors, Firefox >= v31,
630
+ * and the Firebug extension (any Firefox version) are known
631
+ * to support "%c" CSS customizations.
632
+ *
633
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
634
+ */
635
+
636
+ // eslint-disable-next-line complexity
637
+ function useColors() {
638
+ // NB: In an Electron preload script, document will be defined but not fully
639
+ // initialized. Since we know we're in Chrome, we'll just detect this case
640
+ // explicitly
641
+ if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
642
+ return true;
643
+ }
644
+
645
+ // Internet Explorer and Edge do not support colors.
646
+ if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
647
+ return false;
648
+ }
649
+
650
+ let m;
651
+
652
+ // Is webkit? http://stackoverflow.com/a/16459606/376773
653
+ // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
654
+ // eslint-disable-next-line no-return-assign
655
+ return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
656
+ // Is firebug? http://stackoverflow.com/a/398120/376773
657
+ (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
658
+ // Is firefox >= v31?
659
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
660
+ (typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) ||
661
+ // Double check webkit in userAgent just in case we are in a worker
662
+ (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
663
+ }
664
+
665
+ /**
666
+ * Colorize log arguments if enabled.
667
+ *
668
+ * @api public
669
+ */
670
+
671
+ function formatArgs(args) {
672
+ args[0] = (this.useColors ? '%c' : '') +
673
+ this.namespace +
674
+ (this.useColors ? ' %c' : ' ') +
675
+ args[0] +
676
+ (this.useColors ? '%c ' : ' ') +
677
+ '+' + module.exports.humanize(this.diff);
678
+
679
+ if (!this.useColors) {
680
+ return;
681
+ }
682
+
683
+ const c = 'color: ' + this.color;
684
+ args.splice(1, 0, c, 'color: inherit');
685
+
686
+ // The final "%c" is somewhat tricky, because there could be other
687
+ // arguments passed either before or after the %c, so we need to
688
+ // figure out the correct index to insert the CSS into
689
+ let index = 0;
690
+ let lastC = 0;
691
+ args[0].replace(/%[a-zA-Z%]/g, match => {
692
+ if (match === '%%') {
693
+ return;
694
+ }
695
+ index++;
696
+ if (match === '%c') {
697
+ // We only are interested in the *last* %c
698
+ // (the user may have provided their own)
699
+ lastC = index;
700
+ }
701
+ });
702
+
703
+ args.splice(lastC, 0, c);
704
+ }
705
+
706
+ /**
707
+ * Invokes `console.debug()` when available.
708
+ * No-op when `console.debug` is not a "function".
709
+ * If `console.debug` is not available, falls back
710
+ * to `console.log`.
711
+ *
712
+ * @api public
713
+ */
714
+ exports.log = console.debug || console.log || (() => {});
715
+
716
+ /**
717
+ * Save `namespaces`.
718
+ *
719
+ * @param {String} namespaces
720
+ * @api private
721
+ */
722
+ function save(namespaces) {
723
+ try {
724
+ if (namespaces) {
725
+ exports.storage.setItem('debug', namespaces);
726
+ } else {
727
+ exports.storage.removeItem('debug');
728
+ }
729
+ } catch (error) {
730
+ // Swallow
731
+ // XXX (@Qix-) should we be logging these?
732
+ }
733
+ }
734
+
735
+ /**
736
+ * Load `namespaces`.
737
+ *
738
+ * @return {String} returns the previously persisted debug modes
739
+ * @api private
740
+ */
741
+ function load() {
742
+ let r;
743
+ try {
744
+ r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ;
745
+ } catch (error) {
746
+ // Swallow
747
+ // XXX (@Qix-) should we be logging these?
748
+ }
749
+
750
+ // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
751
+ if (!r && typeof process !== 'undefined' && 'env' in process) {
752
+ r = process.env.DEBUG;
753
+ }
754
+
755
+ return r;
756
+ }
757
+
758
+ /**
759
+ * Localstorage attempts to return the localstorage.
760
+ *
761
+ * This is necessary because safari throws
762
+ * when a user disables cookies/localstorage
763
+ * and you attempt to access it.
764
+ *
765
+ * @return {LocalStorage}
766
+ * @api private
767
+ */
768
+
769
+ function localstorage() {
770
+ try {
771
+ // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
772
+ // The Browser also has localStorage in the global context.
773
+ return localStorage;
774
+ } catch (error) {
775
+ // Swallow
776
+ // XXX (@Qix-) should we be logging these?
777
+ }
778
+ }
779
+
780
+ module.exports = requireCommon()(exports);
781
+
782
+ const {formatters} = module.exports;
783
+
784
+ /**
785
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
786
+ */
787
+
788
+ formatters.j = function (v) {
789
+ try {
790
+ return JSON.stringify(v);
791
+ } catch (error) {
792
+ return '[UnexpectedJSONParseError]: ' + error.message;
793
+ }
794
+ };
795
+ } (browser, browser.exports));
796
+ return browser.exports;
797
+ }
798
+
799
+ var browserExports = requireBrowser();
800
+ var debug$3 = /*@__PURE__*/getDefaultExportFromCjs(browserExports);
801
+
802
+ /*!
803
+ * playwright-extra v4.3.5 by berstend
804
+ * https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra#readme
805
+ * @license MIT
806
+ */
807
+
808
+ /** Node.js module loader helper */
809
+ class Loader {
810
+ constructor(moduleName, packageNames) {
811
+ this.moduleName = moduleName;
812
+ this.packageNames = packageNames;
813
+ }
814
+ /**
815
+ * Lazy load a top level export from another module by wrapping it in a JS proxy.
816
+ *
817
+ * This allows us to re-export e.g. `devices` from `playwright` while redirecting direct calls
818
+ * to it to the module version the user has installed, rather than shipping with a hardcoded version.
819
+ *
820
+ * If we don't do this and the user doesn't have the target module installed we'd throw immediately when our code is imported.
821
+ *
822
+ * We use a "super" Proxy defining all traps, so calls like `Object.keys(playwright.devices).length` will return the correct value.
823
+ */
824
+ lazyloadExportOrDie(exportName) {
825
+ const that = this;
826
+ const trapHandler = Object.fromEntries(Object.getOwnPropertyNames(Reflect).map((name) => [
827
+ name,
828
+ function (target, ...args) {
829
+ const moduleExport = that.loadModuleOrDie()[exportName];
830
+ const customTarget = moduleExport;
831
+ const result = Reflect[name](customTarget || target, ...args);
832
+ return result;
833
+ }
834
+ ]));
835
+ return new Proxy({}, trapHandler);
836
+ }
837
+ /** Load the module if possible */
838
+ loadModule() {
839
+ return requirePackages(this.packageNames);
840
+ }
841
+ /** Load the module if possible or throw */
842
+ loadModuleOrDie() {
843
+ const module = requirePackages(this.packageNames);
844
+ if (module) {
845
+ return module;
846
+ }
847
+ throw this.requireError;
848
+ }
849
+ get requireError() {
850
+ const moduleNamePretty = this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1);
851
+ return new Error(`
852
+ ${moduleNamePretty} is missing. :-)
853
+
854
+ I've tried loading ${this.packageNames
855
+ .map(p => `"${p}"`)
856
+ .join(', ')} - no luck.
857
+
858
+ Make sure you install one of those packages or use the named 'addExtra' export,
859
+ to patch a specific (and maybe non-standard) implementation of ${moduleNamePretty}.
860
+
861
+ To get the latest stable version of ${moduleNamePretty} run:
862
+ 'yarn add ${this.moduleName}' or 'npm i ${this.moduleName}'
863
+ `);
864
+ }
865
+ }
866
+ function requirePackages(packageNames) {
867
+ for (const name of packageNames) {
868
+ try {
869
+ return require(name);
870
+ }
871
+ catch (_) {
872
+ continue; // noop
873
+ }
874
+ }
875
+ return;
876
+ }
877
+ /** Playwright specific module loader */
878
+ const playwrightLoader = new Loader('playwright', [
879
+ 'playwright-core',
880
+ 'playwright'
881
+ ]);
882
+
883
+ const debug = debug$3('playwright-extra:puppeteer-compat');
884
+ const isPlaywrightPage = (obj) => {
885
+ return 'unroute' in obj;
886
+ };
887
+ const isPlaywrightFrame = (obj) => {
888
+ return ['parentFrame', 'frameLocator'].every(x => x in obj);
889
+ };
890
+ const isPlaywrightBrowser = (obj) => {
891
+ return 'newContext' in obj;
892
+ };
893
+ const isPuppeteerCompat = (obj) => {
894
+ return !!obj && typeof obj === 'object' && !!obj.isCompatShim;
895
+ };
896
+ const cache = {
897
+ objectToShim: new Map(),
898
+ cdpSession: {
899
+ page: new Map(),
900
+ browser: new Map()
901
+ }
902
+ };
903
+ /** Augment a Playwright object with compatibility with certain Puppeteer methods */
904
+ function addPuppeteerCompat(object) {
905
+ if (!object || typeof object !== 'object') {
906
+ return object;
907
+ }
908
+ if (cache.objectToShim.has(object)) {
909
+ return cache.objectToShim.get(object);
910
+ }
911
+ if (isPuppeteerCompat(object)) {
912
+ return object;
913
+ }
914
+ debug('addPuppeteerCompat', cache.objectToShim.size);
915
+ if (isPlaywrightPage(object) || isPlaywrightFrame(object)) {
916
+ const shim = createPageShim(object);
917
+ cache.objectToShim.set(object, shim);
918
+ return shim;
919
+ }
920
+ if (isPlaywrightBrowser(object)) {
921
+ const shim = createBrowserShim(object);
922
+ cache.objectToShim.set(object, shim);
923
+ return shim;
924
+ }
925
+ debug('Received unknown object:', Reflect.ownKeys(object));
926
+ return object;
927
+ }
928
+ // Only chromium browsers support CDP
929
+ const dummyCDPClient = {
930
+ send: async (...args) => {
931
+ debug('dummy CDP client called', 'send', args);
932
+ },
933
+ on: (...args) => {
934
+ debug('dummy CDP client called', 'on', args);
935
+ }
936
+ };
937
+ async function getPageCDPSession(page) {
938
+ let session = cache.cdpSession.page.get(page);
939
+ if (session) {
940
+ debug('getPageCDPSession: use existing');
941
+ return session;
942
+ }
943
+ debug('getPageCDPSession: use new');
944
+ const context = isPlaywrightFrame(page)
945
+ ? page.page().context()
946
+ : page.context();
947
+ try {
948
+ session = await context.newCDPSession(page);
949
+ cache.cdpSession.page.set(page, session);
950
+ return session;
951
+ }
952
+ catch (err) {
953
+ debug('getPageCDPSession: error while creating session:', err.message);
954
+ debug('getPageCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy');
955
+ }
956
+ return dummyCDPClient;
957
+ }
958
+ async function getBrowserCDPSession(browser) {
959
+ let session = cache.cdpSession.browser.get(browser);
960
+ if (session) {
961
+ debug('getBrowserCDPSession: use existing');
962
+ return session;
963
+ }
964
+ debug('getBrowserCDPSession: use new');
965
+ try {
966
+ session = await browser.newBrowserCDPSession();
967
+ cache.cdpSession.browser.set(browser, session);
968
+ return session;
969
+ }
970
+ catch (err) {
971
+ debug('getBrowserCDPSession: error while creating session:', err.message);
972
+ debug('getBrowserCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy');
973
+ }
974
+ return dummyCDPClient;
975
+ }
976
+ function createPageShim(page) {
977
+ const objId = Math.random().toString(36).substring(2, 7);
978
+ const shim = new Proxy(page, {
979
+ get(target, prop) {
980
+ if (prop === 'isCompatShim' || prop === 'isPlaywright') {
981
+ return true;
982
+ }
983
+ debug('page - get', objId, prop);
984
+ if (prop === '_client') {
985
+ return () => ({
986
+ send: async (method, params) => {
987
+ const session = await getPageCDPSession(page);
988
+ return await session.send(method, params);
989
+ },
990
+ on: (event, listener) => {
991
+ getPageCDPSession(page).then(session => {
992
+ session.on(event, listener);
993
+ });
994
+ }
995
+ });
996
+ }
997
+ if (prop === 'setBypassCSP') {
998
+ return async (enabled) => {
999
+ const session = await getPageCDPSession(page);
1000
+ return await session.send('Page.setBypassCSP', {
1001
+ enabled
1002
+ });
1003
+ };
1004
+ }
1005
+ if (prop === 'setUserAgent') {
1006
+ return async (userAgent, userAgentMetadata) => {
1007
+ const session = await getPageCDPSession(page);
1008
+ return await session.send('Emulation.setUserAgentOverride', {
1009
+ userAgent,
1010
+ userAgentMetadata
1011
+ });
1012
+ };
1013
+ }
1014
+ if (prop === 'browser') {
1015
+ if (isPlaywrightPage(page)) {
1016
+ return () => {
1017
+ let browser = page.context().browser();
1018
+ if (!browser) {
1019
+ debug('page.browser() - not available, most likely due to launchPersistentContext');
1020
+ // Use a page shim as quick drop-in (so browser.userAgent() still works)
1021
+ browser = page;
1022
+ }
1023
+ return addPuppeteerCompat(browser);
1024
+ };
1025
+ }
1026
+ }
1027
+ if (prop === 'evaluateOnNewDocument') {
1028
+ if (isPlaywrightPage(page)) {
1029
+ return async function (pageFunction, ...args) {
1030
+ return await page.addInitScript(pageFunction, args[0]);
1031
+ };
1032
+ }
1033
+ }
1034
+ // Only relevant when page is being used a pseudo stand-in for the browser object (launchPersistentContext)
1035
+ if (prop === 'userAgent') {
1036
+ return async (enabled) => {
1037
+ const session = await getPageCDPSession(page);
1038
+ const data = await session.send('Browser.getVersion');
1039
+ return data.userAgent;
1040
+ };
1041
+ }
1042
+ return Reflect.get(target, prop);
1043
+ }
1044
+ });
1045
+ return shim;
1046
+ }
1047
+ function createBrowserShim(browser) {
1048
+ const objId = Math.random().toString(36).substring(2, 7);
1049
+ const shim = new Proxy(browser, {
1050
+ get(target, prop) {
1051
+ if (prop === 'isCompatShim' || prop === 'isPlaywright') {
1052
+ return true;
1053
+ }
1054
+ debug('browser - get', objId, prop);
1055
+ if (prop === 'pages') {
1056
+ return () => browser
1057
+ .contexts()
1058
+ .flatMap(c => c.pages().map(page => addPuppeteerCompat(page)));
1059
+ }
1060
+ if (prop === 'userAgent') {
1061
+ return async () => {
1062
+ const session = await getBrowserCDPSession(browser);
1063
+ const data = await session.send('Browser.getVersion');
1064
+ return data.userAgent;
1065
+ };
1066
+ }
1067
+ return Reflect.get(target, prop);
1068
+ }
1069
+ });
1070
+ return shim;
1071
+ }
1072
+
1073
+ const debug$1 = debug$3('playwright-extra:plugins');
1074
+ class PluginList {
1075
+ constructor() {
1076
+ this._plugins = [];
1077
+ this._dependencyDefaults = new Map();
1078
+ this._dependencyResolution = new Map();
1079
+ }
1080
+ /**
1081
+ * Get a list of all registered plugins.
1082
+ */
1083
+ get list() {
1084
+ return this._plugins;
1085
+ }
1086
+ /**
1087
+ * Get the names of all registered plugins.
1088
+ */
1089
+ get names() {
1090
+ return this._plugins.map(p => p.name);
1091
+ }
1092
+ /**
1093
+ * Add a new plugin to the list (after checking if it's well-formed).
1094
+ *
1095
+ * @param plugin
1096
+ * @internal
1097
+ */
1098
+ add(plugin) {
1099
+ var _a;
1100
+ if (!this.isValidPluginInstance(plugin)) {
1101
+ return false;
1102
+ }
1103
+ if (!!plugin.onPluginRegistered) {
1104
+ plugin.onPluginRegistered({ framework: 'playwright' });
1105
+ }
1106
+ // PuppeteerExtraPlugin: Populate `_childClassMembers` list containing methods defined by the plugin
1107
+ if (!!plugin._registerChildClassMembers) {
1108
+ plugin._registerChildClassMembers(Object.getPrototypeOf(plugin));
1109
+ }
1110
+ if ((_a = plugin.requirements) === null || _a === void 0 ? void 0 : _a.has('dataFromPlugins')) {
1111
+ plugin.getDataFromPlugins = this.getData.bind(this);
1112
+ }
1113
+ this._plugins.push(plugin);
1114
+ return true;
1115
+ }
1116
+ /** Check if the shape of a plugin is correct or warn */
1117
+ isValidPluginInstance(plugin) {
1118
+ if (!plugin ||
1119
+ typeof plugin !== 'object' ||
1120
+ !plugin._isPuppeteerExtraPlugin) {
1121
+ console.error(`Warning: Plugin is not derived from PuppeteerExtraPlugin, ignoring.`, plugin);
1122
+ return false;
1123
+ }
1124
+ if (!plugin.name) {
1125
+ console.error(`Warning: Plugin with no name registering, ignoring.`, plugin);
1126
+ return false;
1127
+ }
1128
+ return true;
1129
+ }
1130
+ /** Error callback in case calling a plugin method throws an error. Can be overwritten. */
1131
+ onPluginError(plugin, method, err) {
1132
+ console.warn(`An error occured while executing "${method}" in plugin "${plugin.name}":`, err);
1133
+ }
1134
+ /**
1135
+ * Define default values for plugins implicitly required through the `dependencies` plugin stanza.
1136
+ *
1137
+ * @param dependencyPath - The string by which the dependency is listed (not the plugin name)
1138
+ *
1139
+ * @example
1140
+ * chromium.use(stealth)
1141
+ * chromium.plugins.setDependencyDefaults('stealth/evasions/webgl.vendor', { vendor: 'Bob', renderer: 'Alice' })
1142
+ */
1143
+ setDependencyDefaults(dependencyPath, opts) {
1144
+ this._dependencyDefaults.set(dependencyPath, opts);
1145
+ return this;
1146
+ }
1147
+ /**
1148
+ * Define custom plugin modules for plugins implicitly required through the `dependencies` plugin stanza.
1149
+ *
1150
+ * Using this will prevent dynamic imports from being used, which JS bundlers often have issues with.
1151
+ *
1152
+ * @example
1153
+ * chromium.use(stealth)
1154
+ * chromium.plugins.setDependencyResolution('stealth/evasions/webgl.vendor', VendorPlugin)
1155
+ */
1156
+ setDependencyResolution(dependencyPath, pluginModule) {
1157
+ this._dependencyResolution.set(dependencyPath, pluginModule);
1158
+ return this;
1159
+ }
1160
+ /**
1161
+ * Prepare plugins to be used (resolve dependencies, ordering)
1162
+ * @internal
1163
+ */
1164
+ prepare() {
1165
+ this.resolveDependencies();
1166
+ this.order();
1167
+ }
1168
+ /** Return all plugins using the supplied method */
1169
+ filterByMethod(methodName) {
1170
+ return this._plugins.filter(plugin => {
1171
+ // PuppeteerExtraPlugin: The base class will already define all methods, hence we need to do a different check
1172
+ if (!!plugin._childClassMembers &&
1173
+ Array.isArray(plugin._childClassMembers)) {
1174
+ return plugin._childClassMembers.includes(methodName);
1175
+ }
1176
+ return methodName in plugin;
1177
+ });
1178
+ }
1179
+ /** Conditionally add puppeteer compatibility to values provided to the plugins */
1180
+ _addPuppeteerCompatIfNeeded(plugin, method, args) {
1181
+ const canUseShim = plugin._isPuppeteerExtraPlugin && !plugin.noPuppeteerShim;
1182
+ const methodWhitelist = [
1183
+ 'onBrowser',
1184
+ 'onPageCreated',
1185
+ 'onPageClose',
1186
+ 'afterConnect',
1187
+ 'afterLaunch'
1188
+ ];
1189
+ const shouldUseShim = methodWhitelist.includes(method);
1190
+ if (!canUseShim || !shouldUseShim) {
1191
+ return args;
1192
+ }
1193
+ debug$1('add puppeteer compatibility', plugin.name, method);
1194
+ return [...args.map(arg => addPuppeteerCompat(arg))];
1195
+ }
1196
+ /**
1197
+ * Dispatch plugin lifecycle events in a typesafe way.
1198
+ * Only Plugins that expose the supplied property will be called.
1199
+ *
1200
+ * Will not await results to dispatch events as fast as possible to all plugins.
1201
+ *
1202
+ * @param method - The lifecycle method name
1203
+ * @param args - Optional: Any arguments to be supplied to the plugin methods
1204
+ * @internal
1205
+ */
1206
+ dispatch(method, ...args) {
1207
+ var _a, _b;
1208
+ const plugins = this.filterByMethod(method);
1209
+ debug$1('dispatch', method, {
1210
+ all: this._plugins.length,
1211
+ filteredByMethod: plugins.length
1212
+ });
1213
+ for (const plugin of plugins) {
1214
+ try {
1215
+ args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args);
1216
+ const fnType = (_b = (_a = plugin[method]) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name;
1217
+ debug$1('dispatch to plugin', {
1218
+ plugin: plugin.name,
1219
+ method,
1220
+ fnType
1221
+ });
1222
+ if (fnType === 'AsyncFunction') {
1223
+ ;
1224
+ plugin[method](...args).catch((err) => this.onPluginError(plugin, method, err));
1225
+ }
1226
+ else {
1227
+ ;
1228
+ plugin[method](...args);
1229
+ }
1230
+ }
1231
+ catch (err) {
1232
+ this.onPluginError(plugin, method, err);
1233
+ }
1234
+ }
1235
+ }
1236
+ /**
1237
+ * Dispatch plugin lifecycle events in a typesafe way.
1238
+ * Only Plugins that expose the supplied property will be called.
1239
+ *
1240
+ * Can also be used to get a definite return value after passing it to plugins:
1241
+ * Calls plugins sequentially and passes on a value (waterfall style).
1242
+ *
1243
+ * The plugins can either modify the value or return an updated one.
1244
+ * Will return the latest, updated value which ran through all plugins.
1245
+ *
1246
+ * By convention only the first argument will be used as the updated value.
1247
+ *
1248
+ * @param method - The lifecycle method name
1249
+ * @param args - Optional: Any arguments to be supplied to the plugin methods
1250
+ * @internal
1251
+ */
1252
+ async dispatchBlocking(method, ...args) {
1253
+ const plugins = this.filterByMethod(method);
1254
+ debug$1('dispatchBlocking', method, {
1255
+ all: this._plugins.length,
1256
+ filteredByMethod: plugins.length
1257
+ });
1258
+ let retValue = null;
1259
+ for (const plugin of plugins) {
1260
+ try {
1261
+ args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args);
1262
+ retValue = await plugin[method](...args);
1263
+ // In case we got a return value use that as new first argument for followup function calls
1264
+ if (retValue !== undefined) {
1265
+ args[0] = retValue;
1266
+ }
1267
+ }
1268
+ catch (err) {
1269
+ this.onPluginError(plugin, method, err);
1270
+ return retValue;
1271
+ }
1272
+ }
1273
+ return retValue;
1274
+ }
1275
+ /**
1276
+ * Order plugins that have expressed a special placement requirement.
1277
+ *
1278
+ * This is useful/necessary for e.g. plugins that depend on the data from other plugins.
1279
+ *
1280
+ * @private
1281
+ */
1282
+ order() {
1283
+ debug$1('order:before', this.names);
1284
+ const runLast = this._plugins
1285
+ .filter(p => { var _a; return (_a = p.requirements) === null || _a === void 0 ? void 0 : _a.has('runLast'); })
1286
+ .map(p => p.name);
1287
+ for (const name of runLast) {
1288
+ const index = this._plugins.findIndex(p => p.name === name);
1289
+ this._plugins.push(this._plugins.splice(index, 1)[0]);
1290
+ }
1291
+ debug$1('order:after', this.names);
1292
+ }
1293
+ /**
1294
+ * Collects the exposed `data` property of all registered plugins.
1295
+ * Will be reduced/flattened to a single array.
1296
+ *
1297
+ * Can be accessed by plugins that listed the `dataFromPlugins` requirement.
1298
+ *
1299
+ * Implemented mainly for plugins that need data from other plugins (e.g. `user-preferences`).
1300
+ *
1301
+ * @see [PuppeteerExtraPlugin]/data
1302
+ * @param name - Filter data by optional name
1303
+ *
1304
+ * @private
1305
+ */
1306
+ getData(name) {
1307
+ const data = this._plugins
1308
+ .filter((p) => !!p.data)
1309
+ .map((p) => (Array.isArray(p.data) ? p.data : [p.data]))
1310
+ .reduce((acc, arr) => [...acc, ...arr], []);
1311
+ return name ? data.filter((d) => d.name === name) : data;
1312
+ }
1313
+ /**
1314
+ * Handle `plugins` stanza (already instantiated plugins that don't require dynamic imports)
1315
+ */
1316
+ resolvePluginsStanza() {
1317
+ debug$1('resolvePluginsStanza');
1318
+ const pluginNames = new Set(this.names);
1319
+ this._plugins
1320
+ .filter(p => !!p.plugins && p.plugins.length)
1321
+ .filter(p => !pluginNames.has(p.name)) // TBD: Do we want to filter out existing?
1322
+ .forEach(parent => {
1323
+ (parent.plugins || []).forEach(p => {
1324
+ debug$1(parent.name, 'adding missing plugin', p.name);
1325
+ this.add(p);
1326
+ });
1327
+ });
1328
+ }
1329
+ /**
1330
+ * Handle `dependencies` stanza (which requires dynamic imports)
1331
+ *
1332
+ * Plugins can define `dependencies` as a Set or Array of dependency paths, or a Map with additional opts
1333
+ *
1334
+ * @note
1335
+ * - The default opts for implicit dependencies can be defined using `setDependencyDefaults()`
1336
+ * - Dynamic imports can be avoided by providing plugin modules with `setDependencyResolution()`
1337
+ */
1338
+ resolveDependenciesStanza() {
1339
+ debug$1('resolveDependenciesStanza');
1340
+ /** Attempt to dynamically require a plugin module */
1341
+ const requireDependencyOrDie = (parentName, dependencyPath) => {
1342
+ // If the user provided the plugin module already we use that
1343
+ if (this._dependencyResolution.has(dependencyPath)) {
1344
+ return this._dependencyResolution.get(dependencyPath);
1345
+ }
1346
+ const possiblePrefixes = ['puppeteer-extra-plugin-']; // could be extended later
1347
+ const isAlreadyPrefixed = possiblePrefixes.some(prefix => dependencyPath.startsWith(prefix));
1348
+ const packagePaths = [];
1349
+ // If the dependency is not already prefixed we attempt to require all possible combinations to find one that works
1350
+ if (!isAlreadyPrefixed) {
1351
+ packagePaths.push(...possiblePrefixes.map(prefix => prefix + dependencyPath));
1352
+ }
1353
+ // We always attempt to require the path verbatim (as a last resort)
1354
+ packagePaths.push(dependencyPath);
1355
+ const pluginModule = requirePackages(packagePaths);
1356
+ if (pluginModule) {
1357
+ return pluginModule;
1358
+ }
1359
+ const explanation = `
1360
+ The plugin '${parentName}' listed '${dependencyPath}' as dependency,
1361
+ which could not be found. Please install it:
1362
+
1363
+ ${packagePaths
1364
+ .map(packagePath => `yarn add ${packagePath.split('/')[0]}`)
1365
+ .join(`\n or:\n`)}
1366
+
1367
+ Note: You don't need to require the plugin yourself,
1368
+ unless you want to modify it's default settings.
1369
+
1370
+ If your bundler has issues with dynamic imports take a look at '.plugins.setDependencyResolution()'.
1371
+ `;
1372
+ console.warn(explanation);
1373
+ throw new Error('Plugin dependency not found');
1374
+ };
1375
+ const existingPluginNames = new Set(this.names);
1376
+ const recursivelyLoadMissingDependencies = ({ name: parentName, dependencies }) => {
1377
+ if (!dependencies) {
1378
+ return;
1379
+ }
1380
+ const processDependency = (dependencyPath, opts) => {
1381
+ const pluginModule = requireDependencyOrDie(parentName, dependencyPath);
1382
+ opts = opts || this._dependencyDefaults.get(dependencyPath) || {};
1383
+ const plugin = pluginModule(opts);
1384
+ if (existingPluginNames.has(plugin.name)) {
1385
+ debug$1(parentName, '=> dependency already exists:', plugin.name);
1386
+ return;
1387
+ }
1388
+ existingPluginNames.add(plugin.name);
1389
+ debug$1(parentName, '=> adding new dependency:', plugin.name, opts);
1390
+ this.add(plugin);
1391
+ return recursivelyLoadMissingDependencies(plugin);
1392
+ };
1393
+ if (dependencies instanceof Set || Array.isArray(dependencies)) {
1394
+ return [...dependencies].forEach(dependencyPath => processDependency(dependencyPath));
1395
+ }
1396
+ if (dependencies instanceof Map) {
1397
+ // Note: `k,v => v,k` (Map + forEach will reverse the order)
1398
+ return dependencies.forEach((v, k) => processDependency(k, v));
1399
+ }
1400
+ };
1401
+ this.list.forEach(recursivelyLoadMissingDependencies);
1402
+ }
1403
+ /**
1404
+ * Lightweight plugin dependency management to require plugins and code mods on demand.
1405
+ * @private
1406
+ */
1407
+ resolveDependencies() {
1408
+ debug$1('resolveDependencies');
1409
+ this.resolvePluginsStanza();
1410
+ this.resolveDependenciesStanza();
1411
+ }
1412
+ }
1413
+
1414
+ const debug$2 = debug$3('playwright-extra');
1415
+ /**
1416
+ * Modular plugin framework to teach `playwright` new tricks.
1417
+ */
1418
+ class PlaywrightExtraClass {
1419
+ constructor(_launcher) {
1420
+ this._launcher = _launcher;
1421
+ this.plugins = new PluginList();
1422
+ }
1423
+ /**
1424
+ * The **main interface** to register plugins.
1425
+ *
1426
+ * Can be called multiple times to enable multiple plugins.
1427
+ *
1428
+ * Plugins derived from `PuppeteerExtraPlugin` will be used with a compatiblity layer.
1429
+ *
1430
+ * @example
1431
+ * chromium.use(plugin1).use(plugin2)
1432
+ * firefox.use(plugin1).use(plugin2)
1433
+ *
1434
+ * @see [PuppeteerExtraPlugin]
1435
+ *
1436
+ * @return The same `PlaywrightExtra` instance (for optional chaining)
1437
+ */
1438
+ use(plugin) {
1439
+ const isValid = plugin && 'name' in plugin;
1440
+ if (!isValid) {
1441
+ throw new Error('A plugin must be provided to .use()');
1442
+ }
1443
+ if (this.plugins.add(plugin)) {
1444
+ debug$2('Plugin registered', plugin.name);
1445
+ }
1446
+ return this;
1447
+ }
1448
+ /**
1449
+ * In order to support a default export which will require vanilla playwright automatically,
1450
+ * as well as `addExtra` to patch a provided launcher, we need to so some gymnastics here.
1451
+ *
1452
+ * Otherwise this would throw immediately, even when only using the `addExtra` export with an arbitrary compatible launcher.
1453
+ *
1454
+ * The solution is to make the vanilla launcher optional and only throw once we try to effectively use and can't find it.
1455
+ *
1456
+ * @internal
1457
+ */
1458
+ get launcher() {
1459
+ if (!this._launcher) {
1460
+ throw playwrightLoader.requireError;
1461
+ }
1462
+ return this._launcher;
1463
+ }
1464
+ async launch(...args) {
1465
+ if (!this.launcher.launch) {
1466
+ throw new Error('Launcher does not support "launch"');
1467
+ }
1468
+ let [options] = args;
1469
+ options = Object.assign({ args: [] }, (options || {})); // Initialize args array
1470
+ debug$2('launch', options);
1471
+ this.plugins.prepare();
1472
+ // Give plugins the chance to modify the options before continuing
1473
+ options =
1474
+ (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options;
1475
+ debug$2('launch with options', options);
1476
+ if ('userDataDir' in options) {
1477
+ debug$2("A plugin defined userDataDir during .launch, which isn't supported by playwright - ignoring");
1478
+ delete options.userDataDir;
1479
+ }
1480
+ const browser = await this.launcher['launch'](options);
1481
+ await this.plugins.dispatchBlocking('onBrowser', browser);
1482
+ await this._bindBrowserEvents(browser);
1483
+ await this.plugins.dispatchBlocking('afterLaunch', browser);
1484
+ return browser;
1485
+ }
1486
+ async launchPersistentContext(...args) {
1487
+ if (!this.launcher.launchPersistentContext) {
1488
+ throw new Error('Launcher does not support "launchPersistentContext"');
1489
+ }
1490
+ let [userDataDir, options] = args;
1491
+ options = Object.assign({ args: [] }, (options || {})); // Initialize args array
1492
+ debug$2('launchPersistentContext', options);
1493
+ this.plugins.prepare();
1494
+ // Give plugins the chance to modify the options before continuing
1495
+ options =
1496
+ (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options;
1497
+ const context = await this.launcher['launchPersistentContext'](userDataDir, options);
1498
+ await this.plugins.dispatchBlocking('afterLaunch', context);
1499
+ this._bindBrowserContextEvents(context);
1500
+ return context;
1501
+ }
1502
+ async connect(wsEndpointOrOptions, wsOptions = {}) {
1503
+ if (!this.launcher.connect) {
1504
+ throw new Error('Launcher does not support "connect"');
1505
+ }
1506
+ this.plugins.prepare();
1507
+ // Playwright currently supports two function signatures for .connect
1508
+ let options = {};
1509
+ let wsEndpointAsString = false;
1510
+ if (typeof wsEndpointOrOptions === 'object') {
1511
+ options = Object.assign(Object.assign({}, wsEndpointOrOptions), wsOptions);
1512
+ }
1513
+ else {
1514
+ wsEndpointAsString = true;
1515
+ options = Object.assign({ wsEndpoint: wsEndpointOrOptions }, wsOptions);
1516
+ }
1517
+ debug$2('connect', options);
1518
+ // Give plugins the chance to modify the options before launch/connect
1519
+ options =
1520
+ (await this.plugins.dispatchBlocking('beforeConnect', options)) || options;
1521
+ // Follow call signature of end user
1522
+ const args = [];
1523
+ const wsEndpoint = options.wsEndpoint;
1524
+ if (wsEndpointAsString) {
1525
+ delete options.wsEndpoint;
1526
+ args.push(wsEndpoint, options);
1527
+ }
1528
+ else {
1529
+ args.push(options);
1530
+ }
1531
+ const browser = (await this.launcher['connect'](...args));
1532
+ await this.plugins.dispatchBlocking('onBrowser', browser);
1533
+ await this._bindBrowserEvents(browser);
1534
+ await this.plugins.dispatchBlocking('afterConnect', browser);
1535
+ return browser;
1536
+ }
1537
+ async connectOverCDP(wsEndpointOrOptions, wsOptions = {}) {
1538
+ if (!this.launcher.connectOverCDP) {
1539
+ throw new Error(`Launcher does not implement 'connectOverCDP'`);
1540
+ }
1541
+ this.plugins.prepare();
1542
+ // Playwright currently supports two function signatures for .connectOverCDP
1543
+ let options = {};
1544
+ let wsEndpointAsString = false;
1545
+ if (typeof wsEndpointOrOptions === 'object') {
1546
+ options = Object.assign(Object.assign({}, wsEndpointOrOptions), wsOptions);
1547
+ }
1548
+ else {
1549
+ wsEndpointAsString = true;
1550
+ options = Object.assign({ endpointURL: wsEndpointOrOptions }, wsOptions);
1551
+ }
1552
+ debug$2('connectOverCDP');
1553
+ // Give plugins the chance to modify the options before launch/connect
1554
+ options =
1555
+ (await this.plugins.dispatchBlocking('beforeConnect', options)) || options;
1556
+ // Follow call signature of end user
1557
+ const args = [];
1558
+ const endpointURL = options.endpointURL;
1559
+ if (wsEndpointAsString) {
1560
+ delete options.endpointURL;
1561
+ args.push(endpointURL, options);
1562
+ }
1563
+ else {
1564
+ args.push(options);
1565
+ }
1566
+ const browser = (await this.launcher['connectOverCDP'](...args));
1567
+ await this.plugins.dispatchBlocking('onBrowser', browser);
1568
+ await this._bindBrowserEvents(browser);
1569
+ await this.plugins.dispatchBlocking('afterConnect', browser);
1570
+ return browser;
1571
+ }
1572
+ async _bindBrowserContextEvents(context, contextOptions) {
1573
+ debug$2('_bindBrowserContextEvents');
1574
+ this.plugins.dispatch('onContextCreated', context, contextOptions);
1575
+ // Make sure things like `addInitScript` show an effect on the very first page as well
1576
+ context.newPage = ((originalMethod, ctx) => {
1577
+ return async () => {
1578
+ const page = await originalMethod.call(ctx);
1579
+ await page.goto('about:blank');
1580
+ return page;
1581
+ };
1582
+ })(context.newPage, context);
1583
+ context.on('close', () => {
1584
+ // When using `launchPersistentContext` context closing is the same as browser closing
1585
+ if (!context.browser()) {
1586
+ this.plugins.dispatch('onDisconnected');
1587
+ }
1588
+ });
1589
+ context.on('page', page => {
1590
+ this.plugins.dispatch('onPageCreated', page);
1591
+ page.on('close', () => {
1592
+ this.plugins.dispatch('onPageClose', page);
1593
+ });
1594
+ });
1595
+ }
1596
+ async _bindBrowserEvents(browser) {
1597
+ debug$2('_bindPlaywrightBrowserEvents');
1598
+ browser.on('disconnected', () => {
1599
+ this.plugins.dispatch('onDisconnected', browser);
1600
+ });
1601
+ // Note: `browser.newPage` will implicitly call `browser.newContext` as well
1602
+ browser.newContext = ((originalMethod, ctx) => {
1603
+ return async (options = {}) => {
1604
+ const contextOptions = (await this.plugins.dispatchBlocking('beforeContext', options, browser)) || options;
1605
+ const context = await originalMethod.call(ctx, contextOptions);
1606
+ this._bindBrowserContextEvents(context, contextOptions);
1607
+ return context;
1608
+ };
1609
+ })(browser.newContext, browser);
1610
+ }
1611
+ }
1612
+ /**
1613
+ * PlaywrightExtra class with additional launcher methods.
1614
+ *
1615
+ * Augments the class with an instance proxy to pass on methods that are not augmented to the original target.
1616
+ *
1617
+ */
1618
+ const PlaywrightExtra = new Proxy(PlaywrightExtraClass, {
1619
+ construct(classTarget, args) {
1620
+ debug$2(`create instance of ${classTarget.name}`);
1621
+ const result = Reflect.construct(classTarget, args);
1622
+ return new Proxy(result, {
1623
+ get(target, prop) {
1624
+ if (prop in target) {
1625
+ return Reflect.get(target, prop);
1626
+ }
1627
+ debug$2('proxying property to original launcher: ', prop);
1628
+ return Reflect.get(target.launcher, prop);
1629
+ }
1630
+ });
1631
+ }
1632
+ });
1633
+
1634
+ /**
1635
+ * Augment the provided Playwright browser launcher with plugin functionality.
1636
+ *
1637
+ * Using `addExtra` will always create a fresh PlaywrightExtra instance.
1638
+ *
1639
+ * @example
1640
+ * import playwright from 'playwright'
1641
+ * import { addExtra } from 'playwright-extra'
1642
+ *
1643
+ * const chromium = addExtra(playwright.chromium)
1644
+ * chromium.use(plugin)
1645
+ *
1646
+ * @param launcher - Playwright (or compatible) browser launcher
1647
+ */
1648
+ const addExtra = (launcher) => new PlaywrightExtra(launcher);
1649
+ /**
1650
+ * This object can be used to launch or connect to Chromium with plugin functionality.
1651
+ *
1652
+ * This default export will behave exactly the same as the regular playwright
1653
+ * (just with extra plugin functionality) and can be used as a drop-in replacement.
1654
+ *
1655
+ * Behind the scenes it will try to require either the `playwright-core`
1656
+ * or `playwright` module from the installed dependencies.
1657
+ *
1658
+ * @note
1659
+ * Due to Node.js import caching this will result in a single
1660
+ * PlaywrightExtra instance, even when used in different files. If you need multiple
1661
+ * instances with different plugins please use `addExtra`.
1662
+ *
1663
+ * @example
1664
+ * // javascript import
1665
+ * const { chromium } = require('playwright-extra')
1666
+ *
1667
+ * // typescript/es6 module import
1668
+ * import { chromium } from 'playwright-extra'
1669
+ *
1670
+ * // Add plugins
1671
+ * chromium.use(...)
1672
+ */
1673
+ const chromium = addExtra((playwrightLoader.loadModule() || {}).chromium);
1674
+ /**
1675
+ * This object can be used to launch or connect to Firefox with plugin functionality
1676
+ * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra`
1677
+ */
1678
+ addExtra((playwrightLoader.loadModule() || {}).firefox);
1679
+ /**
1680
+ * This object can be used to launch or connect to Webkit with plugin functionality
1681
+ * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra`
1682
+ */
1683
+ addExtra((playwrightLoader.loadModule() || {}).webkit);
1684
+ // Other playwright module exports we simply re-export with lazy loading
1685
+ playwrightLoader.lazyloadExportOrDie('_android');
1686
+ playwrightLoader.lazyloadExportOrDie('_electron');
1687
+ playwrightLoader.lazyloadExportOrDie('request');
1688
+ playwrightLoader.lazyloadExportOrDie('selectors');
1689
+ playwrightLoader.lazyloadExportOrDie('devices');
1690
+ playwrightLoader.lazyloadExportOrDie('errors');
1691
+
1692
+ /*!
1693
+ * puppeteer-extra-plugin v3.2.2 by berstend
1694
+ * https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin
1695
+ * @license MIT
1696
+ */
1697
+
1698
+ /** @private */
1699
+ const merge = require('merge-deep');
1700
+ /**
1701
+ * Base class for `puppeteer-extra` plugins.
1702
+ *
1703
+ * Provides convenience methods to avoid boilerplate.
1704
+ *
1705
+ * All common `puppeteer` browser events will be bound to
1706
+ * the plugin instance, if a respectively named class member is found.
1707
+ *
1708
+ * Please refer to the [puppeteer API documentation](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) as well.
1709
+ *
1710
+ * @example
1711
+ * // hello-world-plugin.js
1712
+ * const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')
1713
+ *
1714
+ * class Plugin extends PuppeteerExtraPlugin {
1715
+ * constructor (opts = { }) { super(opts) }
1716
+ *
1717
+ * get name () { return 'hello-world' }
1718
+ *
1719
+ * async onPageCreated (page) {
1720
+ * this.debug('page created', page.url())
1721
+ * const ua = await page.browser().userAgent()
1722
+ * this.debug('user agent', ua)
1723
+ * }
1724
+ * }
1725
+ *
1726
+ * module.exports = function (pluginConfig) { return new Plugin(pluginConfig) }
1727
+ *
1728
+ *
1729
+ * // foo.js
1730
+ * const puppeteer = require('puppeteer-extra')
1731
+ * puppeteer.use(require('./hello-world-plugin')())
1732
+ *
1733
+ * ;(async () => {
1734
+ * const browser = await puppeteer.launch({headless: false})
1735
+ * const page = await browser.newPage()
1736
+ * await page.goto('http://example.com', {waitUntil: 'domcontentloaded'})
1737
+ * await browser.close()
1738
+ * })()
1739
+ *
1740
+ */
1741
+ class PuppeteerExtraPlugin {
1742
+ constructor(opts) {
1743
+ this._debugBase = debug$3(`puppeteer-extra-plugin:base:${this.name}`);
1744
+ this._childClassMembers = [];
1745
+ this._opts = merge(this.defaults, opts || {});
1746
+ this._debugBase('Initialized.');
1747
+ }
1748
+ /**
1749
+ * Plugin name (required).
1750
+ *
1751
+ * Convention:
1752
+ * - Package: `puppeteer-extra-plugin-anonymize-ua`
1753
+ * - Name: `anonymize-ua`
1754
+ *
1755
+ * @example
1756
+ * get name () { return 'anonymize-ua' }
1757
+ */
1758
+ get name() {
1759
+ throw new Error('Plugin must override "name"');
1760
+ }
1761
+ /**
1762
+ * Plugin defaults (optional).
1763
+ *
1764
+ * If defined will be ([deep-](https://github.com/jonschlinkert/merge-deep))merged with the (optional) user supplied options (supplied during plugin instantiation).
1765
+ *
1766
+ * The result of merging defaults with user supplied options can be accessed through `this.opts`.
1767
+ *
1768
+ * @see [[opts]]
1769
+ *
1770
+ * @example
1771
+ * get defaults () {
1772
+ * return {
1773
+ * stripHeadless: true,
1774
+ * makeWindows: true,
1775
+ * customFn: null
1776
+ * }
1777
+ * }
1778
+ *
1779
+ * // Users can overwrite plugin defaults during instantiation:
1780
+ * puppeteer.use(require('puppeteer-extra-plugin-foobar')({ makeWindows: false }))
1781
+ */
1782
+ get defaults() {
1783
+ return {};
1784
+ }
1785
+ /**
1786
+ * Plugin requirements (optional).
1787
+ *
1788
+ * Signal certain plugin requirements to the base class and the user.
1789
+ *
1790
+ * Currently supported:
1791
+ * - `launch`
1792
+ * - If the plugin only supports locally created browser instances (no `puppeteer.connect()`),
1793
+ * will output a warning to the user.
1794
+ * - `headful`
1795
+ * - If the plugin doesn't work in `headless: true` mode,
1796
+ * will output a warning to the user.
1797
+ * - `dataFromPlugins`
1798
+ * - In case the plugin requires data from other plugins.
1799
+ * will enable usage of `this.getDataFromPlugins()`.
1800
+ * - `runLast`
1801
+ * - In case the plugin prefers to run after the others.
1802
+ * Useful when the plugin needs data from others.
1803
+ *
1804
+ * @example
1805
+ * get requirements () {
1806
+ * return new Set(['runLast', 'dataFromPlugins'])
1807
+ * }
1808
+ */
1809
+ get requirements() {
1810
+ return new Set([]);
1811
+ }
1812
+ /**
1813
+ * Plugin dependencies (optional).
1814
+ *
1815
+ * Missing plugins will be required() by puppeteer-extra.
1816
+ *
1817
+ * @example
1818
+ * get dependencies () {
1819
+ * return new Set(['user-preferences'])
1820
+ * }
1821
+ * // Will ensure the 'puppeteer-extra-plugin-user-preferences' plugin is loaded.
1822
+ */
1823
+ get dependencies() {
1824
+ return new Set([]);
1825
+ }
1826
+ /**
1827
+ * Plugin data (optional).
1828
+ *
1829
+ * Plugins can expose data (an array of objects), which in turn can be consumed by other plugins,
1830
+ * that list the `dataFromPlugins` requirement (by using `this.getDataFromPlugins()`).
1831
+ *
1832
+ * Convention: `[ {name: 'Any name', value: 'Any value'} ]`
1833
+ *
1834
+ * @see [[getDataFromPlugins]]
1835
+ *
1836
+ * @example
1837
+ * // plugin1.js
1838
+ * get data () {
1839
+ * return [
1840
+ * {
1841
+ * name: 'userPreferences',
1842
+ * value: { foo: 'bar' }
1843
+ * },
1844
+ * {
1845
+ * name: 'userPreferences',
1846
+ * value: { hello: 'world' }
1847
+ * }
1848
+ * ]
1849
+ *
1850
+ * // plugin2.js
1851
+ * get requirements () { return new Set(['dataFromPlugins']) }
1852
+ *
1853
+ * async beforeLaunch () {
1854
+ * const prefs = this.getDataFromPlugins('userPreferences').map(d => d.value)
1855
+ * this.debug(prefs) // => [ { foo: 'bar' }, { hello: 'world' } ]
1856
+ * }
1857
+ */
1858
+ get data() {
1859
+ return [];
1860
+ }
1861
+ /**
1862
+ * Access the plugin options (usually the `defaults` merged with user defined options)
1863
+ *
1864
+ * To skip the auto-merging of defaults with user supplied opts don't define a `defaults`
1865
+ * property and set the `this._opts` Object in your plugin constructor directly.
1866
+ *
1867
+ * @see [[defaults]]
1868
+ *
1869
+ * @example
1870
+ * get defaults () { return { foo: "bar" } }
1871
+ *
1872
+ * async onPageCreated (page) {
1873
+ * this.debug(this.opts.foo) // => bar
1874
+ * }
1875
+ */
1876
+ get opts() {
1877
+ return this._opts;
1878
+ }
1879
+ /**
1880
+ * Convenience debug logger based on the [debug] module.
1881
+ * Will automatically namespace the logging output to the plugin package name.
1882
+ * [debug]: https://www.npmjs.com/package/debug
1883
+ *
1884
+ * ```bash
1885
+ * # toggle output using environment variables
1886
+ * DEBUG=puppeteer-extra-plugin:<plugin_name> node foo.js
1887
+ * # to debug all the things:
1888
+ * DEBUG=puppeteer-extra,puppeteer-extra-plugin:* node foo.js
1889
+ * ```
1890
+ *
1891
+ * @example
1892
+ * this.debug('hello world')
1893
+ * // will output e.g. 'puppeteer-extra-plugin:anonymize-ua hello world'
1894
+ */
1895
+ get debug() {
1896
+ return debug$3(`puppeteer-extra-plugin:${this.name}`);
1897
+ }
1898
+ /**
1899
+ * Before a new browser instance is created/launched.
1900
+ *
1901
+ * Can be used to modify the puppeteer launch options by modifying or returning them.
1902
+ *
1903
+ * Plugins using this method will be called in sequence to each
1904
+ * be able to update the launch options.
1905
+ *
1906
+ * @example
1907
+ * async beforeLaunch (options) {
1908
+ * if (this.opts.flashPluginPath) {
1909
+ * options.args.push(`--ppapi-flash-path=${this.opts.flashPluginPath}`)
1910
+ * }
1911
+ * }
1912
+ *
1913
+ * @param options - Puppeteer launch options
1914
+ */
1915
+ async beforeLaunch(options) {
1916
+ // noop
1917
+ }
1918
+ /**
1919
+ * After the browser has launched.
1920
+ *
1921
+ * Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.
1922
+ * It's possible that `pupeeteer.launch` will be called multiple times and more than one browser created.
1923
+ * In order to make the plugins as stateless as possible don't store a reference to the browser instance
1924
+ * in the plugin but rather consider alternatives.
1925
+ *
1926
+ * E.g. when using `onPageCreated` you can get a browser reference by using `page.browser()`.
1927
+ *
1928
+ * Alternatively you could expose a class method that takes a browser instance as a parameter to work with:
1929
+ *
1930
+ * ```es6
1931
+ * const fancyPlugin = require('puppeteer-extra-plugin-fancy')()
1932
+ * puppeteer.use(fancyPlugin)
1933
+ * const browser = await puppeteer.launch()
1934
+ * await fancyPlugin.killBrowser(browser)
1935
+ * ```
1936
+ *
1937
+ * @param browser - The `puppeteer` browser instance.
1938
+ * @param opts.options - Puppeteer launch options used.
1939
+ *
1940
+ * @example
1941
+ * async afterLaunch (browser, opts) {
1942
+ * this.debug('browser has been launched', opts.options)
1943
+ * }
1944
+ */
1945
+ async afterLaunch(browser, opts = { options: {} }) {
1946
+ // noop
1947
+ }
1948
+ /**
1949
+ * Before connecting to an existing browser instance.
1950
+ *
1951
+ * Can be used to modify the puppeteer connect options by modifying or returning them.
1952
+ *
1953
+ * Plugins using this method will be called in sequence to each
1954
+ * be able to update the launch options.
1955
+ *
1956
+ * @param {Object} options - Puppeteer connect options
1957
+ * @return {Object=}
1958
+ */
1959
+ async beforeConnect(options) {
1960
+ // noop
1961
+ }
1962
+ /**
1963
+ * After connecting to an existing browser instance.
1964
+ *
1965
+ * > Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.
1966
+ *
1967
+ * @param browser - The `puppeteer` browser instance.
1968
+ * @param {Object} opts
1969
+ * @param {Object} opts.options - Puppeteer connect options used.
1970
+ *
1971
+ */
1972
+ async afterConnect(browser, opts = {}) {
1973
+ // noop
1974
+ }
1975
+ /**
1976
+ * Called when a browser instance is available.
1977
+ *
1978
+ * This applies to both `puppeteer.launch()` and `puppeteer.connect()`.
1979
+ *
1980
+ * Convenience method created for plugins that need access to a browser instance
1981
+ * and don't mind if it has been created through `launch` or `connect`.
1982
+ *
1983
+ * > Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.
1984
+ *
1985
+ * @param browser - The `puppeteer` browser instance.
1986
+ */
1987
+ async onBrowser(browser, opts) {
1988
+ // noop
1989
+ }
1990
+ /**
1991
+ * Called when a target is created, for example when a new page is opened by window.open or browser.newPage.
1992
+ *
1993
+ * > Note: This includes target creations in incognito browser contexts.
1994
+ *
1995
+ * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.
1996
+ *
1997
+ * @param {Puppeteer.Target} target
1998
+ */
1999
+ async onTargetCreated(target) {
2000
+ // noop
2001
+ }
2002
+ /**
2003
+ * Same as `onTargetCreated` but prefiltered to only contain Pages, for convenience.
2004
+ *
2005
+ * > Note: This includes page creations in incognito browser contexts.
2006
+ *
2007
+ * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.
2008
+ *
2009
+ * @param {Puppeteer.Target} target
2010
+ *
2011
+ * @example
2012
+ * async onPageCreated (page) {
2013
+ * let ua = await page.browser().userAgent()
2014
+ * if (this.opts.stripHeadless) {
2015
+ * ua = ua.replace('HeadlessChrome/', 'Chrome/')
2016
+ * }
2017
+ * this.debug('new ua', ua)
2018
+ * await page.setUserAgent(ua)
2019
+ * }
2020
+ */
2021
+ async onPageCreated(page) {
2022
+ // noop
2023
+ }
2024
+ /**
2025
+ * Called when the url of a target changes.
2026
+ *
2027
+ * > Note: This includes target changes in incognito browser contexts.
2028
+ *
2029
+ * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.
2030
+ *
2031
+ * @param {Puppeteer.Target} target
2032
+ */
2033
+ async onTargetChanged(target) {
2034
+ // noop
2035
+ }
2036
+ /**
2037
+ * Called when a target is destroyed, for example when a page is closed.
2038
+ *
2039
+ * > Note: This includes target destructions in incognito browser contexts.
2040
+ *
2041
+ * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.
2042
+ *
2043
+ * @param {Puppeteer.Target} target
2044
+ */
2045
+ async onTargetDestroyed(target) {
2046
+ // noop
2047
+ }
2048
+ /**
2049
+ * Called when Puppeteer gets disconnected from the Chromium instance.
2050
+ *
2051
+ * This might happen because of one of the following:
2052
+ * - Chromium is closed or crashed
2053
+ * - The `browser.disconnect` method was called
2054
+ */
2055
+ async onDisconnected() {
2056
+ // noop
2057
+ }
2058
+ /**
2059
+ * **Deprecated:** Since puppeteer v1.6.0 `onDisconnected` has been improved
2060
+ * and should be used instead of `onClose`.
2061
+ *
2062
+ * In puppeteer < v1.6.0 `onDisconnected` was not catching all exit scenarios.
2063
+ * In order for plugins to clean up properly (e.g. deleting temporary files)
2064
+ * the `onClose` method had been introduced.
2065
+ *
2066
+ * > Note: Might be called multiple times on exit.
2067
+ *
2068
+ * > Note: This only includes browser instances created through `.launch()`.
2069
+ */
2070
+ async onClose() {
2071
+ // noop
2072
+ }
2073
+ /**
2074
+ * After the plugin has been registered in `puppeteer-extra`.
2075
+ *
2076
+ * Normally right after `puppeteer.use(plugin)` is called
2077
+ */
2078
+ async onPluginRegistered() {
2079
+ // noop
2080
+ }
2081
+ /**
2082
+ * Helper method to retrieve `data` objects from other plugins.
2083
+ *
2084
+ * A plugin needs to state the `dataFromPlugins` requirement
2085
+ * in order to use this method. Will be mapped to `puppeteer.getPluginData`.
2086
+ *
2087
+ * @param name - Filter data by `name` property
2088
+ *
2089
+ * @see [data]
2090
+ * @see [requirements]
2091
+ */
2092
+ getDataFromPlugins(name) {
2093
+ return [];
2094
+ }
2095
+ /**
2096
+ * Will match plugin dependencies against all currently registered plugins.
2097
+ * Is being called by `puppeteer-extra` and used to require missing dependencies.
2098
+ *
2099
+ * @param {Array<Object>} plugins
2100
+ * @return {Set} - list of missing plugin names
2101
+ *
2102
+ * @private
2103
+ */
2104
+ _getMissingDependencies(plugins) {
2105
+ const pluginNames = new Set(plugins.map((p) => p.name));
2106
+ const missing = new Set(Array.from(this.dependencies.values()).filter(x => !pluginNames.has(x)));
2107
+ return missing;
2108
+ }
2109
+ /**
2110
+ * Conditionally bind browser/process events to class members.
2111
+ * The idea is to reduce event binding boilerplate in plugins.
2112
+ *
2113
+ * For efficiency we make sure the plugin is using the respective event
2114
+ * by checking the child class members before registering the listener.
2115
+ *
2116
+ * @param {<Puppeteer.Browser>} browser
2117
+ * @param {Object} opts - Options
2118
+ * @param {string} opts.context - Puppeteer context (launch/connect)
2119
+ * @param {Object} [opts.options] - Puppeteer launch or connect options
2120
+ * @param {Array<string>} [opts.defaultArgs] - The default flags that Chromium will be launched with
2121
+ *
2122
+ * @private
2123
+ */
2124
+ async _bindBrowserEvents(browser, opts = {}) {
2125
+ if (this._hasChildClassMember('onTargetCreated') ||
2126
+ this._hasChildClassMember('onPageCreated')) {
2127
+ browser.on('targetcreated', this._onTargetCreated.bind(this));
2128
+ }
2129
+ if (this._hasChildClassMember('onTargetChanged') && this.onTargetChanged) {
2130
+ browser.on('targetchanged', this.onTargetChanged.bind(this));
2131
+ }
2132
+ if (this._hasChildClassMember('onTargetDestroyed') &&
2133
+ this.onTargetDestroyed) {
2134
+ browser.on('targetdestroyed', this.onTargetDestroyed.bind(this));
2135
+ }
2136
+ if (this._hasChildClassMember('onDisconnected') && this.onDisconnected) {
2137
+ browser.on('disconnected', this.onDisconnected.bind(this));
2138
+ }
2139
+ if (opts.context === 'launch' && this._hasChildClassMember('onClose')) {
2140
+ // The disconnect event has been improved since puppeteer v1.6.0
2141
+ // onClose is being kept mostly for legacy reasons
2142
+ if (this.onClose) {
2143
+ process.on('exit', this.onClose.bind(this));
2144
+ browser.on('disconnected', this.onClose.bind(this));
2145
+ if (opts.options.handleSIGINT !== false) {
2146
+ process.on('SIGINT', this.onClose.bind(this));
2147
+ }
2148
+ if (opts.options.handleSIGTERM !== false) {
2149
+ process.on('SIGTERM', this.onClose.bind(this));
2150
+ }
2151
+ if (opts.options.handleSIGHUP !== false) {
2152
+ process.on('SIGHUP', this.onClose.bind(this));
2153
+ }
2154
+ }
2155
+ }
2156
+ if (opts.context === 'launch' && this.afterLaunch) {
2157
+ await this.afterLaunch(browser, opts);
2158
+ }
2159
+ if (opts.context === 'connect' && this.afterConnect) {
2160
+ await this.afterConnect(browser, opts);
2161
+ }
2162
+ if (this.onBrowser)
2163
+ await this.onBrowser(browser, opts);
2164
+ }
2165
+ /**
2166
+ * @private
2167
+ */
2168
+ async _onTargetCreated(target) {
2169
+ if (this.onTargetCreated)
2170
+ await this.onTargetCreated(target);
2171
+ // Pre filter pages for plugin developers convenience
2172
+ if (target.type() === 'page') {
2173
+ try {
2174
+ const page = await target.page();
2175
+ if (!page) {
2176
+ return;
2177
+ }
2178
+ const validPage = 'isClosed' in page && !page.isClosed();
2179
+ if (this.onPageCreated && validPage) {
2180
+ await this.onPageCreated(page);
2181
+ }
2182
+ }
2183
+ catch (err) {
2184
+ console.error(err);
2185
+ }
2186
+ }
2187
+ }
2188
+ /**
2189
+ * @private
2190
+ */
2191
+ _register(prototype) {
2192
+ this._registerChildClassMembers(prototype);
2193
+ if (this.onPluginRegistered)
2194
+ this.onPluginRegistered();
2195
+ }
2196
+ /**
2197
+ * @private
2198
+ */
2199
+ _registerChildClassMembers(prototype) {
2200
+ this._childClassMembers = Object.getOwnPropertyNames(prototype);
2201
+ }
2202
+ /**
2203
+ * @private
2204
+ */
2205
+ _hasChildClassMember(name) {
2206
+ return !!this._childClassMembers.includes(name);
2207
+ }
2208
+ /**
2209
+ * @private
2210
+ */
2211
+ get _isPuppeteerExtraPlugin() {
2212
+ return true;
2213
+ }
2214
+ }
2215
+
2216
+ var index_esm = /*#__PURE__*/Object.freeze({
2217
+ __proto__: null,
2218
+ PuppeteerExtraPlugin: PuppeteerExtraPlugin
2219
+ });
2220
+
2221
+ var require$$0 = /*@__PURE__*/getAugmentedNamespace(index_esm);
2222
+
2223
+ var puppeteerExtraPluginStealth;
2224
+ var hasRequiredPuppeteerExtraPluginStealth;
2225
+
2226
+ function requirePuppeteerExtraPluginStealth () {
2227
+ if (hasRequiredPuppeteerExtraPluginStealth) return puppeteerExtraPluginStealth;
2228
+ hasRequiredPuppeteerExtraPluginStealth = 1;
2229
+
2230
+ const { PuppeteerExtraPlugin } = require$$0;
2231
+
2232
+ /**
2233
+ * Stealth mode: Applies various techniques to make detection of headless puppeteer harder. 💯
2234
+ *
2235
+ * ### Purpose
2236
+ * There are a couple of ways the use of puppeteer can easily be detected by a target website.
2237
+ * The addition of `HeadlessChrome` to the user-agent being only the most obvious one.
2238
+ *
2239
+ * The goal of this plugin is to be the definite companion to puppeteer to avoid
2240
+ * detection, applying new techniques as they surface.
2241
+ *
2242
+ * As this cat & mouse game is in it's infancy and fast-paced the plugin
2243
+ * is kept as flexibile as possible, to support quick testing and iterations.
2244
+ *
2245
+ * ### Modularity
2246
+ * This plugin uses `puppeteer-extra`'s dependency system to only require
2247
+ * code mods for evasions that have been enabled, to keep things modular and efficient.
2248
+ *
2249
+ * The `stealth` plugin is a convenience wrapper that requires multiple [evasion techniques](./evasions/)
2250
+ * automatically and comes with defaults. You could also bypass the main module and require
2251
+ * specific evasion plugins yourself, if you whish to do so (as they're standalone `puppeteer-extra` plugins):
2252
+ *
2253
+ * ```es6
2254
+ * // bypass main module and require a specific stealth plugin directly:
2255
+ * puppeteer.use(require('puppeteer-extra-plugin-stealth/evasions/console.debug')())
2256
+ * ```
2257
+ *
2258
+ * ### Contributing
2259
+ * PRs are welcome, if you want to add a new evasion technique I suggest you
2260
+ * look at the [template](./evasions/_template) to kickstart things.
2261
+ *
2262
+ * ### Kudos
2263
+ * Thanks to [Evan Sangaline](https://intoli.com/blog/not-possible-to-block-chrome-headless/) and [Paul Irish](https://github.com/paulirish/headless-cat-n-mouse) for kickstarting the discussion!
2264
+ *
2265
+ * ---
2266
+ *
2267
+ * @todo
2268
+ * - white-/blacklist with url globs (make this a generic plugin method?)
2269
+ * - dynamic whitelist based on function evaluation
2270
+ *
2271
+ * @example
2272
+ * const puppeteer = require('puppeteer-extra')
2273
+ * // Enable stealth plugin with all evasions
2274
+ * puppeteer.use(require('puppeteer-extra-plugin-stealth')())
2275
+ *
2276
+ *
2277
+ * ;(async () => {
2278
+ * // Launch the browser in headless mode and set up a page.
2279
+ * const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true })
2280
+ * const page = await browser.newPage()
2281
+ *
2282
+ * // Navigate to the page that will perform the tests.
2283
+ * const testUrl = 'https://intoli.com/blog/' +
2284
+ * 'not-possible-to-block-chrome-headless/chrome-headless-test.html'
2285
+ * await page.goto(testUrl)
2286
+ *
2287
+ * // Save a screenshot of the results.
2288
+ * const screenshotPath = '/tmp/headless-test-result.png'
2289
+ * await page.screenshot({path: screenshotPath})
2290
+ * console.log('have a look at the screenshot:', screenshotPath)
2291
+ *
2292
+ * await browser.close()
2293
+ * })()
2294
+ *
2295
+ * @param {Object} [opts] - Options
2296
+ * @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)
2297
+ *
2298
+ */
2299
+ class StealthPlugin extends PuppeteerExtraPlugin {
2300
+ constructor(opts = {}) {
2301
+ super(opts);
2302
+ }
2303
+
2304
+ get name() {
2305
+ return 'stealth'
2306
+ }
2307
+
2308
+ get defaults() {
2309
+ const availableEvasions = new Set([
2310
+ 'chrome.app',
2311
+ 'chrome.csi',
2312
+ 'chrome.loadTimes',
2313
+ 'chrome.runtime',
2314
+ 'defaultArgs',
2315
+ 'iframe.contentWindow',
2316
+ 'media.codecs',
2317
+ 'navigator.hardwareConcurrency',
2318
+ 'navigator.languages',
2319
+ 'navigator.permissions',
2320
+ 'navigator.plugins',
2321
+ 'navigator.webdriver',
2322
+ 'sourceurl',
2323
+ 'user-agent-override',
2324
+ 'webgl.vendor',
2325
+ 'window.outerdimensions'
2326
+ ]);
2327
+ return {
2328
+ availableEvasions,
2329
+ // Enable all available evasions by default
2330
+ enabledEvasions: new Set([...availableEvasions])
2331
+ }
2332
+ }
2333
+
2334
+ /**
2335
+ * Requires evasion techniques dynamically based on configuration.
2336
+ *
2337
+ * @private
2338
+ */
2339
+ get dependencies() {
2340
+ return new Set(
2341
+ [...this.opts.enabledEvasions].map(e => `${this.name}/evasions/${e}`)
2342
+ )
2343
+ }
2344
+
2345
+ /**
2346
+ * Get all available evasions.
2347
+ *
2348
+ * Please look into the [evasions directory](./evasions/) for an up to date list.
2349
+ *
2350
+ * @type {Set<string>} - A Set of all available evasions.
2351
+ *
2352
+ * @example
2353
+ * const pluginStealth = require('puppeteer-extra-plugin-stealth')()
2354
+ * console.log(pluginStealth.availableEvasions) // => Set { 'user-agent', 'console.debug' }
2355
+ * puppeteer.use(pluginStealth)
2356
+ */
2357
+ get availableEvasions() {
2358
+ return this.defaults.availableEvasions
2359
+ }
2360
+
2361
+ /**
2362
+ * Get all enabled evasions.
2363
+ *
2364
+ * Enabled evasions can be configured either through `opts` or by modifying this property.
2365
+ *
2366
+ * @type {Set<string>} - A Set of all enabled evasions.
2367
+ *
2368
+ * @example
2369
+ * // Remove specific evasion from enabled ones dynamically
2370
+ * const pluginStealth = require('puppeteer-extra-plugin-stealth')()
2371
+ * pluginStealth.enabledEvasions.delete('console.debug')
2372
+ * puppeteer.use(pluginStealth)
2373
+ */
2374
+ get enabledEvasions() {
2375
+ return this.opts.enabledEvasions
2376
+ }
2377
+
2378
+ /**
2379
+ * @private
2380
+ */
2381
+ set enabledEvasions(evasions) {
2382
+ this.opts.enabledEvasions = evasions;
2383
+ }
2384
+
2385
+ async onBrowser(browser) {
2386
+ if (browser && browser.setMaxListeners) {
2387
+ // Increase event emitter listeners to prevent MaxListenersExceededWarning
2388
+ browser.setMaxListeners(30);
2389
+ }
2390
+ }
2391
+ }
2392
+
2393
+ /**
2394
+ * Default export, PuppeteerExtraStealthPlugin
2395
+ *
2396
+ * @param {Object} [opts] - Options
2397
+ * @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)
2398
+ */
2399
+ const defaultExport = opts => new StealthPlugin(opts);
2400
+ puppeteerExtraPluginStealth = defaultExport;
2401
+
2402
+ // const moduleExport = defaultExport
2403
+ // moduleExport.StealthPlugin = StealthPlugin
2404
+ // module.exports = moduleExport
2405
+ return puppeteerExtraPluginStealth;
2406
+ }
2407
+
2408
+ var puppeteerExtraPluginStealthExports = requirePuppeteerExtraPluginStealth();
2409
+ var StealthPlugin = /*@__PURE__*/getDefaultExportFromCjs(puppeteerExtraPluginStealthExports);
2410
+
114
2411
  class BrowserAgent extends BaseBrowserLabelsAgent {
115
2412
  constructor() {
116
2413
  super(...arguments);
@@ -126,32 +2423,30 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
126
2423
  this.cdpWsEndpoint = cdpWsEndpoint;
127
2424
  }
128
2425
  initUserDataDir(userDataDir) {
129
- if (userDataDir) {
130
- this.userDataDir = userDataDir;
131
- }
132
- else {
133
- this.userDataDir = getDefaultChromeUserDataDir(true);
134
- }
2426
+ this.userDataDir = userDataDir;
135
2427
  return this.userDataDir;
136
2428
  }
2429
+ setCookies(cookies) {
2430
+ this.cookies = cookies;
2431
+ }
137
2432
  setOptions(options) {
138
2433
  this.options = options;
139
2434
  }
140
2435
  async screenshot(agentContext) {
141
- let page = await this.currentPage();
142
- let screenshotBuffer = await page.screenshot({
2436
+ const page = await this.currentPage();
2437
+ const screenshotBuffer = await page.screenshot({
143
2438
  fullPage: false,
144
2439
  type: "jpeg",
145
2440
  quality: 60,
146
2441
  });
147
- let base64 = screenshotBuffer.toString("base64");
2442
+ const base64 = screenshotBuffer.toString("base64");
148
2443
  return {
149
2444
  imageType: "image/jpeg",
150
2445
  imageBase64: base64,
151
2446
  };
152
2447
  }
153
2448
  async navigate_to(agentContext, url) {
154
- let page = await this.open_url(agentContext, url);
2449
+ const page = await this.open_url(agentContext, url);
155
2450
  await this.sleep(200);
156
2451
  return {
157
2452
  url: page.url(),
@@ -162,7 +2457,7 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
162
2457
  if (!this.browser_context) {
163
2458
  return [];
164
2459
  }
165
- let result = [];
2460
+ const result = [];
166
2461
  const pages = await this.browser_context.pages();
167
2462
  for (let i = 0; i < pages.length; i++) {
168
2463
  let page = pages[i];
@@ -192,7 +2487,7 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
192
2487
  }
193
2488
  async input_text(agentContext, index, text, enter) {
194
2489
  try {
195
- let elementHandle = await this.get_element(index, true);
2490
+ const elementHandle = await this.get_element(index, true);
196
2491
  await elementHandle.fill("");
197
2492
  await elementHandle.fill(text);
198
2493
  if (enter) {
@@ -206,11 +2501,17 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
206
2501
  }
207
2502
  async click_element(agentContext, index, num_clicks, button) {
208
2503
  try {
209
- let elementHandle = await this.get_element(index, true);
2504
+ const elementHandle = await this.get_element(index, true);
2505
+ const box = await elementHandle.boundingBox();
2506
+ if (box) {
2507
+ const page = await this.currentPage();
2508
+ page.mouse.move(box.x + box.width / 2 + (Math.random() * 10 - 5), box.y + box.height / 2 + (Math.random() * 10 - 5), { steps: Math.floor(Math.random() * 5) + 3 });
2509
+ }
210
2510
  await elementHandle.click({
211
2511
  button,
212
2512
  clickCount: num_clicks,
213
- force: true,
2513
+ force: false,
2514
+ delay: Math.random() * 50 + 20,
214
2515
  });
215
2516
  }
216
2517
  catch (e) {
@@ -219,7 +2520,7 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
219
2520
  }
220
2521
  async hover_to_element(agentContext, index) {
221
2522
  try {
222
- let elementHandle = await this.get_element(index, true);
2523
+ const elementHandle = await this.get_element(index, true);
223
2524
  elementHandle.hover({ force: true });
224
2525
  }
225
2526
  catch (e) {
@@ -227,20 +2528,22 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
227
2528
  }
228
2529
  }
229
2530
  async execute_script(agentContext, func, args) {
230
- let page = await this.currentPage();
2531
+ const page = await this.currentPage();
231
2532
  return await page.evaluate(func, ...args);
232
2533
  }
233
2534
  async open_url(agentContext, url) {
234
- let browser_context = await this.getBrowserContext();
2535
+ const browser_context = await this.getBrowserContext();
235
2536
  const page = await browser_context.newPage();
236
2537
  // await page.setViewportSize({ width: 1920, height: 1080 });
237
2538
  await page.setViewportSize({ width: 1536, height: 864 });
238
2539
  try {
2540
+ await this.autoLoadCookies(url);
2541
+ await this.autoLoadLocalStorage(page, url);
239
2542
  await page.goto(url, {
240
- waitUntil: "networkidle",
2543
+ waitUntil: "domcontentloaded",
241
2544
  timeout: 10000,
242
2545
  });
243
- await page.waitForLoadState("load", { timeout: 8000 });
2546
+ await page.waitForLoadState("networkidle", { timeout: 5000 });
244
2547
  }
245
2548
  catch (e) {
246
2549
  if ((e + "").indexOf("Timeout") == -1) {
@@ -254,7 +2557,7 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
254
2557
  if (this.current_page == null) {
255
2558
  throw new Error("There is no page, please call navigate_to first");
256
2559
  }
257
- let page = this.current_page;
2560
+ const page = this.current_page;
258
2561
  try {
259
2562
  await page.waitForLoadState("domcontentloaded", { timeout: 10000 });
260
2563
  }
@@ -262,7 +2565,7 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
262
2565
  return page;
263
2566
  }
264
2567
  async get_element(index, findInput) {
265
- let page = await this.currentPage();
2568
+ const page = await this.currentPage();
266
2569
  return await page.evaluateHandle((params) => {
267
2570
  let element = window.get_highlight_element(params.index);
268
2571
  if (element && params.findInput) {
@@ -286,88 +2589,120 @@ class BrowserAgent extends BaseBrowserLabelsAgent {
286
2589
  this.current_page = null;
287
2590
  this.browser_context = null;
288
2591
  if (this.cdpWsEndpoint) {
289
- this.browser = await chromium.connectOverCDP(this.cdpWsEndpoint, this.options);
290
- this.browser_context = await this.browser.newContext();
2592
+ this.browser = (await chromium.connectOverCDP(this.cdpWsEndpoint, this.options));
2593
+ this.browser_context = await this.browser.newContext({
2594
+ userAgent: this.getUserAgent(),
2595
+ viewport: { width: 1536, height: 864 },
2596
+ });
291
2597
  }
292
2598
  else if (this.userDataDir) {
293
- this.browser_context = await chromium.launchPersistentContext(this.userDataDir, {
2599
+ this.browser_context = (await chromium.launchPersistentContext(this.userDataDir, {
294
2600
  headless: this.headless,
295
- // channel: 'chrome',
296
- args: [
297
- "--no-sandbox",
298
- "--remote-allow-origins=*",
299
- "--disable-dev-shm-usage",
300
- "--disable-popup-blocking",
301
- "--enable-automation",
302
- "--ignore-ssl-errors",
303
- "--ignore-certificate-errors",
304
- "--ignore-certificate-errors-spki-list",
305
- "--disable-blink-features=AutomationControlled",
306
- ],
2601
+ channel: "chrome",
2602
+ args: this.getChromiumArgs(),
307
2603
  ...this.options,
308
- });
2604
+ }));
309
2605
  }
310
2606
  else {
311
- this.browser = await chromium.launch({
2607
+ this.browser = (await chromium.launch({
312
2608
  headless: this.headless,
313
- args: [
314
- "--no-sandbox",
315
- "--remote-allow-origins=*",
316
- "--disable-dev-shm-usage",
317
- "--disable-popup-blocking",
318
- "--enable-automation",
319
- "--ignore-ssl-errors",
320
- "--ignore-certificate-errors",
321
- "--ignore-certificate-errors-spki-list",
322
- "--disable-blink-features=AutomationControlled",
323
- ],
2609
+ args: this.getChromiumArgs(),
324
2610
  ...this.options,
2611
+ }));
2612
+ this.browser_context = await this.browser.newContext({
2613
+ userAgent: this.getUserAgent(),
2614
+ viewport: { width: 1536, height: 864 },
325
2615
  });
326
- this.browser_context = await this.browser.newContext();
327
2616
  }
328
2617
  // Anti-crawling detection website:
329
2618
  // https://bot.sannysoft.com/
330
- let init_script = await this.initScript();
331
- this.browser_context.addInitScript(init_script);
2619
+ // https://www.browserscan.net/
2620
+ chromium.use(StealthPlugin());
2621
+ const init_script = await this.initScript();
2622
+ if (init_script.content || init_script.path) {
2623
+ this.browser_context.addInitScript(init_script);
2624
+ }
2625
+ this.browser_context.on("page", async (page) => {
2626
+ page.on("framenavigated", async (frame) => {
2627
+ if (frame === page.mainFrame()) {
2628
+ const url = frame.url();
2629
+ if (url.startsWith("http")) {
2630
+ await this.autoLoadCookies(url);
2631
+ await this.autoLoadLocalStorage(page, url);
2632
+ }
2633
+ }
2634
+ });
2635
+ });
2636
+ }
2637
+ if (this.cookies) {
2638
+ this.browser_context?.addCookies(this.cookies);
332
2639
  }
333
2640
  return this.browser_context;
334
2641
  }
2642
+ async autoLoadCookies(url) {
2643
+ try {
2644
+ const cookies = await this.loadCookiesWithUrl(url);
2645
+ if (cookies && cookies.length > 0) {
2646
+ await this.browser_context?.clearCookies();
2647
+ await this.browser_context?.addCookies(cookies);
2648
+ }
2649
+ }
2650
+ catch (e) {
2651
+ Log.error("Failed to auto load cookies: " + url, e);
2652
+ }
2653
+ }
2654
+ async autoLoadLocalStorage(page, url) {
2655
+ try {
2656
+ const localStorageData = await this.loadLocalStorageWithUrl(url);
2657
+ await page.addInitScript((storage) => {
2658
+ try {
2659
+ for (const [key, value] of Object.entries(storage)) {
2660
+ localStorage.setItem(key, value);
2661
+ }
2662
+ }
2663
+ catch (e) {
2664
+ Log.error("Failed to inject localStorage: " + url, e);
2665
+ }
2666
+ }, localStorageData);
2667
+ }
2668
+ catch (e) {
2669
+ Log.error("Failed to auto load localStorage: " + url, e);
2670
+ }
2671
+ }
2672
+ async loadCookiesWithUrl(url) {
2673
+ return [];
2674
+ }
2675
+ async loadLocalStorageWithUrl(url) {
2676
+ return {};
2677
+ }
2678
+ getChromiumArgs() {
2679
+ return [
2680
+ "--no-sandbox",
2681
+ "--remote-allow-origins=*",
2682
+ "--disable-dev-shm-usage",
2683
+ "--disable-popup-blocking",
2684
+ "--ignore-ssl-errors",
2685
+ "--ignore-certificate-errors",
2686
+ "--ignore-certificate-errors-spki-list",
2687
+ "--disable-blink-features=AutomationControlled",
2688
+ "--disable-infobars",
2689
+ "--disable-notifications",
2690
+ "--disable-web-security",
2691
+ "--disable-features=IsolateOrigins,site-per-process",
2692
+ ];
2693
+ }
2694
+ getUserAgent() {
2695
+ // const userAgents = [
2696
+ // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
2697
+ // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
2698
+ // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
2699
+ // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
2700
+ // ];
2701
+ // return userAgents[Math.floor(Math.random() * userAgents.length)];
2702
+ return undefined;
2703
+ }
335
2704
  async initScript() {
336
- return {
337
- content: `
338
- // Webdriver property
339
- Object.defineProperty(navigator, 'webdriver', {
340
- get: () => undefined
341
- });
342
-
343
- // Languages
344
- Object.defineProperty(navigator, 'languages', {
345
- get: () => ['en-US']
346
- });
347
-
348
- // Plugins
349
- Object.defineProperty(navigator, 'plugins', {
350
- get: () => [{name:"1"}, {name:"2"}, {name:"3"}, {name:"4"}, {name:"5"}]
351
- });
352
-
353
- // Chrome runtime
354
- window.chrome = { runtime: {} };
355
-
356
- // Permissions
357
- const originalQuery = window.navigator.permissions.query;
358
- window.navigator.permissions.query = (parameters) => (
359
- parameters.name === 'notifications' ?
360
- Promise.resolve({ state: Notification.permission }) :
361
- originalQuery(parameters)
362
- );
363
- (function () {
364
- const originalAttachShadow = Element.prototype.attachShadow;
365
- Element.prototype.attachShadow = function attachShadow(options) {
366
- return originalAttachShadow.call(this, { ...options, mode: "open" });
367
- };
368
- })();
369
- `,
370
- };
2705
+ return {};
371
2706
  }
372
2707
  }
373
2708