@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.
- package/index.d.ts +12 -2
- package/index.js +62 -11
- 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
|
-
|
|
59
|
-
this.
|
|
60
|
-
this.
|
|
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:
|
|
181
|
+
url: nextUrl,
|
|
182
|
+
headers: updatedRequest.headers,
|
|
132
183
|
}, {
|
|
133
184
|
redirectCount: (redirects?.redirectCount ?? 0) + 1,
|
|
134
|
-
redirectUrls:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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": "
|
|
73
|
+
"gitHead": "49c115e1ce3b3fbf2bef61f16bffeadfdcf5bd19"
|
|
74
74
|
}
|