@browserless/goto 9.12.4 → 10.0.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/LICENSE.md CHANGED
File without changes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@browserless/goto",
3
3
  "description": "Go to a page aborting unnecessary requests",
4
4
  "homepage": "https://browserless.js.org/#/?id=gotopage-options",
5
- "version": "9.12.4",
5
+ "version": "10.0.0",
6
6
  "main": "src/index.js",
7
7
  "author": {
8
8
  "email": "hello@microlink.io",
@@ -29,7 +29,7 @@
29
29
  "puppeteer"
30
30
  ],
31
31
  "dependencies": {
32
- "@browserless/devices": "^9.10.2",
32
+ "@browserless/devices": "^10.0.0",
33
33
  "@cliqz/adblocker-puppeteer": "~1.26.6",
34
34
  "debug-logfmt": "~1.0.4",
35
35
  "got": "~11.8.6",
@@ -39,15 +39,12 @@
39
39
  "pretty-ms": "~7.0.1",
40
40
  "shallow-equal": "~3.1.0",
41
41
  "time-span": "~4.0.0",
42
- "top-user-agents": "~1.0.57",
43
- "tough-cookie": "~4.1.3",
44
- "unique-random-array": "~2.0.0"
42
+ "tough-cookie": "~4.1.3"
45
43
  },
46
44
  "devDependencies": {
47
- "@browserless/test": "^9.11.0",
45
+ "@browserless/test": "^10.0.0",
48
46
  "ava": "latest",
49
- "browserless": "latest",
50
- "fpscanner": "latest",
47
+ "is-ci": "latest",
51
48
  "p-wait-for": "3"
52
49
  },
53
50
  "engines": {
@@ -57,10 +54,6 @@
57
54
  "scripts",
58
55
  "src"
59
56
  ],
60
- "scripts": {
61
- "postinstall": "node scripts/postinstall",
62
- "test": "ava"
63
- },
64
57
  "license": "MIT",
65
58
  "ava": {
66
59
  "files": [
@@ -70,5 +63,9 @@
70
63
  "timeout": "30s",
71
64
  "workerThreads": false
72
65
  },
73
- "gitHead": "5b81d2e12abab0726176894365f4fb4b4efd6580"
74
- }
66
+ "gitHead": "9085ee165094687aaf15c2d9a429d1c6acb9fe83",
67
+ "scripts": {
68
+ "postinstall": "node scripts/postinstall",
69
+ "test": "ava"
70
+ }
71
+ }
package/src/index.js CHANGED
@@ -19,10 +19,6 @@ debug.adblock = require('debug-logfmt')('browserless:goto:adblock')
19
19
 
20
20
  const truncate = (str, n = 80) => (str.length > n ? str.substr(0, n - 1) + '…' : str)
21
21
 
22
- const EVASIONS = require('./evasions')
23
-
24
- const ALL_EVASIONS_KEYS = Object.keys(EVASIONS)
25
-
26
22
  const engine = PuppeteerBlocker.deserialize(
27
23
  new Uint8Array(fs.readFileSync(path.resolve(__dirname, './engine.bin')))
28
24
  )
