@crawlee/http 4.0.0-beta.105 → 4.0.0-beta.106

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.
@@ -242,14 +242,8 @@ JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData,
242
242
  * @category Crawlers
243
243
  */
244
244
  export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends BasicCrawler<Context, ContextExtension, ExtendedContext, Routes> {
245
- private preNavigationHooks;
246
- private postNavigationHooks;
247
- private saveResponseCookies;
248
- private navigationTimeoutMillis;
245
+ #private;
249
246
  private ignoreSslErrors;
250
- private suggestResponseEncoding?;
251
- private forceResponseEncoding?;
252
- private readonly supportedMimeTypes;
253
247
  protected static optionsShape: {
254
248
  // @ts-ignore optional peer dependency or compatibility with es2022
255
249
  navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
@@ -381,11 +375,11 @@ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any
381
375
  * Handles timeout request
382
376
  */
383
377
  private handleRequestTimeout;
384
- private _abortDownloadOfBody;
378
+ private abortDownloadOfBody;
385
379
  /**
386
380
  * @internal wraps public utility for mocking purposes
387
381
  */
388
- private _requestAsBrowser;
382
+ private requestAsBrowser;
389
383
  }
390
384
  /**
391
385
  * Creates new {@link Router} instance that works based on request labels.
@@ -109,14 +109,15 @@ export class HttpCrawler extends BasicCrawler {
109
109
  // extension-aware for consumer DX, but internally the pipeline composes hooks against the
110
110
  // concrete crawling context, which does not statically carry `ContextExtension`. The members
111
111
  // added by `extendContext` are present at runtime regardless.
112
- preNavigationHooks;
113
- postNavigationHooks;
114
- saveResponseCookies;
115
- navigationTimeoutMillis;
112
+ #preNavigationHooks;
113
+ #postNavigationHooks;
114
+ #saveResponseCookies;
115
+ #navigationTimeoutMillis;
116
+ // kept as TS-private: tests read it at runtime
116
117
  ignoreSslErrors;
117
- suggestResponseEncoding;
118
- forceResponseEncoding;
119
- supportedMimeTypes;
118
+ #suggestResponseEncoding;
119
+ #forceResponseEncoding;
120
+ #supportedMimeTypes;
120
121
  static optionsShape = {
121
122
  ...BasicCrawler.optionsShape,
122
123
  navigationTimeoutSecs: ow.optional.number,
@@ -141,28 +142,28 @@ export class HttpCrawler extends BasicCrawler {
141
142
  contextPipelineBuilder: contextPipelineBuilder ??
142
143
  (() => this.buildContextPipeline()),
143
144
  });
144
- this.supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
145
+ this.#supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
145
146
  if (additionalMimeTypes.length)
146
147
  this.extendSupportedMimeTypes(additionalMimeTypes);
147
148
  if (suggestResponseEncoding && forceResponseEncoding) {
148
149
  this.log.warning('Both forceResponseEncoding and suggestResponseEncoding options are set. Using forceResponseEncoding.');
149
150
  }
150
- this.navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
+ this.#navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
152
  this.ignoreSslErrors = ignoreSslErrors;
152
- this.suggestResponseEncoding = suggestResponseEncoding;
153
- this.forceResponseEncoding = forceResponseEncoding;
153
+ this.#suggestResponseEncoding = suggestResponseEncoding;
154
+ this.#forceResponseEncoding = forceResponseEncoding;
154
155
  // Cast away the extension-aware option types to the base internal storage types (see the field
155
156
  // declarations above). This is sound - the hooks only ever receive the base context plus the
156
157
  // members `extendContext` added at runtime.
157
- this.preNavigationHooks = preNavigationHooks;
158
- this.postNavigationHooks = [
159
- ({ request, response }) => this._abortDownloadOfBody(request, response),
158
+ this.#preNavigationHooks = preNavigationHooks;
159
+ this.#postNavigationHooks = [
160
+ ({ request, response }) => this.abortDownloadOfBody(request, response),
160
161
  ...postNavigationHooks,
161
162
  ];
162
- this.saveResponseCookies = saveResponseCookies;
163
+ this.#saveResponseCookies = saveResponseCookies;
163
164
  }
164
165
  getNavigationTimeoutMillis() {
165
- return this.navigationTimeoutMillis;
166
+ return this.#navigationTimeoutMillis;
166
167
  }
167
168
  /**
168
169
  * Folds {@link HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS} into the default system, keeping the user's
@@ -185,9 +186,9 @@ export class HttpCrawler extends BasicCrawler {
185
186
  // A single navigation window covers the pre-navigation hooks, the navigation, and the post-navigation
186
187
  // hooks: the whole phase shares one `navigationTimeoutSecs` budget, so a slow hook eats into the same
187
188
  // window the navigation uses instead of each step being timed on its own.
188
- const navigationTimedOut = `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`;
189
+ const navigationTimedOut = `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`;
189
190
  const windowGuard = (step) => skipGuard(async (ctx) => {
190
- const remaining = remainingNavigationWindowMillis(ctx, this.navigationTimeoutMillis);
191
+ const remaining = remainingNavigationWindowMillis(ctx, this.#navigationTimeoutMillis);
191
192
  if (remaining <= 0) {
192
193
  throw new TimeoutError(navigationTimedOut);
193
194
  }
@@ -196,11 +197,11 @@ export class HttpCrawler extends BasicCrawler {
196
197
  let pipeline = ContextPipeline.create().compose({
197
198
  action: this.prepareHttpRequest.bind(this),
198
199
  });
199
- for (const hook of this.preNavigationHooks) {
200
+ for (const hook of this.#preNavigationHooks) {
200
201
  pipeline = pipeline.compose(windowGuard(hook));
201
202
  }
202
203
  let pipelineWithNavigation = pipeline.compose(skipGuard(this.makeHttpRequest.bind(this)));
203
- for (const hook of this.postNavigationHooks) {
204
+ for (const hook of this.#postNavigationHooks) {
204
205
  pipelineWithNavigation = pipelineWithNavigation.compose(windowGuard(hook));
205
206
  }
206
207
  return pipelineWithNavigation
@@ -234,7 +235,7 @@ export class HttpCrawler extends BasicCrawler {
234
235
  // Bound the request by whatever is left of the shared navigation window (the pre-navigation hooks may
235
236
  // have already spent part of it), so it produces a clean navigation-timeout error rather than the raw
236
237
  // client abort.
237
- const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis)), `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
238
+ const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis)), `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
238
239
  tryCancel();
239
240
  request.loadedUrl = httpResponse?.url;
240
241
  request.state = RequestState.AFTER_NAV;
@@ -264,11 +265,11 @@ export class HttpCrawler extends BasicCrawler {
264
265
  // Reading the body is still part of the navigation, so it draws from the same shared window: on a server
265
266
  // that streams the body slowly the request completes (headers arrive) but the body read would otherwise
266
267
  // run unbounded. `extendTimeout` from a post-navigation hook has already pushed this deadline out if asked.
267
- const remaining = remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis);
268
+ const remaining = remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis);
268
269
  if (remaining <= 0) {
269
- throw new TimeoutError(`Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
270
+ throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
270
271
  }
271
- const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
272
+ const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
272
273
  tryCancel();
273
274
  const response = parsed.response;
274
275
  const contentType = parsed.contentType;
@@ -287,8 +288,8 @@ export class HttpCrawler extends BasicCrawler {
287
288
  }
288
289
  return $;
289
290
  };
290
- this._throwOnBlockedRequest(response.status);
291
- if (this.saveResponseCookies) {
291
+ this.throwOnBlockedRequest(response.status);
292
+ if (this.#saveResponseCookies) {
292
293
  try {
293
294
  for (const cookie of getCookiesFromResponse(response)) {
294
295
  if (!cookie)
@@ -347,7 +348,7 @@ export class HttpCrawler extends BasicCrawler {
347
348
  async requestFunction({ request, session, proxyUrl }) {
348
349
  const opts = this.getRequestOptions(request, session, proxyUrl);
349
350
  try {
350
- return await this._requestAsBrowser(opts, session);
351
+ return await this.requestAsBrowser(opts, session);
351
352
  }
352
353
  catch (e) {
353
354
  if (e instanceof Error && e.constructor.name === 'TimeoutError') {
@@ -355,7 +356,7 @@ export class HttpCrawler extends BasicCrawler {
355
356
  return new Response(); // this will never happen, as handleRequestTimeout always throws
356
357
  }
357
358
  if (this.isProxyError(e)) {
358
- throw new SessionError(this._getMessageFromError(e));
359
+ throw new SessionError(this.getMessageFromError(e));
359
360
  }
360
361
  else {
361
362
  throw e;
@@ -391,10 +392,10 @@ export class HttpCrawler extends BasicCrawler {
391
392
  throw new Error(`${status} - Internal Server Error: ${body.slice(0, 100)}`);
392
393
  }
393
394
  else if (HTML_AND_XML_MIME_TYPES.includes(type)) {
394
- if (!charset && !this.forceResponseEncoding) {
395
+ if (!charset && !this.#forceResponseEncoding) {
395
396
  const rawBytes = Buffer.from(await response.arrayBuffer());
396
397
  const metaCharset = extractCharsetFromHtmlBytes(rawBytes);
397
- const charsetToUse = metaCharset ?? this.suggestResponseEncoding ?? 'utf-8';
398
+ const charsetToUse = metaCharset ?? this.#suggestResponseEncoding ?? 'utf-8';
398
399
  const body = iconv.encodingExists(charsetToUse)
399
400
  ? iconv.decode(rawBytes, charsetToUse)
400
401
  : rawBytes.toString('utf8');
@@ -419,7 +420,7 @@ export class HttpCrawler extends BasicCrawler {
419
420
  url: request.url,
420
421
  method: request.method,
421
422
  proxyUrl,
422
- timeout: this.navigationTimeoutMillis,
423
+ timeout: this.#navigationTimeoutMillis,
423
424
  sessionToken: session,
424
425
  headers: request.headers,
425
426
  https: {
@@ -428,7 +429,7 @@ export class HttpCrawler extends BasicCrawler {
428
429
  body: undefined,
429
430
  };
430
431
  if (requestOptions.headers?.cookie || requestOptions.headers?.Cookie) {
431
- requestOptions.headers.Cookie = this._getCookieHeaderFromRequest(request);
432
+ requestOptions.headers.Cookie = this.getCookieHeaderFromRequest(request);
432
433
  delete requestOptions.headers.cookie;
433
434
  }
434
435
  // Disable SSL verification for MITM proxies
@@ -443,11 +444,11 @@ export class HttpCrawler extends BasicCrawler {
443
444
  return requestOptions;
444
445
  }
445
446
  encodeResponse(request, response, encoding) {
446
- if (this.forceResponseEncoding) {
447
- encoding = this.forceResponseEncoding;
447
+ if (this.#forceResponseEncoding) {
448
+ encoding = this.#forceResponseEncoding;
448
449
  }
449
- else if (!encoding && this.suggestResponseEncoding) {
450
- encoding = this.suggestResponseEncoding;
450
+ else if (!encoding && this.#suggestResponseEncoding) {
451
+ encoding = this.#suggestResponseEncoding;
451
452
  }
452
453
  // Fall back to utf-8 if we still don't have encoding.
453
454
  const utf8 = 'utf8';
@@ -481,12 +482,12 @@ export class HttpCrawler extends BasicCrawler {
481
482
  extendSupportedMimeTypes(additionalMimeTypes) {
482
483
  for (const mimeType of additionalMimeTypes) {
483
484
  if (mimeType === '*/*') {
484
- this.supportedMimeTypes.add(mimeType);
485
+ this.#supportedMimeTypes.add(mimeType);
485
486
  continue;
486
487
  }
