@crawlee/puppeteer 3.0.0-alpha.2 → 3.0.0-alpha.3
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/dist/LICENSE.md +201 -0
- package/{README.md → dist/README.md} +0 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +76 -0
- package/dist/internals/enqueue-links/click-elements.d.ts +188 -0
- package/dist/internals/enqueue-links/click-elements.d.ts.map +1 -0
- package/dist/internals/enqueue-links/click-elements.js +336 -0
- package/dist/internals/enqueue-links/click-elements.js.map +1 -0
- package/dist/internals/puppeteer-crawler.d.ts +149 -0
- package/dist/internals/puppeteer-crawler.d.ts.map +1 -0
- package/dist/internals/puppeteer-crawler.js +117 -0
- package/dist/internals/puppeteer-crawler.js.map +1 -0
- package/dist/internals/puppeteer-launcher.d.ts +134 -0
- package/dist/internals/puppeteer-launcher.d.ts.map +1 -0
- package/dist/internals/puppeteer-launcher.js +146 -0
- package/dist/internals/puppeteer-launcher.js.map +1 -0
- package/dist/internals/stealth/hiding_tricks.d.ts +10 -0
- package/dist/internals/stealth/hiding_tricks.d.ts.map +1 -0
- package/dist/internals/stealth/hiding_tricks.js +334 -0
- package/dist/internals/stealth/hiding_tricks.js.map +1 -0
- package/dist/internals/stealth/index.d.ts +40 -0
- package/dist/internals/stealth/index.d.ts.map +1 -0
- package/dist/internals/stealth/index.js +108 -0
- package/dist/internals/stealth/index.js.map +1 -0
- package/dist/internals/utils/puppeteer_request_interception.d.ts +60 -0
- package/dist/internals/utils/puppeteer_request_interception.d.ts.map +1 -0
- package/dist/internals/utils/puppeteer_request_interception.js +225 -0
- package/dist/internals/utils/puppeteer_request_interception.js.map +1 -0
- package/dist/internals/utils/puppeteer_utils.d.ts +298 -0
- package/dist/internals/utils/puppeteer_utils.d.ts.map +1 -0
- package/dist/internals/utils/puppeteer_utils.js +468 -0
- package/dist/internals/utils/puppeteer_utils.js.map +1 -0
- package/dist/package.json +66 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +8 -7
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Page, Browser } from 'puppeteer';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration of stealth tricks for a proper hiding effect all of them should be set to true.
|
|
4
|
+
* These tricks are applied only when the `stealth` option is set to `true`.
|
|
5
|
+
*/
|
|
6
|
+
export interface StealthOptions {
|
|
7
|
+
/** If plugins should be added to the navigator. */
|
|
8
|
+
addPlugins?: boolean;
|
|
9
|
+
/** Emulates window Iframe. */
|
|
10
|
+
emulateWindowFrame?: boolean;
|
|
11
|
+
/** Emulates graphic card. */
|
|
12
|
+
emulateWebGL?: boolean;
|
|
13
|
+
/** Emulates console.debug to return null. */
|
|
14
|
+
emulateConsoleDebug?: boolean;
|
|
15
|
+
/** Adds languages to the navigator. */
|
|
16
|
+
addLanguage?: boolean;
|
|
17
|
+
/** Hides the webdriver by changing the navigator proto. */
|
|
18
|
+
hideWebDriver?: boolean;
|
|
19
|
+
/** Fakes interaction with permissions. */
|
|
20
|
+
hackPermissions?: boolean;
|
|
21
|
+
/** Adds the chrome runtime properties. */
|
|
22
|
+
mockChrome?: boolean;
|
|
23
|
+
/** Adds the chrome runtime properties inside the every newly created iframe. */
|
|
24
|
+
mockChromeInIframe?: boolean;
|
|
25
|
+
/** Sets device memory to other value than 0. */
|
|
26
|
+
mockDeviceMemory?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The main purpose of this function is to override newPage function and attach selected tricks.
|
|
30
|
+
* @param browser puppeteer browser instance
|
|
31
|
+
* @param options
|
|
32
|
+
* @returns Instance of Browser from puppeteer package
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyStealthToBrowser(browser: Browser, options: StealthOptions): Browser;
|
|
35
|
+
/**
|
|
36
|
+
* Applies stealth tricks to the puppeteer page
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyStealthTricks(page: Page, evaluationDebugMessage: string, options: StealthOptions): Promise<void>;
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internals/stealth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQ1C;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8BAA8B;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,6BAA6B;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uCAAuC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAkBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAwBxF;AAoCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBrH"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyStealthTricks = exports.applyStealthToBrowser = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const utilities_1 = require("@apify/utilities");
|
|
6
|
+
const log_1 = tslib_1.__importDefault(require("@apify/log"));
|
|
7
|
+
const utils_1 = require("@crawlee/utils");
|
|
8
|
+
const hidingTricks = tslib_1.__importStar(require("./hiding_tricks"));
|
|
9
|
+
const log = log_1.default.child({ prefix: 'Stealth' });
|
|
10
|
+
const DEFAULT_STEALTH_OPTIONS = {
|
|
11
|
+
addPlugins: true,
|
|
12
|
+
emulateWindowFrame: true,
|
|
13
|
+
emulateWebGL: true,
|
|
14
|
+
emulateConsoleDebug: true,
|
|
15
|
+
addLanguage: true,
|
|
16
|
+
hackPermissions: true,
|
|
17
|
+
mockChrome: true,
|
|
18
|
+
mockChromeInIframe: true,
|
|
19
|
+
mockDeviceMemory: true,
|
|
20
|
+
};
|
|
21
|
+
const STEALTH_ERROR_MESSAGE_PREFIX = 'StealthError';
|
|
22
|
+
const MAX_IFRAMES = 10;
|
|
23
|
+
const alreadyWrapped = Symbol('alreadyWrapped');
|
|
24
|
+
/**
|
|
25
|
+
* The main purpose of this function is to override newPage function and attach selected tricks.
|
|
26
|
+
* @param browser puppeteer browser instance
|
|
27
|
+
* @param options
|
|
28
|
+
* @returns Instance of Browser from puppeteer package
|
|
29
|
+
*/
|
|
30
|
+
function applyStealthToBrowser(browser, options) {
|
|
31
|
+
const modifiedBrowser = browser;
|
|
32
|
+
const opts = { ...DEFAULT_STEALTH_OPTIONS, ...options };
|
|
33
|
+
const defaultContext = browser.defaultBrowserContext();
|
|
34
|
+
const contextPrototype = Object.getPrototypeOf(defaultContext);
|
|
35
|
+
const prevNewPage = contextPrototype.newPage;
|
|
36
|
+
if (!contextPrototype.newPage[alreadyWrapped]) {
|
|
37
|
+
contextPrototype.newPage = async function (...args) {
|
|
38
|
+
const page = await prevNewPage.bind(this)(...args);
|
|
39
|
+
const evaluationDebugMessage = generateEvaluationDebugMessage();
|
|
40
|
+
addStealthDebugToPage(page, evaluationDebugMessage);
|
|
41
|
+
await applyStealthTricks(page, evaluationDebugMessage, opts);
|
|
42
|
+
return page;
|
|
43
|
+
};
|
|
44
|
+
contextPrototype.newPage[alreadyWrapped] = true;
|
|
45
|
+
}
|
|
46
|
+
return modifiedBrowser;
|
|
47
|
+
}
|
|
48
|
+
exports.applyStealthToBrowser = applyStealthToBrowser;
|
|
49
|
+
function generateEvaluationDebugMessage() {
|
|
50
|
+
const minLength = 6;
|
|
51
|
+
const maxLength = 10;
|
|
52
|
+
const randomLength = Math.random() * (maxLength - minLength) + minLength;
|
|
53
|
+
return (0, utilities_1.cryptoRandomObjectId)(randomLength);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Logs the stealth errors in browser to the node stdout.
|
|
57
|
+
* @param page puppeteer page instance
|
|
58
|
+
* @param evaluationDebugMessage debug message
|
|
59
|
+
*/
|
|
60
|
+
function addStealthDebugToPage(page, evaluationDebugMessage) {
|
|
61
|
+
let warningLogged = false;
|
|
62
|
+
let counter = 1;
|
|
63
|
+
page.on('console', (msg) => {
|
|
64
|
+
const text = msg.text();
|
|
65
|
+
if (text.includes(STEALTH_ERROR_MESSAGE_PREFIX)) {
|
|
66
|
+
log.error(text);
|
|
67
|
+
}
|
|
68
|
+
else if (text.includes(evaluationDebugMessage)) {
|
|
69
|
+
if (counter > MAX_IFRAMES && !warningLogged) {
|
|
70
|
+
log.warning(`Evaluating hiding tricks in too many iframes (limit: ${MAX_IFRAMES}).`
|
|
71
|
+
+ 'You might experience some performance issues. Try setting \'stealth\' false');
|
|
72
|
+
warningLogged = true;
|
|
73
|
+
}
|
|
74
|
+
counter++;
|
|
75
|
+
log.debug('Tricks evaluated', { counter });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Applies stealth tricks to the puppeteer page
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
function applyStealthTricks(page, evaluationDebugMessage, options) {
|
|
84
|
+
const functions = (0, utils_1.keys)(options)
|
|
85
|
+
.filter((key) => {
|
|
86
|
+
return options[key];
|
|
87
|
+
})
|
|
88
|
+
.map((key) => hidingTricks[key].toString());
|
|
89
|
+
/* istanbul ignore next */
|
|
90
|
+
const addFunctions = (functionsArr, errorMessagePrefix, debugMessage) => {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log(debugMessage);
|
|
93
|
+
// add functions
|
|
94
|
+
for (const func of functionsArr) {
|
|
95
|
+
try {
|
|
96
|
+
eval(func)(); // eslint-disable-line
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
const err = e;
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error(`${errorMessagePrefix}: Failed to apply stealth trick reason: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return page.evaluateOnNewDocument(addFunctions, functions, STEALTH_ERROR_MESSAGE_PREFIX, evaluationDebugMessage);
|
|
106
|
+
}
|
|
107
|
+
exports.applyStealthTricks = applyStealthTricks;
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/internals/stealth/index.ts"],"names":[],"mappings":";;;;AACA,gDAAwD;AACxD,6DAA8B;AAC9B,0CAAsC;AACtC,sEAAgD;AAEhD,MAAM,GAAG,GAAG,aAAI,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AA6B9C,MAAM,uBAAuB,GAAG;IAC5B,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,IAAI;IACxB,YAAY,EAAE,IAAI;IAClB,mBAAmB,EAAE,IAAI;IACzB,WAAW,EAAE,IAAI;IACjB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,IAAI;CACzB,CAAC;AAEF,MAAM,4BAA4B,GAAG,cAAc,CAAC;AACpD,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,cAAc,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,OAAgB,EAAE,OAAuB;IAC3E,MAAM,eAAe,GAAG,OAAO,CAAC;IAChC,MAAM,IAAI,GAAG,EAAE,GAAG,uBAAuB,EAAE,GAAG,OAAO,EAAE,CAAC;IACxD,MAAM,cAAc,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACvD,MAAM,gBAAgB,GAAG,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IAE/D,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC;IAE7C,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE;QAC3C,gBAAgB,CAAC,OAAO,GAAG,KAAK,WAAW,GAAG,IAAe;YACzD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YAEnD,MAAM,sBAAsB,GAAG,8BAA8B,EAAE,CAAC;YAEhE,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;YACpD,MAAM,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,CAAC,CAAC;YAE7D,OAAO,IAAI,CAAC;QAChB,CAAC,CAAC;QAEF,gBAAgB,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;KACnD;IAED,OAAO,eAAe,CAAC;AAC3B,CAAC;AAxBD,sDAwBC;AAED,SAAS,8BAA8B;IACnC,MAAM,SAAS,GAAG,CAAC,CAAC;IACpB,MAAM,SAAS,GAAG,EAAE,CAAC;IACrB,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IAEzE,OAAO,IAAA,gCAAoB,EAAC,YAAY,CAAC,CAAC;AAC9C,CAAC;AACD;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,IAAU,EAAE,sBAA8B;IACrE,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE;YAC7C,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SACnB;aAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE;YAC9C,IAAI,OAAO,GAAG,WAAW,IAAI,CAAC,aAAa,EAAE;gBACzC,GAAG,CAAC,OAAO,CACP,wDAAwD,WAAW,IAAI;sBACrE,6EAA6E,CAClF,CAAC;gBAEF,aAAa,GAAG,IAAI,CAAC;aACxB;YACD,OAAO,EAAE,CAAC;YACV,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;SAC9C;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,IAAU,EAAE,sBAA8B,EAAE,OAAuB;IAClG,MAAM,SAAS,GAAG,IAAA,YAAI,EAAC,OAAO,CAAC;SAC1B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAgC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE7E,0BAA0B;IAC1B,MAAM,YAAY,GAAG,CAAC,YAAsB,EAAE,kBAA0B,EAAE,YAAoB,EAAE,EAAE;QAC9F,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,gBAAgB;QAChB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE;YAC7B,IAAI;gBACA,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,sBAAsB;aACvC;YAAC,OAAO,CAAC,EAAE;gBACR,MAAM,GAAG,GAAG,CAAU,CAAC;gBACvB,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,GAAG,kBAAkB,2CAA2C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;aAChG;SACJ;IACL,CAAC,CAAC;IAEF,OAAO,IAAI,CAAC,qBAAqB,CAAC,YAAY,EAAE,SAAS,EAAE,4BAA4B,EAAE,sBAAsB,CAAC,CAAC;AACrH,CAAC;AAxBD,gDAwBC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { HTTPRequest as PuppeteerRequest, Page } from 'puppeteer';
|
|
2
|
+
export declare type InterceptHandler = (request: PuppeteerRequest) => unknown;
|
|
3
|
+
/**
|
|
4
|
+
* Adds request interception handler in similar to `page.on('request', handler);` but in addition to that
|
|
5
|
+
* supports multiple parallel handlers.
|
|
6
|
+
*
|
|
7
|
+
* All the handlers are executed sequentially in the order as they were added.
|
|
8
|
+
* Each of the handlers must call one of `request.continue()`, `request.abort()` and `request.respond()`.
|
|
9
|
+
* In addition to that any of the handlers may modify the request object (method, postData, headers)
|
|
10
|
+
* by passing its overrides to `request.continue()`.
|
|
11
|
+
* If multiple handlers modify same property then the last one wins. Headers are merged separately so you can
|
|
12
|
+
* override only a value of specific header.
|
|
13
|
+
*
|
|
14
|
+
* If one the handlers calls `request.abort()` or `request.respond()` then request is not propagated further
|
|
15
|
+
* to any of the remaining handlers.
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* **Example usage:**
|
|
19
|
+
*
|
|
20
|
+
* ```javascript
|
|
21
|
+
* // Replace images with placeholder.
|
|
22
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
23
|
+
* if (request.resourceType() === 'image') {
|
|
24
|
+
* return request.respond({
|
|
25
|
+
* statusCode: 200,
|
|
26
|
+
* contentType: 'image/jpeg',
|
|
27
|
+
* body: placeholderImageBuffer,
|
|
28
|
+
* });
|
|
29
|
+
* }
|
|
30
|
+
* return request.continue();
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Abort all the scripts.
|
|
34
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
35
|
+
* if (request.resourceType() === 'script') return request.abort();
|
|
36
|
+
* return request.continue();
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Change requests to post.
|
|
40
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
41
|
+
* return request.continue({
|
|
42
|
+
* method: 'POST',
|
|
43
|
+
* });
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* await page.goto('http://example.com');
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param page Puppeteer [`Page`](https://pptr.dev/#?product=Puppeteer&show=api-class-page) object.
|
|
50
|
+
* @param handler Request interception handler.
|
|
51
|
+
*/
|
|
52
|
+
export declare function addInterceptRequestHandler(page: Page, handler: InterceptHandler): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Removes request interception handler for given page.
|
|
55
|
+
*
|
|
56
|
+
* @param page Puppeteer [`Page`](https://pptr.dev/#?product=Puppeteer&show=api-class-page) object.
|
|
57
|
+
* @param handler Request interception handler.
|
|
58
|
+
*/
|
|
59
|
+
export declare function removeInterceptRequestHandler(page: Page, handler: InterceptHandler): Promise<void>;
|
|
60
|
+
//# sourceMappingURL=puppeteer_request_interception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"puppeteer_request_interception.d.ts","sourceRoot":"","sources":["../../../src/internals/utils/puppeteer_request_interception.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,WAAW,IAAI,gBAAgB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAiC/E,oBAAY,gBAAgB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC;AAwEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrG;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BxG"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeInterceptRequestHandler = exports.addInterceptRequestHandler = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const events_1 = require("events");
|
|
6
|
+
const ow_1 = tslib_1.__importDefault(require("ow"));
|
|
7
|
+
const log_1 = tslib_1.__importDefault(require("@apify/log"));
|
|
8
|
+
// We use weak maps here so that the content gets discarded after page gets closed.
|
|
9
|
+
const pageInterceptRequestHandlersMap = new WeakMap(); // Maps page to an array of request interception handlers.
|
|
10
|
+
const pageInterceptRequestMasterHandlerMap = new WeakMap(); // Maps page to master request interception handler.
|
|
11
|
+
const pageInterceptedRequestsMap = new WeakMap(); // Maps page to a set of its pending intercepted requests.
|
|
12
|
+
/**
|
|
13
|
+
* Enables observation of changes of internal state to be able to queue other actions based on it.
|
|
14
|
+
* @ignore
|
|
15
|
+
*/
|
|
16
|
+
class ObservableSet extends events_1.EventEmitter {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
Object.defineProperty(this, "set", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: new Set()
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
add(value) {
|
|
27
|
+
this.set.add(value);
|
|
28
|
+
this.emit('add', value);
|
|
29
|
+
return this.set;
|
|
30
|
+
}
|
|
31
|
+
delete(value) {
|
|
32
|
+
const success = this.set.delete(value);
|
|
33
|
+
this.emit('delete', value);
|
|
34
|
+
return success;
|
|
35
|
+
}
|
|
36
|
+
get size() {
|
|
37
|
+
return this.set.size;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Makes all request headers capitalized to more look like in browser
|
|
42
|
+
*/
|
|
43
|
+
function browserifyHeaders(headers) {
|
|
44
|
+
const finalHeaders = {};
|
|
45
|
+
// eslint-disable-next-line prefer-const
|
|
46
|
+
for (let [key, value] of Object.entries(headers)) {
|
|
47
|
+
key = key.toLowerCase()
|
|
48
|
+
.split('-')
|
|
49
|
+
.map((str) => str.charAt(0).toUpperCase() + str.slice(1))
|
|
50
|
+
.join('-');
|
|
51
|
+
finalHeaders[key] = value;
|
|
52
|
+
}
|
|
53
|
+
return finalHeaders;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes an array for given intercept request handlers for a given request object.
|
|
57
|
+
*
|
|
58
|
+
* @param request Puppeteer's Request object.
|
|
59
|
+
* @param interceptRequestHandlers An array of intercept request handlers.
|
|
60
|
+
* @ignore
|
|
61
|
+
*/
|
|
62
|
+
async function handleRequest(request, interceptRequestHandlers) {
|
|
63
|
+
// If there are no intercept handlers, it means that request interception is not enabled (anymore)
|
|
64
|
+
// and therefore .abort() .respond() and .continue() would throw and crash the process.
|
|
65
|
+
if (!interceptRequestHandlers?.length)
|
|
66
|
+
return;
|
|
67
|
+
let wasAborted = false;
|
|
68
|
+
let wasResponded = false;
|
|
69
|
+
let wasContinued = false;
|
|
70
|
+
const accumulatedOverrides = {
|
|
71
|
+
headers: browserifyHeaders(request.headers()),
|
|
72
|
+
};
|
|
73
|
+
const originalContinue = request.continue.bind(request);
|
|
74
|
+
request.continue = async (overrides = {}) => {
|
|
75
|
+
wasContinued = true;
|
|
76
|
+
const headers = browserifyHeaders({ ...accumulatedOverrides.headers, ...overrides.headers });
|
|
77
|
+
Object.assign(accumulatedOverrides, overrides, { headers });
|
|
78
|
+
};
|
|
79
|
+
const { abort, respond } = request;
|
|
80
|
+
request.abort = async (...args) => {
|
|
81
|
+
wasAborted = true;
|
|
82
|
+
return abort.call(request, ...args);
|
|
83
|
+
};
|
|
84
|
+
request.respond = async (...args) => {
|
|
85
|
+
wasResponded = true;
|
|
86
|
+
return respond.call(request, ...args);
|
|
87
|
+
};
|
|
88
|
+
for (const handler of interceptRequestHandlers) {
|
|
89
|
+
wasContinued = false;
|
|
90
|
+
await handler(request);
|
|
91
|
+
// Check that one of the functions was called.
|
|
92
|
+
if (!wasAborted && !wasResponded && !wasContinued) {
|
|
93
|
+
throw new Error('Intercept request handler must call one of request.continue|respond|abort() methods!');
|
|
94
|
+
}
|
|
95
|
+
// If request was aborted or responded then we can finish immediately.
|
|
96
|
+
if (wasAborted || wasResponded)
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
return originalContinue(accumulatedOverrides);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Adds request interception handler in similar to `page.on('request', handler);` but in addition to that
|
|
103
|
+
* supports multiple parallel handlers.
|
|
104
|
+
*
|
|
105
|
+
* All the handlers are executed sequentially in the order as they were added.
|
|
106
|
+
* Each of the handlers must call one of `request.continue()`, `request.abort()` and `request.respond()`.
|
|
107
|
+
* In addition to that any of the handlers may modify the request object (method, postData, headers)
|
|
108
|
+
* by passing its overrides to `request.continue()`.
|
|
109
|
+
* If multiple handlers modify same property then the last one wins. Headers are merged separately so you can
|
|
110
|
+
* override only a value of specific header.
|
|
111
|
+
*
|
|
112
|
+
* If one the handlers calls `request.abort()` or `request.respond()` then request is not propagated further
|
|
113
|
+
* to any of the remaining handlers.
|
|
114
|
+
*
|
|
115
|
+
*
|
|
116
|
+
* **Example usage:**
|
|
117
|
+
*
|
|
118
|
+
* ```javascript
|
|
119
|
+
* // Replace images with placeholder.
|
|
120
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
121
|
+
* if (request.resourceType() === 'image') {
|
|
122
|
+
* return request.respond({
|
|
123
|
+
* statusCode: 200,
|
|
124
|
+
* contentType: 'image/jpeg',
|
|
125
|
+
* body: placeholderImageBuffer,
|
|
126
|
+
* });
|
|
127
|
+
* }
|
|
128
|
+
* return request.continue();
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // Abort all the scripts.
|
|
132
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
133
|
+
* if (request.resourceType() === 'script') return request.abort();
|
|
134
|
+
* return request.continue();
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* // Change requests to post.
|
|
138
|
+
* await addInterceptRequestHandler(page, (request) => {
|
|
139
|
+
* return request.continue({
|
|
140
|
+
* method: 'POST',
|
|
141
|
+
* });
|
|
142
|
+
* });
|
|
143
|
+
*
|
|
144
|
+
* await page.goto('http://example.com');
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @param page Puppeteer [`Page`](https://pptr.dev/#?product=Puppeteer&show=api-class-page) object.
|
|
148
|
+
* @param handler Request interception handler.
|
|
149
|
+
*/
|
|
150
|
+
async function addInterceptRequestHandler(page, handler) {
|
|
151
|
+
(0, ow_1.default)(page, ow_1.default.object.hasKeys('goto', 'evaluate'));
|
|
152
|
+
(0, ow_1.default)(handler, ow_1.default.function);
|
|
153
|
+
if (!pageInterceptRequestHandlersMap.has(page)) {
|
|
154
|
+
pageInterceptRequestHandlersMap.set(page, []);
|
|
155
|
+
}
|
|
156
|
+
if (!pageInterceptedRequestsMap.has(page)) {
|
|
157
|
+
pageInterceptedRequestsMap.set(page, new ObservableSet());
|
|
158
|
+
}
|
|
159
|
+
const handlersArray = pageInterceptRequestHandlersMap.get(page);
|
|
160
|
+
handlersArray.push(handler);
|
|
161
|
+
// First handler was just added at this point so we need to set up request interception.
|
|
162
|
+
if (handlersArray.length === 1) {
|
|
163
|
+
await page.setRequestInterception(true);
|
|
164
|
+
// This is a handler that gets set in page.on('request', ...) and that executes all the user
|
|
165
|
+
// added custom handlers.
|
|
166
|
+
const masterHandler = async (request) => {
|
|
167
|
+
const interceptedRequests = pageInterceptedRequestsMap.get(page);
|
|
168
|
+
interceptedRequests.add(request);
|
|
169
|
+
const interceptHandlers = pageInterceptRequestHandlersMap.get(page);
|
|
170
|
+
try {
|
|
171
|
+
await handleRequest(request, interceptHandlers);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
interceptedRequests.delete(request);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
pageInterceptRequestMasterHandlerMap.set(page, masterHandler);
|
|
178
|
+
page.on('request', masterHandler);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.addInterceptRequestHandler = addInterceptRequestHandler;
|
|
182
|
+
/**
|
|
183
|
+
* Removes request interception handler for given page.
|
|
184
|
+
*
|
|
185
|
+
* @param page Puppeteer [`Page`](https://pptr.dev/#?product=Puppeteer&show=api-class-page) object.
|
|
186
|
+
* @param handler Request interception handler.
|
|
187
|
+
*/
|
|
188
|
+
async function removeInterceptRequestHandler(page, handler) {
|
|
189
|
+
(0, ow_1.default)(page, ow_1.default.object.hasKeys('goto', 'evaluate'));
|
|
190
|
+
(0, ow_1.default)(handler, ow_1.default.function);
|
|
191
|
+
const handlersArray = pageInterceptRequestHandlersMap
|
|
192
|
+
.get(page)
|
|
193
|
+
.filter((item) => item !== handler);
|
|
194
|
+
pageInterceptRequestHandlersMap.set(page, handlersArray);
|
|
195
|
+
if (handlersArray.length === 0) {
|
|
196
|
+
const interceptedRequestsInProgress = pageInterceptedRequestsMap.get(page);
|
|
197
|
+
// Since handlers can be async, we can't simply turn off request interception
|
|
198
|
+
// when there are no handlers, because some handlers could still
|
|
199
|
+
// be in progress and request.abort|respond|continue() would throw.
|
|
200
|
+
if (interceptedRequestsInProgress.size === 0) {
|
|
201
|
+
await disableRequestInterception(page);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const onDelete = async () => {
|
|
205
|
+
if (interceptedRequestsInProgress.size === 0) {
|
|
206
|
+
try {
|
|
207
|
+
await disableRequestInterception(page);
|
|
208
|
+
interceptedRequestsInProgress.removeListener('delete', onDelete);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
log_1.default.debug('Error while disabling request interception', { error });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
interceptedRequestsInProgress.on('delete', onDelete);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
exports.removeInterceptRequestHandler = removeInterceptRequestHandler;
|
|
220
|
+
async function disableRequestInterception(page) {
|
|
221
|
+
await page.setRequestInterception(false);
|
|
222
|
+
const requestHandler = pageInterceptRequestMasterHandlerMap.get(page);
|
|
223
|
+
page.off('request', requestHandler);
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=puppeteer_request_interception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"puppeteer_request_interception.js","sourceRoot":"","sources":["../../../src/internals/utils/puppeteer_request_interception.ts"],"names":[],"mappings":"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"}
|