@@ -155,12 +151,7 @@ const inject = async (page, { timeout, mediaType, animations, modules, scripts,
155
151
  return Promise.all(postPromises)
156
152
  }
157
153
 
158
- module.exports = ({
159
- evasions = ALL_EVASIONS_KEYS,
160
- defaultDevice = 'Macbook Pro 13',
161
- timeout: globalTimeout,
162
- ...deviceOpts
163
- }) => {
154
+ module.exports = ({ defaultDevice = 'Macbook Pro 13', timeout: globalTimeout, ...deviceOpts }) => {
164
155
  const getDevice = createDevices(deviceOpts)
165
156
  const { viewport: defaultViewport } = getDevice.findDevice(defaultDevice)
166
157
 
@@ -249,7 +240,8 @@ module.exports = ({
249
240
  }
250
241
 
251
242
  const enableInterception =
252
- (onPageRequest || abortTypes.length > 0) && run({ fn: page.setRequestInterception(true), debug: 'enableInterception' })
243
+ (onPageRequest || abortTypes.length > 0) &&
244
+ run({ fn: page.setRequestInterception(true), debug: 'enableInterception' })
253
245
 
254
246
  if (onPageRequest) {
255
247
  Promise.resolve(enableInterception).then(() => page.on('request', onPageRequest))
@@ -363,18 +355,6 @@ module.exports = ({
363
355
  )
364
356
  }
365
357
 
366
- const applyEvasions = castArray(evasions)
367
- .filter(Boolean)
368
- .map(key =>
369
- run({
370
- fn: EVASIONS[key](page),
371
- timeout: actionTimeout,
372
- debug: key
373
- })
374
- )
375
-
376
- await Promise.all(prePromises.concat(applyEvasions))
377
-
378
358
  const { value: response, reason: error } = await run({
379
359
  fn: html
380
360
  ? page.setContent(html, { waitUntil, ...args })
@@ -442,4 +422,3 @@ module.exports = ({
442
422
 
443
423
  module.exports.parseCookies = parseCookies
444
424
  module.exports.inject = inject
445
- module.exports.evasions = ALL_EVASIONS_KEYS
@@ -1,69 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * Pass the Chrome Test.
5
- *
6
- * This will work for iframes as well, except for `srcdoc` iframes:
7
- * https://github.com/puppeteer/puppeteer/issues/1106
8
- *
9
- * Could be mocked further.
10
- */
11
- module.exports = page =>
12
- page.evaluateOnNewDocument(() => {
13
- window.chrome = {
14
- app: {
15
- isInstalled: false,
16
- InstallState: {
17
- DISABLED: 'disabled',
18
- INSTALLED: 'installed',
19
- NOT_INSTALLED: 'not_installed'
20
- },
21
- RunningState: {
22
- CANNOT_RUN: 'cannot_run',
23
- READY_TO_RUN: 'ready_to_run',
24
- RUNNING: 'running'
25
- }
26
- },
27
- runtime: {
28
- OnInstalledReason: {
29
- CHROME_UPDATE: 'chrome_update',
30
- INSTALL: 'install',
31
- SHARED_MODULE_UPDATE: 'shared_module_update',
32
- UPDATE: 'update'
33
- },
34
- OnRestartRequiredReason: {
35
- APP_UPDATE: 'app_update',
36
- OS_UPDATE: 'os_update',
37
- PERIODIC: 'periodic'
38
- },
39
- PlatformArch: {
40
- ARM: 'arm',
41
- ARM64: 'arm64',
42
- MIPS: 'mips',
43
- MIPS64: 'mips64',
44
- X86_32: 'x86-32',
45
- X86_64: 'x86-64'
46
- },
47
- PlatformNaclArch: {
48
- ARM: 'arm',
49
- MIPS: 'mips',
50
- MIPS64: 'mips64',
51
- X86_32: 'x86-32',
52
- X86_64: 'x86-64'
53
- },
54
- PlatformOs: {
55
- ANDROID: 'android',
56
- CROS: 'cros',
57
- LINUX: 'linux',
58
- MAC: 'mac',
59
- OPENBSD: 'openbsd',
60
- WIN: 'win'
61
- },
62
- RequestUpdateCheckStatus: {
63
- NO_UPDATE: 'no_update',
64
- THROTTLED: 'throttled',
65
- UPDATE_AVAILABLE: 'update_available'
66
- }
67
- }
68
- }
69
- })
@@ -1,13 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- chromeRuntime: require('./chrome-runtime'),
5
- mediaCodecs: require('./media-codecs'),
6
- navigatorPermissions: require('./navigator-permissions'),
7
- navigatorPlugins: require('./navigator-plugins'),
8
- navigatorVendor: require('./navigator-vendor'),
9
- randomizeUserAgent: require('./randomize-user-agent'),
10
- stackTraces: require('./stack-traces'),
11
- webglVendor: require('./webgl-vendor'),
12
- windowFrame: require('./window-frame')
13
- }
@@ -1,76 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * Fix Chromium not reporting "probably" to codecs like `videoEl.canPlayType('video/mp4; codecs="avc1.42E01E"')`.
5
- * (Chromium doesn't support proprietary codecs, only Chrome does)
6
- */
7
- module.exports = page =>
8
- page.evaluateOnNewDocument(() => {
9
- try {
10
- /**
11
- * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.
12
- *
13
- * @example
14
- * video/webm; codecs="vp8, vorbis"
15
- * video/mp4; codecs="avc1.42E01E"
16
- * audio/x-m4a;
17
- * audio/ogg; codecs="vorbis"
18
- * @param {String} arg
19
- */
20
- const parseInput = arg => {
21
- const [mime, codecStr] = arg.trim().split(';')
22
- let codecs = []
23
- if (codecStr && codecStr.includes('codecs="')) {
24
- codecs = codecStr
25
- .trim()
26
- .replace('codecs="', '')
27
- .replace('"', '')
28
- .trim()
29
- .split(',')
30
- .filter(x => !!x)
31
- .map(x => x.trim())
32
- }
33
- return { mime, codecStr, codecs }
34
- }
35
-
36
- /* global HTMLMediaElement */
37
- const canPlayType = {
38
- // Make toString() native
39
- get (target, key) {
40
- // Mitigate Chromium bug (#130)
41
- if (typeof target[key] === 'function') {
42
- return target[key].bind(target)
43
- }
44
- return Reflect.get(target, key)
45
- },
46
- // Intercept certain requests
47
- apply: function (target, ctx, args) {
48
- if (!args || !args.length) {
49
- return target.apply(ctx, args)
50
- }
51
- const { mime, codecs } = parseInput(args[0])
52
- // This specific mp4 codec is missing in Chromium
53
- if (mime === 'video/mp4') {
54
- if (codecs.includes('avc1.42E01E')) {
55
- return 'probably'
56
- }
57
- }
58
- // This mimetype is only supported if no codecs are specified
59
- if (mime === 'audio/x-m4a' && !codecs.length) {
60
- return 'maybe'
61
- }
62
-
63
- // This mimetype is only supported if no codecs are specified
64
- if (mime === 'audio/aac' && !codecs.length) {
65
- return 'probably'
66
- }
67
- // Everything else as usual
68
- return target.apply(ctx, args)
69
- }
70
- }
71
- HTMLMediaElement.prototype.canPlayType = new Proxy(
72
- HTMLMediaElement.prototype.canPlayType,
73
- canPlayType
74
- )
75
- } catch (err) {}
76
- })
@@ -1,43 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * Pass the Permissions Test.
5
- */
6
- module.exports = page =>
7
- page.evaluateOnNewDocument(() => {
8
- if (!window.Notification) {
9
- window.Notification = {
10
- permission: 'denied'
11
- }
12
- }
13
-
14
- const originalQuery = window.navigator.permissions.query
15
- // eslint-disable-next-line
16
- window.navigator.permissions.__proto__.query = parameters =>
17
- parameters.name === 'notifications'
18
- ? Promise.resolve({ state: window.Notification.permission })
19
- : originalQuery(parameters)
20
-
21
- // Inspired by: https://github.com/ikarienator/phantomjs_hide_and_seek/blob/master/5.spoofFunctionBind.js
22
- const oldCall = Function.prototype.call
23
- function call () {
24
- return oldCall.apply(this, arguments)
25
- }
26
- // eslint-disable-next-line
27
- Function.prototype.call = call
28
-
29
- const nativeToStringFunctionString = Error.toString().replace(/Error/g, 'toString')
30
- const oldToString = Function.prototype.toString
31
-
32
- function functionToString () {
33
- if (this === window.navigator.permissions.query) {
34
- return 'function query() { [native code] }'
35
- }
36
- if (this === functionToString) {
37
- return nativeToStringFunctionString
38
- }
39
- return oldCall.call(oldToString, this)
40
- }
41
- // eslint-disable-next-line
42
- Function.prototype.toString = functionToString
43
- })
@@ -1,199 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.
5
- * This plugin quite emulates both of these to match regular headful Chrome.
6
- * We even go so far as to mock functional methods, instance types and `.toString` properties. :D
7
- */
8
- module.exports = page =>
9
- page.evaluateOnNewDocument(() => {
10
- function mockPluginsAndMimeTypes () {
11
- /* global MimeType MimeTypeArray PluginArray */
12
-
13
- // Disguise custom functions as being native
14
- const makeFnsNative = (fns = []) => {
15
- const oldCall = Function.prototype.call
16
- function call () {
17
- return oldCall.apply(this, arguments)
18
- }
19
- // eslint-disable-next-line
20
- Function.prototype.call = call
21
-
22
- const nativeToStringFunctionString = Error.toString().replace(/Error/g, 'toString')
23
- const oldToString = Function.prototype.toString
24
-
25
- function functionToString () {
26
- for (const fn of fns) {
27
- if (this === fn.ref) {
28
- return `function ${fn.name}() { [native code] }`
29
- }
30
- }
31
-
32
- if (this === functionToString) {
33
- return nativeToStringFunctionString
34
- }
35
- return oldCall.call(oldToString, this)
36
- }
37
- // eslint-disable-next-line
38
- Function.prototype.toString = functionToString
39
- }
40
-
41
- const mockedFns = []
42
-
43
- const fakeData = {
44
- mimeTypes: [
45
- {
46
- type: 'application/pdf',
47
- suffixes: 'pdf',
48
- description: '',
49
- __pluginName: 'Chrome PDF Viewer'
50
- },
51
- {
52
- type: 'application/x-google-chrome-pdf',
53
- suffixes: 'pdf',
54
- description: 'Portable Document Format',
55
- __pluginName: 'Chrome PDF Plugin'
56
- },
57
- {
58
- type: 'application/x-nacl',
59
- suffixes: '',
60
- description: 'Native Client Executable',
61
- // eslint-disable-next-line
62
- enabledPlugin: Plugin,
63
- __pluginName: 'Native Client'
64
- },
65
- {
66
- type: 'application/x-pnacl',
67
- suffixes: '',
68
- description: 'Portable Native Client Executable',
69
- __pluginName: 'Native Client'
70
- }
71
- ],
72
- plugins: [
73
- {
74
- name: 'Chrome PDF Plugin',
75
- filename: 'internal-pdf-viewer',
76
- description: 'Portable Document Format'
77
- },
78
- {
79
- name: 'Chrome PDF Viewer',
80
- filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
81
- description: ''
82
- },
83
- {
84
- name: 'Native Client',
85
- filename: 'internal-nacl-plugin',
86
- description: ''
87
- }
88
- ],
89
- fns: {
90
- namedItem: instanceName => {
91
- // Returns the Plugin/MimeType with the specified name.
92
- const fn = function (name) {
93
- if (!arguments.length) {
94
- throw new TypeError(
95
- `Failed to execute 'namedItem' on '${instanceName}': 1 argument required, but only 0 present.`
96
- )
97
- }
98
- return this[name] || null
99
- }
100
- mockedFns.push({ ref: fn, name: 'namedItem' })
101
- return fn
102
- },
103
- item: instanceName => {
104
- // Returns the Plugin/MimeType at the specified index into the array.
105
- const fn = function (index) {
106
- if (!arguments.length) {
107
- throw new TypeError(
108
- `Failed to execute 'namedItem' on '${instanceName}': 1 argument required, but only 0 present.`
109
- )
110
- }
111
- return this[index] || null
112
- }
113
- mockedFns.push({ ref: fn, name: 'item' })
114
- return fn
115
- },
116
- refresh: instanceName => {
117
- // Refreshes all plugins on the current page, optionally reloading documents.
118
- const fn = function () {
119
- return undefined
120
- }
121
- mockedFns.push({ ref: fn, name: 'refresh' })
122
- return fn
123
- }
124
- }
125
- }
126
- // Poor mans _.pluck
127
- const getSubset = (keys, obj) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {})
128
-
129
- function generateMimeTypeArray () {
130
- const arr = fakeData.mimeTypes
131
- .map(obj => getSubset(['type', 'suffixes', 'description'], obj))
132
- .map(obj => Object.setPrototypeOf(obj, MimeType.prototype))
133
- arr.forEach(obj => {
134
- arr[obj.type] = obj
135
- })
136
-
137
- // Mock functions
138
- arr.namedItem = fakeData.fns.namedItem('MimeTypeArray')
139
- arr.item = fakeData.fns.item('MimeTypeArray')
140
-
141
- return Object.setPrototypeOf(arr, MimeTypeArray.prototype)
142
- }
143
-
144
- const mimeTypeArray = generateMimeTypeArray()
145
- Object.defineProperty(navigator, 'mimeTypes', {
146
- get: () => mimeTypeArray
147
- })
148
-
149
- function generatePluginArray () {
150
- const arr = fakeData.plugins
151
- .map(obj => getSubset(['name', 'filename', 'description'], obj))
152
- .map(obj => {
153
- const mimes = fakeData.mimeTypes.filter(m => m.__pluginName === obj.name)
154
- // Add mimetypes
155
- mimes.forEach((mime, index) => {
156
- navigator.mimeTypes[mime.type].enabledPlugin = obj
157
- obj[mime.type] = navigator.mimeTypes[mime.type]
158
- obj[index] = navigator.mimeTypes[mime.type]
159
- })
160
- obj.length = mimes.length
161
- return obj
162
- })
163
- .map(obj => {
164
- // Mock functions
165
- obj.namedItem = fakeData.fns.namedItem('Plugin')
166
- obj.item = fakeData.fns.item('Plugin')
167
- return obj
168
- })
169
- // eslint-disable-next-line
170
- .map(obj => Object.setPrototypeOf(obj, Plugin.prototype))
171
- arr.forEach(obj => {
172
- arr[obj.name] = obj
173
- })
174
-
175
- // Mock functions
176
- arr.namedItem = fakeData.fns.namedItem('PluginArray')
177
- arr.item = fakeData.fns.item('PluginArray')
178
- arr.refresh = fakeData.fns.refresh('PluginArray')
179
-
180
- return Object.setPrototypeOf(arr, PluginArray.prototype)
181
- }
182
-
183
- const pluginArray = generatePluginArray()
184
- Object.defineProperty(navigator, 'plugins', {
185
- get: () => pluginArray
186
- })
187
-
188
- // Make mockedFns toString() representation resemble a native function
189
- makeFnsNative(mockedFns)
190
- }
191
- try {
192
- const isPluginArray = navigator.plugins instanceof PluginArray
193
- const hasPlugins = isPluginArray && navigator.plugins.length > 0
194
- if (isPluginArray && hasPlugins) {
195
- return // nothing to do here
196
- }
197
- mockPluginsAndMimeTypes()
198
- } catch (err) {}
199
- })
@@ -1,25 +0,0 @@
1
- module.exports = page =>
2
- page.evaluateOnNewDocument(async () => {
3
- const getUserAgentVendor = userAgent => {
4
- let vendor
5
-
6
- if (userAgent.includes('Firefox')) {
7
- vendor = 'firefox'
8
- } else if (userAgent.includes('Chrome')) {
9
- vendor = 'chrome'
10
- } else {
11
- vendor = 'safari'
12
- }
13
-
14
- return vendor
15
- }
16
-
17
- const userAgent = navigator.userAgent
18
- const userAgentVendor = getUserAgentVendor(userAgent)
19
-
20
- if (userAgentVendor === 'chrome') return
21
-
22
- Object.defineProperty(navigator, 'vendor', {
23
- value: userAgentVendor === 'safari' ? 'Apple Computer, Inc.' : ''
24
- })
25
- })
@@ -1,8 +0,0 @@
1
- 'use strict'
2
-
3
- const uniqueRandomArray = require('unique-random-array')
4
- const userAgents = require('top-user-agents')
5
-
6
- const randomUserAgent = uniqueRandomArray(userAgents)
7
-
8
- module.exports = page => page.setUserAgent(randomUserAgent())
@@ -1,51 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- *
5
- * Prevent detect Puppeteer via variable name
6
- *
7
- * References:
8
- * - https://github.com/digitalhurricane-io/puppeteer-detection-100-percent
9
- * - https://github.com/berstend/puppeteer-extra/issues/209
10
- */
11
- module.exports = page =>
12
- page.evaluateOnNewDocument(() => {
13
- const errors = {
14
- Error,
15
- EvalError,
16
- RangeError,
17
- ReferenceError,
18
- SyntaxError,
19
- TypeError,
20
- URIError
21
- }
22
- for (const name in errors) {
23
- // eslint-disable-next-line
24
- globalThis[name] = (function (NativeError) {
25
- return function (message) {
26
- const err = new NativeError(message)
27
- const stub = {
28
- message: err.message,
29
- name: err.name,
30
- toString: () => err.toString(),
31
- get stack () {
32
- const lines = err.stack.split('\n')
33
- lines.splice(1, 1) // remove anonymous function above
34
- lines.pop() // remove puppeteer line
35
- return lines.join('\n')
36
- }
37
- }
38
- // eslint-disable-next-line
39
- if (this === globalThis) {
40
- // called as function, not constructor
41
- // eslint-disable-next-line
42
- stub.__proto__ = NativeError
43
- return stub
44
- }
45
- Object.assign(this, stub)
46
- // eslint-disable-next-line
47
- this.__proto__ = NativeError
48
- }
49
- })(errors[name])
50
- }
51
- })
@@ -1,50 +0,0 @@
1
- /**
2
- * Fix WebGL Vendor/Renderer being set to Google in headless mode
3
- */
4
- module.exports = page =>
5
- page.evaluateOnNewDocument(() => {
6
- // Remove traces of our Proxy ;-)
7
- const stripErrorStack = stack =>
8
- stack
9
- .split('\n')
10
- .filter(line => !line.includes('at Object.apply'))
11
- .filter(line => !line.includes('at Object.get'))
12
- .join('\n')
13
-
14
- const getParameterProxyHandler = {
15
- get (target, key) {
16
- try {
17
- // Mitigate Chromium bug (#130)
18
- if (typeof target[key] === 'function') {
19
- return target[key].bind(target)
20
- }
21
- return Reflect.get(target, key)
22
- } catch (err) {
23
- err.stack = stripErrorStack(err.stack)
24
- throw err
25
- }
26
- },
27
- apply: function (target, thisArg, args) {
28
- const param = (args || [])[0]
29
- // UNMASKED_VENDOR_WEBGL
30
- if (param === 37445) return 'Intel Inc.'
31
- // UNMASKED_RENDERER_WEBGL
32
- if (param === 37446) return 'Intel(R) Iris(TM) Plus Graphics 640'
33
- try {
34
- return Reflect.apply(target, thisArg, args)
35
- } catch (err) {
36
- err.stack = stripErrorStack(err.stack)
37
- throw err
38
- }
39
- }
40
- }
41
-
42
- ;['WebGLRenderingContext', 'WebGL2RenderingContext'].forEach(function (ctx) {
43
- Object.defineProperty(window[ctx].prototype, 'getParameter', {
44
- configurable: true,
45
- enumerable: false,
46
- writable: false,
47
- value: new Proxy(window[ctx].prototype.getParameter, getParameterProxyHandler)
48
- })
49
- })
50
- })
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = page =>
4
- page.evaluateOnNewDocument(() => {
5
- if (window.outerWidth && window.outerHeight) return
6
- const windowFrame = 85
7
- window.outerWidth = window.innerWidth
8
- window.outerHeight = window.innerHeight + windowFrame
9
- })