UNPKG

puppeteer-extra-plugin-stealth

Version:

Stealth mode: Applies various techniques to make detection of headless puppeteer harder.

102 lines (86 loc) 3.53 kB
'use strict' const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin') const utils = require('../_utils') const withUtils = require('../_utils/withUtils') const { generateMimeTypeArray } = require('./mimeTypes') const { generatePluginArray } = require('./plugins') const { generateMagicArray } = require('./magicArray') const { generateFunctionMocks } = require('./functionMocks') const data = require('./data.json') /** * In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty. * This plugin emulates both of these with functional mocks to match regular headful Chrome. * * Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time. * * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes * @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins * @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray */ class Plugin extends PuppeteerExtraPlugin { constructor(opts = {}) { super(opts) } get name() { return 'stealth/evasions/navigator.plugins' } async onPageCreated(page) { await withUtils(page).evaluateOnNewDocument( (utils, { fns, data }) => { fns = utils.materializeFns(fns) // That means we're running headful const hasPlugins = 'plugins' in navigator && navigator.plugins.length if (hasPlugins) { return // nothing to do here } const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes) const plugins = fns.generatePluginArray(utils, fns)(data.plugins) // Plugin and MimeType cross-reference each other, let's do that now // Note: We're looping through `data.plugins` here, not the generated `plugins` for (const pluginData of data.plugins) { pluginData.__mimeTypes.forEach((type, index) => { plugins[pluginData.name][index] = mimeTypes[type] Object.defineProperty(plugins[pluginData.name], type, { value: mimeTypes[type], writable: false, enumerable: false, // Not enumerable configurable: true }) Object.defineProperty(mimeTypes[type], 'enabledPlugin', { value: type === 'application/x-pnacl' ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks : new Proxy(plugins[pluginData.name], {}), // Prevent circular references writable: false, enumerable: false, // Important: `JSON.stringify(navigator.plugins)` configurable: true }) }) } const patchNavigator = (name, value) => utils.replaceProperty(Object.getPrototypeOf(navigator), name, { get() { return value } }) patchNavigator('mimeTypes', mimeTypes) patchNavigator('plugins', plugins) // All done }, { // We pass some functions to evaluate to structure the code more nicely fns: utils.stringifyFns({ generateMimeTypeArray, generatePluginArray, generateMagicArray, generateFunctionMocks }), data } ) } } module.exports = function(pluginConfig) { return new Plugin(pluginConfig) }