487
488
  try {
488
489
  const parsedType = contentTypeParser.parse(mimeType);
489
- this.supportedMimeTypes.add(parsedType.type);
490
+ this.#supportedMimeTypes.add(parsedType.type);
490
491
  }
491
492
  catch (err) {
492
493
  throw new Error(`Can not parse mime type ${mimeType} from "options.additionalMimeTypes".`);
@@ -498,22 +499,22 @@ export class HttpCrawler extends BasicCrawler {
498
499
  */
499
500
  handleRequestTimeout(session) {
500
501
  session.markBad();
501
- throw new Error(`Request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
502
+ throw new Error(`Request timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
502
503
  }
503
- _abortDownloadOfBody(request, response) {
504
+ abortDownloadOfBody(request, response) {
504
505
  const { status } = response;
505
506
  const { type } = parseContentTypeFromResponse(response);
506
507
  const isTransientContentType = status >= 500 || this.blockedStatusCodes.has(status);
507
- if (!this.supportedMimeTypes.has(type) && !this.supportedMimeTypes.has('*/*') && !isTransientContentType) {
508
+ if (!this.#supportedMimeTypes.has(type) && !this.#supportedMimeTypes.has('*/*') && !isTransientContentType) {
508
509
  request.noRetry = true;
509
510
  throw new Error(`Resource ${request.url} served Content-Type ${type}, ` +
510
- `but only ${Array.from(this.supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
511
+ `but only ${Array.from(this.#supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
511
512
  }
512
513
  }
513
514
  /**
514
515
  * @internal wraps public utility for mocking purposes
515
516
  */
516
- _requestAsBrowser = async (options, session) => {
517
+ requestAsBrowser = async (options, session) => {
517
518
  const opts = processHttpRequestOptions({
518
519
  ...options,
519
520
  responseType: 'text',
@@ -521,7 +522,7 @@ export class HttpCrawler extends BasicCrawler {
521
522
  // When saveResponseCookies is false, the response cookies must not mutate the
522
523
  // session jar. Reads still go through the session (so session.setCookie() in pre-nav
523
524
  // hooks keeps working) but a per-request clone is passed in so writes are discarded.
524
- const cookieJar = this.saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
525
+ const cookieJar = this.#saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
525
526
  // Bind the request to the shared navigation window instead of a fixed per-request timeout, so
526
527
  // `extendTimeout()` can push the deadline and a fixed `AbortSignal.timeout` won't fire on its own and
527
528
  // kill a lazily-read body mid-extension. This aborts the socket only during the header phase; the body
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/http",
3
- "version": "4.0.0-beta.105",
3
+ "version": "4.0.0-beta.106",
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"
@@ -49,11 +49,11 @@
49
49
  "dependencies": {
50
50
  "@apify/timeout": "^0.4.4",
51
51
  "@apify/utilities": "^2.15.5",
52
- "@crawlee/basic": "4.0.0-beta.105",
53
- "@crawlee/core": "4.0.0-beta.105",
54
- "@crawlee/http-client": "4.0.0-beta.105",
55
- "@crawlee/types": "4.0.0-beta.105",
56
- "@crawlee/utils": "4.0.0-beta.105",
52
+ "@crawlee/basic": "4.0.0-beta.106",
53
+ "@crawlee/core": "4.0.0-beta.106",
54
+ "@crawlee/http-client": "4.0.0-beta.106",
55
+ "@crawlee/types": "4.0.0-beta.106",
56
+ "@crawlee/utils": "4.0.0-beta.106",
57
57
  "@types/content-type": "^1.1.8",
58
58
  "cheerio": "^1.0.0",
59
59
  "content-type": "^1.0.5",
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "26073a822c7699ac487931383a39192bc3daae7a"
73
+ "gitHead": "c622f1fc65e65221ea245817c58ecc0ffb4a5cb0"
74
74
  }