@crawlee/browser 4.0.0-beta.99 → 4.0.0-rc.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.
@@ -1,6 +1,8 @@
1
1
  import { Configuration } from '@crawlee/basic';
2
- import type { BrowserPlugin, BrowserPluginOptions } from '@crawlee/browser-pool';
2
+ import type { BrowserPlugin, BrowserPluginOptions, BrowserPoolHooks, BrowserPoolOptions, RemoteBrowserPoolOptions } from '@crawlee/browser-pool';
3
+ import { BrowserPool, RemoteBrowserPool } from '@crawlee/browser-pool';
3
4
  import type { Constructor, Dictionary } from '@crawlee/types';
5
+ import { z } from 'zod';
4
6
  export interface BrowserLaunchContext<TOptions, Launcher> extends BrowserPluginOptions<TOptions> {
5
7
  /**
6
8
  * URL to an HTTP proxy server. It must define the port number,
@@ -64,11 +66,25 @@ export interface BrowserLaunchContext<TOptions, Launcher> extends BrowserPluginO
64
66
  */
65
67
  launcher?: Launcher;
66
68
  }
69
+ /**
70
+ * The {@link BrowserPool} options a launcher-built pool accepts: everything the pool itself takes except
71
+ * `browserPlugins`, which the launcher derives from its launch context. The hooks are deliberately unconstrained -
72
+ * the browser they run against is only known to the concrete `*BrowserPool()` factory, which is where the
73
+ * caller-facing types are pinned down.
74
+ */
75
+ type LauncherBrowserPoolOptions = Omit<BrowserPoolOptions, 'browserPlugins'> & {
76
+ [Hook in keyof BrowserPoolHooks<any, any, any>]?: readonly ((...args: any[]) => unknown)[];
77
+ };
78
+ /**
79
+ * The {@link RemoteBrowserPool} counterpart of {@link LauncherBrowserPoolOptions}.
80
+ */
81
+ type LauncherRemoteBrowserPoolOptions = Omit<RemoteBrowserPoolOptions, 'browserPlugins'>;
67
82
  /**
68
83
  * Abstract class for creating browser launchers, such as `PlaywrightLauncher` and `PuppeteerLauncher`.
69
84
  * @ignore
70
85
  */
71
86
  export declare abstract class BrowserLauncher<Plugin extends BrowserPlugin, Launcher = Plugin['library'], T extends Constructor<Plugin> = Constructor<Plugin>, LaunchOptions extends Dictionary<any> | undefined = Partial<Parameters<Plugin['launch']>[0]>, LaunchResult extends ReturnType<Plugin['launch']> = ReturnType<Plugin['launch']>> {
87
+ #private;
72
88
  readonly configuration: Configuration;
73
89
  launcher: Launcher;
74
90
  proxyUrl?: string;
@@ -77,24 +93,30 @@ export declare abstract class BrowserLauncher<Plugin extends BrowserPlugin, Laun
77
93
  otherLaunchContextProps: Dictionary;
78
94
  Plugin: T;
79
95
  userAgent?: string;
96
+ /**
97
+ * @internal
98
+ */
80
99
  protected static optionsShape: {
81
- // @ts-ignore optional peer dependency or compatibility with es2022
82
- proxyUrl: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
83
- // @ts-ignore optional peer dependency or compatibility with es2022
84
- useChrome: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
85
- // @ts-ignore optional peer dependency or compatibility with es2022
86
- useIncognitoPages: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
87
- // @ts-ignore optional peer dependency or compatibility with es2022
88
- browserPerProxy: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
89
- // @ts-ignore optional peer dependency or compatibility with es2022
90
- ignoreProxyCertificate: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
91
- // @ts-ignore optional peer dependency or compatibility with es2022
92
- userDataDir: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
93
- // @ts-ignore optional peer dependency or compatibility with es2022
94
- launchOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
95
- // @ts-ignore optional peer dependency or compatibility with es2022
96
- userAgent: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
100
+ proxyUrl: z.ZodOptional<z.ZodURL>;
101
+ useChrome: z.ZodOptional<z.ZodBoolean>;
102
+ useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
103
+ browserPerProxy: z.ZodOptional<z.ZodBoolean>;
104
+ ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
105
+ userDataDir: z.ZodOptional<z.ZodString>;
106
+ launchOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
107
+ userAgent: z.ZodOptional<z.ZodString>;
97
108
  };
109
+ /** @internal */
110
+ protected static optionsSchema: z.ZodObject<{
111
+ proxyUrl: z.ZodOptional<z.ZodURL>;
112
+ useChrome: z.ZodOptional<z.ZodBoolean>;
113
+ useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
114
+ browserPerProxy: z.ZodOptional<z.ZodBoolean>;
115
+ ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
116
+ userDataDir: z.ZodOptional<z.ZodString>;
117
+ launchOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
118
+ userAgent: z.ZodOptional<z.ZodString>;
119
+ }, z.core.$strict>;
98
120
  static requireLauncherOrThrow<T>(launcher: string, apifyImageName: string): T;
99
121
  /**
100
122
  * All `BrowserLauncher` parameters are passed via an launchContext object.
@@ -104,6 +126,21 @@ export declare abstract class BrowserLauncher<Plugin extends BrowserPlugin, Laun
104
126
  * @ignore
105
127
  */
106
128
  createBrowserPlugin(): Plugin;
129
+ /**
130
+ * Builds a {@link BrowserPool} running a single plugin for this launcher's browser. Shared body of the
131
+ * per-library `*BrowserPool()` factories, which exist so that configuring a pool never requires assembling
132
+ * a plugin by hand — and therefore never lets the plugin drift away from the crawler it is used with.
133
+ * @internal
134
+ */
135
+ createBrowserPool(options?: LauncherBrowserPoolOptions): BrowserPool<{
136
+ browserPlugins: [Plugin];
137
+ }, [Plugin]>;
138
+ /**
139
+ * The {@link RemoteBrowserPool} counterpart of {@link BrowserLauncher.createBrowserPool}: the launcher
140
+ * supplies the plugin, the caller supplies the remote connection details.
141
+ * @internal
142
+ */
143
+ createRemoteBrowserPool<Page>(options: LauncherRemoteBrowserPoolOptions): RemoteBrowserPool<Page>;
107
144
  /**
108
145
  * Launches a browser instance based on the plugin.
109
146
  * @returns Browser instance.
@@ -111,10 +148,5 @@ export declare abstract class BrowserLauncher<Plugin extends BrowserPlugin, Laun
111
148
  launch(): LaunchResult;
112
149
  createLaunchOptions(): Dictionary;
113
150
  protected getDefaultHeadlessOption(): boolean;
114
- private getChromeExecutablePath;
115
- /**
116
- * Gets a typical path to Chrome executable, depending on the current operating system.
117
- */
118
- private getTypicalChromeExecutablePath;
119
- private validateProxyUrlProtocol;
120
151
  }
