@browserless.io/browserless 2.15.0 → 2.16.0-beta-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,7 +39,6 @@ If you've been struggling to deploy headless browsers without running into issue
39
39
  1. [Full documentation site](https://docs.browserless.io/)
40
40
  2. [Live Debugger (using browserless.io)](https://chrome.browserless.io/)
41
41
  3. [Docker](https://github.com/browserless/browserless/pkgs/container/base)
42
- 4. [Slack](https://join.slack.com/t/browserless/shared_invite/enQtMzA3OTMwNjA3MzY1LTRmMWU5NjQ0MTQ2YTE2YmU3MzdjNmVlMmU4MThjM2UxODNmNzNlZjVkY2U2NjdkMzYyNTgyZTBiMmE3Nzg0MzY)
43
42
 
44
43
  ## Features
45
44
 
@@ -54,14 +53,16 @@ If you've been struggling to deploy headless browsers without running into issue
54
53
  - Error tolerant: if Chrome dies it won't.
55
54
  - Support for running and development on Apple's M1 machines
56
55
 
57
- ### Cloud
56
+ ### Cloud-only
58
57
 
59
58
  Our [cloud accounts](https://www.browserless.io/pricing/) include all the general features plus extras, such as:
60
59
 
61
- - Inbuilt [residential proxy](https://www.browserless.io/blog/2023/09/24/residential-proxying/)
62
- - [/unblock API](https://www.browserless.io/blog/2024/02/26/unblock-api/) for avoiding detectors
63
- - [Hybrid automations](https://www.browserless.io/blog/2024/03/21/hybrid-automations-for-puppeteer/) for streaming login windows during scripts
64
- - Ability to upload and run extensions _(coming soon)_
60
+ - Inbuilt [residential proxy](https://www.browserless.io/blog/residential-proxying/)
61
+ - [/unblock API](https://www.browserless.io/blog/unblock-api) for avoiding detectors
62
+ - [Automated captcha solving](https://www.browserless.io/blog/captcha-solving) for getting past mandatory checks
63
+ - [Hybrid automations](https://www.browserless.io/blog/hybrid-automations-for-puppeteer/) for streaming login windows during scripts
64
+ - [/reconnect API](https://www.browserless.io/blog/reconnect-api) for keeping browsers alive for reuse
65
+ - [REST APIs](https://www.browserless.io/feature/rest-apis) for tasks such as retrieving HTML, PDFs or Lighthouse metrics
65
66
  - SSO, tokens and user roles
66
67
 
67
68
  ## How it works
@@ -29,7 +29,7 @@ export declare class ChromiumCDP extends EventEmitter {
29
29
  close(): Promise<void>;
30
30
  pages(): Promise<Page[]>;
31
31
  process(): import("child_process").ChildProcess | null;
32
- launch(laucherOpts: BrowserLauncherOptions): Promise<Browser>;
32
+ launch({ options, stealth, }: BrowserLauncherOptions): Promise<Browser>;
33
33
  wsEndpoint(): string | null;
34
34
  publicWSEndpoint(token: string | null): string | null;
35
35
  proxyPageWebSocket(req: Request, socket: Duplex, head: Buffer): Promise<void>;
@@ -1,8 +1,7 @@
1
- import { BLESS_PAGE_IDENTIFIER, ServerError, chromeExecutablePath, noop, once, } from '@browserless.io/browserless';
1
+ import { BLESS_PAGE_IDENTIFIER, ServerError, chromeExecutablePath, noop, once, ublockPath, } from '@browserless.io/browserless';
2
2
  import puppeteer from 'puppeteer-core';
3
3
  import { EventEmitter } from 'events';
4
4
  import StealthPlugin from 'puppeteer-extra-plugin-stealth';
5
- import { fileURLToPath } from 'url';
6
5
  import getPort from 'get-port';
7
6
  import httpProxy from 'http-proxy';
8
7
  import path from 'path';
@@ -111,11 +110,17 @@ export class ChromiumCDP extends EventEmitter {
111
110
  process() {
112
111
  return this.browser?.process() || null;
113
112
  }
114
- async launch(laucherOpts) {
115
- const { options, stealth } = laucherOpts;
113
+ async launch({ options, stealth, }) {
116
114
  this.port = await getPort();
117
115
  this.logger.info(`${this.constructor.name} got open port ${this.port}`);
118
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
116
+ const extensionLaunchArgs = options.args?.find((a) => a.startsWith('--load-extension'));
117
+ // Remove extension flags as we recompile them below with our own
118
+ options.args = options.args?.filter((a) => !a.startsWith('--load-extension') &&
119
+ !a.startsWith('--disable-extensions-except'));
120
+ const extensions = [
121
+ this.blockAds ? ublockPath : null,
122
+ extensionLaunchArgs ? extensionLaunchArgs.split('=')[1] : null,
123
+ ].filter((_) => !!_);
119
124
  const finalOptions = {
120
125
  ...options,
121
126
  args: [
@@ -126,11 +131,8 @@ export class ChromiumCDP extends EventEmitter {
126
131
  ].filter((_) => !!_),
127
132
  executablePath: this.executablePath,
128
133
  };
129
- if (this.blockAds) {
130
- // Necessary to load extensions
131
- finalOptions.headless = false;
132
- const loadExtensionPaths = path.join(__dirname, '..', '..', 'extensions', 'ublock');
133
- finalOptions.args.push('--load-extension=' + loadExtensionPaths, '--disable-extensions-except=' + loadExtensionPaths);
134
+ if (extensions.length) {
135
+ finalOptions.args.push('--load-extension=' + extensions.join(','), '--disable-extensions-except=' + extensions.join(','));
134
136
  }
135
137
  const launch = stealth
136
138
  ? puppeteerStealth.launch.bind(puppeteerStealth)
@@ -353,7 +353,7 @@ export class BrowserManager {
353
353
  options: launchOptions,
354
354
  pwVersion,
355
355
  req,
356
- stealth: launchOptions?.stealth
356
+ stealth: launchOptions?.stealth,
357
357
  });
358
358
  await this.hooks.browser({ browser, req });
359
359
  const session = {
@@ -91,6 +91,25 @@ describe(`Limiter`, () => {
91
91
  await wait;
92
92
  expect(handlerTwo.calledOnce).to.be.true;
93
93
  });
94
+ it('continues to process jobs even if an earlier job errors', (d) => {
95
+ const config = new Config();
96
+ const monitoring = new Monitoring(config);
97
+ const metrics = new Metrics();
98
+ config.setConcurrent(1);
99
+ config.setQueued(1);
100
+ config.setTimeout(-1);
101
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
102
+ const errorJob = () => Promise.reject(new Error('Danger, danger. High voltage!'));
103
+ const okJob = spy();
104
+ const jobOne = limiter.limit(errorJob, asyncNoop, asyncNoop, noop);
105
+ const jobTwo = limiter.limit(okJob, asyncNoop, asyncNoop, noop);
106
+ jobOne();
107
+ jobTwo();
108
+ limiter.addEventListener('end', () => {
109
+ expect(okJob.calledOnce).to.be.true;
110
+ d(undefined);
111
+ });
112
+ });
94
113
  it('bubbles up errors', async () => {
95
114
  const config = new Config();
96
115
  const monitoring = new Monitoring(config);
@@ -408,14 +408,14 @@
408
408
  "length": {
409
409
  "type": "number"
410
410
  },
411
- "__@toStringTag@32994": {
411
+ "__@toStringTag@11070": {
412
412
  "type": "string",
413
413
  "const": "Uint8Array"
414
414
  }
415
415
  },
416
416
  "required": [
417
417
  "BYTES_PER_ELEMENT",
418
- "__@toStringTag@32994",
418
+ "__@toStringTag@11070",
419
419
  "buffer",
420
420
  "byteLength",
421
421
  "byteOffset",
@@ -450,13 +450,13 @@
450
450
  "byteLength": {
451
451
  "type": "number"
452
452
  },
453
- "__@toStringTag@32994": {
453
+ "__@toStringTag@11070": {
454
454
  "type": "string"
455
455
  }
456
456
  },
457
457
  "additionalProperties": false,
458
458
  "required": [
459
- "__@toStringTag@32994",
459
+ "__@toStringTag@11070",
460
460
  "byteLength"
461
461
  ]
462
462
  },
@@ -466,18 +466,18 @@
466
466
  "byteLength": {
467
467
  "type": "number"
468
468
  },
469
- "__@species@33095": {
469
+ "__@species@11171": {
470
470
  "$ref": "#/definitions/SharedArrayBuffer"
471
471
  },
472
- "__@toStringTag@32994": {
472
+ "__@toStringTag@11070": {
473
473
  "type": "string",
474
474
  "const": "SharedArrayBuffer"
475
475
  }
476
476
  },
477
477
  "additionalProperties": false,
478
478
  "required": [
479
- "__@species@33095",
480
- "__@toStringTag@32994",
479
+ "__@species@11171",
480
+ "__@toStringTag@11070",
481
481
  "byteLength"
482
482
  ]
483
483
  },
@@ -455,14 +455,14 @@
455
455
  "length": {
456
456
  "type": "number"
457
457
  },
458
- "__@toStringTag@121462": {
458
+ "__@toStringTag@110224": {
459
459
  "type": "string",
460
460
  "const": "Uint8Array"
461
461
  }
462
462
  },
463
463
  "required": [
464
464
  "BYTES_PER_ELEMENT",
465
- "__@toStringTag@121462",
465
+ "__@toStringTag@110224",
466
466
  "buffer",
467
467
  "byteLength",
468
468
  "byteOffset",
@@ -497,13 +497,13 @@
497
497
  "byteLength": {
498
498
  "type": "number"
499
499
  },
500
- "__@toStringTag@121462": {
500
+ "__@toStringTag@110224": {
501
501
  "type": "string"
502
502
  }
503
503
  },
504
504
  "additionalProperties": false,
505
505
  "required": [
506
- "__@toStringTag@121462",
506
+ "__@toStringTag@110224",
507
507
  "byteLength"
508
508
  ]
509
509
  },
@@ -513,18 +513,18 @@
513
513
  "byteLength": {
514
514
  "type": "number"
515
515
  },
516
- "__@species@121563": {
516
+ "__@species@110325": {
517
517
  "$ref": "#/definitions/SharedArrayBuffer"
518
518
  },
519
- "__@toStringTag@121462": {
519
+ "__@toStringTag@110224": {
520
520
  "type": "string",
521
521
  "const": "SharedArrayBuffer"
522
522
  }
523
523
  },
524
524
  "additionalProperties": false,
525
525
  "required": [
526
- "__@species@121563",
527
- "__@toStringTag@121462",
526
+ "__@species@110325",
527
+ "__@toStringTag@110224",
528
528
  "byteLength"
529
529
  ]
530
530
  },
@@ -498,14 +498,14 @@
498
498
  "length": {
499
499
  "type": "number"
500
500
  },
501
- "__@toStringTag@110235": {
501
+ "__@toStringTag@121528": {
502
502
  "type": "string",
503
503
  "const": "Uint8Array"
504
504
  }
505
505
  },
506
506
  "required": [
507
507
  "BYTES_PER_ELEMENT",
508
- "__@toStringTag@110235",
508
+ "__@toStringTag@121528",
509
509
  "buffer",
510
510
  "byteLength",
511
511
  "byteOffset",
@@ -540,13 +540,13 @@
540
540
  "byteLength": {
541
541
  "type": "number"
542
542
  },
543
- "__@toStringTag@110235": {
543
+ "__@toStringTag@121528": {
544
544
  "type": "string"
545
545
  }
546
546
  },
547
547
  "additionalProperties": false,
548
548
  "required": [
549
- "__@toStringTag@110235",
549
+ "__@toStringTag@121528",
550
550
  "byteLength"
551
551
  ]
552
552
  },
@@ -556,18 +556,18 @@
556
556
  "byteLength": {
557
557
  "type": "number"
558
558
  },
559
- "__@species@110336": {
559
+ "__@species@121629": {
560
560
  "$ref": "#/definitions/SharedArrayBuffer"
561
561
  },
562
- "__@toStringTag@110235": {
562
+ "__@toStringTag@121528": {
563
563
  "type": "string",
564
564
  "const": "SharedArrayBuffer"
565
565
  }
566
566
  },
567
567
  "additionalProperties": false,
568
568
  "required": [
569
- "__@species@110336",
570
- "__@toStringTag@110235",
569
+ "__@species@121629",
570
+ "__@toStringTag@121528",
571
571
  "byteLength"
572
572
  ]
573
573
  },
@@ -549,14 +549,14 @@
549
549
  "length": {
550
550
  "type": "number"
551
551
  },
552
- "__@toStringTag@220645": {
552
+ "__@toStringTag@187784": {
553
553
  "type": "string",
554
554
  "const": "Uint8Array"
555
555
  }
556
556
  },
557
557
  "required": [
558
558
  "BYTES_PER_ELEMENT",
559
- "__@toStringTag@220645",
559
+ "__@toStringTag@187784",
560
560
  "buffer",
561
561
  "byteLength",
562
562
  "byteOffset",
@@ -591,13 +591,13 @@
591
591
  "byteLength": {
592
592
  "type": "number"
593
593
  },
594
- "__@toStringTag@220645": {
594
+ "__@toStringTag@187784": {
595
595
  "type": "string"
596
596
  }
597
597
  },
598
598
  "additionalProperties": false,
599
599
  "required": [
600
- "__@toStringTag@220645",
600
+ "__@toStringTag@187784",
601
601
  "byteLength"
602
602
  ]
603
603
  },
@@ -607,18 +607,18 @@
607
607
  "byteLength": {
608
608
  "type": "number"
609
609
  },
610
- "__@species@220746": {
610
+ "__@species@187885": {
611
611
  "$ref": "#/definitions/SharedArrayBuffer"
612
612
  },
613
- "__@toStringTag@220645": {
613
+ "__@toStringTag@187784": {
614
614
  "type": "string",
615
615
  "const": "SharedArrayBuffer"
616
616
  }
617
617
  },
618
618
  "additionalProperties": false,
619
619
  "required": [
620
- "__@species@220746",
621
- "__@toStringTag@220645",
620
+ "__@species@187885",
621
+ "__@toStringTag@187784",
622
622
  "byteLength"
623
623
  ]
624
624
  },
@@ -455,14 +455,14 @@
455
455
  "length": {
456
456
  "type": "number"
457
457
  },
458
- "__@toStringTag@254051": {
458
+ "__@toStringTag@231869": {
459
459
  "type": "string",
460
460
  "const": "Uint8Array"
461
461
  }
462
462
  },
463
463
  "required": [
464
464
  "BYTES_PER_ELEMENT",
465
- "__@toStringTag@254051",
465
+ "__@toStringTag@231869",
466
466
  "buffer",
467
467
  "byteLength",
468
468
  "byteOffset",
@@ -497,13 +497,13 @@
497
497
  "byteLength": {
498
498
  "type": "number"
499
499
  },
500
- "__@toStringTag@254051": {
500
+ "__@toStringTag@231869": {
501
501
  "type": "string"
502
502
  }
503
503
  },
504
504
  "additionalProperties": false,
505
505
  "required": [
506
- "__@toStringTag@254051",
506
+ "__@toStringTag@231869",
507
507
  "byteLength"
508
508
  ]
509
509
  },
@@ -513,18 +513,18 @@
513
513
  "byteLength": {
514
514
  "type": "number"
515
515
  },
516
- "__@species@254152": {
516
+ "__@species@231970": {
517
517
  "$ref": "#/definitions/SharedArrayBuffer"
518
518
  },
519
- "__@toStringTag@254051": {
519
+ "__@toStringTag@231869": {
520
520
  "type": "string",
521
521
  "const": "SharedArrayBuffer"
522
522
  }
523
523
  },
524
524
  "additionalProperties": false,
525
525
  "required": [
526
- "__@species@254152",
527
- "__@toStringTag@254051",
526
+ "__@species@231970",
527
+ "__@toStringTag@231869",
528
528
  "byteLength"
529
529
  ]
530
530
  },
@@ -498,14 +498,14 @@
498
498
  "length": {
499
499
  "type": "number"
500
500
  },
501
- "__@toStringTag@231880": {
501
+ "__@toStringTag@243170": {
502
502
  "type": "string",
503
503
  "const": "Uint8Array"
504
504
  }
505
505
  },
506
506
  "required": [
507
507
  "BYTES_PER_ELEMENT",
508
- "__@toStringTag@231880",
508
+ "__@toStringTag@243170",
509
509
  "buffer",
510
510
  "byteLength",
511
511
  "byteOffset",
@@ -540,13 +540,13 @@
540
540
  "byteLength": {
541
541
  "type": "number"
542
542
  },
543
- "__@toStringTag@231880": {
543
+ "__@toStringTag@243170": {
544
544
  "type": "string"
545
545
  }
546
546
  },
547
547
  "additionalProperties": false,
548
548
  "required": [
549
- "__@toStringTag@231880",
549
+ "__@toStringTag@243170",
550
550
  "byteLength"
551
551
  ]
552
552
  },
@@ -556,18 +556,18 @@
556
556
  "byteLength": {
557
557
  "type": "number"
558
558
  },
559
- "__@species@231981": {
559
+ "__@species@243271": {
560
560
  "$ref": "#/definitions/SharedArrayBuffer"
561
561
  },
562
- "__@toStringTag@231880": {
562
+ "__@toStringTag@243170": {
563
563
  "type": "string",
564
564
  "const": "SharedArrayBuffer"
565
565
  }
566
566
  },
567
567
  "additionalProperties": false,
568
568
  "required": [
569
- "__@species@231981",
570
- "__@toStringTag@231880",
569
+ "__@species@243271",
570
+ "__@toStringTag@243170",
571
571
  "byteLength"
572
572
  ]
573
573
  },
package/build/utils.d.ts CHANGED
@@ -128,4 +128,5 @@ export declare const fetchTimeout: (input: RequestInfo | URL, initWithTimeout?:
128
128
  export declare const untildify: (path: string) => string;
129
129
  export declare const printLogo: (docsLink: string, debugURL?: string | boolean) => string;
130
130
  export declare const getCDPClient: (page: Page) => CDPSession;
131
+ export declare const ublockPath: string;
131
132
  export {};
package/build/utils.js CHANGED
@@ -3,12 +3,14 @@ import { BLESS_PAGE_IDENTIFIER, ChromeCDP, ChromePlaywright, ChromiumCDP, Chromi
3
3
  import playwright from 'playwright-core';
4
4
  import crypto from 'crypto';
5
5
  import debug from 'debug';
6
+ import { fileURLToPath } from 'url';
6
7
  import gradient from 'gradient-string';
7
8
  import { homedir } from 'os';
8
9
  import path from 'path';
9
10
  const isHTTP = (writeable) => {
10
11
  return writeable.writeHead !== undefined;
11
12
  };
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
14
  const getAuthHeaderToken = (header) => {
13
15
  if (header.startsWith('Basic')) {
14
16
  const username = header.split(/\s+/).pop() || '';
@@ -332,7 +334,8 @@ export const availableBrowsers = Promise.all([
332
334
  return availableBrowsers;
333
335
  });
334
336
  export const queryParamsToObject = (params) => [...params.entries()].reduce((accum, [key, value]) => {
335
- accum[key] = value;
337
+ accum[key] =
338
+ value === '' || value === undefined || value === null ? true : value;
336
339
  return accum;
337
340
  }, {});
338
341
  // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -609,3 +612,4 @@ export const getCDPClient = (page) => {
609
612
  const c = page._client;
610
613
  return typeof c === 'function' ? c.call(page) : c;
611
614
  };
615
+ export const ublockPath = path.join(__dirname, '..', 'extensions', 'ublock');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserless.io/browserless",
3
- "version": "2.15.0",
3
+ "version": "2.16.0-beta-1",
4
4
  "license": "SSPL",
5
5
  "description": "The browserless platform",
6
6
  "author": "browserless.io",
@@ -51,7 +51,7 @@
51
51
  "debug": "^4.3.5",
52
52
  "del": "^7.0.0",
53
53
  "enjoi": "^9.0.1",
54
- "file-type": "^19.0.0",
54
+ "file-type": "^19.1.0",
55
55
  "get-port": "^7.1.0",
56
56
  "gradient-string": "^2.0.0",
57
57
  "http-proxy": "^1.18.1",
@@ -76,10 +76,10 @@
76
76
  "@types/http-proxy": "^1.17.14",
77
77
  "@types/micromatch": "^4.0.9",
78
78
  "@types/mocha": "^10.0.7",
79
- "@types/node": "^20.14.9",
79
+ "@types/node": "^20.14.10",
80
80
  "@types/sinon": "^17.0.3",
81
81
  "@typescript-eslint/eslint-plugin": "^7.15.0",
82
- "@typescript-eslint/parser": "^7.15.0",
82
+ "@typescript-eslint/parser": "^7.16.0",
83
83
  "assert": "^2.0.0",
84
84
  "chai": "^5.1.1",
85
85
  "cross-env": "^7.0.3",
@@ -90,7 +90,7 @@
90
90
  "eslint-plugin-typescript-sort-keys": "^3.2.0",
91
91
  "extract-zip": "^2.0.1",
92
92
  "gunzip-maybe": "^1.4.2",
93
- "marked": "^13.0.1",
93
+ "marked": "^13.0.2",
94
94
  "mocha": "^10.6.0",
95
95
  "move-file": "^3.1.0",
96
96
  "prettier": "^3.3.2",
@@ -12,7 +12,7 @@ const moduleMain = path.normalize(import.meta.url).endsWith(process.argv[1]);
12
12
 
13
13
  /**
14
14
  * Find an exported interface in a TypeScript AST
15
- *
15
+ *
16
16
  * @param {ts.Node} node The node to search for the exported interface
17
17
  * @param {string} interfaceName The name of the interface to search for
18
18
  * @returns {ts.InterfaceDeclaration | ts.Identifier | null}
@@ -29,7 +29,11 @@ const findExportedInterface = (node, interfaceName) => {
29
29
  }
30
30
 
31
31
  // Check for re-exported interfaces
32
- if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
32
+ if (
33
+ ts.isExportDeclaration(node) &&
34
+ node.exportClause &&
35
+ ts.isNamedExports(node.exportClause)
36
+ ) {
33
37
  const elements = node.exportClause.elements;
34
38
  for (const element of elements) {
35
39
  if (element.name.text === interfaceName) {
@@ -50,7 +54,7 @@ const findExportedInterface = (node, interfaceName) => {
50
54
 
51
55
  /**
52
56
  * Creates an standard JSON schema file for each route (see https://json-schema.org/specification)
53
- *
57
+ *
54
58
  * @param {string[]} externalHTTPRoutes Additional HTTP routes to parse
55
59
  * @param {string[]} externalWebSocketRoutes Additional WS routes to parse
56
60
  */
@@ -8,12 +8,12 @@ import {
8
8
  chromeExecutablePath,
9
9
  noop,
10
10
  once,
11
+ ublockPath,
11
12
  } from '@browserless.io/browserless';
12
13
  import puppeteer, { Browser, Page, Target } from 'puppeteer-core';
13
14
  import { Duplex } from 'stream';
14
15
  import { EventEmitter } from 'events';
15
16
  import StealthPlugin from 'puppeteer-extra-plugin-stealth';
16
- import { fileURLToPath } from 'url';
17
17
  import getPort from 'get-port';
18
18
  import httpProxy from 'http-proxy';
19
19
  import path from 'path';
@@ -170,11 +170,29 @@ export class ChromiumCDP extends EventEmitter {
170
170
  return this.browser?.process() || null;
171
171
  }
172
172
 
173
- public async launch(laucherOpts: BrowserLauncherOptions): Promise<Browser> {
174
- const { options, stealth } = laucherOpts;
173
+ public async launch({
174
+ options,
175
+ stealth,
176
+ }: BrowserLauncherOptions): Promise<Browser> {
175
177
  this.port = await getPort();
176
178
  this.logger.info(`${this.constructor.name} got open port ${this.port}`);
177
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
179
+
180
+ const extensionLaunchArgs = options.args?.find((a) =>
181
+ a.startsWith('--load-extension'),
182
+ );
183
+
184
+ // Remove extension flags as we recompile them below with our own
185
+ options.args = options.args?.filter(
186
+ (a) =>
187
+ !a.startsWith('--load-extension') &&
188
+ !a.startsWith('--disable-extensions-except'),
189
+ );
190
+
191
+ const extensions = [
192
+ this.blockAds ? ublockPath : null,
193
+ extensionLaunchArgs ? extensionLaunchArgs.split('=')[1] : null,
194
+ ].filter((_) => !!_);
195
+
178
196
  const finalOptions = {
179
197
  ...options,
180
198
  args: [
@@ -186,21 +204,10 @@ export class ChromiumCDP extends EventEmitter {
186
204
  executablePath: this.executablePath,
187
205
  };
188
206
 
189
- if (this.blockAds) {
190
- // Necessary to load extensions
191
- finalOptions.headless = false;
192
-
193
- const loadExtensionPaths: string = path.join(
194
- __dirname,
195
- '..',
196
- '..',
197
- 'extensions',
198
- 'ublock',
199
- );
200
-
207
+ if (extensions.length) {
201
208
  finalOptions.args.push(
202
- '--load-extension=' + loadExtensionPaths,
203
- '--disable-extensions-except=' + loadExtensionPaths,
209
+ '--load-extension=' + extensions.join(','),
210
+ '--disable-extensions-except=' + extensions.join(','),
204
211
  );
205
212
  }
206
213