@crawlee/browser 4.0.0-beta.81 → 4.0.0-beta.83

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.
@@ -31,8 +31,8 @@ export interface BrowserCrawlingContext<Page extends CommonPage = CommonPage, Re
31
31
  */
32
32
  enqueueLinks: (options?: EnqueueLinksOptions) => Promise<BatchAddRequestsResult>;
33
33
  }
34
- export type BrowserHook<Context = BrowserCrawlingContext> = (crawlingContext: Context) => Awaitable<void | Partial<Context>>;
35
- export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Response extends BaseResponse = BaseResponse, Context extends BrowserCrawlingContext<Page, Response, Dictionary> = BrowserCrawlingContext<Page, Response, Dictionary>, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, InternalBrowserPoolOptions extends BrowserPoolOptions = BrowserPoolOptions, __BrowserPlugins extends BrowserPlugin[] = InferBrowserPluginArray<InternalBrowserPoolOptions['browserPlugins']>, __BrowserControllerReturn extends BrowserController = ReturnType<__BrowserPlugins[number]['createController']>, __LaunchContextReturn extends LaunchContext = ReturnType<__BrowserPlugins[number]['createLaunchContext']>> extends Omit<BasicCrawlerOptions<Context, ExtendedContext>, 'requestHandler' | 'failedRequestHandler' | 'errorHandler'> {
34
+ export type BrowserHook<Context = BrowserCrawlingContext, ContextExtension = {}> = (crawlingContext: Context & ContextExtension) => Awaitable<void | Partial<Context>>;
35
+ export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Response extends BaseResponse = BaseResponse, Context extends BrowserCrawlingContext<Page, Response, Dictionary> = BrowserCrawlingContext<Page, Response, Dictionary>, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, InternalBrowserPoolOptions extends BrowserPoolOptions = BrowserPoolOptions, __BrowserPlugins extends BrowserPlugin[] = InferBrowserPluginArray<InternalBrowserPoolOptions['browserPlugins']>, __BrowserControllerReturn extends BrowserController = ReturnType<__BrowserPlugins[number]['createController']>, __LaunchContextReturn extends LaunchContext = ReturnType<__BrowserPlugins[number]['createLaunchContext']>> extends Omit<BasicCrawlerOptions<Context, ContextExtension, ExtendedContext>, 'requestHandler' | 'failedRequestHandler' | 'errorHandler'> {
36
36
  launchContext?: BrowserLaunchContext<any, any>;
37
37
  /**
38
38
  * An existing browser pool instance to use. When provided, the crawler will use this pool directly instead of
@@ -124,8 +124,13 @@ export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Res
124
124
  *
125
125
  * A hook may optionally return a partial object whose properties are merged into the crawling context,
126
126
  * allowing the hook to override context members for subsequent hooks and pipeline stages.
127
+ *
128
+ * The context is built up in the following order: base context (`request`, `session`, helpers, ...) ->
129
+ * `extendContext` -> `preNavigationHooks` -> navigation -> `postNavigationHooks` -> `requestHandler`.
130
+ * This means the members added by `extendContext` are already available here, but navigation-dependent
131
+ * members (e.g. `page`, `response`) are not.
127
132
  */
128
- preNavigationHooks?: BrowserHook<Context>[];
133
+ preNavigationHooks?: BrowserHook<Context, ContextExtension>[];
129
134
  /**
130
135
  * Async functions that are sequentially evaluated after the navigation. Good for checking if the navigation was successful.
131
136
  * The function accepts `crawlingContext` as the only parameter.
@@ -151,7 +156,7 @@ export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Res
151
156
  * ]
152
157
  * ```
153
158
  */
154
- postNavigationHooks?: BrowserHook<Context>[];
159
+ postNavigationHooks?: BrowserHook<Context, ContextExtension>[];
155
160
  /**
156
161
  * Timeout in which page navigation needs to finish, in seconds.
157
162
  */
@@ -219,17 +224,13 @@ export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Res
219
224
  * @category Crawlers
220
225
  */
221
226
  export declare abstract class BrowserCrawler<Page extends CommonPage = CommonPage, Response extends BaseResponse = BaseResponse, InternalBrowserPoolOptions extends BrowserPoolOptions = BrowserPoolOptions, LaunchOptions extends Dictionary | undefined = Dictionary, Context extends BrowserCrawlingContext<Page, Response, Dictionary> = BrowserCrawlingContext<Page, Response, Dictionary>, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, GoToOptions extends Dictionary = Dictionary> extends BasicCrawler<Context, ContextExtension, ExtendedContext> {
227
+ /** Backs the {@link BrowserCrawler.browserPool|`browserPool`} getter. */
228
+ private browserPoolDep;
222
229
  /**
223
230
  * A reference to the underlying browser pool that manages the crawler's browsers. Typed as
224
231
  * {@link IBrowserPool} so custom implementations can be plugged in via the `browserPool` constructor option.
225
232
  */
226
- browserPool: IBrowserPool<Page>;
227
- /**
228
- * Set when the crawler constructed its own pool (a {@link BrowserPool}, or a {@link RemoteBrowserPool}
229
- * built from the `remoteBrowser` option). Holds the same instance as `browserPool` but is the only reference
230
- * the crawler tears down — a user-supplied `browserPool` is never owned and never destroyed by the crawler.
231
- */
232
- private ownedBrowserPool?;
233
+ get browserPool(): IBrowserPool<Page>;
233
234
  launchContext: BrowserLaunchContext<LaunchOptions, unknown>;
234
235
  protected readonly ignoreShadowRoots: boolean;
235
236
  protected readonly ignoreIframes: boolean;
@@ -1,4 +1,4 @@
1
- import { BasicCrawler, browserPoolCookieToToughCookie, ContextPipeline, cookieStringToToughCookie, enqueueLinks, handleRequestTimeout, NavigationSkippedError, RequestState, resolveBaseUrlForEnqueueLinksFiltering, SessionError, toughCookieToBrowserPoolCookie, tryAbsoluteURL, validators, } from '@crawlee/basic';
1
+ import { BasicCrawler, browserPoolCookieToToughCookie, ContextPipeline, cookieStringToToughCookie, enqueueLinks, handleRequestTimeout, NavigationSkippedError, OwnedOrInjected, RequestState, resolveBaseUrlForEnqueueLinksFiltering, SessionError, toughCookieToBrowserPoolCookie, tryAbsoluteURL, validators, } from '@crawlee/basic';
2
2
  import { BrowserPool, RemoteBrowserPool } from '@crawlee/browser-pool';
3
3
  import { CLOUDFLARE_RETRY_CSS_SELECTORS, RETRY_CSS_SELECTORS, sleep } from '@crawlee/utils';
4
4
  import ow from 'ow';
@@ -48,17 +48,15 @@ const readContextField = (ctx, key) => ctx[key];
48
48
  * @category Crawlers
49
49
  */
50
50
  export class BrowserCrawler extends BasicCrawler {
51
+ /** Backs the {@link BrowserCrawler.browserPool|`browserPool`} getter. */
52
+ browserPoolDep;
51
53
  /**
52
54
  * A reference to the underlying browser pool that manages the crawler's browsers. Typed as
53
55
  * {@link IBrowserPool} so custom implementations can be plugged in via the `browserPool` constructor option.
54
56
  */
55
- browserPool;
56
- /**
57
- * Set when the crawler constructed its own pool (a {@link BrowserPool}, or a {@link RemoteBrowserPool}
58
- * built from the `remoteBrowser` option). Holds the same instance as `browserPool` but is the only reference
59
- * the crawler tears down — a user-supplied `browserPool` is never owned and never destroyed by the crawler.
60
- */
61
- ownedBrowserPool;
57
+ get browserPool() {
58
+ return this.browserPoolDep.value;
59
+ }
62
60
  launchContext;
63
61
  ignoreShadowRoots;
64
62
  ignoreIframes;
@@ -104,10 +102,13 @@ export class BrowserCrawler extends BasicCrawler {
104
102
  .compose({ action: this.handleBlockedRequestByContent.bind(this) })
105
103
  .compose({ action: this.restoreRequestState.bind(this) });
106
104
  },
107
- extendContext: extendContext,
105
+ extendContext,
108
106
  });
109
107
  this.launchContext = launchContext;
110
108
  this.navigationTimeoutMillis = navigationTimeoutSecs * 1000;
109
+ // The public option hooks are extension-aware; internal storage uses the base context type
110
+ // (the pipeline composes hooks against the concrete context, which does not statically carry
111
+ // `ContextExtension`). The extension members are present at runtime regardless.
111
112
  this.preNavigationHooks = preNavigationHooks;
112
113
  this.postNavigationHooks = postNavigationHooks;
113
114
  this.ignoreIframes = ignoreIframes;
@@ -117,35 +118,34 @@ export class BrowserCrawler extends BasicCrawler {
117
118
  this.launchContext.launchOptions.headless = headless;
118
119
  }
119
120
  this.saveResponseCookies = saveResponseCookies;
120
- // `browserPool` wins over `remoteBrowser` — a passed-in pool is used as-is, the sugar is ignored.
121
- if (browserPool) {
122
- this.browserPool = browserPool;
123
- return;
124
- }
125
- const resolvedBrowserPoolOptions = browserPoolOptions ?? {};
126
- if (launchContext?.userAgent) {
127
- if (resolvedBrowserPoolOptions.useFingerprints)
128
- this.log.info('Custom user agent provided, disabling automatic browser fingerprint injection!');
129
- resolvedBrowserPoolOptions.useFingerprints = false;
130
- }
131
- if (remoteBrowser) {
132
- // The crawler already built the right plugin for its browser hand it to a RemoteBrowserPool so the
133
- // remote connection is always for the matching browser (no plugin to construct, no way to mismatch).
134
- const { browserPlugins, ...remoteBrowserPoolOptions } = resolvedBrowserPoolOptions;
135
- const remotePool = new RemoteBrowserPool({
136
- browserPlugins: browserPlugins,
137
- ...remoteBrowser,
138
- browserPoolOptions: remoteBrowserPoolOptions,
121
+ // `browserPool` wins over `remoteBrowser` — a passed-in pool is used as-is (borrowed), the sugar is ignored.
122
+ // The default is only built when no pool was injected, so all the option/launchContext fiddling below stays
123
+ // inside the factory.
124
+ this.browserPoolDep = OwnedOrInjected.resolve(browserPool, () => {
125
+ const resolvedBrowserPoolOptions = browserPoolOptions ?? {};
126
+ if (launchContext?.userAgent) {
127
+ if (resolvedBrowserPoolOptions.useFingerprints)
128
+ this.log.info('Custom user agent provided, disabling automatic browser fingerprint injection!');
129
+ resolvedBrowserPoolOptions.useFingerprints = false;
130
+ }
131
+ if (remoteBrowser) {
132
+ // The crawler already built the right plugin for its browser — hand it to a RemoteBrowserPool so the
133
+ // remote connection is always for the matching browser (no plugin to construct, no way to mismatch).
134
+ const { browserPlugins, ...remoteBrowserPoolOptions } = resolvedBrowserPoolOptions;
135
+ return new RemoteBrowserPool({
136
+ browserPlugins: browserPlugins,
137
+ ...remoteBrowser,
138
+ browserPoolOptions: remoteBrowserPoolOptions,
139
+ });
140
+ }
141
+ // Double cast: `BrowserPool` implements `IBrowserPool<PageReturn>`, where `PageReturn` is derived from the
142
+ // plugin/controller generics and doesn't overlap with the crawler's free `Page` type param, so TS won't
143
+ // narrow it directly. The concrete pool does satisfy the `Page`/`destroy` contract at runtime — this is the
144
+ // long-standing `Page` variance gap, not a `destroy`-related hole.
145
+ return new BrowserPool({
146
+ ...resolvedBrowserPoolOptions,
139
147
  });
140
- this.ownedBrowserPool = remotePool;
141
- this.browserPool = remotePool;
142
- return;
143
- }
144
- const ownedBrowserPool = new BrowserPool({
145
- ...resolvedBrowserPoolOptions,
146
148
  });
147
- this.ownedBrowserPool = ownedBrowserPool;
148
- this.browserPool = ownedBrowserPool;
149
149
  }
150
150
  buildContextPipeline() {
151
151
  return ContextPipeline.create().compose({
@@ -360,7 +360,7 @@ export class BrowserCrawler extends BasicCrawler {
360
360
  * @ignore
361
361
  */
362
362
  async teardown() {
363
- await this.ownedBrowserPool?.destroy();
363
+ await this.browserPoolDep.ifOwned((pool) => pool.destroy());
364
364
  await super.teardown();
365
365
  }
366
366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/browser",
3
- "version": "4.0.0-beta.81",
3
+ "version": "4.0.0-beta.83",
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
6
  "node": ">=22.0.0"
@@ -48,10 +48,10 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@apify/timeout": "^0.3.2",
51
- "@crawlee/basic": "4.0.0-beta.81",
52
- "@crawlee/browser-pool": "4.0.0-beta.81",
53
- "@crawlee/types": "4.0.0-beta.81",
54
- "@crawlee/utils": "4.0.0-beta.81",
51
+ "@crawlee/basic": "4.0.0-beta.83",
52
+ "@crawlee/browser-pool": "4.0.0-beta.83",
53
+ "@crawlee/types": "4.0.0-beta.83",
54
+ "@crawlee/utils": "4.0.0-beta.83",
55
55
  "ow": "^2.0.0",
56
56
  "tslib": "^2.8.1",
57
57
  "type-fest": "^4.41.0"
@@ -75,5 +75,5 @@
75
75
  }
76
76
  }
77
77
  },
78
- "gitHead": "80dc6b4fc82237e63a51a71153809ec8dfd0cc50"
78
+ "gitHead": "5f25b90c4914d7e097732cf761e440aeba799137"
79
79
  }