152
+ export {};
@@ -1,8 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import os from 'node:os';
4
- import { Configuration } from '@crawlee/basic';
5
- import ow from 'ow';
4
+ import { Configuration, serviceLocator } from '@crawlee/basic';
5
+ import { BrowserPool, RemoteBrowserPool } from '@crawlee/browser-pool';
6
+ import { schemas } from '@crawlee/utils/internal';
7
+ import { z } from 'zod';
6
8
  const DEFAULT_VIEWPORT = {
7
9
  width: 1366,
8
10
  height: 768,
@@ -22,16 +24,21 @@ export class BrowserLauncher {
22
24
  // to be provided by child classes;
23
25
  Plugin;
24
26
  userAgent;
27
+ /**
28
+ * @internal
29
+ */
25
30
  static optionsShape = {
26
- proxyUrl: ow.optional.string.url,
27
- useChrome: ow.optional.boolean,
28
- useIncognitoPages: ow.optional.boolean,
29
- browserPerProxy: ow.optional.boolean,
30
- ignoreProxyCertificate: ow.optional.boolean,
31
- userDataDir: ow.optional.string,
32
- launchOptions: ow.optional.object,
33
- userAgent: ow.optional.string,
31
+ proxyUrl: z.url().optional(),
32
+ useChrome: z.boolean().optional(),
33
+ useIncognitoPages: z.boolean().optional(),
34
+ browserPerProxy: z.boolean().optional(),
35
+ ignoreProxyCertificate: z.boolean().optional(),
36
+ userDataDir: z.string().optional(),
37
+ launchOptions: schemas.anyObject.optional(),
38
+ userAgent: z.string().optional(),
34
39
  };
40
+ /** @internal */
41
+ static optionsSchema = z.strictObject(BrowserLauncher.optionsShape);
35
42
  static requireLauncherOrThrow(launcher, apifyImageName) {
36
43
  try {
37
44
  return require(launcher); // eslint-disable-line
@@ -54,7 +61,7 @@ export class BrowserLauncher {
54
61
  constructor(launchContext, configuration = Configuration.getGlobalConfiguration()) {
55
62
  this.configuration = configuration;
56
63
  const { launcher, proxyUrl, useChrome, userAgent, launchOptions = {}, ...otherLaunchContextProps } = launchContext;
57
- this.validateProxyUrlProtocol(proxyUrl);
64
+ this.#validateProxyUrlProtocol(proxyUrl);
58
65
  // those need to be reassigned otherwise they are {} in types
59
66
  this.launcher = launcher;
60
67
  this.proxyUrl = proxyUrl;
@@ -73,6 +80,49 @@ export class BrowserLauncher {
73
80
  ...this.otherLaunchContextProps,
74
81
  });
75
82
  }
83
+ /**
84
+ * Builds a {@link BrowserPool} running a single plugin for this launcher's browser. Shared body of the
85
+ * per-library `*BrowserPool()` factories, which exist so that configuring a pool never requires assembling
86
+ * a plugin by hand — and therefore never lets the plugin drift away from the crawler it is used with.
87
+ * @internal
88
+ */
89
+ createBrowserPool(options = {}) {
90
+ // The hook types `BrowserPool` derives from `Plugin` are unresolvable while `Plugin` is still a free type
91
+ // parameter, so the argument cannot be checked here. The concrete `*BrowserPool()` factories are where the
92
+ // caller-facing hook types get pinned down.
93
+ return new BrowserPool({
94
+ ...this.#resolveFingerprinting(options),
95
+ browserPlugins: [this.createBrowserPlugin()],
96
+ });
97
+ }
98
+ /**
99
+ * The {@link RemoteBrowserPool} counterpart of {@link BrowserLauncher.createBrowserPool}: the launcher
100
+ * supplies the plugin, the caller supplies the remote connection details.
101
+ * @internal
102
+ */
103
+ createRemoteBrowserPool(options) {
104
+ return new RemoteBrowserPool({
105
+ ...options,
106
+ browserPlugins: [this.createBrowserPlugin()],
107
+ browserPoolOptions: this.#resolveFingerprinting(options.browserPoolOptions ?? {}),
108
+ });
109
+ }
110
+ /**
111
+ * A custom `userAgent` and Crawlee's fingerprint injection would both write the same headers, so an
112
+ * explicitly requested user agent wins.
113
+ */
114
+ #resolveFingerprinting(options) {
115
+ if (!this.userAgent) {
116
+ return options;
117
+ }
118
+ if (options.useFingerprints) {
119
+ serviceLocator
120
+ .getLogger()
121
+ .child({ prefix: 'BrowserLauncher' })
122
+ .info('Custom user agent provided, disabling automatic browser fingerprint injection!');
123
+ }
124
+ return { ...options, useFingerprints: false };
125
+ }
76
126
  /**
77
127
  * Launches a browser instance based on the plugin.
78
128
  * @returns Browser instance.
@@ -98,20 +148,20 @@ export class BrowserLauncher {
98
148
  launchOptions.headless = this.getDefaultHeadlessOption();
99
149
  }
100
150
  if (this.useChrome && !launchOptions.executablePath) {
101
- launchOptions.executablePath = this.getChromeExecutablePath();
151
+ launchOptions.executablePath = this.#getChromeExecutablePath();
102
152
  }
103
153
  return launchOptions;
104
154
  }
105
155
  getDefaultHeadlessOption() {
106
156
  return this.configuration.headless && !this.configuration.xvfb;
107
157
  }
108
- getChromeExecutablePath() {
109
- return this.configuration.chromeExecutablePath ?? this.getTypicalChromeExecutablePath();
158
+ #getChromeExecutablePath() {
159
+ return this.configuration.chromeExecutablePath ?? this.#getTypicalChromeExecutablePath();
110
160
  }
111
161
  /**
112
162
  * Gets a typical path to Chrome executable, depending on the current operating system.
113
163
  */
114
- getTypicalChromeExecutablePath() {
164
+ #getTypicalChromeExecutablePath() {
115
165
  /**
116
166
  * Returns path of Chrome executable by its OS environment variable to deal with non-english language OS.
117
167
  * Taking also into account the old [chrome 380177 issue](https://bugs.chromium.org/p/chromium/issues/detail?id=380177).
@@ -139,7 +189,7 @@ export class BrowserLauncher {
139
189
  return '/usr/bin/google-chrome';
140
190
  }
141
191
  }
142
- validateProxyUrlProtocol(proxyUrl) {
192
+ #validateProxyUrlProtocol(proxyUrl) {
143
193
  if (!proxyUrl)
144
194
  return;
145
195
  if (!/^(http|https|socks4|socks5)/i.test(proxyUrl)) {
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@crawlee/browser",
3
- "version": "4.0.0-beta.99",
3
+ "version": "4.0.0-rc.1",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
- "node": ">=22.0.0"
6
+ "node": ">=22.13.0"
7
7
  },
8
8
  "type": "module",
9
9
  "exports": {
@@ -47,14 +47,13 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@apify/timeout": "^0.4.4",
51
- "@crawlee/basic": "4.0.0-beta.99",
52
- "@crawlee/browser-pool": "4.0.0-beta.99",
53
- "@crawlee/types": "4.0.0-beta.99",
54
- "@crawlee/utils": "4.0.0-beta.99",
55
- "ow": "^2.0.0",
50
+ "@apify/timeout": "^1.0.1",
51
+ "@crawlee/basic": "4.0.0-rc.1",
52
+ "@crawlee/browser-pool": "4.0.0-rc.1",
53
+ "@crawlee/types": "4.0.0-rc.1",
54
+ "@crawlee/utils": "4.0.0-rc.1",
56
55
  "tslib": "^2.8.1",
57
- "type-fest": "^4.41.0"
56
+ "zod": "^4.5.4"
58
57
  },
59
58
  "peerDependencies": {
60
59
  "playwright": "*",
@@ -75,5 +74,5 @@
75
74
  }
76
75
  }
77
76
  },
78
- "gitHead": "ad2748380941842bb10cff100f4b4caad92049e3"
77
+ "gitHead": "f354ca5e943bed1a5c657a1fce1974c053f9fff0"
79
78
  }