puppeteer-pro 1.5.3 → 1.5.6
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/.env.example +2 -0
- package/.eslintignore +6 -0
- package/.eslintrc.json +39 -0
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/master.cron.code-analyze.yml +58 -0
- package/.github/workflows/master.cron.publish.yml +34 -0
- package/.github/workflows/master.pr.build.yml +31 -0
- package/package.json +8 -8
- package/src/index.ts +279 -0
- package/src/plugins/anonymize.user.agent/index.ts +66 -0
- package/src/plugins/anonymize.user.agent/test.js +35 -0
- package/src/plugins/avoid.detection/index.ts +19 -0
- package/{plugins → src/plugins}/avoid.detection/injections/chrome.runtime.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/console.debug.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/hairline.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/navigator.languages.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/navigator.permissions.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/navigator.plugins.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/navigator.webdriver.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/webgl.js +0 -0
- package/{plugins → src/plugins}/avoid.detection/injections/window.js +0 -0
- package/src/plugins/avoid.detection/test.js +40 -0
- package/src/plugins/block.resources/index.ts +23 -0
- package/src/plugins/block.resources/test.js +48 -0
- package/src/plugins/disable.dialogs/index.ts +22 -0
- package/src/plugins/disable.dialogs/test.js +43 -0
- package/src/plugins/manage.cookies/index.ts +174 -0
- package/src/plugins/manage.cookies/test.js +90 -0
- package/src/plugins/solve.recaptcha/index.ts +95 -0
- package/{plugins → src/plugins}/solve.recaptcha/injections/utils.js +0 -0
- package/src/plugins/solve.recaptcha/test.js +42 -0
- package/test/1.default.js +14 -0
- package/test/2.methods.js +187 -0
- package/test/3.plugins.js +91 -0
- package/tsconfig.json +35 -0
- package/tslint.json +21 -0
- package/index.d.ts +0 -45
- package/index.d.ts.map +0 -1
- package/index.js +0 -244
- package/index.js.map +0 -1
- package/plugins/anonymize.user.agent/index.d.ts +0 -13
- package/plugins/anonymize.user.agent/index.d.ts.map +0 -1
- package/plugins/anonymize.user.agent/index.js +0 -50
- package/plugins/anonymize.user.agent/index.js.map +0 -1
- package/plugins/avoid.detection/index.d.ts +0 -8
- package/plugins/avoid.detection/index.d.ts.map +0 -1
- package/plugins/avoid.detection/index.js +0 -22
- package/plugins/avoid.detection/index.js.map +0 -1
- package/plugins/block.resources/index.d.ts +0 -10
- package/plugins/block.resources/index.d.ts.map +0 -1
- package/plugins/block.resources/index.js +0 -20
- package/plugins/block.resources/index.js.map +0 -1
- package/plugins/disable.dialogs/index.d.ts +0 -8
- package/plugins/disable.dialogs/index.d.ts.map +0 -1
- package/plugins/disable.dialogs/index.js +0 -18
- package/plugins/disable.dialogs/index.js.map +0 -1
- package/plugins/manage.cookies/index.d.ts +0 -36
- package/plugins/manage.cookies/index.d.ts.map +0 -1
- package/plugins/manage.cookies/index.js +0 -140
- package/plugins/manage.cookies/index.js.map +0 -1
- package/plugins/solve.recaptcha/index.d.ts +0 -11
- package/plugins/solve.recaptcha/index.d.ts.map +0 -1
- package/plugins/solve.recaptcha/index.js +0 -83
- package/plugins/solve.recaptcha/index.js.map +0 -1
package/.env.example
ADDED
package/.eslintignore
ADDED
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "eslint:recommended",
|
|
3
|
+
// "extends": "google",
|
|
4
|
+
"parserOptions": {
|
|
5
|
+
"ecmaVersion": 2018
|
|
6
|
+
},
|
|
7
|
+
"env": {
|
|
8
|
+
"node": true,
|
|
9
|
+
"browser": true,
|
|
10
|
+
"es6": true,
|
|
11
|
+
"mocha": true
|
|
12
|
+
},
|
|
13
|
+
"rules": {
|
|
14
|
+
"comma-dangle": [
|
|
15
|
+
"error",
|
|
16
|
+
{
|
|
17
|
+
"arrays": "only-multiline"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"linebreak-style": [
|
|
21
|
+
"error",
|
|
22
|
+
"windows"
|
|
23
|
+
],
|
|
24
|
+
"max-len": [
|
|
25
|
+
"error",
|
|
26
|
+
{
|
|
27
|
+
"code": 1e10
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"no-console": "off",
|
|
31
|
+
"require-await": [
|
|
32
|
+
"error"
|
|
33
|
+
],
|
|
34
|
+
"semi": [
|
|
35
|
+
"error",
|
|
36
|
+
"always"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: npm
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: daily
|
|
7
|
+
time: "03:00"
|
|
8
|
+
timezone: America/New_York
|
|
9
|
+
open-pull-requests-limit: 10
|
|
10
|
+
versioning-strategy: increase
|
|
11
|
+
labels:
|
|
12
|
+
- dependencies
|
|
13
|
+
commit-message:
|
|
14
|
+
prefix: fix
|
|
15
|
+
prefix-development: chore
|
|
16
|
+
include: scope
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master ]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 9 * * 1'
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
analyze:
|
|
13
|
+
name: Analyze
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
actions: read
|
|
17
|
+
contents: read
|
|
18
|
+
security-events: write
|
|
19
|
+
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
language: [ 'javascript' ]
|
|
24
|
+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
25
|
+
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v2
|
|
30
|
+
|
|
31
|
+
# Initializes the CodeQL tools for scanning.
|
|
32
|
+
- name: Initialize CodeQL
|
|
33
|
+
uses: github/codeql-action/init@v1
|
|
34
|
+
with:
|
|
35
|
+
languages: ${{ matrix.language }}
|
|
36
|
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
37
|
+
# By default, queries listed here will override any specified in a config file.
|
|
38
|
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
39
|
+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
40
|
+
|
|
41
|
+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
42
|
+
# If this step fails, then you should remove it and run the build manually (see below)
|
|
43
|
+
- name: Autobuild
|
|
44
|
+
uses: github/codeql-action/autobuild@v1
|
|
45
|
+
|
|
46
|
+
# ℹ️ Command-line programs to run using the OS shell.
|
|
47
|
+
# 📚 https://git.io/JvXDl
|
|
48
|
+
|
|
49
|
+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
50
|
+
# and modify them (or add more) to build your code if your project
|
|
51
|
+
# uses a compiled language
|
|
52
|
+
|
|
53
|
+
#- run: |
|
|
54
|
+
# make bootstrap
|
|
55
|
+
# make release
|
|
56
|
+
|
|
57
|
+
- name: Perform CodeQL Analysis
|
|
58
|
+
uses: github/codeql-action/analyze@v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 9 * * *'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
run:
|
|
10
|
+
name: Publish
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v1
|
|
14
|
+
- uses: actions/setup-node@v1
|
|
15
|
+
with:
|
|
16
|
+
node-version: 16
|
|
17
|
+
- run: npm install
|
|
18
|
+
|
|
19
|
+
- uses: DamianReeves/write-file-action@v1.0
|
|
20
|
+
with:
|
|
21
|
+
path: .env
|
|
22
|
+
contents: ${{ secrets.ENV }}
|
|
23
|
+
write-mode: overwrite
|
|
24
|
+
|
|
25
|
+
- run: npm run build --if-present
|
|
26
|
+
- run: npm test
|
|
27
|
+
|
|
28
|
+
- uses: ItzRabbs/semantic-release-action@master
|
|
29
|
+
with:
|
|
30
|
+
directory: 'dist'
|
|
31
|
+
env:
|
|
32
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
34
|
+
continue-on-error: true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Build on PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
run:
|
|
10
|
+
name: Build on PR
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: github.actor == 'dependabot[bot]'
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v2
|
|
16
|
+
with:
|
|
17
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v1
|
|
20
|
+
with:
|
|
21
|
+
node-version: 16
|
|
22
|
+
- run: npm install
|
|
23
|
+
|
|
24
|
+
- uses: DamianReeves/write-file-action@v1.0
|
|
25
|
+
with:
|
|
26
|
+
path: .env
|
|
27
|
+
contents: ${{ secrets.ENV }}
|
|
28
|
+
write-mode: overwrite
|
|
29
|
+
|
|
30
|
+
- run: npm run build --if-present
|
|
31
|
+
- run: npm test
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puppeteer-pro",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
4
4
|
"description": "A simple puppeteer wrapper to enable useful plugins with ease",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,20 +36,20 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"axios": "^0.27.2",
|
|
39
|
-
"ghost-cursor": "^1.1.
|
|
40
|
-
"user-agents": "^1.0.
|
|
39
|
+
"ghost-cursor": "^1.1.11",
|
|
40
|
+
"user-agents": "^1.0.1042"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^17.0.
|
|
43
|
+
"@types/node": "^17.0.41",
|
|
44
44
|
"@types/user-agents": "^1.0.2",
|
|
45
45
|
"chai": "^4.3.6",
|
|
46
46
|
"copyfiles": "^2.4.1",
|
|
47
47
|
"dotenv-safe": "^8.2.0",
|
|
48
|
-
"eslint": "^8.
|
|
48
|
+
"eslint": "^8.17.0",
|
|
49
49
|
"mocha": "^10.0.0",
|
|
50
|
-
"puppeteer": "^14.
|
|
50
|
+
"puppeteer": "^14.3.0",
|
|
51
51
|
"rimraf": "^3.0.2",
|
|
52
52
|
"tslint": "^6.1.3",
|
|
53
|
-
"typescript": "^4.7.
|
|
53
|
+
"typescript": "^4.7.3"
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as events from 'events';
|
|
2
|
+
|
|
3
|
+
// Puppeteer Defaults
|
|
4
|
+
import * as Puppeteer from 'puppeteer';
|
|
5
|
+
|
|
6
|
+
const browserEvents = new events.EventEmitter();
|
|
7
|
+
|
|
8
|
+
export async function connect(options?: Puppeteer.ConnectOptions): Promise<Puppeteer.Browser> {
|
|
9
|
+
const browser = await Puppeteer.connect(options || {});
|
|
10
|
+
|
|
11
|
+
for (const plugin of plugins) {
|
|
12
|
+
await plugin.init(browser);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const _close = browser.close;
|
|
16
|
+
browser.close = async () => {
|
|
17
|
+
await _close.apply(browser);
|
|
18
|
+
browserEvents.emit('close');
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return browser;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The method launches a browser instance with given arguments. The browser will be closed when the parent node.js process is closed. */
|
|
25
|
+
export async function launch(options?: Puppeteer.LaunchOptions & Puppeteer.BrowserLaunchArgumentOptions & Puppeteer.BrowserConnectOptions): Promise<Puppeteer.Browser> {
|
|
26
|
+
if (!options) options = {};
|
|
27
|
+
|
|
28
|
+
const browser = await Puppeteer.launch({ defaultViewport: undefined, ...options });
|
|
29
|
+
|
|
30
|
+
for (const plugin of plugins) {
|
|
31
|
+
await plugin.init(browser);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const _close = browser.close;
|
|
35
|
+
browser.close = async () => {
|
|
36
|
+
await _close.apply(browser);
|
|
37
|
+
browserEvents.emit('close');
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return browser;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// PuppeteerPro
|
|
44
|
+
let interceptions = 0;
|
|
45
|
+
export class Plugin {
|
|
46
|
+
protected browser: Puppeteer.Browser | null = null;
|
|
47
|
+
private initialized = false;
|
|
48
|
+
private startCounter = 0;
|
|
49
|
+
protected dependencies: Plugin[] = [];
|
|
50
|
+
protected requiresInterception = false;
|
|
51
|
+
|
|
52
|
+
get isInitialized() { return this.initialized; }
|
|
53
|
+
get isStopped() { return this.startCounter === 0; }
|
|
54
|
+
|
|
55
|
+
protected async addDependency(plugin: Plugin) {
|
|
56
|
+
this.dependencies.push(plugin);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async init(browser: Puppeteer.Browser) {
|
|
60
|
+
if (this.initialized) return;
|
|
61
|
+
|
|
62
|
+
this.browser = browser;
|
|
63
|
+
|
|
64
|
+
const offOnClose: (() => void)[] = [];
|
|
65
|
+
browserEvents.once('close', async () => {
|
|
66
|
+
offOnClose.forEach(fn => fn());
|
|
67
|
+
|
|
68
|
+
this.browser = null;
|
|
69
|
+
this.initialized = false;
|
|
70
|
+
this.startCounter = 0;
|
|
71
|
+
|
|
72
|
+
await this.onClose();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.startCounter++;
|
|
76
|
+
|
|
77
|
+
const thisOnTargetCreated = this.onTargetCreated.bind(this);
|
|
78
|
+
browser.on('targetcreated', thisOnTargetCreated);
|
|
79
|
+
offOnClose.push(() => browser.off('targetcreated', thisOnTargetCreated));
|
|
80
|
+
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
|
|
83
|
+
this.dependencies.forEach(x => x.init(browser));
|
|
84
|
+
|
|
85
|
+
return this.afterLaunch(browser);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected async afterLaunch(_browser: Puppeteer.Browser) { }
|
|
89
|
+
protected async onClose() { }
|
|
90
|
+
|
|
91
|
+
protected async onTargetCreated(target: Puppeteer.Target) {
|
|
92
|
+
if (this.isStopped) return;
|
|
93
|
+
|
|
94
|
+
if (target.type() !== 'page') return;
|
|
95
|
+
const page = await target.page() as Puppeteer.Page;
|
|
96
|
+
if (page.isClosed()) return;
|
|
97
|
+
|
|
98
|
+
const offOnClose: (() => void)[] = [];
|
|
99
|
+
page.once('close', async () => {
|
|
100
|
+
offOnClose.forEach(fn => fn());
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const requestHandlers: ((request: any) => void)[] = [];
|
|
104
|
+
page.on('request', request => {
|
|
105
|
+
const _respond = request.respond;
|
|
106
|
+
let responded = 0;
|
|
107
|
+
let respondArgs: IArguments;
|
|
108
|
+
|
|
109
|
+
const _abort = request.abort;
|
|
110
|
+
let aborted = 0;
|
|
111
|
+
let abortArgs: IArguments;
|
|
112
|
+
|
|
113
|
+
const _continue = request.continue;
|
|
114
|
+
let continued = 0;
|
|
115
|
+
let continueArgs: IArguments;
|
|
116
|
+
|
|
117
|
+
// tslint:disable-next-line: only-arrow-functions
|
|
118
|
+
const handleRequest = async function () {
|
|
119
|
+
const total = responded + aborted + continued;
|
|
120
|
+
|
|
121
|
+
if (!(request as any)._interceptionHandled) {
|
|
122
|
+
if (responded === 1) await _respond.apply(request, respondArgs);
|
|
123
|
+
else if (responded === 0 && aborted >= 1 && total === requestHandlers.length) await _abort.apply(request, abortArgs);
|
|
124
|
+
else if (continued === requestHandlers.length) await _continue.apply(request, continueArgs);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// tslint:disable-next-line: only-arrow-functions
|
|
129
|
+
request.respond = async function () { responded++; respondArgs = respondArgs || arguments; await handleRequest(); };
|
|
130
|
+
// tslint:disable-next-line: only-arrow-functions
|
|
131
|
+
request.abort = async function () { aborted++; abortArgs = abortArgs || arguments; await handleRequest(); };
|
|
132
|
+
// tslint:disable-next-line: only-arrow-functions
|
|
133
|
+
request.continue = async function () { continued++; continueArgs = continueArgs || arguments; await handleRequest(); };
|
|
134
|
+
|
|
135
|
+
requestHandlers.forEach(handler => handler(request));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const _pageOn = page.on;
|
|
139
|
+
page.on = function async(eventName, handler) {
|
|
140
|
+
if (eventName === 'request') {
|
|
141
|
+
requestHandlers.push(handler);
|
|
142
|
+
|
|
143
|
+
return page;
|
|
144
|
+
} else {
|
|
145
|
+
return _pageOn.call(page, eventName, handler);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (this.requiresInterception) {
|
|
150
|
+
await page.setRequestInterception(true);
|
|
151
|
+
|
|
152
|
+
const thisOnRequest = this.onRequest.bind(this);
|
|
153
|
+
page.on('request', thisOnRequest);
|
|
154
|
+
offOnClose.push(() => page.off('request', thisOnRequest));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const thisOnDialog = this.onDialog.bind(this);
|
|
158
|
+
page.on('dialog', thisOnDialog);
|
|
159
|
+
offOnClose.push(() => page.off('dialog', thisOnDialog));
|
|
160
|
+
|
|
161
|
+
await this.onPageCreated(page);
|
|
162
|
+
}
|
|
163
|
+
protected async onPageCreated(_page: Puppeteer.Page) { }
|
|
164
|
+
|
|
165
|
+
protected async onRequest(request: Puppeteer.HTTPRequest) {
|
|
166
|
+
const interceptionHandled = (request as any)._interceptionHandled;
|
|
167
|
+
if (interceptionHandled) return;
|
|
168
|
+
if (this.isStopped) return request.continue();
|
|
169
|
+
|
|
170
|
+
await this.processRequest(request);
|
|
171
|
+
}
|
|
172
|
+
protected async processRequest(_request: Puppeteer.HTTPRequest) { }
|
|
173
|
+
|
|
174
|
+
protected async onDialog(dialog: Puppeteer.Dialog) {
|
|
175
|
+
const handled = (dialog as any)._handled;
|
|
176
|
+
|
|
177
|
+
if (handled) return;
|
|
178
|
+
if (this.isStopped) return;
|
|
179
|
+
|
|
180
|
+
await this.processDialog(dialog);
|
|
181
|
+
}
|
|
182
|
+
protected async processDialog(_dialog: Puppeteer.Dialog) { }
|
|
183
|
+
|
|
184
|
+
protected async beforeRestart() { }
|
|
185
|
+
async restart() {
|
|
186
|
+
await this.beforeRestart();
|
|
187
|
+
|
|
188
|
+
this.startCounter++;
|
|
189
|
+
if (this.requiresInterception) interceptions++;
|
|
190
|
+
|
|
191
|
+
this.dependencies.forEach(x => x.restart());
|
|
192
|
+
|
|
193
|
+
await this.afterRestart();
|
|
194
|
+
}
|
|
195
|
+
protected async afterRestart() { }
|
|
196
|
+
|
|
197
|
+
protected async beforeStop() { }
|
|
198
|
+
async stop() {
|
|
199
|
+
await this.beforeStop();
|
|
200
|
+
|
|
201
|
+
this.startCounter--;
|
|
202
|
+
if (this.requiresInterception) interceptions--;
|
|
203
|
+
|
|
204
|
+
if (interceptions === 0 && this.browser) {
|
|
205
|
+
const pages = await this.browser.pages();
|
|
206
|
+
|
|
207
|
+
pages.filter(x => !x.isClosed()).forEach(async (page: Puppeteer.Page) => {
|
|
208
|
+
await page.setRequestInterception(false);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.dependencies.forEach(x => x.stop());
|
|
213
|
+
|
|
214
|
+
await this.afterStop();
|
|
215
|
+
}
|
|
216
|
+
protected async afterStop() { }
|
|
217
|
+
|
|
218
|
+
protected async getFirstPage() {
|
|
219
|
+
if (!this.browser) return null;
|
|
220
|
+
|
|
221
|
+
const pages = await this.browser.pages();
|
|
222
|
+
const openPages = pages.filter(x => !x.isClosed());
|
|
223
|
+
const activePages = pages.filter(x => x.url() !== 'about:blank');
|
|
224
|
+
|
|
225
|
+
return activePages[0] || openPages[0];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let plugins: Plugin[] = [];
|
|
230
|
+
export function addPlugin(plugin: Plugin) { plugins.push(plugin); }
|
|
231
|
+
export async function clearPlugins() {
|
|
232
|
+
plugins.forEach(async plugin => {
|
|
233
|
+
await plugin.stop();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
plugins = [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
import { AnonymizeUserAgentPlugin } from './plugins/anonymize.user.agent/index';
|
|
240
|
+
export function anonymizeUserAgent(): AnonymizeUserAgentPlugin {
|
|
241
|
+
const plugin = new AnonymizeUserAgentPlugin();
|
|
242
|
+
plugins.push(plugin);
|
|
243
|
+
return plugin;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
import { AvoidDetectionPlugin } from './plugins/avoid.detection';
|
|
247
|
+
export function avoidDetection(): AvoidDetectionPlugin {
|
|
248
|
+
const plugin = new AvoidDetectionPlugin();
|
|
249
|
+
plugins.push(plugin);
|
|
250
|
+
return plugin;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
import { BlockResourcesPlugin, Resource } from './plugins/block.resources';
|
|
254
|
+
export function blockResources(...resources: Resource[]): BlockResourcesPlugin {
|
|
255
|
+
const plugin = new BlockResourcesPlugin(resources);
|
|
256
|
+
plugins.push(plugin);
|
|
257
|
+
return plugin;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
import { DisableDialogsPlugin } from './plugins/disable.dialogs';
|
|
261
|
+
export function disableDialogs(logMessages = false): DisableDialogsPlugin {
|
|
262
|
+
const plugin = new DisableDialogsPlugin(logMessages);
|
|
263
|
+
plugins.push(plugin);
|
|
264
|
+
return plugin;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
import { ManageCookiesPlugin, ManageCookiesOption } from './plugins/manage.cookies';
|
|
268
|
+
export function manageCookies(opts: ManageCookiesOption): ManageCookiesPlugin {
|
|
269
|
+
const plugin = new ManageCookiesPlugin(opts);
|
|
270
|
+
plugins.push(plugin);
|
|
271
|
+
return plugin;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
import { SolveRecaptchaPlugin } from './plugins/solve.recaptcha';
|
|
275
|
+
export function solveRecaptchas(accessToken: string): SolveRecaptchaPlugin {
|
|
276
|
+
const plugin = new SolveRecaptchaPlugin(accessToken);
|
|
277
|
+
plugins.push(plugin);
|
|
278
|
+
return plugin;
|
|
279
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as Puppeteer from 'puppeteer';
|
|
2
|
+
import UserAgent = require('user-agents');
|
|
3
|
+
|
|
4
|
+
import { Plugin } from '../../index';
|
|
5
|
+
|
|
6
|
+
const sleep = (time: number) => { return new Promise(resolve => { setTimeout(resolve, time); }); };
|
|
7
|
+
|
|
8
|
+
interface PageUserAgent {
|
|
9
|
+
target: Puppeteer.Page;
|
|
10
|
+
userAgent: string;
|
|
11
|
+
newUserAgent: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class AnonymizeUserAgentPlugin extends Plugin {
|
|
15
|
+
private pages: PageUserAgent[] = [];
|
|
16
|
+
private userAgent?: string;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
this.userAgent = new UserAgent({ vendor: 'Google Inc.', platform: 'Win32' }).toString();
|
|
23
|
+
}
|
|
24
|
+
catch (ex) {
|
|
25
|
+
console.warn('Could not create a random user agent');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected async afterLaunch(browser: Puppeteer.Browser) {
|
|
30
|
+
const _newPage = browser.newPage;
|
|
31
|
+
browser.newPage = async (): Promise<Puppeteer.Page> => {
|
|
32
|
+
const page = await _newPage.apply(browser);
|
|
33
|
+
await sleep(100); // Sleep to allow user agent to set
|
|
34
|
+
return page;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected async onClose() {
|
|
39
|
+
this.pages = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected async onPageCreated(page: Puppeteer.Page) {
|
|
43
|
+
const userAgent = await page.browser().userAgent();
|
|
44
|
+
const newUserAgent = this.userAgent || userAgent.replace('HeadlessChrome/', 'Chrome/').replace(/\(([^)]+)\)/, '(Windows NT 10.0; Win64; x64)');
|
|
45
|
+
|
|
46
|
+
this.pages.push({ target: page, userAgent, newUserAgent });
|
|
47
|
+
|
|
48
|
+
await page.setUserAgent(newUserAgent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected async beforeRestart() {
|
|
52
|
+
for (const page of this.pages) {
|
|
53
|
+
if (page.target.isClosed()) continue;
|
|
54
|
+
|
|
55
|
+
await page.target.setUserAgent(page.newUserAgent);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected async afterStop() {
|
|
60
|
+
for (const page of this.pages) {
|
|
61
|
+
if (page.target.isClosed()) continue;
|
|
62
|
+
|
|
63
|
+
await page.target.setUserAgent(page.userAgent);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const PuppeteerPro = require('../../../dist/index');
|
|
2
|
+
const chai = require('chai');
|
|
3
|
+
|
|
4
|
+
const expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
const sleep = time => { return new Promise(resolve => { setTimeout(resolve, time); }); };
|
|
7
|
+
|
|
8
|
+
module.exports = plugin => async browserWSEndpoint => {
|
|
9
|
+
const browser = browserWSEndpoint ? await PuppeteerPro.connect({ browserWSEndpoint }) : await PuppeteerPro.launch();
|
|
10
|
+
let page;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
page = await browser.newPage();
|
|
14
|
+
|
|
15
|
+
const getResult = async () => {
|
|
16
|
+
await page.goto('https://httpbin.org/headers');
|
|
17
|
+
await sleep(100);
|
|
18
|
+
|
|
19
|
+
const data = await page.evaluate(() => JSON.parse(document.body.innerText));
|
|
20
|
+
return data.headers['User-Agent'];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
expect(await getResult()).to.not.contain('Headless');
|
|
24
|
+
|
|
25
|
+
await plugin.stop();
|
|
26
|
+
expect(await getResult()).to.contain('Headless');
|
|
27
|
+
|
|
28
|
+
await plugin.restart();
|
|
29
|
+
expect(await getResult()).to.not.contain('Headless');
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
if (page) await page.close();
|
|
33
|
+
if (browser) await browser.close();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as Puppeteer from 'puppeteer';
|
|
4
|
+
|
|
5
|
+
import { Plugin } from '../../index';
|
|
6
|
+
import { AnonymizeUserAgentPlugin } from './../anonymize.user.agent/index';
|
|
7
|
+
|
|
8
|
+
const injectionsFolder = path.resolve(`${__dirname}/injections`);
|
|
9
|
+
const injections = fs.readdirSync(injectionsFolder).map(fileName => require(`${injectionsFolder}/${fileName}`));
|
|
10
|
+
|
|
11
|
+
export class AvoidDetectionPlugin extends Plugin {
|
|
12
|
+
dependencies = [new AnonymizeUserAgentPlugin()];
|
|
13
|
+
|
|
14
|
+
protected async onPageCreated(page: Puppeteer.Page) {
|
|
15
|
+
for (const injection of injections) {
|
|
16
|
+
if (!this.isStopped && !page.isClosed()) await page.evaluateOnNewDocument(injection);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const PuppeteerPro = require('../../../dist/index');
|
|
2
|
+
const chai = require('chai');
|
|
3
|
+
|
|
4
|
+
const expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
const sleep = time => { return new Promise(resolve => { setTimeout(resolve, time); }); };
|
|
7
|
+
|
|
8
|
+
module.exports = plugin => async browserWSEndpoint => {
|
|
9
|
+
const browser = browserWSEndpoint ? await PuppeteerPro.connect({ browserWSEndpoint }) : await PuppeteerPro.launch();
|
|
10
|
+
let page;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
page = await browser.newPage();
|
|
14
|
+
|
|
15
|
+
const getResult = async () => {
|
|
16
|
+
try {
|
|
17
|
+
await page.goto('https://bot.sannysoft.com');
|
|
18
|
+
await sleep(1000);
|
|
19
|
+
|
|
20
|
+
// Disable hairline as it seems there is a race condition. Test results keep changing after every run even though the detection is running.
|
|
21
|
+
return await page.evaluate(() => document.querySelector('table').querySelectorAll('.failed:not(#hairline-feature)').length === 0);
|
|
22
|
+
}
|
|
23
|
+
catch (ex) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
expect(await getResult()).to.be.true;
|
|
29
|
+
|
|
30
|
+
await plugin.stop();
|
|
31
|
+
expect(await getResult()).to.be.false;
|
|
32
|
+
|
|
33
|
+
await plugin.restart();
|
|
34
|
+
expect(await getResult()).to.be.true;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (page) await page.close();
|
|
38
|
+
if (browser) await browser.close();
|
|
39
|
+
}
|
|
40
|
+
};
|