@crawlee/impit-client 3.17.1-beta.9 → 3.18.0

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.
Files changed (3) hide show
  1. package/index.d.ts +12 -2
  2. package/index.js +62 -11
  3. package/package.json +3 -3
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BaseHttpClient, HttpRequest, HttpResponse, ResponseTypes, StreamingHttpResponse } from '@crawlee/core';
1
+ import type { BaseHttpClient, HttpRequest, HttpResponse, RedirectHandler, ResponseTypes, StreamingHttpResponse } from '@crawlee/core';
2
2
  import type { ImpitOptions } from 'impit';
3
3
  export declare const Browser: {
4
4
  readonly Chrome: "chrome";
@@ -11,6 +11,7 @@ export declare class ImpitHttpClient implements BaseHttpClient {
11
11
  private impitOptions;
12
12
  private maxRedirects;
13
13
  private followRedirects;
14
+ private cacheClients;
14
15
  /**
15
16
  * Enables reuse of `impit` clients for the same set of options.
16
17
  * This is useful for performance reasons, as creating
@@ -19,8 +20,12 @@ export declare class ImpitHttpClient implements BaseHttpClient {
19
20
  */
20
21
  private clientCache;
21
22
  private getClient;
23
+ /**
24
+ * @param options.cacheClients Whether to cache `impit` clients between requests. Defaults to `true`.
25
+ */
22
26
  constructor(options?: Omit<ImpitOptions, 'proxyUrl'> & {
23
27
  maxRedirects?: number;
28
+ cacheClients?: boolean;
24
29
  });
25
30
  /**
26
31
  * Flattens the headers of a `HttpRequest` to a format that can be passed to `impit`.
@@ -30,6 +35,11 @@ export declare class ImpitHttpClient implements BaseHttpClient {
30
35
  private intoHeaders;
31
36
  private intoImpitBody;
32
37
  private shouldRewriteRedirectToGet;
38
+ /**
39
+ * Converts Fetch/Impit headers into a simple header map.
40
+ * `Object.fromEntries` would keep only the last `set-cookie` value, so those are collected separately.
41
+ */
42
+ private intoSimpleHeaders;
33
43
  /**
34
44
  * Common implementation for `sendRequest` and `stream` methods.
35
45
  * @param request `HttpRequest` object
@@ -44,5 +54,5 @@ export declare class ImpitHttpClient implements BaseHttpClient {
44
54
  /**
45
55
  * @inheritDoc
46
56
  */
47
- stream(request: HttpRequest): Promise<StreamingHttpResponse>;
57
+ stream(request: HttpRequest, onRedirect?: RedirectHandler): Promise<StreamingHttpResponse>;
48
58
  }
package/index.js CHANGED
@@ -14,6 +14,9 @@ exports.Browser = {
14
14
  */
15
15
  class ImpitHttpClient {
16
16
  getClient(options) {
17
+ if (!this.cacheClients) {
18
+ return new impit_1.Impit(options);
19
+ }
17
20
  const { cookieJar, ...rest } = options;
18
21
  const cacheKey = JSON.stringify(rest);
19
22
  const existingClient = this.clientCache.get(cacheKey);
@@ -24,6 +27,9 @@ class ImpitHttpClient {
24
27
  this.clientCache.add(cacheKey, { client, cookieJar: cookieJar });
25
28
  return client;
26
29
  }
30
+ /**
31
+ * @param options.cacheClients Whether to cache `impit` clients between requests. Defaults to `true`.
32
+ */
27
33
  constructor(options) {
28
34
  Object.defineProperty(this, "impitOptions", {
29
35
  enumerable: true,
@@ -43,6 +49,12 @@ class ImpitHttpClient {
43
49
  writable: true,
44
50
  value: void 0
45
51
  });
52
+ Object.defineProperty(this, "cacheClients", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
46
58
  /**
47
59
  * Enables reuse of `impit` clients for the same set of options.
48
60
  * This is useful for performance reasons, as creating
@@ -55,9 +67,11 @@ class ImpitHttpClient {
55
67
  writable: true,
56
68
  value: new datastructures_1.LruCache({ maxLength: 10 })
57
69
  });
58
- this.impitOptions = options ?? {};
59
- this.maxRedirects = options?.maxRedirects ?? 10;
60
- this.followRedirects = options?.followRedirects ?? true;
70
+ const { maxRedirects = 10, followRedirects = true, cacheClients = true, ...impitOptions } = options ?? {};
71
+ this.impitOptions = impitOptions;
72
+ this.maxRedirects = maxRedirects;
73
+ this.followRedirects = followRedirects;
74
+ this.cacheClients = cacheClients;
61
75
  }
62
76
  /**
63
77
  * Flattens the headers of a `HttpRequest` to a format that can be passed to `impit`.
@@ -97,12 +111,29 @@ class ImpitHttpClient {
97
111
  return method !== 'HEAD';
98
112
  return false;
99
113
  }
114
+ /**
115
+ * Converts Fetch/Impit headers into a simple header map.
116
+ * `Object.fromEntries` would keep only the last `set-cookie` value, so those are collected separately.
117
+ */
118
+ intoSimpleHeaders(headers) {
119
+ const result = {};
120
+ for (const [key, value] of headers.entries()) {
121
+ if (key === 'set-cookie')
122
+ continue;
123
+ result[key] = value;
124
+ }
125
+ const setCookies = headers.getSetCookie();
126
+ if (setCookies.length > 0) {
127
+ result['set-cookie'] = setCookies.length === 1 ? setCookies[0] : setCookies;
128
+ }
129
+ return result;
130
+ }
100
131
  /**
101
132
  * Common implementation for `sendRequest` and `stream` methods.
102
133
  * @param request `HttpRequest` object
103
134
  * @returns `HttpResponse` object
104
135
  */
105
- async getResponse(request, redirects) {
136
+ async getResponse(request, redirects, onRedirect) {
106
137
  if ((redirects?.redirectCount ?? 0) > this.maxRedirects) {
107
138
  throw new Error(`Too many redirects, maximum is ${this.maxRedirects}.`);
108
139
  }
@@ -125,14 +156,34 @@ class ImpitHttpClient {
125
156
  if (!location) {
126
157
  throw new Error('Redirect response missing location header.');
127
158
  }
159
+ const nextRedirectUrls = [...(redirects?.redirectUrls ?? []), redirectUrl];
160
+ const updatedRequest = {
161
+ url: redirectUrl.href,
162
+ headers: { ...(request.headers ?? {}) },
163
+ };
164
+ // Match GotScrapingHttpClient: allow HttpCrawler to persist redirect cookies into the session
165
+ // and mutate Cookie / URL for the next hop.
166
+ onRedirect?.({
167
+ redirectUrls: nextRedirectUrls,
168
+ url,
169
+ statusCode: response.status,
170
+ statusMessage: response.statusText,
171
+ headers: this.intoSimpleHeaders(response.headers),
172
+ trailers: {},
173
+ complete: true,
174
+ }, updatedRequest);
175
+ const nextUrl = typeof updatedRequest.url === 'string'
176
+ ? updatedRequest.url
177
+ : (updatedRequest.url?.href ?? redirectUrl.href);
128
178
  return this.getResponse({
129
179
  ...request,
130
180
  method: this.shouldRewriteRedirectToGet(response.status, request.method) ? 'GET' : request.method,
131
- url: redirectUrl.href,
181
+ url: nextUrl,
182
+ headers: updatedRequest.headers,
132
183
  }, {
133
184
  redirectCount: (redirects?.redirectCount ?? 0) + 1,
134
- redirectUrls: [...(redirects?.redirectUrls ?? []), redirectUrl],
135
- });
185
+ redirectUrls: nextRedirectUrls,
186
+ }, onRedirect);
136
187
  }
137
188
  return {
138
189
  response,
@@ -159,7 +210,7 @@ class ImpitHttpClient {
159
210
  throw new Error('Unsupported response type.');
160
211
  }
161
212
  return {
162
- headers: Object.fromEntries(response.headers.entries()),
213
+ headers: this.intoSimpleHeaders(response.headers),
163
214
  statusCode: response.status,
164
215
  url: response.url,
165
216
  request,
@@ -193,8 +244,8 @@ class ImpitHttpClient {
193
244
  /**
194
245
  * @inheritDoc
195
246
  */
196
- async stream(request) {
197
- const { response, redirectUrls } = await this.getResponse(request);
247
+ async stream(request, onRedirect) {
248
+ const { response, redirectUrls } = await this.getResponse(request, undefined, onRedirect);
198
249
  const [stream, getDownloadProgress] = this.getStreamWithProgress(response);
199
250
  return {
200
251
  request,
@@ -207,7 +258,7 @@ class ImpitHttpClient {
207
258
  },
208
259
  uploadProgress: { percent: 100, transferred: 0 },
209
260
  redirectUrls,
210
- headers: Object.fromEntries(response.headers.entries()),
261
+ headers: this.intoSimpleHeaders(response.headers),
211
262
  trailers: {},
212
263
  };
213
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/impit-client",
3
- "version": "3.17.1-beta.9",
3
+ "version": "3.18.0",
4
4
  "description": "impit-based HTTP client implementation for Crawlee. Impersonates browser requests to avoid bot detection.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0"
@@ -56,7 +56,7 @@
56
56
  "@crawlee/core": "^3.12.1"
57
57
  },
58
58
  "devDependencies": {
59
- "@crawlee/core": "^3.17.0"
59
+ "@crawlee/core": "^3.18.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "@apify/datastructures": "^2.0.3",
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "c547ce4489d8617620a51516cc885187503f3136"
73
+ "gitHead": "49c115e1ce3b3fbf2bef61f16bffeadfdcf5bd19"
74
74
  }