[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig helps developers define and maintain\n# consistent coding styles between different editors and IDEs.\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": [\"prettier-standard\"],\n  \"rules\": {\n    \"lines-between-class-members\": \"off\"\n  }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: https://www.buymeacoffee.com/brstnd\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.md",
    "content": "---\nname: Bug report\nabout: Create a bug report\ntitle: '[Bug] '\nlabels: 'issue: bug report, needs triage'\n---\n\n<!--\n  Thanks for filing an issue!\n  Kindly search the issue tracker before posting a new issue.\n\n  Due to bad experiences we have to unfortunately mention it:\n  - Be respectful with our time, this is an open-source project run by volunteers in their free time.\n  - Don't come across as entitled, unfriendly or angry or nobody will help you (general rule in life).\n  - Reporting a \"bug\" without ways to reproduce it is not \"contributing to open-source\".\n\n  - NOTE: \"SiteA.com stopped working\" is not a valid stealth-plugin bug report, unless used\n    as an example to show a technical issue while providing specific example code of the problem.\n    For general purpose usage discussions please use the community discord: https://extra.community\n-->\n\n**Describe the bug**\n\n<!--\n  - What you were trying to accomplish when the bug occurred\n  - A description of what you expected and what actually happened\n  - How to reproduce the issue\n-->\n\n**Code Snippet**\n\n<!--\n  Help us help you! Put down a short code snippet that illustrates your bug and\n  that we can run and debug locally. If possible remove everything\n  that is not related and make it as short as possible. For example:\n-->\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\n\n;(async () => {\n  const browser = await puppeteer.launch()\n  // ...\n})()\n```\n\n**Versions**\n\n<!--\nRun the following command in your project directory, and paste its results here:\n\nnpx envinfo@latest --system --binaries --npmPackages '*(puppeteer*|playwright*|automation-extra*|@extra*)'\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: Questions and Help\n    url: https://github.com/berstend/puppeteer-extra/wiki/Scraping-Chat\n    about: This issue tracker is not for support questions. You can join our Discord server and ask the community for help.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea or feature\ntitle: '[Feature] '\nlabels: 'issue: proposal'\n---\n\n**Feature request**\n\n<!--\n\nLet us know what functionality you'd like to see and what is your use case.\nDo you think others might benefit from this as well?\n\nPlease provide details about:\n\n- What you're trying to do\n- Why you can't use the project for this\n- And maybe how you think the project could handle this\n\n-->\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "# This is used with the label workflow which\n# will triage pull requests and apply a label based on the\n# paths that are modified in the pull request.\n#\n# For more information, see:\n# https://github.com/actions/labeler\n\n'package: core':\n  - packages/automation-extra/**/*\n  - packages/playright-extra/**/*\n  - packages/puppeteer-extra/**/*\n  - packages/automation-extra-plugin/**/*\n  - packages/puppeteer-extra-plugin/**/*\n\n'plugin: automation-extra':\n  - packages/plugin-*/**/*\n\n'plugin: puppeteer-extra':\n  - packages/puppeteer-extra-plugin-*/**/*\n\n'plugin: recaptcha 🏴':\n  - packages/*recaptcha*/**/*\n\n'plugin: stealth ㊙️':\n  - packages/*stealth*/**/*\n"
  },
  {
    "path": ".github/workflows/extract-stealth.yml",
    "content": "name: Extract stealth.min.js\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - 'packages/puppeteer-extra-plugin-stealth/**'\n      - 'packages/extract-stealth-evasions/**'\n      - '.github/workflows/extract-stealth.yml'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Sleep for 190 seconds\n        uses: jakejarvis/wait-action@master\n        with:\n          time: '190s'\n\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      - name: 'Fix for: error fsevents@2.1.2: The platform \"linux\" is incompatible with this module.'\n        run: npx json -I -f package.json -e 'this.resolutions={}'\n\n      - name: Build packages\n        run: |\n          yarn install\n          yarn bootstrap\n          yarn build\n\n      - name: Extract stealth.min.js\n        run: |\n          cd packages/extract-stealth-evasions\n          node index.js\n          cp stealth.min.js ../../\n\n      - name: Commit stealth.min.js\n        uses: EndBug/add-and-commit@v4\n        with:\n          add: 'stealth.min.js'\n          force: true\n          ref: 'stealth-js'\n          message: 'Auto-updated stealth.min.js with newest evasions'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/label.yml",
    "content": "# This workflow will triage pull requests and apply a label based on the\n# paths that are modified in the pull request.\n#\n# To use this workflow, you will need to set up a .github/labeler.yml\n# file with configuration.  For more information, see:\n# https://github.com/actions/labeler\n\nname: \"Pull Request Labeler\"\non:\n- pull_request_target\n\njobs:\n  triage:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/labeler@main\n      with:\n        repo-token: \"${{ secrets.GITHUB_TOKEN }}\"\n        sync-labels: true"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  pull_request:\n    branches:\n      - '*'\n    types:\n      - opened\n      - synchronize\n      - reopened\n  push:\n    branches:\n      - master\n      - 'test/*'\n\n  workflow_dispatch:\n    branches:\n      - '*'\nenv:\n  CI: 'true'\n  FORCE_COLOR: 'true'\n\njobs:\n  test:\n    name: node v${{ matrix.node }}, pptr ${{ matrix.puppeteer_version }}, ${{ matrix.os }}\n\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        node:\n          # - 16\n          - 14\n        puppeteer_version:\n          # - 15.5.0\n          - 14.2.0 # Chromium 103.0.5059.0 # requires >=14.1.0\n          # - 10.2.0 # Chromium 93.0.4577.0\n          # - 7.0.0 # Chromium 90.0.4403.0, Feb 3, 2021\n          # - 5.5.0 # Chromium 88.0.4298.0\n          # - 5.0.0 # Chromium 83.0.4103.0, Jul 2, 2020\n          # - 2.1.1 # Chromium 79.0.3942.0, Oct 24 2019\n          # - 2.0.0 # Chromium 79.0.3942.0, Oct 24 2019\n          # - 1.20.0 # Chromium 78.0.3882.0, Sep 13 2019\n          # - 1.15.0 # Chromium 75.0.3765.0, Apr 26 2019\n          # - 1.9.0 # Chromium 71.0.3563.0, Oct 4, 2018\n          # - 1.6.2 # Chromium 69.0.3494.0, Aug 1, 2018\n\n        os:\n          - ubuntu-latest\n    #           - macOS-latest\n    # - windows-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      - name: Setup Node\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node }}\n\n      - name: 'Fix for: error fsevents@2.1.2: The platform \"linux\" is incompatible with this module.'\n        run: npx json -I -f package.json -e 'this.resolutions={}'\n\n      - name: yarn install\n        uses: bahmutov/npm-install@v1\n\n      - name: yarn bootstrap\n        run: yarn bootstrap\n\n      - name: install puppeteer@${{ matrix.puppeteer_version }}\n        run: yarn lerna add --dev puppeteer@${{ matrix.puppeteer_version }}\n\n      - name: lerna link\n        run: yarn lerna link\n\n      - name: lerna build\n        run: yarn lerna run build --concurrency 1\n\n      - name: debug\n        run: |\n          yarn list --pattern \"puppeteer|puppeteer-extra|playwright\"\n          file node_modules/puppeteer-extra/dist/index.cjs.js\n      - uses: microsoft/playwright-github-action@v1\n\n      - name: playwright install\n        run: cd packages/playwright-extra && yarn playwright install\n\n      - name: test\n        uses: GabrielBB/xvfb-action@v1\n        env:\n          DISPLAY: ':99.0'\n        with:\n          run: yarn test-ci\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n\n# builds\nbuild\ndist\n\n# misc\n.DS_Store\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n.cache\n.rpt2_cache\n\nlerna-debug.log*\nlerna-error.log*\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\nTODO.md\npackages/testing/\npackages/plugin-stealth/\npackages/puppeteer-extra2/\npackages/puppeteer-extra-old/\npackages/test-*\npackages/internal-testing-*\ntesting/\n\n*.tgz*\n\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  ...require('prettier-config-standard'),\n\n  // override for Windows\n  endOfLine: 'lf',\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\ndist: trusty\naddons:\n  apt:\n    packages:\n      # This is required to run new chrome on old trusty\n      - libnss3\n\nlanguage: node_js\n\n# allow headful tests\nbefore_install:\n  - \"export DISPLAY=:99.0\"\n  - \"sh -e /etc/init.d/xvfb start\"\n\n# test against multiple node versions\nnode_js:\n- '13'\n- '10'\n\n# Fix for: error fsevents@2.1.2: The platform \"linux\" is incompatible with this module.\ninstall: skip\n\n# Prevent potential issues\ncache:\n  npm: false\n  yarn: false\n\n# test against multiple puppeteer versions\nenv:\n  - PUPPETEER_VERSION=5.0.0\n  - PUPPETEER_VERSION=2.1.1 # Chromium 79.0.3942.0, Oct 24 2019\n  # - PUPPETEER_VERSION=2.0.0 # Chromium 79.0.3942.0, Oct 24 2019\n  # - PUPPETEER_VERSION=1.20.0 # Chromium 78.0.3882.0, Sep 13 2019\n  # - PUPPETEER_VERSION=1.15.0 # Chromium 75.0.3765.0, Apr 26 2019\n  # - PUPPETEER_VERSION=1.9.0 # Chromium 71.0.3563.0, Oct 4, 2018\n  # - PUPPETEER_VERSION=1.6.2 # Chromium 69.0.3494.0, Aug 1, 2018\n\nscript:\n  # Make sure to use latest @next package\n  # https://github.com/yarnpkg/yarn/issues/4731\n\n  # Fix for: error fsevents@2.1.2: The platform \"linux\" is incompatible with this module.\n  - npx json -I -f package.json -e 'this.resolutions={}'\n  # - npx json -I -f package.json -e 'this.resolutions={\"**/puppeteer\":\"'${PUPPETEER_VERSION}'\"}'\n\n  # Install older version when required\n  - rm -rf ./node_modules/\n  # - 'yarn; echo 0'\n  # - 'yarn lerna exec \"rm -f yarn.lock; rm -rf node_modules; echo 0\"'\n  # - 'rm -rf yarn.lock && yarn cache clean && rm -rf ./node_modules/puppeteer && yarn lerna add puppeteer@${PUPPETEER_VERSION}'\n  # - \"yarn lerna exec --concurrency 1 'yarn set resolution --save puppeteer@* 5.0.0; echo 0'\"\n\n  - 'yarn'\n  - 'yarn bootstrap'\n  -  yarn lerna add puppeteer@${PUPPETEER_VERSION}\n  - 'yarn lerna link'\n  - 'yarn lerna run build --concurrency 1'\n\n  # For debugging\n  - yarn list puppeteer\n  - yarn list puppeteer-extra\n  - file node_modules/puppeteer-extra/dist/index.cjs.js\n\n  # Run tests\n  - yarn test-ci\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019 berstend <github@berstend.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# puppeteer-extra [![Downloads](https://img.shields.io/endpoint?style=social&url=https://runkit.io/fezvrasta/combined-npm-downloads/1.0.0?packages=puppeteer-extra,puppeteer-extra-plugin,puppeteer-extra-plugin-stealth,puppeteer-extra-plugin-recaptcha,puppeteer-extra-plugin-adblocker)](https://github.com/berstend/puppeteer-extra/)\n\nThis is the monorepo for [`puppeteer-extra`](./packages/puppeteer-extra), a modular plugin framework for [`puppeteer`](https://github.com/puppeteer/puppeteer). :-)\n\n🌟 **For the main documentation, please head over to the [`puppeteer-extra`](./packages/puppeteer-extra) package.**\n\nWe've also recently introduced support for Playwright, if you're interested in that head over to [`playwright-extra`](./packages/playwright-extra).\n\n## Monorepo\n\n<details>\n <summary><strong>Contributing</strong></summary>\n\n### Contributing\n\nPRs and new plugins are welcome! The plugin API for `puppeteer-extra` is clean and fun to use. Have a look the [`PuppeteerExtraPlugin`](./packages/puppeteer-extra-plugin) base class documentation to get going and check out the [existing plugins](./packages/) (minimal example is the [anonymize-ua](./packages/puppeteer-extra-plugin-anonymize-ua/index.js) plugin) for reference.\n\nWe use a [monorepo](https://github.com/berstend/puppeteer-extra) powered by [Lerna](https://github.com/lerna/lerna#--use-workspaces) (and yarn workspaces), [ava](https://github.com/avajs/ava) for testing, the [standard](https://standardjs.com/) style for linting and [JSDoc](http://usejsdoc.org/about-getting-started.html) heavily to auto-generate markdown [documentation](https://github.com/documentationjs/documentation) based on code. :-)\n\n</details>\n\n<details>\n <summary><strong>Lerna</strong></summary>\n\n### Lerna\n\nThis monorepo is powered by [Lerna](https://github.com/lerna/lerna) and yarn workspaces.\n\n#### Initial setup\n\n```bash\n# Install deps\nyarn\n\n# Bootstrap the packages in the current Lerna repo.\n# Installs all of their dependencies and links any cross-dependencies.\nyarn bootstrap\n\n# Build all TypeScript sources\nyarn build\n```\n\n#### Development flow\n\n```bash\n# Install debug in all packages\nyarn lerna add debug\n\n# Install fs-extra to puppeteer-extra-plugin-user-data-dir\nyarn lerna add fs-extra --scope=puppeteer-extra-plugin-user-data-dir\n\n# Remove dependency\n# https://github.com/lerna/lerna/issues/833\nyarn lerna exec --concurrency 1 'yarn remove fs-extra; echo 0'\n\n# Run test in all packages\nyarn test\n\n# Update JSDoc based documentation in markdown files\nyarn docs\n\n# Upgrade project wide deps like puppeteer\n# (We keep the devDependency version blurry)\nrm -rf node_modules\nrm -rf yarn.lock\nyarn\nyarn lerna bootstrap\n\n# Update deps within packages (interactive)\nyarn lernaupdate\n\n# If in doubt :-(\nyarn lerna exec \"rm -f yarn.lock; rm -rf node_modules; echo 0\"\nrm -f yarn.lock &&  rm -rf node_modules && yarn cache clean\n\n# Run tests of specific package\ncd packages/puppeteer-extra-plugin-stealth\nyarn test\n\n# Run tests of specific stealth evasion\ncd packages/puppeteer-extra-plugin-stealth\nyarn ava -v ./evasions/user-agent-override/index.test.js\n\n# Test a local monorepo package in an outside folder as it would've been installed from the registry\n# Change PACKAGE_DIR to the path of this monorepo and PACKAGE to the package you wish to install\nPACKAGE=puppeteer-extra PACKAGE_DIR=/Users/foo/puppeteer-extra/packages && yarn remove $(echo $PACKAGE); true && rm -f $(pwd)/$(echo $PACKAGE)-latest.tgz && yarn --cwd $(echo $PACKAGE_DIR)/$(echo $PACKAGE) pack --filename $(pwd)/$(echo $PACKAGE)-latest.tgz && YARN_CACHE_FOLDER=/tmp/yarn yarn add file:$(pwd)/$(echo $PACKAGE)-latest.tgz && rm -rf /tmp/yarn\n```\n\n#### Publishing\n\n```bash\n# make sure you're signed into npm before publishing\n# yarn publishing is broken so lerna uses npm\nnpm whoami\n\n# ensure everything is up2date and peachy\nyarn\nyarn bootstrap\nyarn lerna link\nyarn build\nyarn test\n\n# Phew, let's publish these packages!\n# - Will publish all changed packages\n# - Will ask for new pkg version per package\n# - Will updated inter-package dependency versions automatically\nyarn lerna publish\n\n# Fix new dependency version symlinks\nyarn bootstrap && yarn lerna link\n```\n\n</details>\n\n<br>\n<p align=\"center\">\n  <img src=\"https://i.imgur.com/EuqiF5F.png\"  height=\"240\"  />\n</p>\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"packages\": [\"packages/*\"],\n  \"version\": \"independent\",\n  \"useWorkspaces\": true,\n  \"npmClient\": \"yarn\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"description\": \"Modular framework to teach Puppeteer new tricks.\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"main\": \"packages/puppeteer-extra/index.js\",\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"scripts\": {\n    \"bootstrap\": \"lerna bootstrap\",\n    \"build\": \"yarn lerna exec --concurrency 1 'yarn build; echo 0'\",\n    \"docs\": \"lerna run docs\",\n    \"test\": \"lerna run test --concurrency 1 --stream\",\n    \"test-ci\": \"lerna run test-ci --concurrency 1 --stream\",\n    \"prepare\": \"lerna run prepare\",\n    \"release\": \"lerna publish --npm-client npm\"\n  },\n  \"workspaces\": {\n    \"packages\": [\n      \"packages/*\"\n    ],\n    \"nohoist\": [\n      \"**/@types\",\n      \"**/@types/**\",\n      \"**/typescript\",\n      \"**/typescript/**\"\n    ]\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^6.7.1\",\n    \"eslint-config-prettier\": \"^6.7.0\",\n    \"eslint-config-prettier-standard\": \"^3.0.1\",\n    \"eslint-config-standard\": \"^14.1.0\",\n    \"eslint-plugin-import\": \"^2.18.2\",\n    \"eslint-plugin-node\": \"^10.0.0\",\n    \"eslint-plugin-prettier\": \"^3.1.1\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"eslint-plugin-standard\": \"^4.0.1\",\n    \"lerna\": \"^3.19.0\",\n    \"lerna-update-wizard\": \"^0.17.5\",\n    \"prettier\": \"^1.19.1\",\n    \"prettier-config-standard\": \"^1.0.1\"\n  },\n  \"optionalDependencies\": {\n    \"fsevents\": \"^2.1.2\"\n  },\n  \"resolutions\": {\n    \"**/fsevents\": \"^2.1.2\"\n  },\n  \"dependencies\": {},\n  \"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "packages/extract-stealth-evasions/.gitignore",
    "content": "stealth.min.js\nstealth.js"
  },
  {
    "path": "packages/extract-stealth-evasions/index.js",
    "content": "#!/usr/bin/env node\n\nconst puppeteer = require('puppeteer-extra')\nconst stealth = require('puppeteer-extra-plugin-stealth')()\nconst { minify } = require('terser')\nconst argv = require('yargs')\n  .usage('Usage: $0 [options]')\n  .alias('e', 'exclude')\n  .describe('e', 'Exclude evasion (repeat for multiple)')\n  .alias('i', 'include')\n  .describe('i', 'Include evasion (repeat for multiple)')\n  .alias('l', 'list')\n  .describe('l', 'List available evasions')\n  .alias('m', 'minify')\n  .describe('minify', 'Minify the output')\n  .boolean('m')\n  .default('m', true)\n  .help('h')\n  .alias('h', 'help').argv\nconst fs = require('fs')\n\nconst file = 'stealth' + (argv.minify === true ? '.min' : '') + '.js'\n\nif (argv.exclude) {\n  if (typeof argv.exclude === 'string') {\n    stealth.enabledEvasions.delete(argv.exclude)\n  } else {\n    argv.exclude.forEach(e => {\n      stealth.enabledEvasions.delete(e)\n    })\n  }\n} else if (argv.include) {\n  if (typeof argv.include === 'string') {\n    stealth.enabledEvasions = [argv.include]\n  } else {\n    stealth.enabledEvasions = []\n    argv.include.forEach(e => {\n      stealth.enabledEvasions.push(e)\n    })\n  }\n} else if (argv.list) {\n  console.log('Available evasions:', [...stealth.availableEvasions].join(', '))\n  process.exit(0)\n}\n\nlet scripts = ''\n\npuppeteer\n  .use(stealth)\n  .launch({\n    headless: true\n  })\n  .then(async browser => {\n    // Patch evaluateOnNewDocument()\n    const page = (await browser.pages()).find(Boolean)\n    page.__proto__.evaluateOnNewDocument = patchEval // eslint-disable-line no-proto\n    page.__proto__.evaluate = patchEval // eslint-disable-line no-proto\n\n    await (await browser.newPage()).goto('about:blank')\n    await browser.close()\n\n    fs.writeFile(\n      file,\n      `/*!\n * Note: Auto-generated, do not update manually.\n * Generated by: https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions\n * Generated on: ${new Date().toUTCString()}\n * License: MIT\n */\n` +\n        (argv.minify === true\n          ? (await minify(scripts, { toplevel: true })).code\n          : scripts),\n      err => {\n        if (err) throw err\n        console.log(`File ${file} written!`)\n        console.log(\n          'Included evasions: ',\n          [...stealth.enabledEvasions].join(', ')\n        )\n      }\n    )\n  })\n\nfunction patchEval(f, args) {\n  // Check if there are options supplied\n  if (typeof args !== 'undefined') {\n    scripts += '(' + f.toString() + ')(' + JSON.stringify(args) + ');\\n'\n  } else {\n    scripts += '(' + f.toString() + ')();\\n'\n  }\n}\n"
  },
  {
    "path": "packages/extract-stealth-evasions/package.json",
    "content": "{\n  \"name\": \"extract-stealth-evasions\",\n  \"version\": \"2.7.3\",\n  \"description\": \"Extract stealth evasions from puppeteer-extra-plugin-stealth\",\n  \"main\": \"index.js\",\n  \"bin\": {\n    \"extract-stealth-evasions\": \"./index.js\"\n  },\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions#readme\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"dependencies\": {\n    \"puppeteer\": \"*\",\n    \"puppeteer-extra\": \"^3.3.6\",\n    \"puppeteer-extra-plugin-stealth\": \"^2.11.2\",\n    \"terser\": \"^5.1.0\",\n    \"yargs\": \"^15.4.1\"\n  }\n}\n"
  },
  {
    "path": "packages/extract-stealth-evasions/readme.md",
    "content": "# extract-stealth-evasions\n\nThis script offers a quick way to extract the latest stealth evasions from [puppeteer-extra-stealth](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth) to (minified) JavaScript. The resulting JS file can be used in pure [CDP](https://chromedevtools.github.io/devtools-protocol/tot/) implementations or to test the evasions in your devtools.\n\n#### Usage with `npx`\n\nYou don't need to install anything, `npx` runs wherever NodeJS is installed. :-)\n\n```bash\nnpx extract-stealth-evasions\n```\n\nWill create a `stealth.min.js` file in the current folder.\n\n#### Using the CDN version\n\nYou can also fetch the latest version from [gitCDN](https://gitcdn.xyz/repo/berstend/puppeteer-extra/stealth-js/stealth.min.js). For example, paste this one-liner in your browser devtools console:\n\n```js\ndocument.body.appendChild(Object.assign(document.createElement('script'), {src: 'https://gitcdn.xyz/repo/berstend/puppeteer-extra/stealth-js/stealth.min.js'}))\n```\n\n#### How to use locally\n\n```bash\nyarn install\nnode index.js\n```\n\nUse the resulting `stealth.min.js` file however you like.\n\n#### Options\n\n```bash\n$ npx extract-stealth-evasions -h\nUsage: extract-stealth-evasions [options]\n\nOptions:\n  --version      Show version number                                   [boolean]\n  -e, --exclude  Exclude evasion (repeat for multiple)\n  -i, --include  Include evasion (repeat for multiple)\n  -l, --list     List available evasions\n  -h, --help     Show help                                             [boolean]\n  -m, --minify   Minify the output                     [boolean] [default: true]\n```"
  },
  {
    "path": "packages/playwright-extra/.prettierrc.js",
    "content": "module.exports = 'prettier-config-standard'\n"
  },
  {
    "path": "packages/playwright-extra/package.json",
    "content": "{\n  \"name\": \"playwright-extra\",\n  \"version\": \"4.3.6\",\n  \"description\": \"Teach playwright new tricks through plugins.\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra#readme\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"typings\": \"dist/index.d.ts\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup ambient-dts\",\n    \"build:tsc\": \"tsc --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"echo \\\"No docs\\\"\",\n    \"test\": \"yarn playwright test --config test/playwright.config.ts\",\n    \"test-ci\": \"run-s test\",\n    \"ambient-dts\": \"run-s ambient-dts-copy ambient-dts-fix-path\",\n    \"ambient-dts-copy\": \"copyfiles -u 1 \\\"src/**/*.d.ts\\\" dist\",\n    \"ambient-dts-fix-path\": \"replace-in-files --string='/// <reference path=\\\"../src/' --replacement='/// <reference path=\\\"../dist/' 'dist/**/*.d.ts'\"\n  },\n  \"keywords\": [\n    \"playwright\",\n    \"playwright-extra\",\n    \"stealth\",\n    \"recaptcha\",\n    \"user-preferences\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"engines\": {\n    \"node\": \">=12\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.23.1\",\n    \"@types/debug\": \"^4.1.7\",\n    \"@types/node\": \"^18.0.0\",\n    \"esbuild\": \"^0.14.47\",\n    \"esbuild-register\": \"^3.3.3\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"playwright\": \"1.24.2\",\n    \"prettier\": \"^2.7.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"puppeteer-extra-plugin-anonymize-ua\": \"^2.4.5\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup\": \"^1.27.5\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"typescript\": \"4.4.3\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.3.4\"\n  },\n  \"peerDependencies\": {\n    \"playwright\": \"*\",\n    \"playwright-core\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"playwright\": {\n      \"optional\": true\n    },\n    \"playwright-core\": {\n      \"optional\": true\n    }\n  }\n}\n"
  },
  {
    "path": "packages/playwright-extra/readme.md",
    "content": "# playwright-extra [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push)](https://github.com/berstend/puppeteer-extra/actions) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/playwright-extra.svg)](https://www.npmjs.com/package/playwright-extra)\n\n> A modular plugin framework for [playwright](https://github.com/microsoft/playwright) to enable cool [plugins](#plugins) through a clean interface.\n\n## Installation\n\n```bash\nyarn add playwright playwright-extra\n# - or -\nnpm install playwright playwright-extra\n```\n\n<details>\n <summary>Changelog</summary>\n\n> Please check the `announcements` channel in our [discord server](https://extra.community) until we've automated readme updates. :)\n\n- **v4.3**\n  - Rerelease due to versioning issues with previous beta packages\n- **v3.3**\n  - Initial public release\n  </details>\n\n## Quickstart\n\n```js\n// playwright-extra is a drop-in replacement for playwright,\n// it augments the installed playwright with plugin functionality\nconst { chromium } = require('playwright-extra')\n\n// Load the stealth plugin and use defaults (all tricks to hide playwright usage)\n// Note: playwright-extra is compatible with most puppeteer-extra plugins\nconst stealth = require('puppeteer-extra-plugin-stealth')()\n\n// Add the plugin to playwright (any number of plugins can be added)\nchromium.use(stealth)\n\n// That's it, the rest is playwright usage as normal 😊\nchromium.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n\n  console.log('Testing the stealth plugin..')\n  await page.goto('https://bot.sannysoft.com', { waitUntil: 'networkidle' })\n  await page.screenshot({ path: 'stealth.png', fullPage: true })\n\n  console.log('All done, check the screenshot. ✨')\n  await browser.close()\n})\n```\n\nThe above example uses the compatible [`stealth`](/packages/puppeteer-extra-plugin-stealth) plugin from puppeteer-extra, that plugin needs to be installed as well:\n\n```bash\nyarn add puppeteer-extra-plugin-stealth\n# - or -\nnpm install puppeteer-extra-plugin-stealth\n```\n\nIf you'd like to see debug output just run your script like so:\n\n```bash\n# macOS/Linux (Bash)\nDEBUG=playwright-extra*,puppeteer-extra* node myscript.js\n\n# Windows (Powershell)\n$env:DEBUG='playwright-extra*,puppeteer-extra*';node myscript.js\n```\n\n### More examples\n\n<details>\n <summary><strong>TypeScript & ESM usage</strong></summary><br/>\n\n`playwright-extra` and most plugins are written in TS, so you get perfect type support out of the box. :)\n\n```ts\n// playwright-extra is a drop-in replacement for playwright,\n// it augments the installed playwright with plugin functionality\nimport { chromium } from 'playwright-extra'\n\n// Load the stealth plugin and use defaults (all tricks to hide playwright usage)\n// Note: playwright-extra is compatible with most puppeteer-extra plugins\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\n\n// Add the plugin to playwright (any number of plugins can be added)\nchromium.use(StealthPlugin())\n\n// ...(the rest of the quickstart code example is the same)\nchromium.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n\n  console.log('Testing the stealth plugin..')\n  await page.goto('https://bot.sannysoft.com', { waitUntil: 'networkidle' })\n  await page.screenshot({ path: 'stealth.png', fullPage: true })\n\n  console.log('All done, check the screenshot. ✨')\n  await browser.close()\n})\n```\n\nNew to Typescript? Here it is in 30 seconds or less 😄:\n\n```bash\n# Optional: If you don't have yarn yet\nnpm i --global yarn\n\n# Optional: Create new package.json if it's a new project\nyarn init -y\n\n# Add basic typescript dependencies\nyarn add --dev typescript @types/node esbuild esbuild-register\n\n# Bootstrap a tsconfig.json\nyarn tsc --init --target ES2020 --lib ES2020 --module commonjs --rootDir src --outDir dist\n\n# Add dependencies used in the quick start example\nyarn add playwright playwright-extra puppeteer-extra-plugin-stealth\n\n# Create source folder for the .ts files\nmkdir src\n\n# Now place the example code above in `src/index.ts`\n\n# Run the typescript code without the need of compiling it first\nnode -r esbuild-register src/index.ts\n\n# You can now add Typescript to your CV 🎉\n```\n\n</details>\n<details>\n <summary><strong>Using different browsers</strong></summary><br/>\n\n```ts\n// Any browser supported by playwright can be used with plugins\nimport { chromium, firefox, webkit } from 'playwright-extra'\n\nchromium.use(plugin)\nfirefox.use(plugin)\nwebkit.use(plugin)\n```\n\n</details>\n<details>\n <summary><strong>Multiple instances with different plugins</strong></summary><br/>\n\nNode.js imports are cached, therefore the default `chromium`, `firefox`, `webkit` export from `playwright-extra` will always return the same playwright instance.\n\n```ts\n// Use `addExtra` to create a fresh and independent instance\nimport playwright from 'playwright'\nimport { addExtra } from 'playwright-extra'\n\nconst chromium1 = addExtra(playwright.chromium)\nconst chromium2 = addExtra(playwright.chromium)\n\nchromium1.use(onePlugin)\nchromium2.use(anotherPlugin)\n// chromium1 and chromium2 are independent\n```\n\n</details>\n\n---\n\n## Plugins\n\nWe're currently in the process of making the existing [puppeteer-extra](/packages/puppeteer-extra) plugins compatible with playwright-extra, the following plugins have been successfully tested already:\n\n### 🔥 [`puppeteer-extra-plugin-stealth`](/packages/puppeteer-extra-plugin-stealth)\n\n- Applies various evasion techniques to make detection of an automated browser harder\n- Compatible with Puppeteer & Playwright and chromium based browsers\n\n<details>\n<summary>&nbsp;&nbsp;Example: Using stealth in Playwright with custom options</summary>\n\n```js\n// The stealth plugin is optimized for chromium based browsers currently\nimport { chromium } from 'playwright-extra'\n\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\nchromium.use(StealthPlugin())\n\n// New way to overwrite the default options of stealth evasion plugins\n// https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions\nchromium.plugins.setDependencyDefaults('stealth/evasions/webgl.vendor', {\n  vendor: 'Bob',\n  renderer: 'Alice'\n})\n\n// That's it, the rest is playwright usage as normal 😊\nchromium.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n\n  console.log('Testing the webgl spoofing feature of the stealth plugin..')\n  await page.goto('https://webglreport.com', { waitUntil: 'networkidle' })\n  await page.screenshot({ path: 'webgl.png', fullPage: true })\n\n  console.log('All done, check the screenshot. ✨')\n  await browser.close()\n})\n```\n\n</details>\n\n### 🏴 [`puppeteer-extra-plugin-recaptcha`](/packages/puppeteer-extra-plugin-recaptcha)\n\n- Solves reCAPTCHAs and hCaptchas automatically, using a single line of code: `page.solveRecaptchas()`\n- Compatible with Puppeteer & Playwright and all browsers (chromium, firefox, webkit)\n<details>\n<summary>&nbsp;&nbsp;Example: Solving captchas in Playwright & Firefox</summary>\n\n```js\n// Any browser (chromium, webkit, firefox) can be used\nimport { firefox } from 'playwright-extra'\n\nimport RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha'\nfirefox.use(\n  RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN || 'YOUR_API_KEY'\n    }\n  })\n)\n\n// Works in headless as well, just so you can see it in action\nfirefox.launch({ headless: false }).then(async browser => {\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  const url = 'https://www.google.com/recaptcha/api2/demo'\n  await page.goto(url, { waitUntil: 'networkidle' })\n\n  console.log('Solving captchas..')\n  await page.solveRecaptchas()\n\n  await Promise.all([\n    page.waitForNavigation({ waitUntil: 'networkidle' }),\n    page.click(`#recaptcha-demo-submit`)\n  ])\n\n  const content = await page.content()\n  const isSuccess = content.includes('Verification Success')\n  console.log('Done', { isSuccess })\n  await browser.close()\n})\n```\n\n</details>\n\n### 🆕 [`plugin-proxy-router`](/packages/plugin-proxy-router)\n\n- Use multiple proxies dynamically with flexible per-host routing and more\n- Compatible with Puppeteer & Playwright and all browsers (chromium, firefox, webkit)\n\n**Notes**\n\n- If you're in need of adblocking use [this package](https://www.npmjs.com/package/@cliqz/adblocker-playwright) or [block resources natively](https://github.com/berstend/puppeteer-extra/wiki/Block-resources-without-request-interception)\n- We're focussing on compatiblity with existing plugins at the moment, more documentation on how to write your own playwright-extra plugins will follow\n\n---\n\n## Contributors\n\n<a href=\"https://github.com/berstend/puppeteer-extra/graphs/contributors\">\n  <img src=\"https://contributors-img.firebaseapp.com/image?repo=berstend/puppeteer-extra\" />\n</a>\n\n---\n\n## License\n\nCopyright © 2018 - 2023, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](https://github.com/berstend). Released under the MIT License.\n\n<!--\n  Reference links\n-->\n\n[playwright-extra]: https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra\n[puppeteer-extra]: https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra\n[`puppeteer-extra`]: https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra\n"
  },
  {
    "path": "packages/playwright-extra/rollup.config.ts",
    "content": "import commonjs from 'rollup-plugin-commonjs'\nimport resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nconst defaultExportOutro = `\n  module.exports = exports.default || {}\n  Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value })\n`\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      exports: 'named',\n      outro: defaultExportOutro,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      exports: 'named',\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve(),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/playwright-extra/src/extra.ts",
    "content": "import Debug from 'debug'\nconst debug = Debug('playwright-extra')\n\nimport type * as pw from 'playwright-core'\nimport type { CompatiblePlugin, Plugin } from './types'\n\nimport { PluginList } from './plugins'\nimport { playwrightLoader } from './helper/loader'\n\ntype PlaywrightBrowserLauncher = pw.BrowserType\n\n/**\n * The Playwright browser launcher APIs we're augmenting\n * @private\n */\ninterface AugmentedLauncherAPIs\n  extends Pick<\n    PlaywrightBrowserLauncher,\n    'launch' | 'launchPersistentContext' | 'connect' | 'connectOverCDP'\n  > {}\n\n/**\n * Modular plugin framework to teach `playwright` new tricks.\n */\nexport class PlaywrightExtraClass implements AugmentedLauncherAPIs {\n  /** Plugin manager */\n  public readonly plugins: PluginList\n\n  constructor(private _launcher?: Partial<PlaywrightBrowserLauncher>) {\n    this.plugins = new PluginList()\n  }\n\n  /**\n   * The **main interface** to register plugins.\n   *\n   * Can be called multiple times to enable multiple plugins.\n   *\n   * Plugins derived from `PuppeteerExtraPlugin` will be used with a compatiblity layer.\n   *\n   * @example\n   * chromium.use(plugin1).use(plugin2)\n   * firefox.use(plugin1).use(plugin2)\n   *\n   * @see [PuppeteerExtraPlugin]\n   *\n   * @return The same `PlaywrightExtra` instance (for optional chaining)\n   */\n  public use(plugin: CompatiblePlugin): this {\n    const isValid = plugin && 'name' in plugin\n    if (!isValid) {\n      throw new Error('A plugin must be provided to .use()')\n    }\n    if (this.plugins.add(plugin as Plugin)) {\n      debug('Plugin registered', plugin.name)\n    }\n    return this\n  }\n\n  /**\n   * In order to support a default export which will require vanilla playwright automatically,\n   * as well as `addExtra` to patch a provided launcher, we need to so some gymnastics here.\n   *\n   * Otherwise this would throw immediately, even when only using the `addExtra` export with an arbitrary compatible launcher.\n   *\n   * The solution is to make the vanilla launcher optional and only throw once we try to effectively use and can't find it.\n   *\n   * @internal\n   */\n  public get launcher(): Partial<PlaywrightBrowserLauncher> {\n    if (!this._launcher) {\n      throw playwrightLoader.requireError\n    }\n    return this._launcher\n  }\n\n  public async launch(\n    ...args: Parameters<PlaywrightBrowserLauncher['launch']>\n  ): ReturnType<PlaywrightBrowserLauncher['launch']> {\n    if (!this.launcher.launch) {\n      throw new Error('Launcher does not support \"launch\"')\n    }\n\n    let [options] = args\n    options = { args: [], ...(options || {}) } // Initialize args array\n    debug('launch', options)\n    this.plugins.prepare()\n\n    // Give plugins the chance to modify the options before continuing\n    options =\n      (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options\n\n    debug('launch with options', options)\n    if ('userDataDir' in options) {\n      debug(\n        \"A plugin defined userDataDir during .launch, which isn't supported by playwright - ignoring\"\n      )\n      delete (options as any).userDataDir\n    }\n    const browser = await this.launcher['launch'](options)\n    await this.plugins.dispatchBlocking('onBrowser', browser)\n    await this._bindBrowserEvents(browser)\n    await this.plugins.dispatchBlocking('afterLaunch', browser)\n    return browser\n  }\n\n  public async launchPersistentContext(\n    ...args: Parameters<PlaywrightBrowserLauncher['launchPersistentContext']>\n  ): ReturnType<PlaywrightBrowserLauncher['launchPersistentContext']> {\n    if (!this.launcher.launchPersistentContext) {\n      throw new Error('Launcher does not support \"launchPersistentContext\"')\n    }\n\n    let [userDataDir, options] = args\n    options = { args: [], ...(options || {}) } // Initialize args array\n    debug('launchPersistentContext', options)\n    this.plugins.prepare()\n\n    // Give plugins the chance to modify the options before continuing\n    options =\n      (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options\n\n    const context = await this.launcher['launchPersistentContext'](\n      userDataDir,\n      options\n    )\n    await this.plugins.dispatchBlocking('afterLaunch', context)\n    this._bindBrowserContextEvents(context)\n    return context\n  }\n\n  async connect(\n    wsEndpointOrOptions: string | (pw.ConnectOptions & { wsEndpoint?: string }),\n    wsOptions: pw.ConnectOptions = {}\n  ): ReturnType<PlaywrightBrowserLauncher['connect']> {\n    if (!this.launcher.connect) {\n      throw new Error('Launcher does not support \"connect\"')\n    }\n    this.plugins.prepare()\n\n    // Playwright currently supports two function signatures for .connect\n    let options: pw.ConnectOptions & { wsEndpoint?: string } = {}\n    let wsEndpointAsString = false\n    if (typeof wsEndpointOrOptions === 'object') {\n      options = { ...wsEndpointOrOptions, ...wsOptions }\n    } else {\n      wsEndpointAsString = true\n      options = { wsEndpoint: wsEndpointOrOptions, ...wsOptions }\n    }\n    debug('connect', options)\n\n    // Give plugins the chance to modify the options before launch/connect\n    options =\n      (await this.plugins.dispatchBlocking('beforeConnect', options)) || options\n\n    // Follow call signature of end user\n    const args: any[] = []\n    const wsEndpoint = options.wsEndpoint\n    if (wsEndpointAsString) {\n      delete options.wsEndpoint\n      args.push(wsEndpoint, options)\n    } else {\n      args.push(options)\n    }\n\n    const browser = (await (this.launcher['connect'] as any)(\n      ...args\n    )) as pw.Browser\n\n    await this.plugins.dispatchBlocking('onBrowser', browser)\n    await this._bindBrowserEvents(browser)\n    await this.plugins.dispatchBlocking('afterConnect', browser)\n    return browser\n  }\n\n  async connectOverCDP(\n    wsEndpointOrOptions:\n      | string\n      | (pw.ConnectOverCDPOptions & { endpointURL?: string }),\n    wsOptions: pw.ConnectOverCDPOptions = {}\n  ): ReturnType<PlaywrightBrowserLauncher['connectOverCDP']> {\n    if (!this.launcher.connectOverCDP) {\n      throw new Error(`Launcher does not implement 'connectOverCDP'`)\n    }\n    this.plugins.prepare()\n\n    // Playwright currently supports two function signatures for .connectOverCDP\n    let options: pw.ConnectOverCDPOptions & { endpointURL?: string } = {}\n    let wsEndpointAsString = false\n    if (typeof wsEndpointOrOptions === 'object') {\n      options = { ...wsEndpointOrOptions, ...wsOptions }\n    } else {\n      wsEndpointAsString = true\n      options = { endpointURL: wsEndpointOrOptions, ...wsOptions }\n    }\n    debug('connectOverCDP'), options\n\n    // Give plugins the chance to modify the options before launch/connect\n    options =\n      (await this.plugins.dispatchBlocking('beforeConnect', options)) || options\n\n    // Follow call signature of end user\n    const args: any[] = []\n    const endpointURL = options.endpointURL\n    if (wsEndpointAsString) {\n      delete options.endpointURL\n      args.push(endpointURL, options)\n    } else {\n      args.push(options)\n    }\n\n    const browser = (await (this.launcher['connectOverCDP'] as any)(\n      ...args\n    )) as pw.Browser\n\n    await this.plugins.dispatchBlocking('onBrowser', browser)\n    await this._bindBrowserEvents(browser)\n    await this.plugins.dispatchBlocking('afterConnect', browser)\n    return browser\n  }\n\n  protected async _bindBrowserContextEvents(\n    context: pw.BrowserContext,\n    contextOptions?: pw.BrowserContextOptions\n  ) {\n    debug('_bindBrowserContextEvents')\n    this.plugins.dispatch('onContextCreated', context, contextOptions)\n\n    // Make sure things like `addInitScript` show an effect on the very first page as well\n    context.newPage = ((originalMethod, ctx) => {\n      return async () => {\n        const page = await originalMethod.call(ctx)\n        await page.goto('about:blank')\n        return page\n      }\n    })(context.newPage, context)\n\n    context.on('close', () => {\n      // When using `launchPersistentContext` context closing is the same as browser closing\n      if (!context.browser()) {\n        this.plugins.dispatch('onDisconnected')\n      }\n    })\n    context.on('page', page => {\n      this.plugins.dispatch('onPageCreated', page)\n      page.on('close', () => {\n        this.plugins.dispatch('onPageClose', page)\n      })\n    })\n  }\n\n  protected async _bindBrowserEvents(browser: pw.Browser) {\n    debug('_bindPlaywrightBrowserEvents')\n\n    browser.on('disconnected', () => {\n      this.plugins.dispatch('onDisconnected', browser)\n    })\n\n    // Note: `browser.newPage` will implicitly call `browser.newContext` as well\n    browser.newContext = ((originalMethod, ctx) => {\n      return async (options: pw.BrowserContextOptions = {}) => {\n        const contextOptions: pw.BrowserContextOptions =\n          (await this.plugins.dispatchBlocking(\n            'beforeContext',\n            options,\n            browser\n          )) || options\n        const context = await originalMethod.call(ctx, contextOptions)\n        this._bindBrowserContextEvents(context, contextOptions)\n        return context\n      }\n    })(browser.newContext, browser)\n  }\n}\n\n/**\n * PlaywrightExtra class with additional launcher methods.\n *\n * Augments the class with an instance proxy to pass on methods that are not augmented to the original target.\n *\n */\nexport const PlaywrightExtra = new Proxy(PlaywrightExtraClass, {\n  construct(classTarget, args) {\n    debug(`create instance of ${classTarget.name}`)\n    const result = Reflect.construct(classTarget, args) as PlaywrightExtraClass\n    return new Proxy(result, {\n      get(target, prop) {\n        if (prop in target) {\n          return Reflect.get(target, prop)\n        }\n        debug('proxying property to original launcher: ', prop)\n        return Reflect.get(target.launcher, prop)\n      }\n    })\n  }\n})\n"
  },
  {
    "path": "packages/playwright-extra/src/helper/loader.ts",
    "content": "import type * as pw from 'playwright-core'\n\n/** Node.js module loader helper */\nexport class Loader<TargetModule> {\n  constructor(public moduleName: string, public packageNames: string[]) {}\n\n  /**\n   * Lazy load a top level export from another module by wrapping it in a JS proxy.\n   *\n   * This allows us to re-export e.g. `devices` from `playwright` while redirecting direct calls\n   * to it to the module version the user has installed, rather than shipping with a hardcoded version.\n   *\n   * If we don't do this and the user doesn't have the target module installed we'd throw immediately when our code is imported.\n   *\n   * We use a \"super\" Proxy defining all traps, so calls like `Object.keys(playwright.devices).length` will return the correct value.\n   */\n  public lazyloadExportOrDie<T extends keyof TargetModule>(exportName: T) {\n    const that = this\n    const trapHandler = Object.fromEntries(\n      Object.getOwnPropertyNames(Reflect).map((name: any) => [\n        name,\n        function (target: any, ...args: any[]) {\n          const moduleExport = that.loadModuleOrDie()[exportName]\n          const customTarget = moduleExport as any\n          const result = ((Reflect as any)[name] as any)(\n            customTarget || target,\n            ...args\n          )\n          return result\n        }\n      ])\n    )\n    return new Proxy({}, trapHandler) as TargetModule[T]\n  }\n\n  /** Load the module if possible */\n  public loadModule() {\n    return requirePackages<TargetModule>(this.packageNames)\n  }\n\n  /** Load the module if possible or throw */\n  public loadModuleOrDie(): TargetModule {\n    const module = requirePackages<TargetModule>(this.packageNames)\n    if (module) {\n      return module\n    }\n    throw this.requireError\n  }\n\n  public get requireError() {\n    const moduleNamePretty =\n      this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1)\n    return new Error(`\n  ${moduleNamePretty} is missing. :-)\n\n  I've tried loading ${this.packageNames\n    .map(p => `\"${p}\"`)\n    .join(', ')} - no luck.\n\n  Make sure you install one of those packages or use the named 'addExtra' export,\n  to patch a specific (and maybe non-standard) implementation of ${moduleNamePretty}.\n\n  To get the latest stable version of ${moduleNamePretty} run:\n  'yarn add ${this.moduleName}' or 'npm i ${this.moduleName}'\n  `)\n  }\n}\n\nexport function requirePackages<TargetModule = any>(packageNames: string[]) {\n  for (const name of packageNames) {\n    try {\n      return require(name) as TargetModule\n    } catch (_) {\n      continue // noop\n    }\n  }\n  return\n}\n\n/** Playwright specific module loader */\nexport const playwrightLoader = new Loader<typeof pw>('playwright', [\n  'playwright-core',\n  'playwright'\n])\n"
  },
  {
    "path": "packages/playwright-extra/src/index.ts",
    "content": "import type * as pw from 'playwright-core'\n\nimport { PlaywrightExtra, PlaywrightExtraClass } from './extra'\nimport { PluginList } from './plugins'\nimport { playwrightLoader as loader } from './helper/loader'\n\nexport { PlaywrightExtra, PlaywrightExtraClass } from './extra'\nexport { PluginList } from './plugins'\n\n/** A playwright browser launcher */\nexport type PlaywrightBrowserLauncher = pw.BrowserType<{}>\n/** A playwright browser launcher with plugin functionality */\nexport type AugmentedBrowserLauncher = PlaywrightExtraClass &\n  PlaywrightBrowserLauncher\n\n/**\n * The minimum shape we expect from a playwright compatible launcher object.\n * We intentionally keep this not strict so other custom or compatible launchers can be used.\n */\nexport interface PlaywrightCompatibleLauncher {\n  connect(...args: any[]): Promise<any>\n  launch(...args: any[]): Promise<any>\n}\n\n/** Our custom module exports */\ninterface ExtraModuleExports {\n  PlaywrightExtra: typeof PlaywrightExtra\n  PlaywrightExtraClass: typeof PlaywrightExtraClass\n  PluginList: typeof PluginList\n  addExtra: typeof addExtra\n  chromium: AugmentedBrowserLauncher\n  firefox: AugmentedBrowserLauncher\n  webkit: AugmentedBrowserLauncher\n}\n\n/** Vanilla playwright module exports */\ntype PlaywrightModuleExports = typeof pw\n\n/**\n * Augment the provided Playwright browser launcher with plugin functionality.\n *\n * Using `addExtra` will always create a fresh PlaywrightExtra instance.\n *\n * @example\n * import playwright from 'playwright'\n * import { addExtra } from 'playwright-extra'\n *\n * const chromium = addExtra(playwright.chromium)\n * chromium.use(plugin)\n *\n * @param launcher - Playwright (or compatible) browser launcher\n */\nexport const addExtra = <Launcher extends PlaywrightCompatibleLauncher>(\n  launcher?: Launcher\n) => new PlaywrightExtra(launcher) as PlaywrightExtraClass & Launcher\n\n/**\n * This object can be used to launch or connect to Chromium with plugin functionality.\n *\n * This default export will behave exactly the same as the regular playwright\n * (just with extra plugin functionality) and can be used as a drop-in replacement.\n *\n * Behind the scenes it will try to require either the `playwright-core`\n * or `playwright` module from the installed dependencies.\n *\n * @note\n * Due to Node.js import caching this will result in a single\n * PlaywrightExtra instance, even when used in different files. If you need multiple\n * instances with different plugins please use `addExtra`.\n *\n * @example\n * // javascript import\n * const { chromium } = require('playwright-extra')\n *\n * // typescript/es6 module import\n * import { chromium } from 'playwright-extra'\n *\n * // Add plugins\n * chromium.use(...)\n */\nexport const chromium = addExtra((loader.loadModule() || {}).chromium)\n/**\n * This object can be used to launch or connect to Firefox with plugin functionality\n * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra`\n */\nexport const firefox = addExtra((loader.loadModule() || {}).firefox)\n/**\n * This object can be used to launch or connect to Webkit with plugin functionality\n * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra`\n */\nexport const webkit = addExtra((loader.loadModule() || {}).webkit)\n\n// Other playwright module exports we simply re-export with lazy loading\nexport const _android = loader.lazyloadExportOrDie('_android')\nexport const _electron = loader.lazyloadExportOrDie('_electron')\nexport const request = loader.lazyloadExportOrDie('request')\nexport const selectors = loader.lazyloadExportOrDie('selectors')\nexport const devices = loader.lazyloadExportOrDie('devices')\nexport const errors = loader.lazyloadExportOrDie('errors')\n\n/** Playwright with plugin functionality */\nconst moduleExports: ExtraModuleExports & PlaywrightModuleExports = {\n  // custom exports\n  PlaywrightExtra,\n  PlaywrightExtraClass,\n  PluginList,\n  addExtra,\n  chromium,\n  firefox,\n  webkit,\n\n  // vanilla exports\n  _android,\n  _electron,\n  request,\n  selectors,\n  devices,\n  errors\n}\n\nexport default moduleExports\n"
  },
  {
    "path": "packages/playwright-extra/src/plugins.ts",
    "content": "import Debug from 'debug'\nconst debug = Debug('playwright-extra:plugins')\n\nimport {\n  Plugin,\n  PluginMethodName,\n  PluginMethodFn,\n  PluginModule,\n  CompatiblePluginModule\n} from './types'\n\nimport { requirePackages } from './helper/loader'\nimport { addPuppeteerCompat } from './puppeteer-compatiblity-shim'\n\nexport class PluginList {\n  private readonly _plugins: Plugin[] = []\n  private readonly _dependencyDefaults: Map<string, any> = new Map()\n  private readonly _dependencyResolution: Map<string, CompatiblePluginModule> =\n    new Map()\n\n  constructor() {}\n\n  /**\n   * Get a list of all registered plugins.\n   */\n  public get list() {\n    return this._plugins\n  }\n\n  /**\n   * Get the names of all registered plugins.\n   */\n  public get names() {\n    return this._plugins.map(p => p.name)\n  }\n\n  /**\n   * Add a new plugin to the list (after checking if it's well-formed).\n   *\n   * @param plugin\n   * @internal\n   */\n  public add(plugin: Plugin) {\n    if (!this.isValidPluginInstance(plugin)) {\n      return false\n    }\n    if (!!plugin.onPluginRegistered) {\n      plugin.onPluginRegistered({ framework: 'playwright' })\n    }\n    // PuppeteerExtraPlugin: Populate `_childClassMembers` list containing methods defined by the plugin\n    if (!!plugin._registerChildClassMembers) {\n      plugin._registerChildClassMembers(Object.getPrototypeOf(plugin))\n    }\n    if (plugin.requirements?.has('dataFromPlugins')) {\n      plugin.getDataFromPlugins = this.getData.bind(this)\n    }\n    this._plugins.push(plugin)\n    return true\n  }\n\n  /** Check if the shape of a plugin is correct or warn */\n  protected isValidPluginInstance(plugin: Plugin) {\n    if (\n      !plugin ||\n      typeof plugin !== 'object' ||\n      !plugin._isPuppeteerExtraPlugin\n    ) {\n      console.error(\n        `Warning: Plugin is not derived from PuppeteerExtraPlugin, ignoring.`,\n        plugin\n      )\n      return false\n    }\n    if (!plugin.name) {\n      console.error(\n        `Warning: Plugin with no name registering, ignoring.`,\n        plugin\n      )\n      return false\n    }\n    return true\n  }\n\n  /** Error callback in case calling a plugin method throws an error. Can be overwritten. */\n  public onPluginError(plugin: Plugin, method: PluginMethodName, err: Error) {\n    console.warn(\n      `An error occured while executing \"${method}\" in plugin \"${plugin.name}\":`,\n      err\n    )\n  }\n\n  /**\n   * Define default values for plugins implicitly required through the `dependencies` plugin stanza.\n   *\n   * @param dependencyPath - The string by which the dependency is listed (not the plugin name)\n   *\n   * @example\n   * chromium.use(stealth)\n   * chromium.plugins.setDependencyDefaults('stealth/evasions/webgl.vendor', { vendor: 'Bob', renderer: 'Alice' })\n   */\n  public setDependencyDefaults(dependencyPath: string, opts: any) {\n    this._dependencyDefaults.set(dependencyPath, opts)\n    return this\n  }\n\n  /**\n   * Define custom plugin modules for plugins implicitly required through the `dependencies` plugin stanza.\n   *\n   * Using this will prevent dynamic imports from being used, which JS bundlers often have issues with.\n   *\n   * @example\n   * chromium.use(stealth)\n   * chromium.plugins.setDependencyResolution('stealth/evasions/webgl.vendor', VendorPlugin)\n   */\n  public setDependencyResolution(\n    dependencyPath: string,\n    pluginModule: CompatiblePluginModule\n  ) {\n    this._dependencyResolution.set(dependencyPath, pluginModule)\n    return this\n  }\n\n  /**\n   * Prepare plugins to be used (resolve dependencies, ordering)\n   * @internal\n   */\n  public prepare() {\n    this.resolveDependencies()\n    this.order()\n  }\n\n  /** Return all plugins using the supplied method */\n  protected filterByMethod(methodName: PluginMethodName) {\n    return this._plugins.filter(plugin => {\n      // PuppeteerExtraPlugin: The base class will already define all methods, hence we need to do a different check\n      if (\n        !!plugin._childClassMembers &&\n        Array.isArray(plugin._childClassMembers)\n      ) {\n        return plugin._childClassMembers.includes(methodName)\n      }\n      return methodName in plugin\n    })\n  }\n\n  /** Conditionally add puppeteer compatibility to values provided to the plugins */\n  protected _addPuppeteerCompatIfNeeded<TMethod extends PluginMethodName>(\n    plugin: Plugin,\n    method: TMethod,\n    args: Parameters<PluginMethodFn<TMethod>>\n  ) {\n    const canUseShim = plugin._isPuppeteerExtraPlugin && !plugin.noPuppeteerShim\n    const methodWhitelist: PluginMethodName[] = [\n      'onBrowser',\n      'onPageCreated',\n      'onPageClose',\n      'afterConnect',\n      'afterLaunch'\n    ]\n    const shouldUseShim = methodWhitelist.includes(method)\n    if (!canUseShim || !shouldUseShim) {\n      return args\n    }\n    debug('add puppeteer compatibility', plugin.name, method)\n    return [...args.map(arg => addPuppeteerCompat(arg as any))] as Parameters<\n      PluginMethodFn<TMethod>\n    >\n  }\n\n  /**\n   * Dispatch plugin lifecycle events in a typesafe way.\n   * Only Plugins that expose the supplied property will be called.\n   *\n   * Will not await results to dispatch events as fast as possible to all plugins.\n   *\n   * @param method - The lifecycle method name\n   * @param args - Optional: Any arguments to be supplied to the plugin methods\n   * @internal\n   */\n  public dispatch<TMethod extends PluginMethodName>(\n    method: TMethod,\n    ...args: Parameters<PluginMethodFn<TMethod>>\n  ): void {\n    const plugins = this.filterByMethod(method)\n    debug('dispatch', method, {\n      all: this._plugins.length,\n      filteredByMethod: plugins.length\n    })\n    for (const plugin of plugins) {\n      try {\n        args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args)\n        const fnType = plugin[method]?.constructor?.name\n        debug('dispatch to plugin', {\n          plugin: plugin.name,\n          method,\n          fnType\n        })\n        if (fnType === 'AsyncFunction') {\n          ;(plugin[method] as any)(...args).catch((err: any) =>\n            this.onPluginError(plugin, method, err)\n          )\n        } else {\n          ;(plugin[method] as any)(...args)\n        }\n      } catch (err) {\n        this.onPluginError(plugin, method, err as any)\n      }\n    }\n  }\n\n  /**\n   * Dispatch plugin lifecycle events in a typesafe way.\n   * Only Plugins that expose the supplied property will be called.\n   *\n   * Can also be used to get a definite return value after passing it to plugins:\n   * Calls plugins sequentially and passes on a value (waterfall style).\n   *\n   * The plugins can either modify the value or return an updated one.\n   * Will return the latest, updated value which ran through all plugins.\n   *\n   * By convention only the first argument will be used as the updated value.\n   *\n   * @param method - The lifecycle method name\n   * @param args - Optional: Any arguments to be supplied to the plugin methods\n   * @internal\n   */\n  public async dispatchBlocking<TMethod extends PluginMethodName>(\n    method: TMethod,\n    ...args: Parameters<PluginMethodFn<TMethod>>\n  ): Promise<ReturnType<PluginMethodFn<TMethod>>> {\n    const plugins = this.filterByMethod(method)\n    debug('dispatchBlocking', method, {\n      all: this._plugins.length,\n      filteredByMethod: plugins.length\n    })\n\n    let retValue: any = null\n    for (const plugin of plugins) {\n      try {\n        args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args)\n        retValue = await (plugin[method] as any)(...args)\n        // In case we got a return value use that as new first argument for followup function calls\n        if (retValue !== undefined) {\n          args[0] = retValue\n        }\n      } catch (err) {\n        this.onPluginError(plugin, method, err as any)\n        return retValue\n      }\n    }\n    return retValue\n  }\n\n  /**\n   * Order plugins that have expressed a special placement requirement.\n   *\n   * This is useful/necessary for e.g. plugins that depend on the data from other plugins.\n   *\n   * @private\n   */\n  protected order() {\n    debug('order:before', this.names)\n    const runLast = this._plugins\n      .filter(p => p.requirements?.has('runLast'))\n      .map(p => p.name)\n    for (const name of runLast) {\n      const index = this._plugins.findIndex(p => p.name === name)\n      this._plugins.push(this._plugins.splice(index, 1)[0])\n    }\n    debug('order:after', this.names)\n  }\n\n  /**\n   * Collects the exposed `data` property of all registered plugins.\n   * Will be reduced/flattened to a single array.\n   *\n   * Can be accessed by plugins that listed the `dataFromPlugins` requirement.\n   *\n   * Implemented mainly for plugins that need data from other plugins (e.g. `user-preferences`).\n   *\n   * @see [PuppeteerExtraPlugin]/data\n   * @param name - Filter data by optional name\n   *\n   * @private\n   */\n  protected getData(name?: string) {\n    const data = this._plugins\n      .filter((p: any) => !!p.data)\n      .map((p: any) => (Array.isArray(p.data) ? p.data : [p.data]))\n      .reduce((acc, arr) => [...acc, ...arr], [])\n    return name ? data.filter((d: any) => d.name === name) : data\n  }\n\n  /**\n   * Handle `plugins` stanza (already instantiated plugins that don't require dynamic imports)\n   */\n  protected resolvePluginsStanza() {\n    debug('resolvePluginsStanza')\n    const pluginNames = new Set(this.names)\n    this._plugins\n      .filter(p => !!p.plugins && p.plugins.length)\n      .filter(p => !pluginNames.has(p.name)) // TBD: Do we want to filter out existing?\n      .forEach(parent => {\n        ;(parent.plugins || []).forEach(p => {\n          debug(parent.name, 'adding missing plugin', p.name)\n          this.add(p as Plugin)\n        })\n      })\n  }\n\n  /**\n   * Handle `dependencies` stanza (which requires dynamic imports)\n   *\n   * Plugins can define `dependencies` as a Set or Array of dependency paths, or a Map with additional opts\n   *\n   * @note\n   * - The default opts for implicit dependencies can be defined using `setDependencyDefaults()`\n   * - Dynamic imports can be avoided by providing plugin modules with `setDependencyResolution()`\n   */\n  protected resolveDependenciesStanza() {\n    debug('resolveDependenciesStanza')\n\n    /** Attempt to dynamically require a plugin module */\n    const requireDependencyOrDie = (\n      parentName: string,\n      dependencyPath: string\n    ) => {\n      // If the user provided the plugin module already we use that\n      if (this._dependencyResolution.has(dependencyPath)) {\n        return this._dependencyResolution.get(dependencyPath) as PluginModule\n      }\n\n      const possiblePrefixes = ['puppeteer-extra-plugin-'] // could be extended later\n      const isAlreadyPrefixed = possiblePrefixes.some(prefix =>\n        dependencyPath.startsWith(prefix)\n      )\n      const packagePaths: string[] = []\n      // If the dependency is not already prefixed we attempt to require all possible combinations to find one that works\n      if (!isAlreadyPrefixed) {\n        packagePaths.push(\n          ...possiblePrefixes.map(prefix => prefix + dependencyPath)\n        )\n      }\n      // We always attempt to require the path verbatim (as a last resort)\n      packagePaths.push(dependencyPath)\n      const pluginModule = requirePackages<PluginModule>(packagePaths)\n      if (pluginModule) {\n        return pluginModule\n      }\n\n      const explanation = `\nThe plugin '${parentName}' listed '${dependencyPath}' as dependency,\nwhich could not be found. Please install it:\n\n${packagePaths\n  .map(packagePath => `yarn add ${packagePath.split('/')[0]}`)\n  .join(`\\n or:\\n`)}\n\nNote: You don't need to require the plugin yourself,\nunless you want to modify it's default settings.\n\nIf your bundler has issues with dynamic imports take a look at '.plugins.setDependencyResolution()'.\n      `\n      console.warn(explanation)\n      throw new Error('Plugin dependency not found')\n    }\n\n    const existingPluginNames = new Set(this.names)\n    const recursivelyLoadMissingDependencies = ({\n      name: parentName,\n      dependencies\n    }: Plugin): any => {\n      if (!dependencies) {\n        return\n      }\n      const processDependency = (dependencyPath: string, opts?: any) => {\n        const pluginModule = requireDependencyOrDie(parentName, dependencyPath)\n        opts = opts || this._dependencyDefaults.get(dependencyPath) || {}\n        const plugin = pluginModule(opts)\n        if (existingPluginNames.has(plugin.name)) {\n          debug(parentName, '=> dependency already exists:', plugin.name)\n          return\n        }\n        existingPluginNames.add(plugin.name)\n        debug(parentName, '=> adding new dependency:', plugin.name, opts)\n        this.add(plugin)\n        return recursivelyLoadMissingDependencies(plugin)\n      }\n\n      if (dependencies instanceof Set || Array.isArray(dependencies)) {\n        return [...dependencies].forEach(dependencyPath =>\n          processDependency(dependencyPath)\n        )\n      }\n      if (dependencies instanceof Map) {\n        // Note: `k,v => v,k` (Map + forEach will reverse the order)\n        return dependencies.forEach((v, k) => processDependency(k, v))\n      }\n    }\n    this.list.forEach(recursivelyLoadMissingDependencies)\n  }\n\n  /**\n   * Lightweight plugin dependency management to require plugins and code mods on demand.\n   * @private\n   */\n  protected resolveDependencies() {\n    debug('resolveDependencies')\n    this.resolvePluginsStanza()\n    this.resolveDependenciesStanza()\n  }\n}\n"
  },
  {
    "path": "packages/playwright-extra/src/puppeteer-compatiblity-shim/index.ts",
    "content": "import Debug from 'debug'\nconst debug = Debug('playwright-extra:puppeteer-compat')\n\nimport type * as pw from 'playwright-core'\n\nexport type PlaywrightObject = pw.Page | pw.Frame | pw.Browser\n\nexport interface PuppeteerBrowserShim {\n  isCompatShim?: boolean\n  isPlaywright?: boolean\n  pages?: pw.BrowserContext['pages']\n  userAgent: () => Promise<'string'>\n}\n\nexport interface PuppeteerPageShim {\n  isCompatShim?: boolean\n  isPlaywright?: boolean\n  browser?: () => pw.Browser\n  evaluateOnNewDocument?: pw.Page['addInitScript']\n  _client: () => pw.CDPSession\n}\n\nexport const isPlaywrightPage = (obj: unknown): obj is pw.Page => {\n  return 'unroute' in (obj as pw.Page)\n}\nexport const isPlaywrightFrame = (obj: unknown): obj is pw.Frame => {\n  return ['parentFrame', 'frameLocator'].every(x => x in (obj as pw.Frame))\n}\nexport const isPlaywrightBrowser = (obj: unknown): obj is pw.Browser => {\n  return 'newContext' in (obj as pw.Browser)\n}\nexport const isPuppeteerCompat = (obj?: unknown): obj is PlaywrightObject => {\n  return !!obj && typeof obj === 'object' && !!(obj as any).isCompatShim\n}\n\nconst cache = {\n  objectToShim: new Map<PlaywrightObject, PlaywrightObject>(),\n  cdpSession: {\n    page: new Map<pw.Page | pw.Frame, pw.CDPSession>(),\n    browser: new Map<pw.Browser, pw.CDPSession>()\n  }\n}\n\n/** Augment a Playwright object with compatibility with certain Puppeteer methods */\nexport function addPuppeteerCompat<\n  Input extends pw.Page | pw.Frame | pw.Browser | null\n>(object: Input): Input {\n  if (!object || typeof object !== 'object') {\n    return object\n  }\n  if (cache.objectToShim.has(object)) {\n    return cache.objectToShim.get(object) as Input\n  }\n  if (isPuppeteerCompat(object)) {\n    return object\n  }\n  debug('addPuppeteerCompat', cache.objectToShim.size)\n  if (isPlaywrightPage(object) || isPlaywrightFrame(object)) {\n    const shim = createPageShim(object)\n    cache.objectToShim.set(object, shim)\n    return shim as Input\n  }\n  if (isPlaywrightBrowser(object)) {\n    const shim = createBrowserShim(object)\n    cache.objectToShim.set(object, shim)\n    return shim as Input\n  }\n  debug('Received unknown object:', Reflect.ownKeys(object))\n  return object\n}\n\n// Only chromium browsers support CDP\nconst dummyCDPClient = {\n  send: async (...args: any[]) => {\n    debug('dummy CDP client called', 'send', args)\n  },\n  on: (...args: any[]) => {\n    debug('dummy CDP client called', 'on', args)\n  }\n} as pw.CDPSession\n\nexport async function getPageCDPSession(page: pw.Page | pw.Frame) {\n  let session = cache.cdpSession.page.get(page)\n  if (session) {\n    debug('getPageCDPSession: use existing')\n    return session\n  }\n  debug('getPageCDPSession: use new')\n  const context = isPlaywrightFrame(page)\n    ? page.page().context()\n    : page.context()\n  try {\n    session = await context.newCDPSession(page)\n    cache.cdpSession.page.set(page, session)\n    return session\n  } catch (err: any) {\n    debug('getPageCDPSession: error while creating session:', err.message)\n    debug(\n      'getPageCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy'\n    )\n  }\n  return dummyCDPClient\n}\n\nexport async function getBrowserCDPSession(browser: pw.Browser) {\n  let session = cache.cdpSession.browser.get(browser)\n  if (session) {\n    debug('getBrowserCDPSession: use existing')\n    return session\n  }\n  debug('getBrowserCDPSession: use new')\n  try {\n    session = await browser.newBrowserCDPSession()\n    cache.cdpSession.browser.set(browser, session)\n    return session\n  } catch (err: any) {\n    debug('getBrowserCDPSession: error while creating session:', err.message)\n    debug(\n      'getBrowserCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy'\n    )\n  }\n  return dummyCDPClient\n}\n\nexport function createPageShim(page: pw.Page | pw.Frame) {\n  const objId = Math.random().toString(36).substring(2, 7)\n  const shim = new Proxy(page, {\n    get(target, prop) {\n      if (prop === 'isCompatShim' || prop === 'isPlaywright') {\n        return true\n      }\n      debug('page - get', objId, prop)\n      if (prop === '_client') {\n        return () => ({\n          send: async (method: string, params: any) => {\n            const session = await getPageCDPSession(page)\n            return await session.send(method as any, params)\n          },\n          on: (event: string, listener: any) => {\n            getPageCDPSession(page).then(session => {\n              session.on(event as any, listener)\n            })\n          }\n        })\n      }\n      if (prop === 'setBypassCSP') {\n        return async (enabled: boolean) => {\n          const session = await getPageCDPSession(page)\n          return await session.send('Page.setBypassCSP', {\n            enabled\n          })\n        }\n      }\n      if (prop === 'setUserAgent') {\n        return async (userAgent: string, userAgentMetadata?: any) => {\n          const session = await getPageCDPSession(page)\n          return await session.send('Emulation.setUserAgentOverride', {\n            userAgent,\n            userAgentMetadata\n          })\n        }\n      }\n      if (prop === 'browser') {\n        if (isPlaywrightPage(page)) {\n          return () => {\n            let browser = page.context().browser()\n            if (!browser) {\n              debug(\n                'page.browser() - not available, most likely due to launchPersistentContext'\n              )\n              // Use a page shim as quick drop-in (so browser.userAgent() still works)\n              browser = page as any\n            }\n            return addPuppeteerCompat(browser)\n          }\n        }\n      }\n      if (prop === 'evaluateOnNewDocument') {\n        if (isPlaywrightPage(page)) {\n          return async function (pageFunction: any | string, ...args: any[]) {\n            return await page.addInitScript(pageFunction, args[0])\n          }\n        }\n      }\n      // Only relevant when page is being used a pseudo stand-in for the browser object (launchPersistentContext)\n      if (prop === 'userAgent') {\n        return async (enabled: boolean) => {\n          const session = await getPageCDPSession(page)\n          const data = await session.send('Browser.getVersion')\n          return data.userAgent\n        }\n      }\n      return Reflect.get(target, prop)\n    }\n  })\n  return shim\n}\n\nexport function createBrowserShim(browser: pw.Browser) {\n  const objId = Math.random().toString(36).substring(2, 7)\n  const shim = new Proxy(browser, {\n    get(target, prop) {\n      if (prop === 'isCompatShim' || prop === 'isPlaywright') {\n        return true\n      }\n      debug('browser - get', objId, prop)\n      if (prop === 'pages') {\n        return () =>\n          browser\n            .contexts()\n            .flatMap(c => c.pages().map(page => addPuppeteerCompat(page)))\n      }\n      if (prop === 'userAgent') {\n        return async () => {\n          const session = await getBrowserCDPSession(browser)\n          const data = await session.send('Browser.getVersion')\n          return data.userAgent\n        }\n      }\n      return Reflect.get(target, prop)\n    }\n  })\n  return shim\n}\n"
  },
  {
    "path": "packages/playwright-extra/src/puppeteer-compatiblity-shim/playwright-shim.d.ts",
    "content": "// Playwright objects extended with puppeteer compatiblity shims\n\nimport type {} from 'playwright-core'\n\nimport type { PuppeteerPageShim, PuppeteerBrowserShim } from '.'\n\ndeclare module 'playwright-core' {\n  interface Page extends PuppeteerPageShim {}\n  interface Frame extends PuppeteerPageShim {}\n  interface Browser extends PuppeteerBrowserShim {}\n}\n"
  },
  {
    "path": "packages/playwright-extra/src/types/index.ts",
    "content": "import type * as pw from 'playwright-core'\n\ntype PropType<TObj, TProp extends keyof TObj> = TObj[TProp]\ntype PluginEnv = { framework: 'playwright' }\n\n/** Strongly typed plugin lifecycle events for internal use */\nexport abstract class PluginLifecycleMethods {\n  async onPluginRegistered(env?: PluginEnv): Promise<void> {}\n  async beforeLaunch(\n    options: pw.LaunchOptions\n  ): Promise<pw.LaunchOptions | void> {}\n  async afterLaunch(browserOrContext?: pw.Browser | pw.BrowserContext) {}\n  async beforeConnect(\n    options: pw.ConnectOptions\n  ): Promise<pw.ConnectOptions | void> {}\n  async afterConnect(browser: pw.Browser) {}\n  async onBrowser(browser: pw.Browser) {}\n  async onPageCreated(page: pw.Page) {}\n  async onPageClose(page: pw.Page) {}\n  async onDisconnected(browser?: pw.Browser) {}\n  // Playwright only at the moment\n  async beforeContext(\n    options?: pw.BrowserContextOptions,\n    browser?: pw.Browser\n  ): Promise<pw.BrowserContextOptions | void> {}\n  async onContextCreated(\n    context?: pw.BrowserContext,\n    options?: pw.BrowserContextOptions\n  ) {}\n}\n\n/** A valid plugin method name */\nexport type PluginMethodName = keyof PluginLifecycleMethods\n/** A valid plugin method function */\nexport type PluginMethodFn<TName extends PluginMethodName> = PropType<\n  PluginLifecycleMethods,\n  TName\n>\n\ntype PluginRequirements = Set<\n  'launch' | 'headful' | 'dataFromPlugins' | 'runLast'\n>\n\n// PuppeteerExtraPlugin only supports Set, the others are future proofing\ntype PluginDependencies = Set<string> | Map<string, any> | string[]\n\ninterface PluginData {\n  name:\n    | string\n    // below is compat with a previously incorrect typing\n    | {\n        [key: string]: any\n      }\n  value: {\n    [key: string]: any\n  }\n}\n\nexport interface CompatiblePluginLifecycleMethods {\n  onPluginRegistered(...any: any[]): Promise<any> | any\n  beforeLaunch(...any: any[]): Promise<any> | any\n  afterLaunch(...any: any[]): Promise<any> | any\n  beforeConnect(...any: any[]): Promise<any> | any\n  afterConnect(...any: any[]): Promise<any> | any\n  onBrowser(...any: any[]): Promise<any> | any\n  onPageCreated(...any: any[]): Promise<any> | any\n  onPageClose(...any: any[]): Promise<any> | any\n  onDisconnected(...any: any[]): Promise<any> | any\n  // Playwright only at the moment\n  beforeContext(...any: any[]): Promise<any> | any\n  onContextCreated(...any: any[]): Promise<any> | any\n}\n\n/**\n * PuppeteerExtraPlugin interface, strongly typed for internal use\n * @private\n */\nexport interface PuppeteerExtraPlugin extends Partial<PluginLifecycleMethods> {\n  _isPuppeteerExtraPlugin: boolean\n  name: string\n  /** Disable the puppeteer compatibility shim for this plugin */\n  noPuppeteerShim?: boolean\n  requirements?: PluginRequirements\n  dependencies?: PluginDependencies\n  data?: PluginData[]\n  getDataFromPlugins?(name?: string): void\n  _registerChildClassMembers?(prototype: any): void\n  _childClassMembers?: string[]\n  plugins?: CompatiblePlugin[]\n  // [propName: string]: any\n}\n\n/**\n * Minimal compatible PuppeteerExtraPlugin interface\n * @private\n */\nexport interface CompatiblePuppeteerPlugin\n  extends Partial<CompatiblePluginLifecycleMethods> {\n  _isPuppeteerExtraPlugin: boolean\n  name?: string\n}\n// Future proofing\nexport interface CompatiblePlaywrightPlugin\n  extends Partial<CompatiblePluginLifecycleMethods> {\n  _isPlaywrightExtraPlugin: boolean\n  name?: string\n}\n// Future proofing\nexport interface CompatibleExtraPlugin\n  extends Partial<CompatiblePluginLifecycleMethods> {\n  _isExtraPlugin: boolean\n  name?: string\n}\n\n/**\n * A compatible plugin\n */\nexport type CompatiblePlugin =\n  | CompatiblePuppeteerPlugin\n  | CompatiblePlaywrightPlugin\n  | CompatibleExtraPlugin\nexport type CompatiblePluginModule = (...args: any[]) => CompatiblePlugin\n\nexport type Plugin = PuppeteerExtraPlugin\nexport type PluginModule = (...args: any[]) => Plugin\n"
  },
  {
    "path": "packages/playwright-extra/test/exports.spec.ts",
    "content": "import { test, expect } from './fixtures/extra'\n\ntest('should export the basic functionality', async ({ playwrightExtra }) => {\n  expect(playwrightExtra.addExtra).toBeDefined()\n  expect(playwrightExtra.chromium).toBeDefined()\n  expect(playwrightExtra.chromium.use).toBeDefined()\n  expect(playwrightExtra.chromium.plugins).toBeDefined()\n  expect(playwrightExtra.chromium.plugins.list).toBeDefined()\n  expect(playwrightExtra.chromium.plugins.names).toBeDefined()\n  expect(playwrightExtra.chromium.plugins.onPluginError).toBeDefined()\n  expect(playwrightExtra.chromium.launch).toBeDefined()\n  expect(playwrightExtra.chromium.launchPersistentContext).toBeDefined()\n  expect(playwrightExtra.chromium.connect).toBeDefined()\n  expect(playwrightExtra.chromium.connectOverCDP).toBeDefined()\n  expect(playwrightExtra.firefox).toBeDefined()\n  expect(playwrightExtra.firefox.use).toBeDefined()\n  expect(playwrightExtra.firefox.launch).toBeDefined()\n  expect(playwrightExtra.firefox.connect).toBeDefined()\n  expect(playwrightExtra.webkit).toBeDefined()\n  expect(playwrightExtra.webkit.use).toBeDefined()\n  expect(playwrightExtra.webkit.launch).toBeDefined()\n  expect(playwrightExtra.webkit.connect).toBeDefined()\n  expect((playwrightExtra as any).nonexistent).toBeUndefined()\n})\n\ntest('chromium export should be well formed', async ({ playwrightExtra }) => {\n  const { chromium } = playwrightExtra\n  expect(typeof chromium).toBe('object')\n  expect(typeof chromium.use).toBe('function')\n  expect(typeof chromium.launch).toBe('function')\n  expect(typeof chromium.connect).toBe('function')\n  expect(typeof chromium.name).toBe('function')\n  expect(typeof chromium.name()).toBe('string')\n  expect(chromium.constructor.name).toBe('PlaywrightExtraClass')\n})\n\ntest('addExtra export should be well formed', async ({ playwrightExtra }) => {\n  const { addExtra } = playwrightExtra\n  expect(typeof addExtra).toBe('function')\n\n  const launcher = addExtra()\n  expect(typeof launcher).toBe('object')\n  expect(launcher.constructor.name).toBe('PlaywrightExtraClass')\n})\n\ntest('should re-export the same additional exports verbatim', async ({\n  playwrightExtra,\n  playwrightVanilla\n}) => {\n  expect(playwrightExtra.errors).toStrictEqual(playwrightVanilla.errors)\n  expect(playwrightExtra.devices).toStrictEqual(playwrightVanilla.devices)\n  expect(playwrightExtra.selectors).toStrictEqual(playwrightVanilla.selectors)\n  expect(playwrightExtra.request).toStrictEqual(playwrightVanilla.request)\n})\n"
  },
  {
    "path": "packages/playwright-extra/test/fixtures/dummyplugin.ts",
    "content": "import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'\n\nexport class DummyPlugin extends PuppeteerExtraPlugin {\n  public pluginEventList: string[] = []\n  public pluginEventMap: Map<string, any> = new Map()\n\n  constructor(opts = {}) {\n    super(opts)\n  }\n  get name() {\n    return 'dummy'\n  }\n\n  async onPluginRegistered(...args: any[]) {\n    this.pluginEventList.push('onPluginRegistered')\n  }\n  async beforeLaunch(...args: any[]) {\n    this.pluginEventList.push('beforeLaunch')\n  }\n  async afterLaunch(...args: any[]) {\n    this.pluginEventList.push('afterLaunch')\n  }\n  async beforeConnect(...args: any[]) {\n    this.pluginEventList.push('beforeConnect')\n  }\n  async afterConnect(...args: any[]) {\n    this.pluginEventList.push('afterConnect')\n  }\n  async onBrowser(...args: any[]) {\n    this.pluginEventList.push('onBrowser')\n  }\n  async onTargetCreated(...args: any[]) {\n    this.pluginEventList.push('onTargetCreated')\n  }\n  async onPageCreated(...args: any[]) {\n    this.pluginEventList.push('onPageCreated')\n  }\n  async onTargetChanged(...args: any[]) {\n    this.pluginEventList.push('onTargetChanged')\n  }\n  async onTargetDestroyed(...args: any[]) {\n    this.pluginEventList.push('onTargetDestroyed')\n  }\n  async onDisconnected(...args: any[]) {\n    this.pluginEventList.push('onDisconnected')\n  }\n  async onClose(...args: any[]) {\n    this.pluginEventList.push('onClose')\n  }\n\n  // playwright only at the moment\n  async beforeContext(...args: any[]) {\n    this.pluginEventList.push('beforeContext')\n  }\n  async onContextCreated(...args: any[]) {\n    this.pluginEventList.push('onContextCreated')\n  }\n}\n"
  },
  {
    "path": "packages/playwright-extra/test/fixtures/extra.ts",
    "content": "// Playwrights test runner is great, originally based on folio (which unfortunately isn't maintained anymore): https://github.com/microsoft/folio\nimport { test as base } from '@playwright/test'\nimport * as pwTest from '@playwright/test'\n\nimport * as pwExtraModule from '../../src'\nimport * as pwVanillaModule from 'playwright-core'\n\ntype PluginModuleWithOptions = { module: any; opts?: Record<string, any> }\n\nexport type ExtraOptions = {}\n\nexport type ExtraFixtures = {\n  /** playwright-extra module */\n  playwrightExtra: typeof pwExtraModule\n  /** playwright-core module */\n  playwrightVanilla: typeof pwVanillaModule\n  /** Augmented launcher */\n  extraLauncher: pwExtraModule.AugmentedBrowserLauncher\n}\n\ntype WorkerFixtures = {\n  _connectedBrowser: pwTest.Browser | undefined\n  _browserOptions: pwTest.LaunchOptions\n  _artifactsDir: () => string\n  _snapshotSuffix: string\n\n  plugins: PluginModuleWithOptions[]\n}\n\nexport const worker = base.extend<{}, WorkerFixtures>({\n  plugins: [[], { option: true, scope: 'worker' as any }],\n\n  browser: async (\n    { playwright, browserName, _connectedBrowser, plugins },\n    use\n  ) => {\n    if (_connectedBrowser) {\n      await use(_connectedBrowser)\n      return\n    }\n\n    if (!['chromium', 'firefox', 'webkit'].includes(browserName))\n      throw new Error(\n        `Unexpected browserName \"${browserName}\", must be one of \"chromium\", \"firefox\" or \"webkit\"`\n      )\n    const launcher = pwExtraModule.addExtra(playwright[browserName])\n\n    plugins.forEach(({ module: pluginModule, opts }) => {\n      launcher.use(pluginModule(opts))\n    })\n\n    const browser = await launcher.launch()\n    ;(browser as any)._launcher = launcher\n    await use(browser as any)\n    await browser.close()\n  }\n})\n\n// Extend base test by providing \"todoPage\" and \"settingsPage\".\n// This new \"test\" can be used in multiple test files, and each of them will get the fixtures.\nexport const test = worker.extend<ExtraOptions & ExtraFixtures>({\n  extraLauncher: async (\n    { plugins, playwrightExtra, playwrightVanilla, browserName },\n    use\n  ) => {\n    const launcher = playwrightExtra.addExtra(playwrightVanilla[browserName])\n    plugins.forEach(({ module: pluginModule, opts }) => {\n      launcher.use(pluginModule(opts))\n    })\n    await use(launcher)\n  },\n  playwrightExtra: async ({}, use) => {\n    await use(pwExtraModule)\n  },\n  playwrightVanilla: async ({}, use) => {\n    await use(pwVanillaModule)\n  }\n})\n\nexport { expect } from '@playwright/test'\n"
  },
  {
    "path": "packages/playwright-extra/test/playwright.config.ts",
    "content": "import { type PlaywrightTestConfig } from '@playwright/test'\n\nconst config: PlaywrightTestConfig = {\n  retries: 3,\n  workers: 3,\n\n  use: {\n    browserName: 'chromium'\n  },\n\n  projects: [\n    {\n      name: 'chromium',\n      use: {\n        browserName: 'chromium',\n        launchOptions: {\n          chromiumSandbox: process.env.CI ? false : true,\n          args: process.env.CI\n            ? ['--no-sandbox', '--disable-setuid-sandbox']\n            : []\n        }\n      }\n    },\n    {\n      name: 'firefox',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'webkit',\n      use: {\n        browserName: 'webkit'\n        // Note: webkit doesn't support --no-sandbox\n      }\n    }\n  ]\n}\n\nexport default config\n"
  },
  {
    "path": "packages/playwright-extra/test/plugin-events.spec.ts",
    "content": "import { test, expect } from './fixtures/extra'\n\nimport { DummyPlugin } from './fixtures/dummyplugin'\n\ntest.use({ plugins: [{ module: (opts: any) => new DummyPlugin(opts) }] })\n\ntest('emits correct events for launch', async ({ extraLauncher }) => {\n  const browser = await extraLauncher.launch()\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.close()\n  await browser.close()\n\n  const plugin = extraLauncher.plugins.list[0] as unknown as DummyPlugin\n  expect(plugin.pluginEventList).toStrictEqual([\n    'onPluginRegistered',\n    'beforeLaunch',\n    'onBrowser',\n    'afterLaunch',\n    'beforeContext',\n    'onContextCreated',\n    'onPageCreated',\n    'onDisconnected'\n  ])\n})\n\ntest('emits correct events for launch without .newContext()', async ({\n  extraLauncher\n}) => {\n  const browser = await extraLauncher.launch()\n  const page = await browser.newPage()\n  await page.close()\n  await browser.close()\n\n  const plugin = extraLauncher.plugins.list[0] as unknown as DummyPlugin\n  expect(plugin.pluginEventList).toStrictEqual([\n    'onPluginRegistered',\n    'beforeLaunch',\n    'onBrowser',\n    'afterLaunch',\n    'beforeContext',\n    'onContextCreated',\n    'onPageCreated',\n    'onDisconnected'\n  ])\n})\n\ntest('emits correct events for launchPersistentContext', async ({\n  extraLauncher\n}) => {\n  const context = await extraLauncher.launchPersistentContext('')\n  const page = await context.newPage()\n  await page.close()\n  await context.close()\n\n  const plugin = extraLauncher.plugins.list[0] as unknown as DummyPlugin\n  expect(plugin.pluginEventList).toStrictEqual([\n    'onPluginRegistered',\n    'beforeLaunch',\n    'afterLaunch',\n    'onContextCreated',\n    'onPageCreated',\n    'onDisconnected'\n  ])\n})\n\ntest('emits correct events for connect', async ({ extraLauncher }) => {\n  const server = await extraLauncher.launchServer()\n\n  const browser = await extraLauncher.connect(server.wsEndpoint())\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.close()\n  await browser.close()\n  await server.close()\n\n  const plugin = extraLauncher.plugins.list[0] as unknown as DummyPlugin\n  expect(plugin.pluginEventList).toStrictEqual([\n    'onPluginRegistered',\n    'beforeConnect',\n    'onBrowser',\n    'afterConnect',\n    'beforeContext',\n    'onContextCreated',\n    'onPageCreated',\n    'onDisconnected'\n  ])\n})\n\ntest('emits correct events for connectOverCDP', async ({\n  extraLauncher,\n  browserName\n}) => {\n  test.skip(browserName !== 'chromium', 'Chromium only')\n\n  const server = await extraLauncher.launchServer({\n    args: ['--remote-debugging-port=9333']\n  })\n\n  const browser = await extraLauncher.connectOverCDP('http://localhost:9333')\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.close()\n  await browser.close()\n  await server.close()\n\n  const plugin = extraLauncher.plugins.list[0] as unknown as DummyPlugin\n  expect(plugin.pluginEventList).toStrictEqual([\n    'onPluginRegistered',\n    'beforeConnect',\n    'onBrowser',\n    'afterConnect',\n    'beforeContext',\n    'onContextCreated',\n    'onPageCreated',\n    'onDisconnected'\n  ])\n})\n"
  },
  {
    "path": "packages/playwright-extra/test/puppeteer-plugins/anonymize-ua.spec.ts",
    "content": "import { test, expect } from '../fixtures/extra'\n\nimport AnonymizeUAPlugin from 'puppeteer-extra-plugin-anonymize-ua'\n\ntest('puppeteer-extra-plugin-anonymize-ua will remove headless', async ({\n  browserName,\n  extraLauncher,\n  _browserOptions\n}) => {\n  test.skip(browserName !== 'chromium', 'Chromium only')\n\n  const pluginErrors = []\n  extraLauncher.plugins.onPluginError = (plugin, method, err) => {\n    pluginErrors.push(err)\n  }\n\n  extraLauncher.use(AnonymizeUAPlugin())\n  expect(extraLauncher.plugins.list.length).toEqual(1)\n  expect(extraLauncher.plugins.list[0].name).toEqual('anonymize-ua')\n\n  const browser = await extraLauncher.launch(_browserOptions)\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.goto('https://example.com')\n\n  const ua = await page.evaluate(() => navigator.userAgent)\n  expect(ua.includes('Headless')).toBeFalsy()\n  expect(pluginErrors).toStrictEqual([])\n\n  await browser.close()\n})\n\ntest('puppeteer-extra-plugin-anonymize-ua will allow a custom UA', async ({\n  browserName,\n  extraLauncher,\n  _browserOptions\n}) => {\n  test.skip(browserName !== 'chromium', 'Chromium only')\n\n  const pluginErrors = []\n  extraLauncher.plugins.onPluginError = (plugin, method, err) => {\n    pluginErrors.push(err)\n  }\n  extraLauncher.use(\n    AnonymizeUAPlugin({\n      customFn: ua => 'MyCoolUserAgent'\n    })\n  )\n  expect(extraLauncher.plugins.list.length).toEqual(1)\n  expect(extraLauncher.plugins.list[0].name).toEqual('anonymize-ua')\n\n  const browser = await extraLauncher.launch(_browserOptions)\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.goto('https://example.com')\n\n  const ua = await page.evaluate(() => navigator.userAgent)\n  expect(ua).toBe('MyCoolUserAgent')\n  expect(pluginErrors).toStrictEqual([])\n\n  await browser.close()\n})\n"
  },
  {
    "path": "packages/playwright-extra/test/puppeteer-plugins/recaptcha.spec.ts",
    "content": "import { test, expect } from '../fixtures/extra'\n\nimport RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha'\n\n// Supports all browsers\ntest('puppeteer-extra-plugin-recaptcha will detect captchas', async ({\n  extraLauncher,\n  _browserOptions\n}) => {\n  const pluginErrors = []\n  extraLauncher.plugins.onPluginError = (plugin, method, err) => {\n    pluginErrors.push(err)\n  }\n\n  const instance = RecaptchaPlugin()\n  extraLauncher.use(instance)\n  expect(extraLauncher.plugins.list.length).toEqual(1)\n  expect(extraLauncher.plugins.list[0].name).toEqual(instance.name)\n\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-checkbox-auto.html'\n  const browser = await extraLauncher.launch(_browserOptions)\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.goto(url)\n\n  const { captchas, error } = await (page as any).findRecaptchas()\n\n  expect(error).toBeFalsy()\n  expect(captchas).toBeTruthy()\n  expect(captchas.length).toBe(1)\n  const captcha = captchas[0]\n  expect(captcha._vendor).toBe('recaptcha')\n  expect(captcha._type).toBe('checkbox')\n  expect(captcha.url).toBe(url)\n  expect(captcha.id).toBeTruthy()\n  expect(captcha.sitekey).toBeTruthy()\n\n  expect(pluginErrors).toStrictEqual([])\n  await browser.close()\n})\n\ntest('puppeteer-extra-plugin-recaptcha will solve captchas', async ({\n  extraLauncher,\n  _browserOptions\n}) => {\n  test.skip(!process.env.TWOCAPTCHA_TOKEN, 'TWOCAPTCHA_TOKEN not set')\n  test.slow()\n\n  const pluginErrors = []\n  extraLauncher.plugins.onPluginError = (plugin, method, err) => {\n    pluginErrors.push(err)\n  }\n\n  const instance = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN\n    }\n  })\n  extraLauncher.use(instance)\n  expect(extraLauncher.plugins.list.length).toEqual(1)\n  expect(extraLauncher.plugins.list[0].name).toEqual(instance.name)\n\n  const url = 'https://www.google.com/recaptcha/api2/demo'\n  const browser = await extraLauncher.launch(_browserOptions)\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.goto(url, { waitUntil: 'networkidle' })\n\n  const { solved, error } = await (page as any).solveRecaptchas()\n  expect(error).toBeFalsy()\n  expect(solved).toBeTruthy()\n  expect(solved.length).toBe(1)\n\n  await Promise.all([\n    page.waitForNavigation({ waitUntil: 'networkidle' }),\n    page.click(`#recaptcha-demo-submit`)\n  ])\n  const content = await page.content()\n  expect(content).toMatch('Verification Success... Hooray!')\n\n  expect(pluginErrors).toStrictEqual([])\n  await browser.close()\n})\n"
  },
  {
    "path": "packages/playwright-extra/test/puppeteer-plugins/stealth.spec.ts",
    "content": "import { test, expect } from '../fixtures/extra'\n\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\n\ntest('puppeteer-extra-plugin-stealth will work', async ({\n  browserName,\n  extraLauncher,\n  _browserOptions\n}) => {\n  test.skip(browserName !== 'chromium', 'Chromium only')\n\n  const pluginErrors = []\n  extraLauncher.plugins.onPluginError = (plugin, method, err) => {\n    pluginErrors.push(err)\n  }\n\n  extraLauncher.use(StealthPlugin())\n  expect(extraLauncher.plugins.list.length).toEqual(1)\n  expect(extraLauncher.plugins.list[0].name).toEqual('stealth')\n\n  extraLauncher.plugins.setDependencyDefaults('stealth/evasions/webgl.vendor', {\n    vendor: 'Bob',\n    renderer: 'Alice'\n  })\n\n  const browser = await extraLauncher.launch(_browserOptions)\n  const context = await browser.newContext()\n  const page = await context.newPage()\n  await page.goto('https://example.com')\n\n  const webgl = await page.evaluate(getWebglUnmasked)\n  expect(webgl).toStrictEqual({ renderer: 'Alice', vendor: 'Bob' })\n\n  expect(pluginErrors).toStrictEqual([])\n\n  await browser.close()\n})\n\nfunction getWebglUnmasked() {\n  const gl = document.createElement('canvas').getContext('webgl') as any\n  if (!gl) {\n    return {\n      error: 'no webgl'\n    }\n  }\n  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')\n  if (debugInfo) {\n    return {\n      vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),\n      renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)\n    }\n  }\n  return {\n    error: 'no WEBGL_debug_renderer_info'\n  }\n}\n"
  },
  {
    "path": "packages/playwright-extra/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"dom\"],\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"./src/**/*.tsx\", \"./src/**/*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/package.json",
    "content": "{\n  \"name\": \"@extra/proxy-router\",\n  \"version\": \"3.1.6\",\n  \"description\": \"A plugin for playwright & puppeteer to route proxies dynamically.\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/plugin-proxy-router\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"tscheck\": \"tsc --pretty --noEmit\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup\",\n    \"build:tsc\": \"tsc --project tsconfig.json --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"node -e 0\",\n    \"test\": \"run-s build\",\n    \"pretest-ci\": \"run-s build\",\n    \"test-ci\": \"run-s build\"\n  },\n  \"engines\": {\n    \"node\": \">=14\"\n  },\n  \"prettier\": {\n    \"printWidth\": 80,\n    \"semi\": false,\n    \"singleQuote\": true\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"playwright\",\n    \"puppeteer-extra\",\n    \"playwright-extra\",\n    \"proxy\",\n    \"proxy-router\",\n    \"headless\",\n    \"luminati\"\n  ],\n  \"devDependencies\": {\n    \"@types/debug\": \"^4.1.5\",\n    \"@types/node\": \"14.17.6\",\n    \"@types/puppeteer\": \"*\",\n    \"ava\": \"2.4.0\",\n    \"copyfiles\": \"^2.1.1\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"playwright-core\": \"1.24.2\",\n    \"prettier\": \"^2.7.1\",\n    \"puppeteer\": \"^15.5.0\",\n    \"puppeteer-extra\": \"^3.3.6\",\n    \"replace-in-files-cli\": \"^0.3.1\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"ts-node\": \"^8.5.4\",\n    \"typescript\": \"^4.7.4\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"merge-deep\": \"^3.0.2\",\n    \"proxy-chain\": \"^2.0.6\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  }\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/readme.md",
    "content": "# @extra/proxy-router [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push)](https://github.com/berstend/puppeteer-extra/actions) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/@extra/proxy-router.svg)](https://www.npmjs.com/package/@extra/proxy-router)\n\n> A plugin for [playwright-extra] and [puppeteer-extra] to route proxies dynamically.\n\n## Install\n\n```bash\nyarn add @extra/proxy-router\n# - or -\nnpm install @extra/proxy-router\n```\n\n<details>\n <summary>Playwright</summary>\n\nIf this is your first [playwright-extra] plugin here's everything you need:\n\n```bash\nyarn add playwright playwright-extra @extra/proxy-router\n# - or -\nnpm install playwright playwright-extra @extra/proxy-router\n```\n\n</details>\n<details>\n <summary>Puppeteer</summary>\n\nIf this is your first [puppeteer-extra] plugin here's everything you need:\n\n```bash\nyarn add puppeteer puppeteer-extra @extra/proxy-router\n# - or -\nnpm install puppeteer puppeteer-extra @extra/proxy-router\n```\n\n</details>\n\n### Compatibility\n\n|           💫           | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chromium/chromium.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](#)<br/>Chromium | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](#)<br/>Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](#)<br/>Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Webkit\" width=\"24px\" height=\"24px\" />](#)<br/>Webkit |\n| :--------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------: |\n| **[playwright-extra]** |                                                                               ✅                                                                               |                                                                               ✅                                                                               |                                                                                 ✅                                                                                 |                                                                               ✅                                                                               |\n| **[puppeteer-extra]**  |                                                                               ✅                                                                               |                                                                               ✅                                                                               |                                      [🕒](https://github.com/berstend/puppeteer-extra/wiki/Is-Puppeteer-Firefox-ready-yet%3F)                                      |                                                                               -                                                                                |\n\n| Headless | Headful | Launch |             Connect              |\n| :------: | :-----: | :----: | :------------------------------: |\n|    ✅    |   ✅    |   ✅   | ✅ <sup><sub>(local)</sub></sup> |\n\n### Features\n\nThe plugin makes using proxies in the browser a lot more convenient:\n\n- Handles proxy authentication\n- Multiple proxies can be used\n- Flexible proxy routing using the host/domain\n- Change proxies dynamically after browser launch\n- Collect traffic stats per proxy or host\n- Uses native browser features, no performance loss\n\n## Usage\n\n> Using puppeteer? To use the following playwright examples simply change your [imports](#imports)\n\n### Simple\n\nA single proxy for all browser connections\n\n```js\n// playwright-extra is a drop-in replacement for playwright,\n// it augments the installed playwright with plugin functionality\n// Note: Instead of chromium you can use firefox and webkit as well.\nconst { chromium } = require('playwright-extra')\n\n// Configure and add the proxy router plugin with a default proxy\nconst ProxyRouter = require('@extra/proxy-router')\nchromium.use(\n  ProxyRouter({\n    proxies: { DEFAULT: 'http://user:pass@proxyhost:port' },\n  })\n)\n\n// That's it, the default proxy will be used and proxy authentication handled automatically\nchromium.launch({ headless: false }).then(async (browser) => {\n  const page = await browser.newPage()\n  await page.goto('https://canhazip.com', { waitUntil: 'domcontentloaded' })\n  const ip = await page.evaluate('document.body.innerText')\n  console.log('Outbound IP:', ip)\n  await browser.close()\n})\n```\n\n### Dynamic routing\n\nUse multiple proxies and route connections flexibly\n\n```js\n// playwright-extra is a drop-in replacement for playwright,\n// it augments the installed playwright with plugin functionality\n// Note: Instead of chromium you can use firefox and webkit as well.\nconst { chromium } = require('playwright-extra')\n\n// Configure the proxy router plugin\nconst ProxyRouter = require('@extra/proxy-router')\nconst proxyRouter = ProxyRouter({\n  // define the available proxies (replace this with your proxies)\n  proxies: {\n    // the default browser proxy, can be `null` as well for direct connections\n    DEFAULT: 'http://user:pass@proxyhost:port',\n    // optionally define more proxies you can use in `routeByHost`\n    // you can use whatever names you'd like for them\n    DATACENTER: 'http://user:pass@proxyhost2:port',\n    RESIDENTIAL_US: 'http://user:pass@proxyhost3:port',\n  },\n  // optional function for flexible proxy routing\n  // if this is not specified the `DEFAULT` proxy will be used for all connections\n  routeByHost: async ({ host }) => {\n    if (['pagead2.googlesyndication.com', 'fonts.gstatic.com'].includes(host)) {\n      return 'ABORT' // block connection to certain hosts\n    }\n    if (host.includes('google')) {\n      return 'DIRECT' // use a direct connection for all google domains\n    }\n    if (host.endsWith('.tile.openstreetmap.org')) {\n      return 'DATACENTER' // route heavy images through datacenter proxy\n    }\n    if (host === 'canhazip.com') {\n      return 'RESIDENTIAL_US' // special proxy for this domain\n    }\n    // everything else will use `DEFAULT` proxy\n  },\n})\n\n// Add the plugin\nchromium.use(proxyRouter)\n\n// Launch a browser and run some IP checks\nchromium.launch({ headless: true }).then(async (browser) => {\n  const page = await browser.newPage()\n\n  await page.goto('https://showmyip.com/', { waitUntil: 'domcontentloaded' })\n  const ip1 = await page.evaluate(\"document.querySelector('#ipv4').innerText\")\n  console.log('Outbound IP #1:', ip1)\n  // => 77.191.128.0 (the DEFAULT proxy)\n\n  await page.goto('https://canhazip.com', { waitUntil: 'domcontentloaded' })\n  const ip2 = await page.evaluate('document.body.innerText')\n  console.log('Outbound IP #2:', ip2)\n  // => 104.179.129.27 (the RESIDENTIAL_US proxy)\n\n  console.log(proxyRouter.stats.connectionLog) // list of connections (host => proxy name)\n  // { id: 0, proxy: 'DIRECT', host: 'accounts.google.com' },\n  // { id: 1, proxy: 'DEFAULT', host: 'www.showmyip.com' },\n  // { id: 2, proxy: 'ABORT', host: 'pagead2.googlesyndication.com' },\n  // { id: 3, proxy: 'DEFAULT', host: 'unpkg.com' },\n  // ...\n\n  console.log(proxyRouter.stats.byProxy) // bytes used by proxy\n  // {\n  //   DATACENTER: 441734,\n  //   DEFAULT: 125823,\n  //   DIRECT: 100457,\n  //   RESIDENTIAL_US: 4764,\n  //   ABORT: 0\n  // }\n\n  console.log(proxyRouter.stats.byHost) // bytes used by host\n  // {\n  //   'a.tile.openstreetmap.org': 150685,\n  //   'c.tile.openstreetmap.org': 147054,\n  //   'b.tile.openstreetmap.org': 143995,\n  //   'unpkg.com': 57621,\n  //   'www.googletagmanager.com': 49572,\n  //   'www.showmyip.com': 40408,\n  // ...\n\n  await browser.close()\n})\n```\n\n### Imports\n\n<details>\n <summary>Usage with Puppeteer</summary><br/>\n\n> The code is essentially the same as the playwright example above. :-)\n\nJust change the import and package name:\n\n```diff\n- const { chromium } = require('playwright-extra')\n+ const puppeteer = require('puppeteer-extra')\n// ...\n- chromium.use(proxyRouter)\n+ puppeteer.use(proxyRouter)\n// ...\n- chromium.launch()\n+ puppeteer.launch()\n// ...\n```\n\n</details>\n\n<details>\n <summary>Typescript & ESM</summary>\n<br/>\n\n> The plugin is written in Typescript and ships with types.\n\n**Playwright:**\n\n```js\n// You can use any browser: chromium, firefox, webkit\nimport { firefox } from 'playwright-extra'\nimport ProxyRouter from '@extra/proxy-router'\n// ...\nfirefox.use(proxyRouter)\n```\n\n**Puppeteer:**\n\n```js\nimport puppeteer from 'puppeteer-extra'\nimport ProxyRouter from '@extra/proxy-router'\n// ...\npuppeteer.use(proxyRouter)\n```\n\n</details>\n\n### Debug logs\n\nIf you'd like to see debug output just run your script like so:\n\n```bash\n# macOS/Linux (Bash)\nDEBUG=*proxy-router* node myscript.js\n\n# Windows (Powershell)\n$env:DEBUG='*proxy-router*';node myscript.js\n```\n\n## How it works\n\nThe proxy router will launch a local proxy server and instruct the browser to use it.\nThat local proxy server will in turn connect to the configured upstream proxy servers and relay connections depending on the optional user-defined routing function, while handling upstream proxy authentication and a few other things.\n\n## API\n\n### Options\n\n```ts\nexport interface ProxyRouterOpts {\n  /**\n   * A dictionary of proxies to be made available to the browser and router.\n   *\n   * An optional entry named `DEFAULT` will be used for all requests, unless overriden by `routeByHost`.\n   * If the `DEFAULT` entry is omitted no proxy will be used by default.\n   *\n   * The value of an entry can be a string (format: `http://user:pass@proxyhost:port`) or `null` (direct connection).\n   * Proxy authentication is handled automatically by the router.\n   *\n   * @example\n   * proxies: {\n   *   DEFAULT: \"http://user:pass@proxyhost:port\", // use this proxy by default\n   *   RESIDENTIAL_US: \"http://user:pass@proxyhost2:port\" // use this for specific hosts with `routeByHost`\n   * }\n   */\n  proxies?: {\n    /**\n     * The default proxy for the browser (format: `http://user:pass@proxyhost:port`),\n     * if omitted or `null` no proxy will be used by default\n     */\n    DEFAULT?: string | null\n    /**\n     * Any other custom proxy names which can be used for routing later\n     * (e.g. `'DATACENTER_US': 'http://user:pass@proxyhost:port'`)\n     */\n    [key: string]: string | null\n  }\n\n  /**\n   * An optional function to allow proxy routing based on the target host of the request.\n   *\n   * A return value of nothing, `null` or `DEFAULT` will result in the DEFAULT proxy being used as configured.\n   * A return value of `DIRECT` will result in no proxy being used.\n   * A return value of `ABORT` will cancel/block this request.\n   *\n   * Any other string as return value is assumed to be a reference to the configured `proxies` dict.\n   *\n   * @note The browser will most often establish only a single proxy connection per host.\n   *\n   * @example\n   * routeByHost: async ({ host }) => {\n   *   if (host.includes('google')) { return \"DIRECT\" }\n   *   return 'RESIDENTIAL_US'\n   * }\n   *\n   */\n  routeByHost?: RouteByHostFn\n  /** Collect traffic and connection stats, default: true */\n  collectStats?: boolean\n  /** Don't print any proxy connection errors to stderr, default: false */\n  muteProxyErrors?: boolean\n  /** Suppress proxy errors for specific hosts */\n  muteProxyErrorsForHost?: string[]\n  /** Options for the local proxy-chain server  */\n  proxyServerOpts?: ProxyServerOpts\n  /**\n   * Optionally exempt hosts from going through a proxy, even our internal routing proxy.\n   *\n   * Examples:\n   * `.com` or `chromium.org` or `.domain.com`\n   *\n   * @see\n   * https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-bypass-rules\n   * https://www-archive.mozilla.org/quality/networking/docs/aboutno_proxy_for.html\n   */\n  proxyBypassList?: string[]\n}\n```\n\n## Alternatives\n\n### Proxy.pac files <sup><sub>[Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file)</sub></sup>\n\n- Only supported in chromium in headful mode\n  - Despite the name (`FindProxyForURL`) can only route by host\n- Firefox supports PAC files and including the path through a pref\n- Only loaded once at browser launch, no dynamic proxies possible\n- Does not handle authentication\n\n### Various \"per-page proxy\" plugins for puppeteer\n\n- Advantage: Route proxies by page not host\n- They rely on a massive hack: Using Node.js to send the requests instead of the browser\n  - Will change the TLS fingerprint, error prone\n- Uses CDP request interception which is chromium only\n- Increased latency and resource overhead\n\n## License\n\nCopyright © 2018 - 2023, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](https://github.com/berstend). Released under the MIT License.\n\n<!--\n  Reference links\n-->\n\n[playwright-extra]: https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra\n[puppeteer-extra]: https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra\n"
  },
  {
    "path": "packages/plugin-proxy-router/rollup.config.ts",
    "content": "import resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nconst defaultExportOutro = `\n  module.exports = exports.default || {}\n  Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value })\n`\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      exports: 'named',\n      outro: defaultExportOutro,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      exports: 'named',\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    // commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve(),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/src/index.ts",
    "content": "import { ExtraPluginProxyRouter, ExtraPluginProxyRouterOptions } from './plugin'\n\nexport * from './plugin'\nexport * from './router'\nexport * from './stats'\n\n/** Default export, ExtraPluginProxyRouter  */\nconst defaultExport = (options?: Partial<ExtraPluginProxyRouterOptions>) => {\n  return new ExtraPluginProxyRouter(options || {})\n}\n\nexport default defaultExport\n"
  },
  {
    "path": "packages/plugin-proxy-router/src/plugin.ts",
    "content": "import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'\nimport { ProxyRouter, ProxyRouterOpts } from './router'\n\nexport type ExtraPluginProxyRouterOptions = ProxyRouterOpts & {\n  /**\n   * Optionally exempt hosts from going through a proxy, even our internal routing proxy.\n   *\n   * Examples:\n   * `.com` or `chromium.org` or `.domain.com`\n   *\n   * @see\n   * https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-bypass-rules\n   * https://www-archive.mozilla.org/quality/networking/docs/aboutno_proxy_for.html\n   */\n  proxyBypassList?: string[]\n}\n\nexport class ExtraPluginProxyRouter extends PuppeteerExtraPlugin {\n  /** The underlying proxy router instance */\n  public router: ProxyRouter\n  /** The name of the automation framework used */\n  public framework: 'playwright' | 'puppeteer' | null = null\n  // Disable the puppeteer compat shim when used with playwright-extra\n  public noPuppeteerShim = true\n\n  constructor(opts: Partial<ExtraPluginProxyRouterOptions>) {\n    super(opts)\n    this.debug('Initialized', this.opts)\n    this.router = new ProxyRouter(this.opts)\n  }\n\n  get name() {\n    return 'proxy-router'\n  }\n\n  get defaults(): ExtraPluginProxyRouterOptions {\n    return {\n      collectStats: true,\n      proxyServerOpts: {\n        port: 2800,\n      },\n    }\n  }\n\n  // Make accessing router methods shorter\n  /** Get or set proxies at runtime */\n  public get proxies() {\n    return this.router.proxies\n  }\n  public set proxies(proxies) {\n    this.router.proxies = proxies\n  }\n\n  /** Retrieve traffic statistics */\n  public get stats() {\n    return this.router.stats\n  }\n\n  /** Get or set the `routeByHost` function at runtime */\n  public get routeByHost() {\n    return this.router.routeByHost\n  }\n  public set routeByHost(fn) {\n    this.router.routeByHost = fn\n  }\n\n  private get proxyBypassListString() {\n    return (this.opts.proxyBypassList || []).join(',') || undefined\n  }\n\n  async onPluginRegistered(args?: { framework: 'playwright' }): Promise<void> {\n    this.framework =\n      args?.framework === 'playwright' ? 'playwright' : 'puppeteer'\n    this.debug('plugin registered', this.framework)\n  }\n\n  async beforeLaunch(options: unknown = {}): Promise<void> {\n    this.debug('beforeLaunch - before', options)\n    await this.router.listen()\n\n    const proxyUrl = this.router.proxyServerUrl\n    if (!proxyUrl) {\n      throw new Error('No local proxy server available')\n    }\n\n    if (this.framework === 'playwright') {\n      const pwOptions = options as PlaywrightLaunchOptions\n      pwOptions.proxy = {\n        server: proxyUrl,\n        bypass: this.proxyBypassListString,\n      }\n    } else if (this.framework === 'puppeteer') {\n      const pptrOptions = options as PuppeteerLaunchOptions\n      pptrOptions.args = pptrOptions.args || []\n      pptrOptions.args.push(`--proxy-server=${proxyUrl}`)\n      if (this.proxyBypassListString) {\n        pptrOptions.args.push(\n          `--proxy-bypass-list=${this.proxyBypassListString}`\n        )\n      }\n    } else {\n      this.debug('Unsupported framework, not setting proxy')\n    }\n    this.debug('beforeLaunch - after', options)\n  }\n\n  async onDisconnected(): Promise<void> {\n    await this.router.close().catch(this.debug)\n  }\n}\n\ninterface PuppeteerLaunchOptions {\n  args?: string[]\n}\n\ninterface PlaywrightLaunchOptions {\n  proxy?: {\n    /**\n     * Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or\n     * `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy.\n     */\n    server: string\n\n    /**\n     * Optional comma-separated domains to bypass proxy, for example `\".com, chromium.org, .domain.com\"`.\n     */\n    bypass?: string\n  }\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/src/router.ts",
    "content": "import { Server as ProxyServer, RequestError, redactUrl } from 'proxy-chain'\nimport type * as ProxyChain from 'proxy-chain'\n\nimport getPort from './utils/port'\n\nimport { ProxyRouterStats } from './stats'\n\nimport Debug from 'debug'\n\nconst debug = Debug('puppeteer-extra:proxy-router')\nconst debugVerbose = debug.extend('verbose')\nconst warn = console.warn.bind(console, `\\n[proxy-router] %s`) // Preserves line numbers\n\ntype ProxyServerOpts = ConstructorParameters<typeof ProxyServer>[0]\n\nexport interface Proxies {\n  /** The default proxy for the browser (format: `http://user:pass@proxyhost:port`), if omitted or `null` no proxy will be used by default */\n  DEFAULT?: string | null\n  /** Any other custom proxy names which can be used for routing later (e.g. `'DATACENTER_US': 'http://user:pass@proxyhost:port'`) */\n  [key: string]: string | null\n}\n\nexport type ProxyName = 'DIRECT' | 'DEFAULT' | 'ABORT' | string\n\n/** Data available to the `routeByHost` function */\nexport interface RouteByHostArgs {\n  /** Request URL host */\n  host: string\n  /** Whether the request is http or not */\n  isHttp: boolean\n  /** Request port (typically 443 or 80) */\n  port: number\n}\nexport type RouteByHostResponse = ProxyName | void\nexport type RouteByHostFn = (\n  args: RouteByHostArgs\n) => Promise<RouteByHostResponse>\n\nexport interface ProxyRouterOpts {\n  /**\n   * A dictionary of proxies to be made available to the browser and router.\n   *\n   * An optional entry named `DEFAULT` will be used for all requests, unless overriden by `routeByHost`.\n   * If the `DEFAULT` entry is omitted no proxy will be used by default.\n   *\n   * The value of an entry can be a string (format: `http://user:pass@proxyhost:port`) or `null` (direct connection).\n   * Proxy authentication is handled automatically by the router.\n   *\n   * @example\n   * proxies: {\n   *   DEFAULT: \"http://user:pass@proxyhost:port\", // use this proxy by default\n   *   RESIDENTIAL_US: \"http://user:pass@proxyhost2:port\" // use this for specific hosts with `routeByHost`\n   * }\n   */\n  proxies?: Proxies\n\n  /**\n   * An optional function to allow proxy routing based on the target host of the request.\n   *\n   * A return value of nothing, `null` or `DEFAULT` will result in the DEFAULT proxy being used as configured.\n   * A return value of `DIRECT` will result in no proxy being used.\n   * A return value of `ABORT` will cancel/block this request.\n   *\n   * Any other string as return value is assumed to be a reference to the configured `proxies` dict.\n   *\n   * @note The browser will most often establish only a single proxy connection per host.\n   *\n   * @example\n   * routeByHost: async ({ host }) => {\n   *   if (host.includes('google')) { return \"DIRECT\" }\n   *   return 'RESIDENTIAL_US'\n   * }\n   *\n   */\n  routeByHost?: RouteByHostFn\n  /** Collect traffic and connection stats, default: true */\n  collectStats?: boolean\n  /** Don't print any proxy connection errors to stderr, default: false */\n  muteProxyErrors?: boolean\n  /** Suppress proxy errors for specific hosts */\n  muteProxyErrorsForHost?: string[]\n  /** Options for the local proxy-chain server  */\n  proxyServerOpts?: ProxyServerOpts\n}\n\nexport class ProxyRouter {\n  /** The underlying local proxy server used for routing to upstream proxies */\n  public proxyServer: ProxyChain.Server\n  /** An optional function to route hosts  */\n  public routeByHost: RouteByHostFn | null\n  /**\n   * The dictionary of proxies made available (format: `FOOBAR: 'http://user:pass@proxyhost:port'`).\n   * Can be modified at runtime.\n   */\n  public proxies: Proxies\n  /** Traffic stats collector */\n  public readonly stats: ProxyRouterStats\n\n  public isListening: boolean = false\n  protected serverStartPromise: Promise<number> | null\n  protected collectStats: boolean\n  protected muteProxyErrors: boolean\n  protected muteProxyErrorsForHost: string[]\n  /** Internal list of failed connections to only print the same connection issue once */\n  protected failedConnections: { host: string; proxy: string }[] = []\n\n  constructor(opts: ProxyRouterOpts = {}) {\n    const proxyServerOpts: ProxyServerOpts = {\n      ...opts.proxyServerOpts,\n      prepareRequestFunction: this.handleProxyServerRequest.bind(this),\n    }\n    proxyServerOpts.port = proxyServerOpts.port || 2800\n\n    this.proxies = opts.proxies || {}\n\n    this.routeByHost = opts.routeByHost || null\n    this.proxyServer = new ProxyServer(proxyServerOpts)\n    this.collectStats = opts.collectStats ?? true\n    this.stats = new ProxyRouterStats(this.proxyServer)\n\n    this.muteProxyErrors = opts.muteProxyErrors ?? false\n    this.muteProxyErrorsForHost = opts.muteProxyErrorsForHost || []\n\n    debug('initialized', opts)\n\n    // Emitted when HTTP connection is closed\n    this.proxyServer.on('connectionClosed', ({ connectionId, stats }) => {\n      if (stats && this.collectStats) {\n        this.stats.addStats(connectionId as number, stats)\n      }\n      debugVerbose(`Connection ${connectionId} closed`)\n    })\n\n    // Emitted when a HTTP request fails\n    this.proxyServer.on('requestFailed', ({ request, error }) => {\n      if (!this.muteProxyErrors) {\n        warn('Request failed:', request.url, error)\n      }\n    })\n\n    // Emitted in case of a upstream proxy error (which can mean various things)\n    this.proxyServer.on(\n      'proxyAuthenticationFailed',\n      ({\n        connectionId,\n        str: errorStr,\n      }: {\n        connectionId: unknown\n        str: string\n      }) => {\n        // resolve the affected host and proxy\n        const { host, proxy } =\n          this.stats.connectionLog.find(({ id }) => id === connectionId) || {}\n        const proxyUrl = !!proxy ? this.getProxyForName(proxy) : null\n\n        const info: string[] = [errorStr]\n        info.push(\n          \"This error can be thrown if a resource on a site simply can't be accessed (often temporarily), in this case this can be ignored.\",\n          ` - To not have errors like this printed to the console you can set 'muteProxyErrors: true' ${\n            !!host ? `or 'muteProxyErrorsForHost: [\"${host}\"]'` : ''\n          }`,\n          'It can also indicate incorrect proxy credentials or that the target host is blocked by the proxy.',\n          ' - Make sure the provided proxy string and credentials are correct and the site is not blocked by the proxy (or vice versa).',\n          \" - In case the site is blocked by the proxy: Use 'routeByHost' to route the host through a different proxy or as 'DIRECT' or 'ABORT'.\"\n        )\n        if (host && proxy) {\n          info.push(\n            '',\n            `Affected target host: \"${host}\"`,\n            `Affected proxy name: \"${proxy}\"`\n          )\n        }\n        if (proxyUrl) {\n          info.push(`Affected proxy URL: \"${proxyUrl}\"`)\n          info.push(\n            '',\n            `To test the proxy with curl: curl -v --proxy '${proxyUrl}' 'https://${host}'`,\n            ''\n          )\n          if (!`${proxyUrl}`.includes('http://')) {\n            info.push('PS: Did you forget to prefix the proxy with \"http://\"?')\n          }\n        }\n        const probablyNoise =\n          errorStr.includes('authenticate') && errorStr.includes('522')\n        const isMuted =\n          this.muteProxyErrors || this.muteProxyErrorsForHost.includes(host)\n        const alreadySeen = !!this.failedConnections.find(\n          (entry) => entry.host === host && entry.proxy === proxy\n        )\n        const logger = probablyNoise || isMuted || alreadySeen ? debug : warn\n        logger(info.join('\\n'))\n        if (host && proxy) {\n          this.failedConnections.push({ host, proxy })\n        }\n      }\n    )\n\n    // Resurface some errors that proxy-chain seems to swallow\n    this.proxyServer.log = (function (originalMethod, context) {\n      return function (connectionId: unknown, str: string) {\n        if (`${str}`.includes('Failed to authenticate upstream proxy')) {\n          context.emit('proxyAuthenticationFailed', {\n            connectionId,\n            str,\n          })\n        }\n        if (`${str}`.includes('Error: Invalid \"upstreamProxyUrl\" provided')) {\n          context.emit('proxyAuthenticationFailed', {\n            connectionId,\n            str,\n          })\n        }\n        if (`${str}`.includes('Failed to connect to upstream proxy')) {\n          context.emit('proxyAuthenticationFailed', {\n            connectionId,\n            str,\n          })\n        }\n        originalMethod.apply(context, [connectionId, str])\n      }\n    })(this.proxyServer.log, this.proxyServer)\n  }\n\n  /** Proxy server URL of the local proxy server used for routing */\n  public get proxyServerUrl() {\n    const port = this.proxyServer?.port\n    if (!port || !this.isListening) {\n      return\n    }\n    return `http://localhost:${port}`\n  }\n\n  public get effectiveProxies() {\n    return {\n      DIRECT: null,\n      ...(this.proxies || {}),\n    }\n  }\n\n  /** Start the local proxy server and accept connections */\n  public async listen(): Promise<number> {\n    debug('starting server..')\n    if (this.serverStartPromise) {\n      debug('server start promise exists already')\n      return this.serverStartPromise\n    }\n    this.serverStartPromise = new Promise(async (resolve) => {\n      if (this.isListening) {\n        debug('server listening already')\n        return resolve(this.proxyServer.port)\n      }\n      const desiredPort = this.proxyServer.port\n      debug('finding available port', { desiredPort })\n      const availablePort = await getPort({ port: desiredPort })\n      debug('availablePort:', availablePort)\n      this.proxyServer.port = availablePort\n      this.proxyServer.listen((err) => {\n        if (err === null) {\n          debug(`server listening on port ${this.proxyServer.port}`)\n          this.isListening = true\n          return resolve(this.proxyServer.port)\n        }\n        warn('Unable to start local server:', err)\n      })\n    })\n    return this.serverStartPromise\n  }\n\n  /** Stop the local proxy server */\n  public async close(): Promise<NodeJS.ErrnoException | null> {\n    debug('closing..')\n    return new Promise((resolve) => {\n      this.proxyServer.close(true, (err) => {\n        if (err === null) {\n          debug('closed without error')\n          return resolve(null)\n        }\n        debug('closed with error', err)\n        return resolve(err)\n      })\n    })\n  }\n\n  public getProxyForName(name: ProxyName): string | null {\n    return this.effectiveProxies[name]\n  }\n\n  /** Handle requests to the proxy server */\n  protected async handleProxyServerRequest({\n    request,\n    hostname: host,\n    port,\n    connectionId,\n    isHttp,\n  }: ProxyChain.PrepareRequestFunctionOpts): Promise<void | ProxyChain.PrepareRequestFunctionResult> {\n    let proxyName = 'DEFAULT'\n    if (!!this.routeByHost) {\n      const fnResult = await this.routeByHost({ host, isHttp, port })\n      if (typeof fnResult === 'string' && !!fnResult) {\n        proxyName = fnResult\n      }\n    }\n    if (this.collectStats) {\n      this.stats.addConnection(connectionId, proxyName, host)\n    }\n    let proxyUrl = this.getProxyForName(proxyName)\n    debugVerbose(\n      'handleProxyServerRequest',\n      host,\n      proxyName,\n      redactProxyUrl(proxyUrl)\n    )\n    if (proxyName === 'ABORT') {\n      throw new RequestError('Request aborted', 400)\n    }\n    if (!proxyUrl && proxyUrl !== null) {\n      warn(\n        `No proxy configured for proxy name \"${proxyName}\" - configuration error?`\n      )\n      proxyUrl = null\n    }\n    return {\n      upstreamProxyUrl: proxyUrl,\n    }\n  }\n}\n\nfunction redactProxyUrl(input: unknown) {\n  if (!input || typeof input !== 'string') {\n    return `${input}`\n  }\n  try {\n    return redactUrl(input)\n  } catch (err) {\n    return `${input}`\n  }\n}\n\n/** Standalone proxy router not requiring plugin events */\nexport const ProxyRouterStandalone = ProxyRouter\n"
  },
  {
    "path": "packages/plugin-proxy-router/src/stats.ts",
    "content": "import type { Server as ProxyServer } from 'proxy-chain'\n\nexport interface ConnectionLogEntry {\n  /** Connection Id */\n  id: number\n  /** Proxy name */\n  proxy: string\n  /** Host */\n  host: string\n}\n\nexport interface ConnectionStats {\n  srcTxBytes: number\n  srcRxBytes: number\n  trgTxBytes: number\n  trgRxBytes: number\n}\n\nexport class ProxyRouterStats {\n  /** Log of all connections (id, proxyName, host) */\n  public connectionLog: ConnectionLogEntry[] = []\n  protected connectionStats: Map<number, ConnectionStats> = new Map()\n\n  constructor(private proxyServer: ProxyServer) {}\n\n  /** @internal */\n  public addConnection(id: number, proxy: string, host: string) {\n    this.connectionLog.push({ id, proxy, host })\n  }\n  /** @internal */\n  public addStats(connectionId: number, stats: ConnectionStats) {\n    this.connectionStats.set(connectionId as number, stats)\n  }\n\n  /** Get bytes transferred by proxy */\n  public get byProxy() {\n    this.getStatsFromActiveConnections()\n    // Get unique proxy names from our actual connection logs\n    const proxyNames = Array.from(\n      new Set(this.connectionLog.map(({ proxy }) => proxy))\n    )\n    const getConnectionIdsForProxy = (proxyName: string) =>\n      this.connectionLog\n        .filter(({ proxy }) => proxy === proxyName)\n        .map(({ id }) => id)\n    const trafficByProxy = Object.fromEntries(\n      proxyNames\n        .map((proxyName) => {\n          const ids = getConnectionIdsForProxy(proxyName)\n          const stats = ids.map((id) => this.connectionStats.get(id))\n          const totalBytes = stats\n            .map((stat) => this.calculateProxyBytes(stat))\n            .reduce((a, b) => a + b)\n          return [proxyName, totalBytes]\n        })\n        // Sort by most bytes on top\n        .sort((a, b) => (b[1] as number) - (a[1] as number))\n    )\n    return trafficByProxy\n  }\n\n  /** Get bytes transferred by host */\n  public get byHost() {\n    this.getStatsFromActiveConnections()\n    // Get unique proxy names from our actual connection logs\n    const hostNames = Array.from(\n      new Set(this.connectionLog.map(({ host }) => host))\n    )\n    const getConnectionIdsForHost = (hostName: string) =>\n      this.connectionLog\n        .filter(({ host }) => host === hostName)\n        .map(({ id }) => id)\n    const trafficByHost = Object.fromEntries(\n      hostNames\n        .map((hostName) => {\n          const ids = getConnectionIdsForHost(hostName)\n          const stats = ids.map((id) => this.connectionStats.get(id))\n          const totalBytes = stats\n            .map((stat) => this.calculateProxyBytes(stat))\n            .reduce((a, b) => a + b)\n          return [hostName, totalBytes]\n        })\n        // Sort by most bytes on top\n        .sort((a, b) => (b[1] as number) - (a[1] as number))\n    )\n    return trafficByHost\n  }\n\n  protected getStatsFromActiveConnections() {\n    // collect stats for active connections\n    this.proxyServer.getConnectionIds().forEach((connectionId) => {\n      const stats = this.proxyServer.getConnectionStats(connectionId)\n      if (stats) {\n        this.connectionStats.set(connectionId as number, stats)\n      }\n    })\n  }\n\n  protected calculateProxyBytes(stats?: Partial<ConnectionStats>) {\n    if (!stats) {\n      return 0\n    }\n    return (stats.trgRxBytes || 0) + (stats.trgTxBytes || 0)\n  }\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/src/utils/port.ts",
    "content": "import net from 'net'\n\nexport interface Options {\n  /**\n   * A preferred port or an array of preferred ports to use.\n   */\n  port?: number | ReadonlyArray<number>\n\n  /**\n   * The host on which port resolution should be performed. Can be either an IPv4 or IPv6 address.\n   */\n  host?: string\n}\n\nconst isAvailable = (options: Options): Promise<number> =>\n  new Promise((resolve, reject) => {\n    const server = net.createServer()\n    server.unref()\n    server.on('error', reject)\n    server.listen(options, () => {\n      const { port } = server.address() as any\n      server.close(() => {\n        resolve(port as number)\n      })\n    })\n  })\n\nconst getPort = (options: Options) => {\n  options = Object.assign({}, options)\n\n  if (typeof options.port === 'number') {\n    options.port = [options.port]\n  }\n\n  return (options.port || []).reduce(\n    (seq, port) =>\n      seq.catch(() => isAvailable(Object.assign({}, options, { port }))),\n    Promise.reject()\n  )\n}\n\nexport default (options?: Options) =>\n  options\n    ? getPort(options).catch(() => getPort(Object.assign(options, { port: 0 })))\n    : getPort({ port: 0 })\n"
  },
  {
    "path": "packages/plugin-proxy-router/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"es2019\", \"dom\"],\n    // \"noResolve\": true, // Important: Otherwise TS would rewrite our ambient d.ts file locations (see: yarn copy-dts) :(\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"./src/**/*.tsx\",\n    \"./src/**/*.ts\",\n    \"./src/**/*.d.ts\",\n    \"./src/**/*.test.ts\",\n    \"./test/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/plugin-proxy-router/tslint.json",
    "content": "{\n  \"extends\": [\"tslint-config-standard\", \"tslint-config-prettier\"],\n  \"rules\": {\n    \"ordered-imports\": true\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/ava.config-ts.js",
    "content": "export default {\n  compileEnhancements: false,\n  environmentVariables: {\n    TS_NODE_COMPILER_OPTIONS: '{\"module\":\"commonjs\"}'\n  },\n  files: ['test/*.ts'],\n  extensions: ['ts'],\n  require: ['ts-node/register']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/ava.config.js",
    "content": "export default {\n  files: ['test/*.js']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra\",\n  \"version\": \"3.3.6\",\n  \"description\": \"Teach puppeteer new tricks through plugins.\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"typings\": \"dist/index.d.ts\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup ambient-dts\",\n    \"build:tsc\": \"tsc --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"documentation readme --quiet --shallow --github --markdown-theme transitivebs --readme-file readme.md --section API ./src/index.ts\",\n    \"postdocs\": \"npx prettier --write readme.md\",\n    \"test:ts\": \"ava -v --config ava.config-ts.js\",\n    \"test:js\": \"ava -v --serial --concurrency 1 --fail-fast\",\n    \"test\": \"run-p test:js test:ts\",\n    \"test-ci\": \"run-s test\",\n    \"ambient-dts\": \"run-s ambient-dts-copy ambient-dts-fix-path\",\n    \"ambient-dts-copy\": \"copyfiles -u 1 \\\"src/**/*.d.ts\\\" dist\",\n    \"ambient-dts-fix-path\": \"replace-in-files --string='/// <reference path=\\\"../src/' --replacement='/// <reference path=\\\"../dist/' 'dist/**/*.d.ts'\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"flash\",\n    \"stealth\",\n    \"prefs\",\n    \"user-preferences\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^18.0.0\",\n    \"@types/puppeteer\": \"*\",\n    \"ava\": \"^2.4.0\",\n    \"documentation-markdown-themes\": \"^12.1.5\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^10.2.0\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"puppeteer-extra-plugin-anonymize-ua\": \"^2.4.6\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup\": \"^1.27.5\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"ts-node\": \"^8.5.4\",\n    \"tslint\": \"^5.20.1\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-config-standard\": \"^9.0.0\",\n    \"typescript\": \"4.4.3\"\n  },\n  \"dependencies\": {\n    \"@types/debug\": \"^4.1.0\",\n    \"debug\": \"^4.1.1\",\n    \"deepmerge\": \"^4.2.2\"\n  },\n  \"peerDependencies\": {\n    \"@types/puppeteer\": \"*\",\n    \"puppeteer\": \"*\",\n    \"puppeteer-core\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer\": {\n      \"optional\": true\n    },\n    \"puppeteer-core\": {\n      \"optional\": true\n    },\n    \"@types/puppeteer\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/readme.md",
    "content": "# puppeteer-extra [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push)](https://github.com/berstend/puppeteer-extra/actions) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/puppeteer-extra.svg)](https://www.npmjs.com/package/puppeteer-extra) [![npm](https://img.shields.io/npm/dt/puppeteer-extra.svg)](https://www.npmjs.com/package/puppeteer-extra) [![npm](https://img.shields.io/npm/l/puppeteer-extra.svg)](https://www.npmjs.com/package/puppeteer-extra)\n\n> A light-weight wrapper around [`puppeteer`](https://github.com/GoogleChrome/puppeteer) and [friends](#more-examples) to enable cool [plugins](#plugins) through a clean interface.\n\n<a href=\"https://github.com/berstend/puppeteer-extra\"><img src=\"https://i.imgur.com/qtlnoQL.png\" width=\"279px\" height=\"187px\" align=\"right\" /></a>\n\n## Installation\n\n```bash\nyarn add puppeteer puppeteer-extra\n# - or -\nnpm install puppeteer puppeteer-extra\n\n# puppeteer-extra works with any puppeteer version:\nyarn add puppeteer@2.0.0 puppeteer-extra\n```\n\n## Quickstart\n\n```js\n// puppeteer-extra is a drop-in replacement for puppeteer,\n// it augments the installed puppeteer with plugin functionality.\n// Any number of plugins can be added through `puppeteer.use()`\nconst puppeteer = require('puppeteer-extra')\n\n// Add stealth plugin and use defaults (all tricks to hide puppeteer usage)\nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')\npuppeteer.use(StealthPlugin())\n\n// Add adblocker plugin to block all ads and trackers (saves bandwidth)\nconst AdblockerPlugin = require('puppeteer-extra-plugin-adblocker')\npuppeteer.use(AdblockerPlugin({ blockTrackers: true }))\n\n// That's it, the rest is puppeteer usage as normal 😊\npuppeteer.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n\n  console.log(`Testing adblocker plugin..`)\n  await page.goto('https://www.vanityfair.com')\n  await page.waitForTimeout(1000)\n  await page.screenshot({ path: 'adblocker.png', fullPage: true })\n\n  console.log(`Testing the stealth plugin..`)\n  await page.goto('https://bot.sannysoft.com')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: 'stealth.png', fullPage: true })\n\n  console.log(`All done, check the screenshots. ✨`)\n  await browser.close()\n})\n```\n\nThe above example uses the [`stealth`](/packages/puppeteer-extra-plugin-stealth) and [`adblocker`](/packages/puppeteer-extra-plugin-adblocker) plugin, which need to be installed as well:\n\n```bash\nyarn add puppeteer-extra-plugin-stealth puppeteer-extra-plugin-adblocker\n# - or -\nnpm install puppeteer-extra-plugin-stealth puppeteer-extra-plugin-adblocker\n```\n\nIf you'd like to see debug output just run your script like so:\n\n```bash\nDEBUG=puppeteer-extra,puppeteer-extra-plugin:* node myscript.js\n```\n\n### More examples\n\n<details>\n <summary><strong>TypeScript usage</strong></summary><br/>\n\n> `puppeteer-extra` and most plugins are written in TS,\n> so you get perfect type support out of the box. :)\n\n```ts\nimport puppeteer from 'puppeteer-extra'\n\nimport AdblockerPlugin from 'puppeteer-extra-plugin-adblocker'\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\n\npuppeteer.use(AdblockerPlugin()).use(StealthPlugin())\n\npuppeteer\n  .launch({ headless: false, defaultViewport: null })\n  .then(async browser => {\n    const page = await browser.newPage()\n    await page.goto('https://bot.sannysoft.com')\n    await page.waitForTimeout(5000)\n    await page.screenshot({ path: 'stealth.png', fullPage: true })\n    await browser.close()\n  })\n```\n\n> Please check this [wiki](https://github.com/berstend/puppeteer-extra/wiki/TypeScript-usage) entry in case you have TypeScript related import issues.\n\n![typings](https://i.imgur.com/bNtuTOt.png 'Typings')\n\n</details>\n\n<details>\n <summary><strong>Playwright usage</strong></summary><br/>\n\n[`playright-extra`](/packages/playwright-extra) with plugin support is available as well.\n\n</details>\n\n<details>\n <summary><strong>Multiple puppeteers with different plugins</strong></summary><br/>\n\n```js\nconst vanillaPuppeteer = require('puppeteer')\n\nconst { addExtra } = require('puppeteer-extra')\nconst AnonymizeUA = require('puppeteer-extra-plugin-anonymize-ua')\n\nasync function main() {\n  const pptr1 = addExtra(vanillaPuppeteer)\n  pptr1.use(\n    AnonymizeUA({\n      customFn: ua => 'Hello1/' + ua.replace('Chrome', 'Beer')\n    })\n  )\n\n  const pptr2 = addExtra(vanillaPuppeteer)\n  pptr2.use(\n    AnonymizeUA({\n      customFn: ua => 'Hello2/' + ua.replace('Chrome', 'Beer')\n    })\n  )\n\n  await checkUserAgent(pptr1)\n  await checkUserAgent(pptr2)\n}\n\nmain()\n\nasync function checkUserAgent(pptr) {\n  const browser = await pptr.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('https://httpbin.org/headers', {\n    waitUntil: 'domcontentloaded'\n  })\n  const content = await page.content()\n  console.log(content)\n  await browser.close()\n}\n```\n\n</details>\n\n<details>\n <summary><strong>Using with <code>puppeteer-cluster</code></strong></summary><br/>\n\n> [puppeteer-cluster](https://github.com/thomasdondorf/puppeteer-cluster) allows you to create a cluster of puppeteer workers and plays well together with `puppeteer-extra`.\n\n```js\nconst { Cluster } = require('puppeteer-cluster')\nconst vanillaPuppeteer = require('puppeteer')\n\nconst { addExtra } = require('puppeteer-extra')\nconst Stealth = require('puppeteer-extra-plugin-stealth')\nconst Recaptcha = require('puppeteer-extra-plugin-recaptcha')\n\nasync function main() {\n  // Create a custom puppeteer-extra instance using `addExtra`,\n  // so we could create additional ones with different plugin config.\n  const puppeteer = addExtra(vanillaPuppeteer)\n  puppeteer.use(Stealth())\n  puppeteer.use(Recaptcha())\n\n  // Launch cluster with puppeteer-extra\n  const cluster = await Cluster.launch({\n    puppeteer,\n    maxConcurrency: 2,\n    concurrency: Cluster.CONCURRENCY_CONTEXT\n  })\n\n  // Define task handler\n  await cluster.task(async ({ page, data: url }) => {\n    await page.goto(url)\n\n    const { hostname } = new URL(url)\n    const { captchas } = await page.findRecaptchas()\n    console.log(`Found ${captchas.length} captcha on ${hostname}`)\n\n    await page.screenshot({ path: `${hostname}.png`, fullPage: true })\n  })\n\n  // Queue any number of tasks\n  cluster.queue('https://bot.sannysoft.com')\n  cluster.queue('https://www.google.com/recaptcha/api2/demo')\n  cluster.queue('http://www.wikipedia.org/')\n\n  await cluster.idle()\n  await cluster.close()\n  console.log(`All done, check the screenshots. ✨`)\n}\n\n// Let's go\nmain().catch(console.warn)\n```\n\nFor using with TypeScript, just change your imports to:\n\n```ts\nimport { Cluster } from 'puppeteer-cluster'\nimport vanillaPuppeteer from 'puppeteer'\n\nimport { addExtra } from 'puppeteer-extra'\nimport Stealth from 'puppeteer-extra-plugin-stealth'\nimport Recaptcha from 'puppeteer-extra-plugin-recaptcha'\n```\n\n</details>\n\n<details>\n <summary><strong>Using with <code>chrome-aws-lambda</code></strong></summary><br/>\n\n> If you plan to use [chrome-aws-lambda](https://github.com/alixaxel/chrome-aws-lambda) with the [`stealth`](/packages/puppeteer-extra-plugin-stealth) plugin, you'll need to modify the default args to remove the\n> `--disable-notifications` flag to pass all the tests.\n\n```js\nconst chromium = require('chrome-aws-lambda')\nconst { addExtra } = require('puppeteer-extra')\nconst puppeteerExtra = addExtra(chromium.puppeteer)\n\nconst launch = async () => {\n  puppeteerExtra\n    .launch({\n      args: chromium.args,\n      defaultViewport: chromium.defaultViewport,\n      executablePath: await chromium.executablePath,\n      headless: chromium.headless\n    })\n    .then(async browser => {\n      const page = await browser.newPage()\n      await page.goto('https://www.spacejam.com/archive/spacejam/movie/jam.htm')\n      await page.waitForTimeout(10 * 1000)\n      await browser.close()\n    })\n}\n\nlaunch() // Launch Browser\n```\n\n</details>\n\n<details>\n <summary><strong>Using with <code>Kikobeats/browserless</code></strong></summary><br/>\n\n> [Kikobeats/browserless](https://github.com/Kikobeats/browserless) is a puppeteer-like Node.js library for interacting with Headless production scenarios.\n\n```js\nconst puppeteer = require('puppeteer-extra')\nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')\npuppeteer.use(StealthPlugin())\n\nconst browserless = require('browserless')({ puppeteer })\n\nconst saveBufferToFile = (buffer, fileName) => {\n  const wstream = require('fs').createWriteStream(fileName)\n  wstream.write(buffer)\n  wstream.end()\n}\n\nbrowserless\n  .screenshot('https://bot.sannysoft.com', { device: 'iPhone 6' })\n  .then(buffer => {\n    const fileName = 'screenshot.png'\n    saveBufferToFile(buffer, fileName)\n    console.log(`your screenshot is here: `, fileName)\n  })\n```\n\n</details>\n\n---\n\n## Plugins\n\n#### 🔥 [`puppeteer-extra-plugin-stealth`](/packages/puppeteer-extra-plugin-stealth)\n\n- Applies various evasion techniques to make detection of puppeteer harder.\n\n#### 🏴 [`puppeteer-extra-plugin-recaptcha`](/packages/puppeteer-extra-plugin-recaptcha)\n\n- Solves reCAPTCHAs and hCaptchas automatically, using a single line of code: `page.solveRecaptchas()`.\n\n#### [`puppeteer-extra-plugin-adblocker`](/packages/puppeteer-extra-plugin-adblocker)\n\n- Very fast & efficient blocker for ads and trackers. Reduces bandwidth & load times.\n\n#### [`puppeteer-extra-plugin-devtools`](/packages/puppeteer-extra-plugin-devtools)\n\n- Makes puppeteer browser debugging possible from anywhere.\n- Creates a secure tunnel to make the devtools frontend (**incl. screencasting**) accessible from the public internet\n\n#### [`puppeteer-extra-plugin-repl`](/packages/puppeteer-extra-plugin-repl)\n\n- Makes quick puppeteer debugging and exploration fun with an interactive REPL.\n\n#### [`puppeteer-extra-plugin-block-resources`](/packages/puppeteer-extra-plugin-block-resources)\n\n- Blocks resources (images, media, css, etc.) in puppeteer.\n- Supports all resource types, blocking can be toggled dynamically.\n\n#### [`puppeteer-extra-plugin-flash`](/packages/puppeteer-extra-plugin-flash)\n\n- Allows flash content to run on all sites without user interaction.\n\n#### [`puppeteer-extra-plugin-anonymize-ua`](/packages/puppeteer-extra-plugin-anonymize-ua)\n\n- Anonymizes the user-agent on all pages.\n- Supports dynamic replacing, so the browser version stays intact and recent.\n\n#### [`puppeteer-extra-plugin-user-preferences`](/packages/puppeteer-extra-plugin-user-preferences)\n\n- Allows setting custom Chrome/Chromium user preferences.\n- Has itself a plugin interface which is used by e.g. [`puppeteer-extra-plugin-font-size`](/packages/puppeteer-extra-plugin-font-size).\n\n> Check out the [packages folder](/packages/) for more plugins.\n\n### Community Plugins\n\n_These plugins have been generously contributed by members of the community._\n_Please note that they're hosted outside the main project and not under our control or supervision._\n\n#### [`puppeteer-extra-plugin-minmax`](https://github.com/Stillerman/puppeteer-extra-minmax)\n\n- Minimize and maximize puppeteer in real time.\n- Great for manually solving captchas.\n\n#### [`puppeteer-extra-plugin-portal`](https://github.com/claabs/puppeteer-extra-plugin-portal)\n\n- Use the Chromium screencast API to remotely view and interact with puppeteer sessions.\n- Great for remotely intervening when an automated task gets stuck, like captchas.\n\n> Please check the `Contributing` section below if you're interested in creating a plugin as well.\n\n---\n\n## Contributors\n\n<a href=\"https://github.com/berstend/puppeteer-extra/graphs/contributors\">\n  <img src=\"https://contributors-img.firebaseapp.com/image?repo=berstend/puppeteer-extra\" />\n</a>\n\n## Further info\n\n<details>\n <summary><strong>Contributing</strong></summary><br/>\n\nPRs and new plugins are welcome! 🎉 The plugin API for `puppeteer-extra` is clean and fun to use. Have a look the [PuppeteerExtraPlugin](/packages/puppeteer-extra-plugin) base class documentation to get going and check out the [existing plugins](./packages/) (minimal example is the [anonymize-ua](/packages/puppeteer-extra-plugin-anonymize-ua/index.js) plugin) for reference.\n\nWe use a [monorepo](/) powered by [Lerna](https://github.com/lerna/lerna#--use-workspaces) (and yarn workspaces), [ava](https://github.com/avajs/ava) for testing, TypeScript for the core, the [standard](https://standardjs.com/) style for linting and [JSDoc](http://usejsdoc.org/about-getting-started.html) heavily to auto-generate markdown [documentation](https://github.com/documentationjs/documentation) based on code. :-)\n\n</details>\n\n<details>\n <summary><strong>Kudos</strong></summary><br/>\n\n- Thanks to [skyiea](https://github.com/skyiea) for [this PR](https://github.com/GoogleChrome/puppeteer/pull/1806) that started the project idea.\n- Thanks to [transitive-bullshit](https://github.com/transitive-bullshit) for [suggesting](https://github.com/berstend/puppeteer-extra/issues/2) a modular plugin design, which was fun to implement.\n\n</details>\n\n<details>\n <summary><strong>Compatibility</strong></summary><br/>\n\n`puppeteer-extra` and all plugins are [tested continously](https://github.com/berstend/puppeteer-extra/actions) in a matrix of current (stable & LTS) NodeJS and puppeteer versions.\nWe never broke compatibility and still support puppeteer down to very early versions from 2018.\n\nA few plugins won't work in headless mode (it's noted if that's the case) due to Chrome limitations (e.g. the [`user-preferences`](/packages/puppeteer-extra-plugin-user-preferences) plugin), look into `xvfb-run` if you still require a headless experience in these circumstances.\n\n</details>\n\n## Changelog\n\n<details>\n <summary><code>2.1.6 ➠ 3.1.1</code></summary>\n\n### `2.1.6` ➠ `3.1.1`\n\nBig refactor, the core is now **written in TypeScript** 🎉\nThat means out of the box type safety for fellow TS users and nice auto-completion in VSCode for JS users. Also:\n\n- A new [`addExtra`](#addextrapuppeteer) export, to **patch any puppeteer compatible library with plugin functionality** (`chrome-aws-lambda`, etc). This also allows for multiple puppeteer instances with different plugins.\n\nThe API is backwards compatible, I bumped the major version just in case I missed something. Please report any issues you might find with the new release. :)\n\n</details>\n\n---\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: PuppeteerExtra](#class-puppeteerextra)\n  - [.use(plugin)](#useplugin)\n  - [.launch(options?)](#launchoptions)\n  - [.connect(options?)](#connectoptions)\n  - [.defaultArgs(options?)](#defaultargsoptions)\n  - [.executablePath()](#executablepath)\n  - [.createBrowserFetcher(options?)](#createbrowserfetcheroptions)\n  - [.plugins](#plugins)\n  - [.getPluginData(name?)](#getplugindataname)\n- [defaultExport()](#defaultexport)\n- [addExtra(puppeteer)](#addextrapuppeteer)\n\n### class: [PuppeteerExtra](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L67-L474)\n\nModular plugin framework to teach `puppeteer` new tricks.\n\nThis module acts as a drop-in replacement for `puppeteer`.\n\nAllows PuppeteerExtraPlugin's to register themselves and\nto extend puppeteer with additional functionality.\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\npuppeteer.use(\n  require('puppeteer-extra-plugin-font-size')({ defaultFontSize: 18 })\n)\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  await page.goto('http://example.com', { waitUntil: 'domcontentloaded' })\n  await browser.close()\n})()\n```\n\n---\n\n#### .[use(plugin)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L85-L107)\n\n- `plugin` **PuppeteerExtraPlugin**\n\nReturns: **this** The same `PuppeteerExtra` instance (for optional chaining)\n\nThe **main interface** to register `puppeteer-extra` plugins.\n\nExample:\n\n```javascript\npuppeteer.use(plugin1).use(plugin2)\n```\n\n- **See: [PuppeteerExtraPlugin]**\n\n---\n\n#### .[launch(options?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L153-L177)\n\n- `options` **Puppeteer.LaunchOptions?** See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).\n\nReturns: **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Puppeteer.Browser>**\n\nThe method launches a browser instance with given arguments. The browser will be closed when the parent node.js process is closed.\n\nAugments the original `puppeteer.launch` method with plugin lifecycle methods.\n\nAll registered plugins that have a `beforeLaunch` method will be called\nin sequence to potentially update the `options` Object before launching the browser.\n\nExample:\n\n```javascript\nconst browser = await puppeteer.launch({\n  headless: false,\n  defaultViewport: null\n})\n```\n\n---\n\n#### .[connect(options?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L189-L208)\n\n- `options` **Puppeteer.ConnectOptions?** See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions).\n\nReturns: **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Puppeteer.Browser>**\n\nAttach Puppeteer to an existing Chromium instance.\n\nAugments the original `puppeteer.connect` method with plugin lifecycle methods.\n\nAll registered plugins that have a `beforeConnect` method will be called\nin sequence to potentially update the `options` Object before launching the browser.\n\n---\n\n#### .[defaultArgs(options?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L215-L217)\n\n- `options` **Puppeteer.ChromeArgOptions?** See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerdefaultargsoptions).\n\nReturns: **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>**\n\nThe default flags that Chromium will be launched with.\n\n---\n\n#### .[executablePath()](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L220-L222)\n\nReturns: **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**\n\nPath where Puppeteer expects to find bundled Chromium.\n\n---\n\n#### .[createBrowserFetcher(options?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L229-L233)\n\n- `options` **Puppeteer.FetcherOptions?** See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteercreatebrowserfetcheroptions).\n\nReturns: **Puppeteer.BrowserFetcher**\n\nThis methods attaches Puppeteer to an existing Chromium instance.\n\n---\n\n#### .[plugins](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L283-L285)\n\nType: **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;PuppeteerExtraPlugin>**\n\nGet a list of all registered plugins.\n\n---\n\n#### .[getPluginData(name?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L310-L315)\n\n- `name` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Filter data by optional plugin name\n\nCollects the exposed `data` property of all registered plugins.\nWill be reduced/flattened to a single array.\n\nCan be accessed by plugins that listed the `dataFromPlugins` requirement.\n\nImplemented mainly for plugins that need data from other plugins (e.g. `user-preferences`).\n\n- **See: [PuppeteerExtraPlugin]/data**\n\n---\n\n### [defaultExport()](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L494-L496)\n\nType: **[PuppeteerExtra](#puppeteerextra)**\n\nThe **default export** will behave exactly the same as the regular puppeteer\n(just with extra plugin functionality) and can be used as a drop-in replacement.\n\nBehind the scenes it will try to require either `puppeteer`\nor [`puppeteer-core`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core)\nfrom the installed dependencies.\n\nExample:\n\n```javascript\n// javascript import\nconst puppeteer = require('puppeteer-extra')\n\n// typescript/es6 module import\nimport puppeteer from 'puppeteer-extra'\n\n// Add plugins\npuppeteer.use(...)\n```\n\n---\n\n### [addExtra(puppeteer)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra/src/index.ts#L519-L520)\n\n- `puppeteer` **VanillaPuppeteer** Any puppeteer API-compatible puppeteer implementation or version.\n\nReturns: **[PuppeteerExtra](#puppeteerextra)** A fresh PuppeteerExtra instance using the provided puppeteer\n\nAn **alternative way** to use `puppeteer-extra`: Augments the provided puppeteer with extra plugin functionality.\n\nThis is useful in case you need multiple puppeteer instances with different plugins or to add plugins to a non-standard puppeteer package.\n\nExample:\n\n```javascript\n// js import\nconst puppeteerVanilla = require('puppeteer')\nconst { addExtra } = require('puppeteer-extra')\n\n// ts/es6 import\nimport puppeteerVanilla from 'puppeteer'\nimport { addExtra } from 'puppeteer-extra'\n\n// Patch provided puppeteer and add plugins\nconst puppeteer = addExtra(puppeteerVanilla)\npuppeteer.use(...)\n```\n\n---\n\n## License\n\nCopyright © 2018 - 2023, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](mailto:github@berstend.com?subject=[GitHub]%20PuppeteerExtra). Released under the MIT License.\n\n<!-- Markdown footnotes (for links) -->\n\n[puppeteerextraplugin]: https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin 'PuppeteerExtraPlugin Documentation'\n"
  },
  {
    "path": "packages/puppeteer-extra/rollup.config.ts",
    "content": "import commonjs from 'rollup-plugin-commonjs'\nimport resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nconst defaultExportOutro = `\n  module.exports = exports.default || {}\n  Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value })\n`\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      exports: 'named',\n      outro: defaultExportOutro,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      exports: 'named',\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve(),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/src/ambient.d.ts",
    "content": "export {}\n\n// https://github.com/sindresorhus/type-fest/issues/19\ndeclare global {\n  interface SymbolConstructor {\n    readonly observable: symbol\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/src/index.ts",
    "content": "/// <reference path=\"./puppeteer-legacy.d.ts\" />\nimport { PuppeteerNode, Browser, Page } from 'puppeteer'\n\nimport Debug from 'debug'\nconst debug = Debug('puppeteer-extra')\n\nimport merge from 'deepmerge'\n\n/**\n * Original Puppeteer API\n * @private\n */\nexport interface VanillaPuppeteer\n  extends Pick<\n    PuppeteerNode,\n    | 'connect'\n    | 'defaultArgs'\n    | 'executablePath'\n    | 'launch'\n    | 'createBrowserFetcher'\n  > {}\n\n/**\n * Minimal plugin interface\n * @private\n */\nexport interface PuppeteerExtraPlugin {\n  _isPuppeteerExtraPlugin: boolean\n  [propName: string]: any\n}\n\n/**\n * We need to hook into non-public APIs in rare occasions to fix puppeteer bugs. :(\n * @private\n */\ninterface BrowserInternals extends Browser {\n  _createPageInContext(contextId?: string): Promise<Page>\n}\n\n/**\n * Modular plugin framework to teach `puppeteer` new tricks.\n *\n * This module acts as a drop-in replacement for `puppeteer`.\n *\n * Allows PuppeteerExtraPlugin's to register themselves and\n * to extend puppeteer with additional functionality.\n *\n * @class PuppeteerExtra\n * @implements {VanillaPuppeteer}\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n * puppeteer.use(require('puppeteer-extra-plugin-font-size')({defaultFontSize: 18}))\n *\n * ;(async () => {\n *   const browser = await puppeteer.launch({headless: false})\n *   const page = await browser.newPage()\n *   await page.goto('http://example.com', {waitUntil: 'domcontentloaded'})\n *   await browser.close()\n * })()\n */\nexport class PuppeteerExtra implements VanillaPuppeteer {\n  private _plugins: PuppeteerExtraPlugin[] = []\n\n  constructor(\n    private _pptr?: VanillaPuppeteer,\n    private _requireError?: Error\n  ) {}\n\n  /**\n   * The **main interface** to register `puppeteer-extra` plugins.\n   *\n   * @example\n   * puppeteer.use(plugin1).use(plugin2)\n   *\n   * @see [PuppeteerExtraPlugin]\n   *\n   * @return The same `PuppeteerExtra` instance (for optional chaining)\n   */\n  use(plugin: PuppeteerExtraPlugin): this {\n    if (typeof plugin !== 'object' || !plugin._isPuppeteerExtraPlugin) {\n      console.error(\n        `Warning: Plugin is not derived from PuppeteerExtraPlugin, ignoring.`,\n        plugin\n      )\n      return this\n    }\n    if (!plugin.name) {\n      console.error(\n        `Warning: Plugin with no name registering, ignoring.`,\n        plugin\n      )\n      return this\n    }\n    if (plugin.requirements.has('dataFromPlugins')) {\n      plugin.getDataFromPlugins = this.getPluginData.bind(this)\n    }\n    plugin._register(Object.getPrototypeOf(plugin))\n    this._plugins.push(plugin)\n    debug('plugin registered', plugin.name)\n    return this\n  }\n\n  /**\n   * To stay backwards compatible with puppeteer's (and our) default export after adding `addExtra`\n   * we need to defer the check if we have a puppeteer instance to work with.\n   * Otherwise we would throw even if the user intends to use their non-standard puppeteer implementation.\n   *\n   * @private\n   */\n  get pptr(): VanillaPuppeteer {\n    if (this._pptr) {\n      return this._pptr\n    }\n\n    // Whoopsie\n    console.warn(`\n    Puppeteer is missing. :-)\n\n    Note: puppeteer is a peer dependency of puppeteer-extra,\n    which means you can install your own preferred version.\n\n    - To get the latest stable version run: 'yarn add puppeteer' or 'npm i puppeteer'\n\n    Alternatively:\n    - To get puppeteer without the bundled Chromium browser install 'puppeteer-core'\n    `)\n    throw this._requireError || new Error('No puppeteer instance provided.')\n  }\n\n  /**\n   * The method launches a browser instance with given arguments. The browser will be closed when the parent node.js process is closed.\n   *\n   * Augments the original `puppeteer.launch` method with plugin lifecycle methods.\n   *\n   * All registered plugins that have a `beforeLaunch` method will be called\n   * in sequence to potentially update the `options` Object before launching the browser.\n   *\n   * @example\n   * const browser = await puppeteer.launch({\n   *   headless: false,\n   *   defaultViewport: null\n   * })\n   *\n   * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).\n   */\n  async launch(\n    options?: Parameters<VanillaPuppeteer['launch']>[0]\n  ): ReturnType<VanillaPuppeteer['launch']> {\n    // Ensure there are certain properties (e.g. the `options.args` array)\n    const defaultLaunchOptions = { args: [] }\n    options = merge(defaultLaunchOptions, options || {})\n    this.resolvePluginDependencies()\n    this.orderPlugins()\n\n    // Give plugins the chance to modify the options before launch\n    options = await this.callPluginsWithValue('beforeLaunch', options)\n\n    const opts = {\n      context: 'launch',\n      options,\n      defaultArgs: this.defaultArgs\n    }\n\n    // Let's check requirements after plugin had the chance to modify the options\n    this.checkPluginRequirements(opts)\n\n    const browser = await this.pptr.launch(options)\n    this._patchPageCreationMethods(browser as BrowserInternals)\n\n    await this.callPlugins('_bindBrowserEvents', browser, opts)\n    return browser\n  }\n\n  /**\n   * Attach Puppeteer to an existing Chromium instance.\n   *\n   * Augments the original `puppeteer.connect` method with plugin lifecycle methods.\n   *\n   * All registered plugins that have a `beforeConnect` method will be called\n   * in sequence to potentially update the `options` Object before launching the browser.\n   *\n   * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions).\n   */\n  async connect(\n    options: Parameters<VanillaPuppeteer['connect']>[0]\n  ): ReturnType<VanillaPuppeteer['connect']> {\n    this.resolvePluginDependencies()\n    this.orderPlugins()\n\n    // Give plugins the chance to modify the options before connect\n    options = await this.callPluginsWithValue('beforeConnect', options)\n\n    const opts = { context: 'connect', options }\n\n    // Let's check requirements after plugin had the chance to modify the options\n    this.checkPluginRequirements(opts)\n\n    const browser = await this.pptr.connect(options)\n    this._patchPageCreationMethods(browser as BrowserInternals)\n\n    await this.callPlugins('_bindBrowserEvents', browser, opts)\n    return browser\n  }\n\n  /**\n   * The default flags that Chromium will be launched with.\n   *\n   * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerdefaultargsoptions).\n   */\n  defaultArgs(\n    options?: Parameters<VanillaPuppeteer['defaultArgs']>[0]\n  ): ReturnType<VanillaPuppeteer['defaultArgs']> {\n    return this.pptr.defaultArgs(options)\n  }\n\n  /** Path where Puppeteer expects to find bundled Chromium. */\n  executablePath(): string {\n    return this.pptr.executablePath()\n  }\n\n  /**\n   * This methods attaches Puppeteer to an existing Chromium instance.\n   *\n   * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteercreatebrowserfetcheroptions).\n   */\n  createBrowserFetcher(\n    options: Parameters<VanillaPuppeteer['createBrowserFetcher']>[0]\n  ): ReturnType<VanillaPuppeteer['createBrowserFetcher']> {\n    return this.pptr.createBrowserFetcher(options)\n  }\n\n  /**\n   * Patch page creation methods (both regular and incognito contexts).\n   *\n   * Unfortunately it's possible that the `targetcreated` events are not triggered\n   * early enough for listeners (e.g. plugins using `onPageCreated`) to be able to\n   * modify the page instance (e.g. user-agent) before the browser request occurs.\n   *\n   * This only affects the first request of a newly created page target.\n   *\n   * As a workaround I've noticed that navigating to `about:blank` (again),\n   * right after a page has been created reliably fixes this issue and adds\n   * no noticable delay or side-effects.\n   *\n   * This problem is not specific to `puppeteer-extra` but default Puppeteer behaviour.\n   *\n   * Note: This patch only fixes explicitly created pages, implicitly created ones\n   * (e.g. through `window.open`) are still subject to this issue. I didn't find a\n   * reliable mitigation for implicitly created pages yet.\n   *\n   * Puppeteer issues:\n   * https://github.com/GoogleChrome/puppeteer/issues/2669\n   * https://github.com/puppeteer/puppeteer/issues/3667\n   * https://github.com/GoogleChrome/puppeteer/issues/386#issuecomment-343059315\n   * https://github.com/GoogleChrome/puppeteer/issues/1378#issue-273733905\n   *\n   * @private\n   */\n  private _patchPageCreationMethods(browser: BrowserInternals) {\n    if (!browser._createPageInContext) {\n      debug(\n        'warning: _patchPageCreationMethods failed (no browser._createPageInContext)'\n      )\n      return\n    }\n    browser._createPageInContext = (function(originalMethod, context) {\n      return async function() {\n        const page = await originalMethod.apply(context, arguments as any)\n        await page.goto('about:blank')\n        return page\n      }\n    })(browser._createPageInContext, browser)\n  }\n\n  /**\n   * Get a list of all registered plugins.\n   *\n   * @member {Array<PuppeteerExtraPlugin>}\n   */\n  get plugins() {\n    return this._plugins\n  }\n\n  /**\n   * Get the names of all registered plugins.\n   *\n   * @member {Array<string>}\n   * @private\n   */\n  get pluginNames() {\n    return this._plugins.map(p => p.name)\n  }\n\n  /**\n   * Collects the exposed `data` property of all registered plugins.\n   * Will be reduced/flattened to a single array.\n   *\n   * Can be accessed by plugins that listed the `dataFromPlugins` requirement.\n   *\n   * Implemented mainly for plugins that need data from other plugins (e.g. `user-preferences`).\n   *\n   * @see [PuppeteerExtraPlugin]/data\n   * @param name - Filter data by optional plugin name\n   *\n   * @private\n   */\n  public getPluginData(name?: string) {\n    const data = this._plugins\n      .map(p => (Array.isArray(p.data) ? p.data : [p.data]))\n      .reduce((acc, arr) => [...acc, ...arr], [])\n    return name ? data.filter((d: any) => d.name === name) : data\n  }\n\n  /**\n   * Get all plugins that feature a given property/class method.\n   *\n   * @private\n   */\n  private getPluginsByProp(prop: string): PuppeteerExtraPlugin[] {\n    return this._plugins.filter(plugin => prop in plugin)\n  }\n\n  /**\n   * Lightweight plugin dependency management to require plugins and code mods on demand.\n   *\n   * This uses the `dependencies` stanza (a `Set`) exposed by `puppeteer-extra` plugins.\n   *\n   * @todo Allow objects as depdencies that contains opts for the requested plugin.\n   *\n   * @private\n   */\n  private resolvePluginDependencies() {\n    // Request missing dependencies from all plugins and flatten to a single Set\n    const missingPlugins = this._plugins\n      .map(p => p._getMissingDependencies(this._plugins))\n      .reduce((combined, list) => {\n        return new Set([...combined, ...list])\n      }, new Set())\n    if (!missingPlugins.size) {\n      debug('no dependencies are missing')\n      return\n    }\n    debug('dependencies missing', missingPlugins)\n    // Loop through all dependencies declared missing by plugins\n    for (let name of [...missingPlugins]) {\n      // Check if the dependency hasn't been registered as plugin already.\n      // This might happen when multiple plugins have nested dependencies.\n      if (this.pluginNames.includes(name)) {\n        debug(`ignoring dependency '${name}', which has been required already.`)\n        continue\n      }\n      // We follow a plugin naming convention, but let's rather enforce it <3\n      name = name.startsWith('puppeteer-extra-plugin')\n        ? name\n        : `puppeteer-extra-plugin-${name}`\n      // In case a module sub resource is requested print out the main package name\n      // e.g. puppeteer-extra-plugin-stealth/evasions/console.debug => puppeteer-extra-plugin-stealth\n      const packageName = name.split('/')[0]\n      let dep = null\n      try {\n        // Try to require and instantiate the stated dependency\n        dep = require(name)()\n        // Register it with `puppeteer-extra` as plugin\n        this.use(dep)\n      } catch (err) {\n        console.warn(`\n          A plugin listed '${name}' as dependency,\n          which is currently missing. Please install it:\n\n          yarn add ${packageName}\n\n          Note: You don't need to require the plugin yourself,\n          unless you want to modify it's default settings.\n          `)\n        throw err\n      }\n      // Handle nested dependencies :D\n      if (dep.dependencies.size) {\n        this.resolvePluginDependencies()\n      }\n    }\n  }\n\n  /**\n   * Order plugins that have expressed a special placement requirement.\n   *\n   * This is useful/necessary for e.g. plugins that depend on the data from other plugins.\n   *\n   * @todo Support more than 'runLast'.\n   * @todo If there are multiple plugins defining 'runLast', sort them depending on who depends on whom. :D\n   *\n   * @private\n   */\n  private orderPlugins() {\n    debug('orderPlugins:before', this.pluginNames)\n    const runLast = this._plugins\n      .filter(p => p.requirements.has('runLast'))\n      .map(p => p.name)\n    for (const name of runLast) {\n      const index = this._plugins.findIndex(p => p.name === name)\n      this._plugins.push(this._plugins.splice(index, 1)[0])\n    }\n    debug('orderPlugins:after', this.pluginNames)\n  }\n\n  /**\n   * Lightweight plugin requirement checking.\n   *\n   * The main intent is to notify the user when a plugin won't work as expected.\n   *\n   * @todo This could be improved, e.g. be evaluated by the plugin base class.\n   *\n   * @private\n   */\n  private checkPluginRequirements(opts = {} as any) {\n    for (const plugin of this._plugins) {\n      for (const requirement of plugin.requirements) {\n        if (\n          opts.context === 'launch' &&\n          requirement === 'headful' &&\n          opts.options.headless\n        ) {\n          console.warn(\n            `Warning: Plugin '${plugin.name}' is not supported in headless mode.`\n          )\n        }\n        if (opts.context === 'connect' && requirement === 'launch') {\n          console.warn(\n            `Warning: Plugin '${plugin.name}' doesn't support puppeteer.connect().`\n          )\n        }\n      }\n    }\n  }\n\n  /**\n   * Call plugins sequentially with the same values.\n   * Plugins that expose the supplied property will be called.\n   *\n   * @param prop - The plugin property to call\n   * @param values - Any number of values\n   * @private\n   */\n  private async callPlugins(prop: string, ...values: any[]) {\n    for (const plugin of this.getPluginsByProp(prop)) {\n      await plugin[prop].apply(plugin, values)\n    }\n  }\n\n  /**\n   * Call plugins sequentially and pass on a value (waterfall style).\n   * Plugins that expose the supplied property will be called.\n   *\n   * The plugins can either modify the value or return an updated one.\n   * Will return the latest, updated value which ran through all plugins.\n   *\n   * @param prop - The plugin property to call\n   * @param value - Any value\n   * @return The new updated value\n   * @private\n   */\n  private async callPluginsWithValue(prop: string, value: any) {\n    for (const plugin of this.getPluginsByProp(prop)) {\n      const newValue = await plugin[prop](value)\n      if (newValue) {\n        value = newValue\n      }\n    }\n    return value\n  }\n}\n\n/**\n * The **default export** will behave exactly the same as the regular puppeteer\n * (just with extra plugin functionality) and can be used as a drop-in replacement.\n *\n * Behind the scenes it will try to require either `puppeteer`\n * or [`puppeteer-core`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core)\n * from the installed dependencies.\n *\n * @example\n * // javascript import\n * const puppeteer = require('puppeteer-extra')\n *\n * // typescript/es6 module import\n * import puppeteer from 'puppeteer-extra'\n *\n * // Add plugins\n * puppeteer.use(...)\n */\nconst defaultExport: PuppeteerExtra = (() => {\n  return new PuppeteerExtra(...requireVanillaPuppeteer())\n})()\n\nexport default defaultExport\n\n/**\n * An **alternative way** to use `puppeteer-extra`: Augments the provided puppeteer with extra plugin functionality.\n *\n * This is useful in case you need multiple puppeteer instances with different plugins or to add plugins to a non-standard puppeteer package.\n *\n * @example\n * // js import\n * const { addExtra } = require('puppeteer-extra')\n *\n * // ts/es6 import\n * import { addExtra } from 'puppeteer-extra'\n *\n * // Patch e.g. puppeteer-firefox and add plugins\n * const puppeteer = addExtra(require('puppeteer-firefox'))\n * puppeteer.use(...)\n *\n * @param puppeteer Any puppeteer API-compatible puppeteer implementation or version.\n * @return A fresh PuppeteerExtra instance using the provided puppeteer\n */\nexport const addExtra = (puppeteer: VanillaPuppeteer): PuppeteerExtra =>\n  new PuppeteerExtra(puppeteer)\n\n/**\n * Attempt to require puppeteer or puppeteer-core from dependencies.\n * To stay backwards compatible with the existing default export we have to do some gymnastics here.\n *\n * @return Either a Puppeteer instance or an Error, which we'll throw later if need be.\n * @private\n */\nfunction requireVanillaPuppeteer(): [VanillaPuppeteer?, Error?] {\n  try {\n    return [require('puppeteer'), undefined]\n  } catch (_) {\n    // noop\n  }\n  try {\n    return [require('puppeteer-core'), undefined]\n  } catch (err) {\n    return [undefined, err as Error]\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/src/puppeteer-legacy.d.ts",
    "content": "// @ts-nocheck\n// NOTE: The above comment is crucial for all this to work\n// The puppeteer project caused a type breaking shift in v6 while switching from @types/puppeteer to built-in types\n// This type definition file is only relevant when puppeteer < v6 is being used,\n// if we don't instruct TS to skip checking this file it would cause errors when pptr >= v6 is used (e.g. ChromeArgOptions is missing)\nimport {} from 'puppeteer'\nimport { Browser, ConnectOptions, ChromeArgOptions, LaunchOptions, FetcherOptions, BrowserFetcher} from \"puppeteer\"\n\n// Make puppeteer-extra typings backwards compatible with puppeteer < v6\n// In pptr >= v6 they switched to built-in types and the `@types/puppeteer` package is not needed anymore.\n// This is essentially a shim for `PuppeteerNode`, which is found in pptr >= v6 and missing in `@types/puppeteer`.\n// Requires the `@types/puppeteer` package to be installed when using pptr < v6, `@types/puppeteer` will be ignored by TS when built-in types are available.\ninterface VanillaPuppeteer {\n  /** Attaches Puppeteer to an existing Chromium instance */\n  connect(options?: ConnectOptions): Promise<Browser>\n  /** The default flags that Chromium will be launched with */\n  defaultArgs(options?: ChromeArgOptions): string[]\n  /** Path where Puppeteer expects to find bundled Chromium */\n  executablePath(): string\n  /** The method launches a browser instance with given arguments. The browser will be closed when the parent node.js process is closed. */\n  launch(options?: LaunchOptions): Promise<Browser>\n  /** This methods attaches Puppeteer to an existing Chromium instance. */\n  createBrowserFetcher(\n    options?: FetcherOptions\n  ): BrowserFetcher\n}\n\ndeclare module 'puppeteer' {\n  interface PuppeteerNode extends VanillaPuppeteer {}\n}\ndeclare module 'puppeteer-core' {\n  interface PuppeteerNode extends VanillaPuppeteer {}\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/test/addExtra.ts",
    "content": "import test from 'ava'\n\nimport { addExtra } from '../src/index'\n\ntest('is a function', async t => {\n  t.is(typeof addExtra, 'function')\n})\n\ntest('is an instance of Function', async t => {\n  t.is(addExtra.constructor.name, 'Function')\n})\n\ntest('returns an object', async t => {\n  t.is(typeof addExtra(null as any), 'object')\n})\n\ntest('returns an instance of PuppeteerExtra', async t => {\n  t.is(addExtra(null as any).constructor.name, 'PuppeteerExtra')\n})\n\ntest('will throw without puppeteer', async t => {\n  const pptr = addExtra(null as any)\n  t.throws(() => pptr.pptr, null, 'No puppeteer instance provided.')\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/test/basic.ts",
    "content": "import test from 'ava'\n\nimport puppeteer from '../src/index'\n\ntest('is an object', async t => {\n  t.is(typeof puppeteer, 'object')\n})\n\ntest('is an instance of PuppeteerExtra', async t => {\n  t.is(puppeteer.constructor.name, 'PuppeteerExtra')\n})\n\ntest('should have the public class members', async t => {\n  t.true(puppeteer.use instanceof Function)\n  t.true(puppeteer.plugins instanceof Array)\n  t.true(puppeteer.pluginNames instanceof Array)\n  t.true(puppeteer.getPluginData instanceof Function)\n})\n\ntest('should have the internal class members', async t => {\n  t.true('getPluginsByProp' in puppeteer)\n  t.true('resolvePluginDependencies' in puppeteer)\n  t.true('orderPlugins' in puppeteer)\n  t.true('checkPluginRequirements' in puppeteer)\n  t.true('callPlugins' in puppeteer)\n  t.true('callPluginsWithValue' in puppeteer)\n})\n\ntest('should have the orginal puppeteer public class members', async t => {\n  t.true(puppeteer.launch instanceof Function)\n  t.true(puppeteer.connect instanceof Function)\n  t.true(puppeteer.executablePath instanceof Function)\n  t.true(puppeteer.defaultArgs instanceof Function)\n  t.true(puppeteer.createBrowserFetcher instanceof Function)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/test/connect.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  try {\n    delete require.cache[require.resolve('puppeteer-extra')]\n    // delete require.cache[require.resolve('puppeteer-extra-plugin')]\n  } catch (error) {\n    console.log(error)\n  }\n})\n\ntest('will remove headless from remote browser', async t => {\n  // Mitigate CI quirks\n  try {\n    // Launch vanilla puppeteer browser with no plugins\n    const puppeteerVanilla = require('puppeteer')\n    const browserVanilla = await puppeteerVanilla.launch({\n      args: PUPPETEER_ARGS\n    })\n    const browserWSEndpoint = browserVanilla.wsEndpoint()\n\n    // Use puppeteer-extra with plugin to conntect to existing browser\n    const puppeteer = require('puppeteer-extra')\n    puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n    const browser = await puppeteer.connect({ browserWSEndpoint })\n\n    // Let's ensure we've anonymized the user-agent, despite not using .launch\n    const page = await browser.newPage()\n    const ua = await page.evaluate(() => window.navigator.userAgent)\n    t.true(!ua.includes('HeadlessChrome'))\n\n    await browser.close()\n    t.true(true)\n  } catch (err) {\n    console.log(`Caught error:`, err)\n    if (\n      err.message &&\n      err.message.includes(\n        'Session closed. Most likely the page has been closed'\n      )\n    ) {\n      t.true(true) // ignore this error\n    } else {\n      throw err\n    }\n  }\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/test/events.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\nconst puppeteerVanilla = require('puppeteer')\nconst { addExtra } = require('puppeteer-extra')\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  try {\n    delete require.cache[require.resolve('puppeteer-extra')]\n    delete require.cache[require.resolve('puppeteer-extra-plugin')]\n  } catch (error) {\n    console.log(error)\n  }\n})\n\ntest('will bind launched browser events to plugins', async t => {\n  const PLUGIN_EVENTS = []\n\n  const puppeteer = addExtra(puppeteerVanilla)\n  const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n  const pluginName = 'hello-world'\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n\n    onPluginRegistered() {\n      PLUGIN_EVENTS.push('onPluginRegistered')\n    }\n    beforeLaunch() {\n      PLUGIN_EVENTS.push('beforeLaunch')\n    }\n    afterLaunch() {\n      PLUGIN_EVENTS.push('afterLaunch')\n    }\n    beforeConnect() {\n      PLUGIN_EVENTS.push('beforeConnect')\n    }\n    afterConnect() {\n      PLUGIN_EVENTS.push('afterConnect')\n    }\n    onBrowser() {\n      PLUGIN_EVENTS.push('onBrowser')\n    }\n    onTargetCreated() {\n      PLUGIN_EVENTS.push('onTargetCreated')\n    }\n    onPageCreated() {\n      PLUGIN_EVENTS.push('onPageCreated')\n    }\n    onTargetChanged() {\n      PLUGIN_EVENTS.push('onTargetChanged')\n    }\n    onTargetDestroyed() {\n      PLUGIN_EVENTS.push('onTargetDestroyed')\n    }\n    onDisconnected() {\n      PLUGIN_EVENTS.push('onDisconnected')\n    }\n    onClose() {\n      PLUGIN_EVENTS.push('onClose')\n    }\n  }\n\n  const instance = new Plugin()\n  puppeteer.use(instance)\n  t.true(PLUGIN_EVENTS.includes('onPluginRegistered'))\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  t.true(PLUGIN_EVENTS.includes('beforeLaunch'))\n  t.true(PLUGIN_EVENTS.includes('afterLaunch'))\n  // t.true(!PLUGIN_EVENTS.includes('beforeConnect'))\n  // t.true(!PLUGIN_EVENTS.includes('afterConnect'))\n  t.true(PLUGIN_EVENTS.includes('onBrowser'))\n  const page = await browser.newPage().catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onTargetCreated'))\n  t.true(PLUGIN_EVENTS.includes('onPageCreated'))\n  await page.goto('about:blank#foo').catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onTargetChanged'))\n  await page.close().catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onTargetDestroyed'))\n  await browser.close().catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onDisconnected'))\n  t.true(PLUGIN_EVENTS.includes('onClose'))\n})\n\ntest('will bind connected browser events to plugins', async t => {\n  const PLUGIN_EVENTS = []\n\n  // Launch vanilla puppeteer browser with no plugins\n\n  const pptr1 = addExtra(puppeteerVanilla)\n\n  const browserVanilla = await pptr1.launch({\n    args: PUPPETEER_ARGS\n  })\n  const browserWSEndpoint = browserVanilla.wsEndpoint()\n\n  const puppeteer = addExtra(puppeteerVanilla)\n  const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n  const pluginName = 'hello-world'\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n\n    onPluginRegistered() {\n      PLUGIN_EVENTS.push('onPluginRegistered')\n    }\n    beforeLaunch() {\n      PLUGIN_EVENTS.push('beforeLaunch')\n    }\n    afterLaunch() {\n      PLUGIN_EVENTS.push('afterLaunch')\n    }\n    beforeConnect() {\n      PLUGIN_EVENTS.push('beforeConnect')\n    }\n    afterConnect() {\n      PLUGIN_EVENTS.push('afterConnect')\n    }\n    onBrowser() {\n      PLUGIN_EVENTS.push('onBrowser')\n    }\n    onTargetCreated() {\n      PLUGIN_EVENTS.push('onTargetCreated')\n    }\n    onPageCreated() {\n      PLUGIN_EVENTS.push('onPageCreated')\n    }\n    onTargetChanged() {\n      PLUGIN_EVENTS.push('onTargetChanged')\n    }\n    onTargetDestroyed() {\n      PLUGIN_EVENTS.push('onTargetDestroyed')\n    }\n    onDisconnected() {\n      PLUGIN_EVENTS.push('onDisconnected')\n    }\n    onClose() {\n      PLUGIN_EVENTS.push('onClose')\n    }\n  }\n\n  const instance = new Plugin()\n  puppeteer.use(instance)\n  t.true(PLUGIN_EVENTS.includes('onPluginRegistered'))\n  const browser = await puppeteer\n    .connect({ browserWSEndpoint })\n    .catch(console.log)\n  t.true(!PLUGIN_EVENTS.includes('beforeLaunch'))\n  t.true(!PLUGIN_EVENTS.includes('afterLaunch'))\n  t.true(PLUGIN_EVENTS.includes('beforeConnect'))\n  t.true(PLUGIN_EVENTS.includes('afterConnect'))\n  t.true(PLUGIN_EVENTS.includes('onBrowser'))\n  const page = await browser.newPage()\n  t.true(PLUGIN_EVENTS.includes('onTargetCreated'))\n  t.true(PLUGIN_EVENTS.includes('onPageCreated'))\n  await page.goto('about:blank#foo').catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onTargetChanged'))\n  await page.close().catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onTargetDestroyed'))\n  await browser.close().catch(console.log)\n  t.true(PLUGIN_EVENTS.includes('onDisconnected'))\n  t.true(!PLUGIN_EVENTS.includes('onClose'))\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/test/options.js",
    "content": "'use strict'\n\nimport test, { beforeEach } from 'ava'\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\nbeforeEach(t => {\n  // Make sure we work with pristine modules\n  try {\n    delete require.cache[require.resolve('puppeteer-extra')]\n    delete require.cache[require.resolve('puppeteer-extra-plugin')]\n  } catch (error) {\n    console.log(error)\n  }\n})\n\ntest('will modify puppeteer launch options through plugins', async t => {\n  let FINAL_OPTIONS = null\n\n  const puppeteer = require('puppeteer-extra')\n  const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n  const pluginName = 'hello-world'\n  const pluginData = [{ name: 'foo', value: 'bar' }]\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n    get data() {\n      return pluginData\n    }\n    beforeLaunch(options) {\n      options.args.push('--foobar=true')\n      options.timeout = 60 * 1000\n      options.headless = true\n    }\n    afterLaunch(browser, opts) {\n      FINAL_OPTIONS = opts.options\n    }\n  }\n  const instance = new Plugin()\n  puppeteer.use(instance)\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: false\n  })\n\n  t.deepEqual(FINAL_OPTIONS, {\n    headless: true,\n    timeout: 60000,\n    args: [].concat(PUPPETEER_ARGS, ['--foobar=true'])\n  })\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will modify puppeteer connect options through plugins', async t => {\n  let FINAL_OPTIONS = null\n\n  // Launch vanilla puppeteer browser with no plugins\n  const puppeteerVanilla = require('puppeteer')\n  const browserVanilla = await puppeteerVanilla.launch({\n    args: PUPPETEER_ARGS\n  })\n  const browserWSEndpoint = browserVanilla.wsEndpoint()\n\n  const puppeteer = require('puppeteer-extra')\n  const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n  const pluginName = 'hello-world'\n  const pluginData = [{ name: 'foo', value: 'bar' }]\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n    get data() {\n      return pluginData\n    }\n    beforeConnect(options) {\n      options.foo1 = 60 * 1000\n      options.foo2 = true\n    }\n    afterConnect(browser, opts) {\n      FINAL_OPTIONS = opts.options\n    }\n  }\n  const instance = new Plugin()\n  puppeteer.use(instance)\n  const browser = await puppeteer.connect({ browserWSEndpoint })\n\n  t.deepEqual(FINAL_OPTIONS, {\n    foo1: 60 * 1000,\n    foo2: true,\n    browserWSEndpoint\n  })\n\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/test/plugin-support.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\nconst PAGE_TIMEOUT = 60 * 1000 // 60s\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  try {\n    delete require.cache[require.resolve('puppeteer-extra')]\n    delete require.cache[require.resolve('puppeteer-extra-plugin')]\n  } catch (error) {\n    console.log(error)\n  }\n})\n\ntest('will launch the browser normally', async t => {\n  const puppeteer = require('puppeteer-extra')\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  const page = await browser.newPage()\n  await page.goto('http://example.com', {\n    waitUntil: 'domcontentloaded',\n    timeout: PAGE_TIMEOUT\n  })\n  await browser.close()\n  t.true(true)\n})\n\ntest('will launch puppeteer with plugin support', async t => {\n  const puppeteer = require('puppeteer-extra')\n  const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n  const pluginName = 'hello-world'\n  const pluginData = [{ name: 'foo', value: 'bar' }]\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n    get data() {\n      return pluginData\n    }\n  }\n  const instance = new Plugin()\n  puppeteer.use(instance)\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  const page = await browser.newPage()\n\n  t.is(puppeteer.plugins.length, 1)\n  t.is(puppeteer.plugins[0].name, pluginName)\n  t.is(puppeteer.pluginNames.length, 1)\n  t.is(puppeteer.pluginNames[0], pluginName)\n  t.is(puppeteer.getPluginData().length, 1)\n  t.deepEqual(puppeteer.getPluginData()[0], pluginData[0])\n  t.deepEqual(puppeteer.getPluginData('foo')[0], pluginData[0])\n  t.is(puppeteer.getPluginData('not-existing').length, 0)\n\n  await page.goto('http://example.com', {\n    waitUntil: 'domcontentloaded',\n    timeout: PAGE_TIMEOUT\n  })\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"dom\"],\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"./src/**/*.tsx\", \"./src/**/*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra/tslint.json",
    "content": "{\n  \"extends\": [\"tslint-config-standard\", \"tslint-config-prettier\"],\n  \"rules\": {\n    \"ordered-imports\": true\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/ava.config-ts.js",
    "content": "export default {\n  compileEnhancements: false,\n  environmentVariables: {\n    TS_NODE_COMPILER_OPTIONS: '{\"module\":\"commonjs\"}'\n  },\n  files: ['src/**/*.test.ts'],\n  extensions: ['ts'],\n  require: ['ts-node/register']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/ava.config.js",
    "content": "export default {\n  files: ['dist/*.test.js']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin\",\n  \"version\": \"3.2.3\",\n  \"description\": \"Base class for puppeteer-extra plugins.\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup\",\n    \"build:tsc\": \"tsc --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"documentation readme --quiet --shallow --github --markdown-theme transitivebs --readme-file readme.md --section API ./src/index.ts\",\n    \"postdocs\": \"npx prettier --write readme.md\",\n    \"test\": \"ava -v --config ava.config-ts.js\",\n    \"pretest-ci\": \"run-s build\",\n    \"test-ci\": \"ava --fail-fast -v\"\n  },\n  \"engines\": {\n    \"node\": \">=9.11.2\"\n  },\n  \"prettier\": {\n    \"printWidth\": 80,\n    \"semi\": false,\n    \"singleQuote\": true\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"ua\",\n    \"user-agent\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"@types/node\": \"14.14.34\",\n    \"@types/puppeteer\": \"*\",\n    \"ava\": \"2.4.0\",\n    \"documentation-markdown-themes\": \"^12.1.5\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"9\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup\": \"^1.27.5\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"ts-node\": \"^8.5.4\",\n    \"tslint\": \"^5.12.1\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-config-standard\": \"^9.0.0\",\n    \"typescript\": \"4.4.3\"\n  },\n  \"dependencies\": {\n    \"@types/debug\": \"^4.1.0\",\n    \"debug\": \"^4.1.1\",\n    \"merge-deep\": \"^3.0.1\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/readme.md",
    "content": "# puppeteer-extra-plugin [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push)](https://github.com/berstend/puppeteer-extra/actions) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/puppeteer-extra-plugin.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin)\n\n## Installation\n\n```bash\nyarn add puppeteer-extra-plugin\n```\n\n## Changelog\n\n<details>\n <summary><strong>v3.0.1</strong></summary><br>\n\n- Now written in TypeScript 🎉\n- **Breaking change:** Now using a named export:\n\n```js\n// Before\nconst PuppeteerExtraPlugin = require('puppeteer-extra-plugin')\n\n// After (>= v3.0.1)\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n```\n\n</details>\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [puppeteer-extra-plugin ![GitHub Workflow Status](https://github.com/berstend/puppeteer-extra/actions) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/puppeteer-extra-plugin.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin)](#puppeteer-extra-plugin---)\n  - [Installation](#installation)\n  - [Changelog](#changelog)\n  - [API](#api)\n    - [Table of Contents](#table-of-contents)\n    - [class: PuppeteerExtraPlugin](#class-puppeteerextraplugin)\n      - [.name](#name)\n      - [.defaults](#defaults)\n      - [.requirements](#requirements)\n      - [.dependencies](#dependencies)\n      - [.data](#data)\n      - [.opts](#opts)\n      - [.debug](#debug)\n      - [.beforeLaunch(options)](#beforelaunchoptions)\n      - [.afterLaunch(browser, opts)](#afterlaunchbrowser-opts)\n      - [.beforeConnect(options)](#beforeconnectoptions)\n      - [.afterConnect(browser, opts)](#afterconnectbrowser-opts)\n      - [.onBrowser(browser, opts)](#onbrowserbrowser-opts)\n      - [.onTargetCreated(target)](#ontargetcreatedtarget)\n      - [.onPageCreated(page, target)](#onpagecreatedpage-target)\n      - [.onTargetChanged(target)](#ontargetchangedtarget)\n      - [.onTargetDestroyed(target)](#ontargetdestroyedtarget)\n      - [.onDisconnected()](#ondisconnected)\n      - [.onClose()](#onclose)\n      - [.onPluginRegistered()](#onpluginregistered)\n      - [.getDataFromPlugins(name?)](#getdatafrompluginsname)\n\n### class: [PuppeteerExtraPlugin](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L65-L572)\n\n- `opts` **PluginOptions?**\n\nBase class for `puppeteer-extra` plugins.\n\nProvides convenience methods to avoid boilerplate.\n\nAll common `puppeteer` browser events will be bound to\nthe plugin instance, if a respectively named class member is found.\n\nPlease refer to the [puppeteer API documentation](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) as well.\n\nExample:\n\n```javascript\n// hello-world-plugin.js\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'hello-world'\n  }\n\n  async onPageCreated(page) {\n    this.debug('page created', page.url())\n    const ua = await page.browser().userAgent()\n    this.debug('user agent', ua)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n\n// foo.js\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('./hello-world-plugin')())\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  await page.goto('http://example.com', { waitUntil: 'domcontentloaded' })\n  await browser.close()\n})()\n```\n\n---\n\n#### .[name](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L92-L94)\n\nType: **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**\n\nPlugin name (required).\n\nConvention:\n\n- Package: `puppeteer-extra-plugin-anonymize-ua`\n- Name: `anonymize-ua`\n\nExample:\n\n```javascript\nget name () { return 'anonymize-ua' }\n```\n\n---\n\n#### .[defaults](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L117-L119)\n\nType: **PluginOptions**\n\nPlugin defaults (optional).\n\nIf defined will be ([deep-](https://github.com/jonschlinkert/merge-deep))merged with the (optional) user supplied options (supplied during plugin instantiation).\n\nThe result of merging defaults with user supplied options can be accessed through `this.opts`.\n\nExample:\n\n```javascript\nget defaults () {\n  return {\n    stripHeadless: true,\n    makeWindows: true,\n    customFn: null\n  }\n}\n\n// Users can overwrite plugin defaults during instantiation:\npuppeteer.use(require('puppeteer-extra-plugin-foobar')({ makeWindows: false }))\n```\n\n- **See: \\[[opts]]**\n\n---\n\n#### .[requirements](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L145-L147)\n\nType: **PluginRequirements**\n\nPlugin requirements (optional).\n\nSignal certain plugin requirements to the base class and the user.\n\nCurrently supported:\n\n- `launch`\n  - If the plugin only supports locally created browser instances (no `puppeteer.connect()`),\n    will output a warning to the user.\n- `headful`\n  - If the plugin doesn't work in `headless: true` mode,\n    will output a warning to the user.\n- `dataFromPlugins`\n  - In case the plugin requires data from other plugins.\n    will enable usage of `this.getDataFromPlugins()`.\n- `runLast`\n  - In case the plugin prefers to run after the others.\n    Useful when the plugin needs data from others.\n\nExample:\n\n```javascript\nget requirements () {\n  return new Set(['runLast', 'dataFromPlugins'])\n}\n```\n\n---\n\n#### .[dependencies](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L160-L162)\n\nType: **PluginDependencies**\n\nPlugin dependencies (optional).\n\nMissing plugins will be required() by puppeteer-extra.\n\nExample:\n\n```javascript\nget dependencies () {\n  return new Set(['user-preferences'])\n}\n// Will ensure the 'puppeteer-extra-plugin-user-preferences' plugin is loaded.\n```\n\n---\n\n#### .[data](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L196-L198)\n\nType: **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;PluginData>**\n\nPlugin data (optional).\n\nPlugins can expose data (an array of objects), which in turn can be consumed by other plugins,\nthat list the `dataFromPlugins` requirement (by using `this.getDataFromPlugins()`).\n\nConvention: `[ {name: 'Any name', value: 'Any value'} ]`\n\nExample:\n\n```javascript\n// plugin1.js\nget data () {\n  return [\n    {\n      name: 'userPreferences',\n      value: { foo: 'bar' }\n    },\n    {\n      name: 'userPreferences',\n      value: { hello: 'world' }\n    }\n  ]\n\n// plugin2.js\nget requirements () { return new Set(['dataFromPlugins']) }\n\nasync beforeLaunch () {\n  const prefs = this.getDataFromPlugins('userPreferences').map(d => d.value)\n  this.debug(prefs) // => [ { foo: 'bar' }, { hello: 'world' } ]\n}\n```\n\n- **See: \\[[getDataFromPlugins]]**\n\n---\n\n#### .[opts](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L215-L217)\n\nType: **PluginOptions**\n\nAccess the plugin options (usually the `defaults` merged with user defined options)\n\nTo skip the auto-merging of defaults with user supplied opts don't define a `defaults`\nproperty and set the `this._opts` Object in your plugin constructor directly.\n\nExample:\n\n```javascript\nget defaults () { return { foo: \"bar\" } }\n\nasync onPageCreated (page) {\n  this.debug(this.opts.foo) // => bar\n}\n```\n\n- **See: \\[[defaults]]**\n\n---\n\n#### .[debug](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L235-L237)\n\nType: **Debugger**\n\nConvenience debug logger based on the [debug] module.\nWill automatically namespace the logging output to the plugin package name.\n\n[debug]: https://www.npmjs.com/package/debug\n\n```bash\n# toggle output using environment variables\nDEBUG=puppeteer-extra-plugin:<plugin_name> node foo.js\n# to debug all the things:\nDEBUG=puppeteer-extra,puppeteer-extra-plugin:* node foo.js\n```\n\nExample:\n\n```javascript\nthis.debug('hello world')\n// will output e.g. 'puppeteer-extra-plugin:anonymize-ua hello world'\n```\n\n---\n\n#### .[beforeLaunch(options)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L256-L258)\n\n- `options` **any** Puppeteer launch options\n\nBefore a new browser instance is created/launched.\n\nCan be used to modify the puppeteer launch options by modifying or returning them.\n\nPlugins using this method will be called in sequence to each\nbe able to update the launch options.\n\nExample:\n\n```javascript\nasync beforeLaunch (options) {\n  if (this.opts.flashPluginPath) {\n    options.args.push(`--ppapi-flash-path=${this.opts.flashPluginPath}`)\n  }\n}\n```\n\n---\n\n#### .[afterLaunch(browser, opts)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L287-L292)\n\n- `browser` **Puppeteer.Browser** The `puppeteer` browser instance.\n- `opts` (optional, default `{options:({}as Puppeteer.LaunchOptions)}`)\n\nAfter the browser has launched.\n\nNote: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\nIt's possible that `pupeeteer.launch` will be called multiple times and more than one browser created.\nIn order to make the plugins as stateless as possible don't store a reference to the browser instance\nin the plugin but rather consider alternatives.\n\nE.g. when using `onPageCreated` you can get a browser reference by using `page.browser()`.\n\nAlternatively you could expose a class method that takes a browser instance as a parameter to work with:\n\n```es6\nconst fancyPlugin = require('puppeteer-extra-plugin-fancy')()\npuppeteer.use(fancyPlugin)\nconst browser = await puppeteer.launch()\nawait fancyPlugin.killBrowser(browser)\n```\n\nExample:\n\n```javascript\nasync afterLaunch (browser, opts) {\n  this.debug('browser has been launched', opts.options)\n}\n```\n\n---\n\n#### .[beforeConnect(options)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L305-L307)\n\n- `options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer connect options\n\nBefore connecting to an existing browser instance.\n\nCan be used to modify the puppeteer connect options by modifying or returning them.\n\nPlugins using this method will be called in sequence to each\nbe able to update the launch options.\n\n---\n\n#### .[afterConnect(browser, opts)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L319-L321)\n\n- `browser` **Puppeteer.Browser** The `puppeteer` browser instance.\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)\n  - `opts.options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer connect options used.\n\nAfter connecting to an existing browser instance.\n\n> Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\n\n---\n\n#### .[onBrowser(browser, opts)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L335-L337)\n\n- `browser` **Puppeteer.Browser** The `puppeteer` browser instance.\n- `opts` **any**\n\nReturns: **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;void>**\n\nCalled when a browser instance is available.\n\nThis applies to both `puppeteer.launch()` and `puppeteer.connect()`.\n\nConvenience method created for plugins that need access to a browser instance\nand don't mind if it has been created through `launch` or `connect`.\n\n> Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\n\n---\n\n#### .[onTargetCreated(target)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L348-L350)\n\n- `target` **Puppeteer.Target**\n\nCalled when a target is created, for example when a new page is opened by window.open or browser.newPage.\n\n> Note: This includes target creations in incognito browser contexts.\n>\n> Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n\n---\n\n#### .[onPageCreated(page, target)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L371-L373)\n\n- `page` **Puppeteer.Page**\n- `target` **Puppeteer.Target**\n\nSame as `onTargetCreated` but prefiltered to only contain Pages, for convenience.\n\n> Note: This includes page creations in incognito browser contexts.\n>\n> Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n\nExample:\n\n```javascript\nasync onPageCreated (page) {\n  let ua = await page.browser().userAgent()\n  if (this.opts.stripHeadless) {\n    ua = ua.replace('HeadlessChrome/', 'Chrome/')\n  }\n  this.debug('new ua', ua)\n  await page.setUserAgent(ua)\n}\n```\n\n---\n\n#### .[onTargetChanged(target)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L384-L386)\n\n- `target` **Puppeteer.Target**\n\nCalled when the url of a target changes.\n\n> Note: This includes target changes in incognito browser contexts.\n>\n> Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n\n---\n\n#### .[onTargetDestroyed(target)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L397-L399)\n\n- `target` **Puppeteer.Target**\n\nCalled when a target is destroyed, for example when a page is closed.\n\n> Note: This includes target destructions in incognito browser contexts.\n>\n> Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n\n---\n\n#### .[onDisconnected()](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L408-L410)\n\nCalled when Puppeteer gets disconnected from the Chromium instance.\n\nThis might happen because of one of the following:\n\n- Chromium is closed or crashed\n- The `browser.disconnect` method was called\n\n---\n\n#### .[onClose()](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L424-L426)\n\n**Deprecated:** Since puppeteer v1.6.0 `onDisconnected` has been improved\nand should be used instead of `onClose`.\n\nIn puppeteer &lt; v1.6.0 `onDisconnected` was not catching all exit scenarios.\nIn order for plugins to clean up properly (e.g. deleting temporary files)\nthe `onClose` method had been introduced.\n\n> Note: Might be called multiple times on exit.\n>\n> Note: This only includes browser instances created through `.launch()`.\n\n---\n\n#### .[onPluginRegistered()](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L433-L435)\n\nAfter the plugin has been registered in `puppeteer-extra`.\n\nNormally right after `puppeteer.use(plugin)` is called\n\n---\n\n#### .[getDataFromPlugins(name?)](https://github.com/berstend/puppeteer-extra/blob/dc8b90260a927c0c66c4585c5a56092ea9c35049/packages/puppeteer-extra-plugin/src/index.ts#L448-L450)\n\n- `name` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Filter data by `name` property\n\nReturns: **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;PluginData>**\n\nHelper method to retrieve `data` objects from other plugins.\n\nA plugin needs to state the `dataFromPlugins` requirement\nin order to use this method. Will be mapped to `puppeteer.getPluginData`.\n\n- **See: [data]**\n- **See: [requirements]**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/rollup.config.ts",
    "content": "import commonjs from 'rollup-plugin-commonjs'\nimport resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve(),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/src/ambient.d.ts",
    "content": "export {}\n\n// https://github.com/sindresorhus/type-fest/issues/19\ndeclare global {\n  interface SymbolConstructor {\n    readonly observable: symbol\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/src/index.test.ts",
    "content": "import test from 'ava'\n\nimport { PuppeteerExtraPlugin } from '.'\n\ntest('is a function', async t => {\n  t.is(typeof PuppeteerExtraPlugin, 'function')\n})\n\ntest('will throw without a name', async t => {\n  class Derived extends PuppeteerExtraPlugin {}\n  const error = await t.throws(() => new Derived())\n  t.is(error.message, `Plugin must override \"name\"`)\n})\n\ntest('should have the basic class members', async t => {\n  const pluginName = 'hello-world'\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n  }\n  const instance = new Plugin()\n\n  t.is(instance.name, pluginName)\n  t.true(instance.requirements instanceof Set)\n  t.true(instance.dependencies instanceof Set)\n  t.true(instance.data instanceof Array)\n  t.true(instance.defaults instanceof Object)\n  t.is(instance.data.length, 0)\n  t.true(instance.debug instanceof Function)\n  t.is(instance.debug.namespace, `puppeteer-extra-plugin:${pluginName}`)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have the public class members', async t => {\n  const pluginName = 'hello-world'\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n  }\n  const instance = new Plugin()\n\n  t.true(instance.beforeLaunch instanceof Function)\n  t.true(instance.afterLaunch instanceof Function)\n  t.true(instance.onTargetCreated instanceof Function)\n  t.true(instance.onBrowser instanceof Function)\n  t.true(instance.onPageCreated instanceof Function)\n  t.true(instance.onTargetChanged instanceof Function)\n  t.true(instance.onTargetDestroyed instanceof Function)\n  t.true(instance.onDisconnected instanceof Function)\n  t.true(instance.onClose instanceof Function)\n  t.true(instance.onPluginRegistered instanceof Function)\n  t.true(instance.getDataFromPlugins instanceof Function)\n})\n\ntest('should have the internal class members', async t => {\n  const pluginName = 'hello-world'\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n  }\n  const instance = new Plugin()\n\n  t.true(instance._getMissingDependencies instanceof Function)\n  t.true(instance._bindBrowserEvents instanceof Function)\n  t.true(instance._onTargetCreated instanceof Function)\n  t.true(instance._register instanceof Function)\n  t.true(instance._registerChildClassMembers instanceof Function)\n  t.true(instance._hasChildClassMember instanceof Function)\n})\n\ntest('should merge opts with defaults automatically', async t => {\n  const pluginName = 'hello-world'\n  const pluginDefaults = { foo: 'bar', foo2: 'bar2', extra1: 123 }\n  const userOpts = { foo2: 'bob', extra2: 666 }\n\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n    get defaults() {\n      return pluginDefaults\n    }\n  }\n  const instance = new Plugin(userOpts)\n\n  t.deepEqual(instance.defaults, pluginDefaults)\n  t.is(instance.opts.foo, pluginDefaults.foo)\n  t.is(instance.opts.foo2, userOpts.foo2)\n  t.is(instance.opts.extra1, pluginDefaults.extra1)\n  t.is(instance.opts.extra2, userOpts.extra2)\n})\n\ntest('should have opts when defaults is not defined', async t => {\n  const pluginName = 'hello-world'\n  const userOpts = { foo2: 'bob', extra2: 666 }\n\n  class Plugin extends PuppeteerExtraPlugin {\n    constructor(opts = {}) {\n      super(opts)\n    }\n    get name() {\n      return pluginName\n    }\n  }\n  const instance = new Plugin(userOpts)\n\n  t.deepEqual(instance.opts, userOpts)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/src/index.ts",
    "content": "import debug, { Debugger } from 'debug'\nimport * as Puppeteer from './puppeteer'\n\n/** @private */\nconst merge = require('merge-deep')\n\nexport interface PluginOptions {\n  [key: string]: any\n}\nexport interface PluginData {\n  name: {\n    [key: string]: any\n  }\n  value: {\n    [key: string]: any\n  }\n}\n\nexport type PluginDependencies = Set<string>\nexport type PluginRequirements = Set<\n  'launch' | 'headful' | 'dataFromPlugins' | 'runLast'\n>\n\n/**\n * Base class for `puppeteer-extra` plugins.\n *\n * Provides convenience methods to avoid boilerplate.\n *\n * All common `puppeteer` browser events will be bound to\n * the plugin instance, if a respectively named class member is found.\n *\n * Please refer to the [puppeteer API documentation](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) as well.\n *\n * @example\n * // hello-world-plugin.js\n * const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n *\n * class Plugin extends PuppeteerExtraPlugin {\n *   constructor (opts = { }) { super(opts) }\n *\n *   get name () { return 'hello-world' }\n *\n *   async onPageCreated (page) {\n *     this.debug('page created', page.url())\n *     const ua = await page.browser().userAgent()\n *     this.debug('user agent', ua)\n *   }\n * }\n *\n * module.exports = function (pluginConfig) { return new Plugin(pluginConfig) }\n *\n *\n * // foo.js\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('./hello-world-plugin')())\n *\n * ;(async () => {\n *   const browser = await puppeteer.launch({headless: false})\n *   const page = await browser.newPage()\n *   await page.goto('http://example.com', {waitUntil: 'domcontentloaded'})\n *   await browser.close()\n * })()\n *\n */\nexport abstract class PuppeteerExtraPlugin {\n  /** @private */\n  private _debugBase: Debugger\n  /** @private */\n  private _opts: PluginOptions\n  /** @private */\n  private _childClassMembers: string[]\n\n  constructor(opts?: PluginOptions) {\n    this._debugBase = debug(`puppeteer-extra-plugin:base:${this.name}`)\n    this._childClassMembers = []\n\n    this._opts = merge(this.defaults, opts || {})\n\n    this._debugBase('Initialized.')\n  }\n\n  /**\n   * Plugin name (required).\n   *\n   * Convention:\n   * - Package: `puppeteer-extra-plugin-anonymize-ua`\n   * - Name: `anonymize-ua`\n   *\n   * @example\n   * get name () { return 'anonymize-ua' }\n   */\n  get name(): string {\n    throw new Error('Plugin must override \"name\"')\n  }\n\n  /**\n   * Plugin defaults (optional).\n   *\n   * If defined will be ([deep-](https://github.com/jonschlinkert/merge-deep))merged with the (optional) user supplied options (supplied during plugin instantiation).\n   *\n   * The result of merging defaults with user supplied options can be accessed through `this.opts`.\n   *\n   * @see [[opts]]\n   *\n   * @example\n   * get defaults () {\n   *   return {\n   *     stripHeadless: true,\n   *     makeWindows: true,\n   *     customFn: null\n   *   }\n   * }\n   *\n   * // Users can overwrite plugin defaults during instantiation:\n   * puppeteer.use(require('puppeteer-extra-plugin-foobar')({ makeWindows: false }))\n   */\n  get defaults(): PluginOptions {\n    return {}\n  }\n\n  /**\n   * Plugin requirements (optional).\n   *\n   * Signal certain plugin requirements to the base class and the user.\n   *\n   * Currently supported:\n   * - `launch`\n   *   - If the plugin only supports locally created browser instances (no `puppeteer.connect()`),\n   *     will output a warning to the user.\n   * - `headful`\n   *   - If the plugin doesn't work in `headless: true` mode,\n   *     will output a warning to the user.\n   * - `dataFromPlugins`\n   *   - In case the plugin requires data from other plugins.\n   *     will enable usage of `this.getDataFromPlugins()`.\n   * - `runLast`\n   *   - In case the plugin prefers to run after the others.\n   *     Useful when the plugin needs data from others.\n   *\n   * @example\n   * get requirements () {\n   *   return new Set(['runLast', 'dataFromPlugins'])\n   * }\n   */\n  get requirements(): PluginRequirements {\n    return new Set([])\n  }\n\n  /**\n   * Plugin dependencies (optional).\n   *\n   * Missing plugins will be required() by puppeteer-extra.\n   *\n   * @example\n   * get dependencies () {\n   *   return new Set(['user-preferences'])\n   * }\n   * // Will ensure the 'puppeteer-extra-plugin-user-preferences' plugin is loaded.\n   */\n  get dependencies(): PluginDependencies {\n    return new Set([])\n  }\n\n  /**\n   * Plugin data (optional).\n   *\n   * Plugins can expose data (an array of objects), which in turn can be consumed by other plugins,\n   * that list the `dataFromPlugins` requirement (by using `this.getDataFromPlugins()`).\n   *\n   * Convention: `[ {name: 'Any name', value: 'Any value'} ]`\n   *\n   * @see [[getDataFromPlugins]]\n   *\n   * @example\n   * // plugin1.js\n   * get data () {\n   *   return [\n   *     {\n   *       name: 'userPreferences',\n   *       value: { foo: 'bar' }\n   *     },\n   *     {\n   *       name: 'userPreferences',\n   *       value: { hello: 'world' }\n   *     }\n   *   ]\n   *\n   * // plugin2.js\n   * get requirements () { return new Set(['dataFromPlugins']) }\n   *\n   * async beforeLaunch () {\n   *   const prefs = this.getDataFromPlugins('userPreferences').map(d => d.value)\n   *   this.debug(prefs) // => [ { foo: 'bar' }, { hello: 'world' } ]\n   * }\n   */\n  get data(): PluginData[] {\n    return []\n  }\n\n  /**\n   * Access the plugin options (usually the `defaults` merged with user defined options)\n   *\n   * To skip the auto-merging of defaults with user supplied opts don't define a `defaults`\n   * property and set the `this._opts` Object in your plugin constructor directly.\n   *\n   * @see [[defaults]]\n   *\n   * @example\n   * get defaults () { return { foo: \"bar\" } }\n   *\n   * async onPageCreated (page) {\n   *   this.debug(this.opts.foo) // => bar\n   * }\n   */\n  get opts(): PluginOptions {\n    return this._opts\n  }\n\n  /**\n   *  Convenience debug logger based on the [debug] module.\n   *  Will automatically namespace the logging output to the plugin package name.\n   *  [debug]: https://www.npmjs.com/package/debug\n   *\n   *  ```bash\n   *  # toggle output using environment variables\n   *  DEBUG=puppeteer-extra-plugin:<plugin_name> node foo.js\n   *  # to debug all the things:\n   *  DEBUG=puppeteer-extra,puppeteer-extra-plugin:* node foo.js\n   *  ```\n   *\n   * @example\n   * this.debug('hello world')\n   * // will output e.g. 'puppeteer-extra-plugin:anonymize-ua hello world'\n   */\n  get debug(): Debugger {\n    return debug(`puppeteer-extra-plugin:${this.name}`)\n  }\n\n  /**\n   * Before a new browser instance is created/launched.\n   *\n   * Can be used to modify the puppeteer launch options by modifying or returning them.\n   *\n   * Plugins using this method will be called in sequence to each\n   * be able to update the launch options.\n   *\n   * @example\n   * async beforeLaunch (options) {\n   *   if (this.opts.flashPluginPath) {\n   *     options.args.push(`--ppapi-flash-path=${this.opts.flashPluginPath}`)\n   *   }\n   * }\n   *\n   * @param options - Puppeteer launch options\n   */\n  async beforeLaunch(options: any) {\n    // noop\n  }\n\n  /**\n   * After the browser has launched.\n   *\n   * Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\n   * It's possible that `pupeeteer.launch` will be  called multiple times and more than one browser created.\n   * In order to make the plugins as stateless as possible don't store a reference to the browser instance\n   * in the plugin but rather consider alternatives.\n   *\n   * E.g. when using `onPageCreated` you can get a browser reference by using `page.browser()`.\n   *\n   * Alternatively you could expose a class method that takes a browser instance as a parameter to work with:\n   *\n   * ```es6\n   * const fancyPlugin = require('puppeteer-extra-plugin-fancy')()\n   * puppeteer.use(fancyPlugin)\n   * const browser = await puppeteer.launch()\n   * await fancyPlugin.killBrowser(browser)\n   * ```\n   *\n   * @param  browser - The `puppeteer` browser instance.\n   * @param  opts.options - Puppeteer launch options used.\n   *\n   * @example\n   * async afterLaunch (browser, opts) {\n   *   this.debug('browser has been launched', opts.options)\n   * }\n   */\n  async afterLaunch(\n    browser: Puppeteer.Browser,\n    opts = { options: {} as Puppeteer.LaunchOptions }\n  ) {\n    // noop\n  }\n\n  /**\n   * Before connecting to an existing browser instance.\n   *\n   * Can be used to modify the puppeteer connect options by modifying or returning them.\n   *\n   * Plugins using this method will be called in sequence to each\n   * be able to update the launch options.\n   *\n   * @param  {Object} options - Puppeteer connect options\n   * @return {Object=}\n   */\n  async beforeConnect(options: Puppeteer.ConnectOptions) {\n    // noop\n  }\n\n  /**\n   * After connecting to an existing browser instance.\n   *\n   * > Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\n   *\n   * @param browser - The `puppeteer` browser instance.\n   * @param  {Object} opts\n   * @param  {Object} opts.options - Puppeteer connect options used.\n   *\n   */\n  async afterConnect(browser: Puppeteer.Browser, opts = {}) {\n    // noop\n  }\n\n  /**\n   * Called when a browser instance is available.\n   *\n   * This applies to both `puppeteer.launch()` and `puppeteer.connect()`.\n   *\n   * Convenience method created for plugins that need access to a browser instance\n   * and don't mind if it has been created through `launch` or `connect`.\n   *\n   * > Note: Don't assume that there will only be a single browser instance during the lifecycle of a plugin.\n   *\n   * @param browser - The `puppeteer` browser instance.\n   */\n  public async onBrowser(browser: Puppeteer.Browser, opts: any): Promise<void> {\n    // noop\n  }\n\n  /**\n   * Called when a target is created, for example when a new page is opened by window.open or browser.newPage.\n   *\n   * > Note: This includes target creations in incognito browser contexts.\n   *\n   * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n   *\n   * @param  {Puppeteer.Target} target\n   */\n  async onTargetCreated(target: Puppeteer.Target) {\n    // noop\n  }\n\n  /**\n   * Same as `onTargetCreated` but prefiltered to only contain Pages, for convenience.\n   *\n   * > Note: This includes page creations in incognito browser contexts.\n   *\n   * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n   *\n   * @param  {Puppeteer.Target} target\n   *\n   * @example\n   * async onPageCreated (page) {\n   *   let ua = await page.browser().userAgent()\n   *   if (this.opts.stripHeadless) {\n   *     ua = ua.replace('HeadlessChrome/', 'Chrome/')\n   *   }\n   *   this.debug('new ua', ua)\n   *   await page.setUserAgent(ua)\n   * }\n   */\n  async onPageCreated(page: Puppeteer.Page) {\n    // noop\n  }\n\n  /**\n   * Called when the url of a target changes.\n   *\n   * > Note: This includes target changes in incognito browser contexts.\n   *\n   * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n   *\n   * @param  {Puppeteer.Target} target\n   */\n  async onTargetChanged(target: Puppeteer.Target) {\n    // noop\n  }\n\n  /**\n   * Called when a target is destroyed, for example when a page is closed.\n   *\n   * > Note: This includes target destructions in incognito browser contexts.\n   *\n   * > Note: This includes browser instances created through `.launch()` as well as `.connect()`.\n   *\n   * @param  {Puppeteer.Target} target\n   */\n  async onTargetDestroyed(target: Puppeteer.Target) {\n    // noop\n  }\n\n  /**\n   * Called when Puppeteer gets disconnected from the Chromium instance.\n   *\n   * This might happen because of one of the following:\n   * - Chromium is closed or crashed\n   * - The `browser.disconnect` method was called\n   */\n  async onDisconnected() {\n    // noop\n  }\n\n  /**\n   * **Deprecated:** Since puppeteer v1.6.0 `onDisconnected` has been improved\n   * and should be used instead of `onClose`.\n   *\n   * In puppeteer < v1.6.0 `onDisconnected` was not catching all exit scenarios.\n   * In order for plugins to clean up properly (e.g. deleting temporary files)\n   * the `onClose` method had been introduced.\n   *\n   * > Note: Might be called multiple times on exit.\n   *\n   * > Note: This only includes browser instances created through `.launch()`.\n   */\n  async onClose() {\n    // noop\n  }\n\n  /**\n   * After the plugin has been registered in `puppeteer-extra`.\n   *\n   * Normally right after `puppeteer.use(plugin)` is called\n   */\n  async onPluginRegistered() {\n    // noop\n  }\n\n  /**\n   * Helper method to retrieve `data` objects from other plugins.\n   *\n   * A plugin needs to state the `dataFromPlugins` requirement\n   * in order to use this method. Will be mapped to `puppeteer.getPluginData`.\n   *\n   * @param name - Filter data by `name` property\n   *\n   * @see [data]\n   * @see [requirements]\n   */\n  getDataFromPlugins(name?: string): PluginData[] {\n    return []\n  }\n\n  /**\n   * Will match plugin dependencies against all currently registered plugins.\n   * Is being called by `puppeteer-extra` and used to require missing dependencies.\n   *\n   * @param  {Array<Object>} plugins\n   * @return {Set} - list of missing plugin names\n   *\n   * @private\n   */\n  _getMissingDependencies(plugins: any) {\n    const pluginNames = new Set(plugins.map((p: any) => p.name))\n    const missing = new Set(\n      Array.from(this.dependencies.values()).filter(x => !pluginNames.has(x))\n    )\n    return missing\n  }\n\n  /**\n   * Conditionally bind browser/process events to class members.\n   * The idea is to reduce event binding boilerplate in plugins.\n   *\n   * For efficiency we make sure the plugin is using the respective event\n   * by checking the child class members before registering the listener.\n   *\n   * @param  {<Puppeteer.Browser>} browser\n   * @param  {Object} opts - Options\n   * @param  {string} opts.context - Puppeteer context (launch/connect)\n   * @param  {Object} [opts.options] - Puppeteer launch or connect options\n   * @param  {Array<string>} [opts.defaultArgs] - The default flags that Chromium will be launched with\n   *\n   * @private\n   */\n  async _bindBrowserEvents(browser: Puppeteer.Browser, opts: any = {}) {\n    if (\n      this._hasChildClassMember('onTargetCreated') ||\n      this._hasChildClassMember('onPageCreated')\n    ) {\n      browser.on('targetcreated', this._onTargetCreated.bind(this))\n    }\n    if (this._hasChildClassMember('onTargetChanged') && this.onTargetChanged) {\n      browser.on('targetchanged', this.onTargetChanged.bind(this))\n    }\n    if (\n      this._hasChildClassMember('onTargetDestroyed') &&\n      this.onTargetDestroyed\n    ) {\n      browser.on('targetdestroyed', this.onTargetDestroyed.bind(this))\n    }\n    if (this._hasChildClassMember('onDisconnected') && this.onDisconnected) {\n      browser.on('disconnected', this.onDisconnected.bind(this))\n    }\n    if (opts.context === 'launch' && this._hasChildClassMember('onClose')) {\n      // The disconnect event has been improved since puppeteer v1.6.0\n      // onClose is being kept mostly for legacy reasons\n      if (this.onClose) {\n        process.on('exit', this.onClose.bind(this))\n        browser.on('disconnected', this.onClose.bind(this))\n\n        if (opts.options.handleSIGINT !== false) {\n          process.on('SIGINT', this.onClose.bind(this))\n        }\n        if (opts.options.handleSIGTERM !== false) {\n          process.on('SIGTERM', this.onClose.bind(this))\n        }\n        if (opts.options.handleSIGHUP !== false) {\n          process.on('SIGHUP', this.onClose.bind(this))\n        }\n      }\n    }\n    if (opts.context === 'launch' && this.afterLaunch) {\n      await this.afterLaunch(browser, opts)\n    }\n    if (opts.context === 'connect' && this.afterConnect) {\n      await this.afterConnect(browser, opts)\n    }\n    if (this.onBrowser) await this.onBrowser(browser, opts)\n  }\n\n  /**\n   * @private\n   */\n  async _onTargetCreated(target: Puppeteer.Target) {\n    if (this.onTargetCreated) await this.onTargetCreated(target)\n    // Pre filter pages for plugin developers convenience\n    if (target.type() === 'page') {\n      try {\n        const page = await target.page()\n        if (!page) {\n          return\n        }\n        const validPage = 'isClosed' in page && !page.isClosed()\n        if (this.onPageCreated && validPage) {\n          await this.onPageCreated(page)\n        }\n      } catch (err) {\n        console.error(err)\n      }\n    }\n  }\n\n  /**\n   * @private\n   */\n  _register(prototype: any) {\n    this._registerChildClassMembers(prototype)\n    if (this.onPluginRegistered) this.onPluginRegistered()\n  }\n\n  /**\n   * @private\n   */\n  _registerChildClassMembers(prototype: any) {\n    this._childClassMembers = Object.getOwnPropertyNames(prototype)\n  }\n\n  /**\n   * @private\n   */\n  _hasChildClassMember(name: string) {\n    return !!this._childClassMembers.includes(name)\n  }\n\n  /**\n   * @private\n   */\n  get _isPuppeteerExtraPlugin() {\n    return true\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/src/puppeteer.ts",
    "content": "// A wildcard import would result in a `require(\"puppeteer\")` statement\n// at the top of the transpiled js file, not what we want. :-/\n// \"import type\" is a solution here but requires TS >= v3.8 which we don't want to require yet as a minimum.\n\nexport { Browser } from 'puppeteer'\nexport { Page } from 'puppeteer'\nexport { Target } from 'puppeteer'\nexport { ConnectOptions } from 'puppeteer'\nexport { LaunchOptions } from 'puppeteer'\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"dom\"],\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"./src/**/*.tsx\",\n    \"./src/**/*.ts\",\n    \"./src/**/*.test.ts\",\n    \"./test/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin/tslint.json",
    "content": "{\n  \"extends\": [\"tslint-config-standard\", \"tslint-config-prettier\"],\n  \"rules\": {}\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/ava.config-ts.js",
    "content": "export default {\n  compileEnhancements: false,\n  environmentVariables: {\n    TS_NODE_COMPILER_OPTIONS: '{\"module\":\"commonjs\"}'\n  },\n  files: ['src/**.test.ts'],\n  extensions: ['ts'],\n  require: ['ts-node/register']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/ava.config.js",
    "content": "export default {\n  files: ['dist/*.test.js']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/build_version_check.js",
    "content": "const pkg = require('./package.json')\n\nconst isIncompatiblePuppeteerVersion = () => {\n  const version = pkg.devDependencies.puppeteer\n  const majorVersion = parseInt(version.split('.')[0])\n  if (majorVersion >= 6) {\n    return true\n  } else {\n    return false\n  }\n}\n\nconst incompatible = isIncompatiblePuppeteerVersion()\nif (incompatible) {\n  console.warn(\n    'ERR: The adblocker plugin requires pptr >= 6',\n    process.env.PUPPETEER_VERSION\n  )\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-adblocker\",\n  \"version\": \"2.13.6\",\n  \"description\": \"A puppeteer-extra plugin to block ads and trackers.\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-adblocker\",\n  \"author\": \"remusao\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"tscheck\": \"tsc --pretty --noEmit\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup; node build_version_check.js\",\n    \"build:tsc\": \"tsc --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"node -e 0\",\n    \"test\": \"ava -v --config ava.config-ts.js\",\n    \"pretest-ci\": \"run-s build\",\n    \"test-ci-back\": \"ava --concurrency 1 --serial --fail-fast -v\",\n    \"test-ci\": \"exit 0\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"prettier\": {\n    \"printWidth\": 80,\n    \"semi\": false,\n    \"singleQuote\": true\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"ads\",\n    \"adblocker\",\n    \"adblocking\"\n  ],\n  \"devDependencies\": {\n    \"@types/debug\": \"^4.1.5\",\n    \"@types/node-fetch\": \"^2.5.4\",\n    \"@types/puppeteer\": \"*\",\n    \"ava\": \"^2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^10.2.0\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup\": \"^1.27.5\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"ts-node\": \"^8.5.4\",\n    \"tslint\": \"^5.20.1\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-config-standard\": \"^9.0.0\",\n    \"typescript\": \"4.7.4\"\n  },\n  \"dependencies\": {\n    \"@cliqz/adblocker-puppeteer\": \"1.23.8\",\n    \"debug\": \"^4.1.1\",\n    \"node-fetch\": \"^2.6.0\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"puppeteer\": \"*\",\n    \"puppeteer-core\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer\": {\n      \"optional\": true\n    },\n    \"puppeteer-core\": {\n      \"optional\": true\n    },\n    \"puppeteer-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"72fe830c158f1e971c8499fdd5232338dd53c220\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/readme.md",
    "content": "# puppeteer-extra-plugin-adblocker [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/puppeteer-extra-plugin-adblocker.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin-adblocker)\n\n> A [puppeteer-extra](https://github.com/berstend/puppeteer-extra) plugin to block ads and trackers.\n\n## Features\n\n- Extremely efficient adblocker (both in memory usage and raw speed)\n- Pure JavaScript implementation\n- Effectively blocks all types of ads and tracking\n- Small and minimal (only 64KB minified and gzipped)\n\n> Thanks to [@remusao](https://github.com/remusao) for contributing this sweet plugin and [adblocker engine](https://github.com/cliqz-oss/adblocker)! 👏\n\n## Installation\n\n```bash\nyarn add puppeteer-extra-plugin-adblocker\n# - or -\nnpm install puppeteer-extra-plugin-adblocker\n```\n\nIf this is your first [puppeteer-extra](https://github.com/berstend/puppeteer-extra) plugin here's everything you need:\n\n```bash\nyarn add puppeteer puppeteer-extra puppeteer-extra-plugin-adblocker\n# - or -\nnpm install puppeteer puppeteer-extra puppeteer-extra-plugin-adblocker\n```\n\n## Usage\n\nThe plugin enables adblocking in puppeteer, optionally blocking trackers.\n\n```javascript\n// puppeteer-extra is a drop-in replacement for puppeteer,\n// it augments the installed puppeteer with plugin functionality\nconst puppeteer = require('puppeteer-extra')\n\n// Add adblocker plugin, which will transparently block ads in all pages you\n// create using puppeteer.\nconst { DEFAULT_INTERCEPT_RESOLUTION_PRIORITY } = require('puppeteer')\nconst AdblockerPlugin = require('puppeteer-extra-plugin-adblocker')\npuppeteer.use(\n  AdblockerPlugin({\n    // Optionally enable Cooperative Mode for several request interceptors\n    interceptResolutionPriority: DEFAULT_INTERCEPT_RESOLUTION_PRIORITY\n  })\n)\n\n// puppeteer usage as normal\npuppeteer.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n  // Visit a page, ads are blocked automatically!\n  await page.goto('https://www.google.com/search?q=rent%20a%20car')\n\n  await page.waitForTimeout(5 * 1000)\n  await page.screenshot({ path: 'response.png', fullPage: true })\n\n  console.log(`All done, check the screenshots. ✨`)\n  await browser.close()\n})\n```\n\n<details>\n <summary><strong>TypeScript usage</strong></summary><br/>\n\n```ts\nimport puppeteer from 'puppeteer-extra'\nimport Adblocker from 'puppeteer-extra-plugin-adblocker'\n\npuppeteer.use(Adblocker({ blockTrackers: true }))\n\npuppeteer\n  .launch({ headless: false, defaultViewport: null })\n  .then(async browser => {\n    const page = await browser.newPage()\n    await page.goto('https://www.vanityfair.com')\n    await page.waitForTimeout(60 * 1000)\n    await browser.close()\n  })\n```\n\n</details>\n\n## Options\n\nUsage:\n\n```js\nconst AdblockerPlugin = require('puppeteer-extra-plugin-adblocker')\nconst adblocker = AdblockerPlugin({\n  blockTrackers: true // default: false\n})\npuppeteer.use(adblocker)\n```\n\nAvailable options:\n\n```ts\ninterface PluginOptions {\n  /** Whether or not to block trackers (in addition to ads). Default: false */\n  blockTrackers: boolean\n  /** Whether or not to block trackers and other annoyances, including cookie\n      notices. Default: false */\n  blockTrackersAndAnnoyances: boolean\n  /** Persist adblocker engine cache to disk for speedup. Default: true */\n  useCache: boolean\n  /** Optional custom directory for adblocker cache files. Default: undefined */\n  cacheDir?: string\n}\n```\n\n## Motivation\n\nAds and trackers are on most pages and often cost a lot of bandwidth and time\nto load pages. Blocking ads and trackers allows pages to load much faster,\nbecause less requests are made and less JavaScript need to run. Also, in cases\nwhere you want to take screenshots of pages, it's nice to have an option to\nremove the ads before.\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/rollup.config.ts",
    "content": "import resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nconst defaultExportOutro = `\n  module.exports = exports.default || {}\n  Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value })\n`\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      exports: 'named',\n      outro: defaultExportOutro,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      exports: 'named',\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {}),\n    'fs',\n    'os',\n    'path'\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    // commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve({\n      preferBuiltins: true\n    }),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/src/ambient.d.ts",
    "content": "export {}\n\n// https://github.com/sindresorhus/type-fest/issues/19\ndeclare global {\n  interface SymbolConstructor {\n    readonly observable: symbol\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/src/index.test.ts",
    "content": "import test from 'ava'\n\nimport AdblockerPlugin from './index'\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest('will block ads', async t => {\n  const puppeteer = require('puppeteer-extra')\n  const adblockerPlugin = AdblockerPlugin({\n    blockTrackers: true\n  })\n  puppeteer.use(adblockerPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n\n  const blocker = await adblockerPlugin.getBlocker()\n\n  const page = await browser.newPage()\n\n  let blockedRequests = 0\n  blocker.on('request-blocked', () => {\n    blockedRequests += 1\n  })\n\n  let hiddenAds = 0\n  blocker.on('style-injected', () => {\n    hiddenAds += 1\n  })\n\n  const url = 'https://www.google.com/search?q=rent%20a%20car'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  t.not(hiddenAds, 0)\n  t.not(blockedRequests, 0)\n\n  await browser.close()\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/src/index.ts",
    "content": "import { promises as fs } from 'fs'\nimport os from 'os'\nimport path from 'path'\n\nimport { PuppeteerBlocker } from '@cliqz/adblocker-puppeteer'\nimport fetch from 'node-fetch'\nimport { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'\n\nconst pkg = require('../package.json')\nconst engineCacheFilename = `${pkg.name}-${pkg.version}-engine.bin`\n\n/** Available plugin options */\nexport interface PluginOptions {\n  /** Whether or not to block trackers (in addition to ads). Default: false */\n  blockTrackers: boolean\n  /** Whether or not to block trackers and other annoyances, including cookie\n      notices. Default: false */\n  blockTrackersAndAnnoyances: boolean\n  /** Persist adblocker engine cache to disk for speedup. Default: true */\n  useCache: boolean\n  /** Optional custom directory for adblocker cache files. Default: undefined */\n  cacheDir?: string\n  /** Optional custom priority for interception resolution. Default: undefined */\n  interceptResolutionPriority?: number\n}\n\n/**\n * A puppeteer-extra plugin to automatically block ads and trackers.\n */\nexport class PuppeteerExtraPluginAdblocker extends PuppeteerExtraPlugin {\n  private blocker: PuppeteerBlocker | undefined\n\n  constructor(opts: Partial<PluginOptions>) {\n    super(opts)\n    this.debug('Initialized', this.opts)\n  }\n\n  get name() {\n    return 'adblocker'\n  }\n\n  get defaults(): PluginOptions {\n    return {\n      blockTrackers: false,\n      blockTrackersAndAnnoyances: false,\n      useCache: true,\n      cacheDir: undefined,\n      interceptResolutionPriority: undefined\n    }\n  }\n\n  get engineCacheFile() {\n    const cacheDir = this.opts.cacheDir || os.tmpdir()\n    return path.join(cacheDir, engineCacheFilename)\n  }\n\n  /**\n   * Cache an instance of `PuppeteerBlocker` to disk if 'cacheDir' option was\n   * specified for the plugin. It can then be used the next time this plugin is\n   * used to load the adblocker faster.\n   */\n  private async persistToCache(blocker: PuppeteerBlocker): Promise<void> {\n    if (!this.opts.useCache) {\n      return\n    }\n    this.debug('persist to cache', this.engineCacheFile)\n    await fs.writeFile(this.engineCacheFile, blocker.serialize())\n  }\n\n  /**\n   * Initialize instance of `PuppeteerBlocker` from cache if possible.\n   * Otherwise, it throws and we will try to initialize it from remote instead.\n   */\n  private async loadFromCache(): Promise<PuppeteerBlocker> {\n    if (!this.opts.useCache) {\n      throw new Error('caching disabled')\n    }\n    this.debug('load from cache', this.engineCacheFile)\n    return PuppeteerBlocker.deserialize(\n      new Uint8Array(await fs.readFile(this.engineCacheFile))\n    )\n  }\n\n  /**\n   * Initialize instance of `PuppeteerBlocker` from remote (either by fetching\n   * a serialized version of the engine when available, or by downloading raw\n   * lists for filters such as EasyList then parsing them to initialize\n   * blocker).\n   */\n  private async loadFromRemote(): Promise<PuppeteerBlocker> {\n    this.debug('load from remote', {\n      blockTrackers: this.opts.blockTrackers,\n      blockTrackersAndAnnoyances: this.opts.blockTrackersAndAnnoyances\n    })\n    if (this.opts.blockTrackersAndAnnoyances === true) {\n      return PuppeteerBlocker.fromPrebuiltFull(fetch)\n    } else if (this.opts.blockTrackers === true) {\n      return PuppeteerBlocker.fromPrebuiltAdsAndTracking(fetch)\n    } else {\n      return PuppeteerBlocker.fromPrebuiltAdsOnly(fetch)\n    }\n  }\n\n  /**\n   * Return instance of `PuppeteerBlocker`. It will take care of initializing\n   * it if necessary (first time it is called), or return the existing instance\n   * if it already exists.\n   */\n  async getBlocker(): Promise<PuppeteerBlocker> {\n    this.debug('getBlocker', { hasBlocker: !!this.blocker })\n    if (this.blocker === undefined) {\n      try {\n        this.blocker = await this.loadFromCache()\n        this.setRequestInterceptionPriority()\n      } catch (ex) {\n        this.blocker = await this.loadFromRemote()\n        this.setRequestInterceptionPriority()\n        await this.persistToCache(this.blocker)\n      }\n    }\n    return this.blocker\n  }\n\n  /**\n   * Sets the request interception priority on the `PuppeteerBlocker` instance.\n   */\n  private setRequestInterceptionPriority(): void {\n    this.blocker?.setRequestInterceptionPriority(this.opts.interceptResolutionPriority)\n  }\n\n  /**\n   * Hook into this blocking event to make sure the cache is initialized before navigation.\n   */\n  async beforeLaunch() {\n    this.debug('beforeLaunch')\n    await this.getBlocker()\n  }\n\n  /**\n   * Hook into this blocking event to make sure the cache is initialized before navigation.\n   */\n  async beforeConnect() {\n    this.debug('beforeConnect')\n    await this.getBlocker()\n  }\n\n  /**\n   * Enable adblocking in `page`.\n   */\n  async onPageCreated(page: any) {\n    this.debug('onPageCreated')\n    ;(await this.getBlocker()).enableBlockingInPage(page)\n  }\n}\n\nexport default (options: Partial<PluginOptions> = {}) => {\n  return new PuppeteerExtraPluginAdblocker(options)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"dom\"],\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"./src/**/*.tsx\",\n    \"./src/**/*.ts\",\n    \"./src/**/*.test.ts\",\n    \"./test/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-adblocker/tslint.json",
    "content": "{\n  \"extends\": [\"tslint-config-standard\", \"tslint-config-prettier\"],\n  \"rules\": {\n    \"ordered-imports\": true\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/index.d.ts",
    "content": "declare const PuppeteerExtraPlugin: typeof import(\"puppeteer-extra-plugin\").PuppeteerExtraPlugin;\ndeclare const Page: typeof import(\"puppeteer\").Page;\ntype CustomFn = ((ua: string) => string | null) | null;\ndeclare class Plugin extends PuppeteerExtraPlugin {\n\tget name(): string;\n\tget defaults(): {\n\t\tstripHeadless: boolean;\n\t\tmakeWindows: boolean;\n\t\tcustomFn: CustomFn;\n\t};\n  async onPageCreated(page: Page): void;\n\t}\nexport default function (options?: {\n\tstripHeadless?: true;\n\tmakeWindows?: true;\t\t\n  customFn?: CustomFn;\n}): Plugin;\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Anonymize the User-Agent on all pages.\n *\n * Supports dynamic replacing, so the Chrome version stays intact and recent.\n *\n * @param {Object} opts - Options\n * @param {boolean} [opts.stripHeadless=true] - Replace `HeadlessChrome` with `Chrome`.\n * @param {boolean} [opts.makeWindows=true] - Sets the platform to Windows 10, 64bit (most common).\n * @param {Function} [opts.customFn=null] - A custom UA replacer function.\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n * // or\n * puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')({\n *   customFn: (ua) => 'MyCoolAgent/' + ua.replace('Chrome', 'Beer')})\n * )\n * const browser = await puppeteer.launch()\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'anonymize-ua'\n  }\n\n  get defaults() {\n    return {\n      stripHeadless: true,\n      makeWindows: true,\n      customFn: null\n    }\n  }\n\n  async onPageCreated(page) {\n    let ua = await page.browser().userAgent()\n    if (this.opts.stripHeadless) {\n      ua = ua.replace('HeadlessChrome/', 'Chrome/')\n    }\n    if (this.opts.makeWindows) {\n      ua = ua.replace(/\\(([^)]+)\\)/, '(Windows NT 10.0; Win64; x64)')\n    }\n    if (this.opts.customFn) {\n      ua = this.opts.customFn(ua)\n    }\n    this.debug('new ua', ua)\n    await page.setUserAgent(ua)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/index.test.js",
    "content": "'use strict'\n\nconst PLUGIN_NAME = 'anonymize-ua'\n\nconst test = require('ava')\n\nconst Plugin = require('.')\n\ntest('is a function', async t => {\n  t.is(typeof Plugin, 'function')\n})\n\ntest('should have the basic class members', async t => {\n  const instance = new Plugin()\n\n  t.is(instance.name, PLUGIN_NAME)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have the public child class members', async t => {\n  const instance = new Plugin()\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('name'))\n  t.true(childClassMembers.includes('defaults'))\n  t.true(childClassMembers.includes('onPageCreated'))\n  t.true(childClassMembers.length === 4)\n})\n\ntest('should have opts with default values', async t => {\n  const instance = new Plugin()\n  const opts = instance.opts\n\n  t.is(opts.stripHeadless, true)\n  t.is(opts.makeWindows, true)\n  t.is(opts.customFn, null)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-anonymize-ua\",\n  \"version\": \"2.4.6\",\n  \"description\": \"Anonymize User-Agent in puppeteer.\",\n  \"main\": \"index.js\",\n  \"types\": \"index.d.ts\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test:js\": \"ava -v --serial --concurrency 1 --fail-fast\",\n    \"test\": \"run-p test:js lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"ua\",\n    \"user-agent\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\",\n    \"puppeteer-extra\": \"^3.3.6\",\n    \"puppeteer-extra-plugin-devtools\": \"^2.4.6\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/readme.md",
    "content": "# puppeteer-extra-plugin-anonymize-ua\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-anonymize-ua\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-anonymize-ua/index.js#L24-L51)\n\n**Extends: PuppeteerExtraPlugin**\n\nAnonymize the User-Agent on all pages.\n\nSupports dynamic replacing, so the Chrome version stays intact and recent.\n\nType: `function (opts)`\n\n-   `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n    -   `opts.stripHeadless` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Replace `HeadlessChrome` with `Chrome`. (optional, default `true`)\n    -   `opts.makeWindows` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Sets the platform to Windows 10, 64bit (most common). (optional, default `true`)\n    -   `opts.customFn` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** A custom UA replacer function. (optional, default `null`)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n// or\npuppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')({\n  customFn: (ua) => 'MyCoolAgent/' + ua.replace('Chrome', 'Beer')})\n)\nconst browser = await puppeteer.launch()\n```\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/test/headless.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  delete require.cache[require.resolve('puppeteer-extra')]\n  delete require.cache[require.resolve('puppeteer-extra-plugin-anonymize-ua')]\n})\n\ntest('will remove headless from the user-agent', async t => {\n  const puppeteer = require('puppeteer-extra')\n  const AnonymizeUA = require('puppeteer-extra-plugin-anonymize-ua')()\n  puppeteer.use(AnonymizeUA)\n\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  const page = await browser.newPage()\n  await page.goto('https://httpbin.org/headers', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  const content = await page.content()\n  t.true(content.includes('Windows NT 10.0'))\n  t.true(!content.includes('HeadlessChrome'))\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will remove headless from the user-agent in incognito page', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  // Requires puppeteer@next currrently\n  if (browser.createIncognitoBrowserContext) {\n    const context = await browser.createIncognitoBrowserContext()\n    const page = await context.newPage()\n    await page.goto('https://httpbin.org/headers', {\n      waitUntil: 'domcontentloaded'\n    })\n\n    const content = await page.content()\n    t.true(content.includes('Windows NT 10.0'))\n    t.true(!content.includes('HeadlessChrome'))\n  }\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will use a custom fn to modify the user-agent', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(\n    require('puppeteer-extra-plugin-anonymize-ua')({\n      customFn: ua => 'MyCoolAgent/' + ua.replace('Chrome', 'Beer')\n    })\n  )\n\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  const page = await browser.newPage()\n  await page.goto('https://httpbin.org/headers', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  const content = await page.content()\n  t.true(content.includes('Windows NT 10.0'))\n  t.true(!content.includes('HeadlessChrome'))\n  t.true(content.includes('MyCoolAgent/Mozilla'))\n  t.true(content.includes('Beer/'))\n\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/test/headless_off.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  delete require.cache[require.resolve('puppeteer-extra')]\n  delete require.cache[require.resolve('puppeteer-extra-plugin-anonymize-ua')]\n})\n\ntest('will not modify the user-agent when disabled', async t => {\n  const puppeteer = require('puppeteer-extra')\n  const AnonymizeUA = require('puppeteer-extra-plugin-anonymize-ua')({\n    stripHeadless: false,\n    makeWindows: false,\n    customFn: null\n  })\n  puppeteer.use(AnonymizeUA)\n\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n  const page = await browser.newPage()\n  await page.goto('https://httpbin.org/headers', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  const content = await page.content()\n  t.true(content.includes('HeadlessChrome'))\n  t.true(!content.includes('MyCoolAgent/Mozilla'))\n  t.true(!content.includes('Beer/'))\n\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/test/popup.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\nconst waitEvent = function(emitter, eventName) {\n  return new Promise(resolve => emitter.once(eventName, resolve))\n}\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  delete require.cache[require.resolve('puppeteer-extra')]\n  delete require.cache[require.resolve('puppeteer-extra-plugin-anonymize-ua')]\n})\n\ntest('known issue: will not remove headless from implicitly created popup pages', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  const pages = await Promise.all([...Array(10)].map(slot => browser.newPage()))\n  for (const page of pages) {\n    // Works\n    const ua = await page.evaluate(() => window.navigator.userAgent)\n    t.true(!ua.includes('HeadlessChrome'))\n\n    // Works\n    await page.goto('about:blank')\n    const ua2 = await page.evaluate(() => window.navigator.userAgent)\n    t.true(!ua2.includes('HeadlessChrome'))\n\n    // Does NOT work:\n    // https://github.com/GoogleChrome/puppeteer/issues/2669\n    page.evaluate(url => window.open(url), 'about:blank')\n    const popupTarget = await waitEvent(browser, 'targetcreated')\n    const popupPage = await popupTarget.page()\n    const ua3 = await popupPage.evaluate(() => window.navigator.userAgent)\n    // Test against the problem until it's fixed\n    t.true(ua3.includes('HeadlessChrome')) // should be: !ua3.includes('HeadlessChrome')\n\n    // Works: The bug only affects newly created popups, subsequent page navigations are fine.\n    await popupPage.goto('about:blank')\n    const ua4 = await page.evaluate(() => window.navigator.userAgent)\n    t.true(!ua4.includes('HeadlessChrome'))\n  }\n\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-anonymize-ua/test/stresstest.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  delete require.cache[require.resolve('puppeteer-extra')]\n  delete require.cache[require.resolve('puppeteer-extra-plugin-anonymize-ua')]\n})\n\ntest('will remove headless from the user-agent on multiple browsers', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  const browsers = await Promise.all(\n    [...Array(5)].map(slot => puppeteer.launch({ args: PUPPETEER_ARGS }))\n  )\n  for (const browser of browsers) {\n    const page = await browser.newPage()\n    const ua = await page.evaluate(() => window.navigator.userAgent)\n    t.true(ua.includes('Windows NT 10.0'))\n    t.true(!ua.includes('HeadlessChrome'))\n  }\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will remove headless from the user-agent on many pages', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  const pages = await Promise.all([...Array(30)].map(slot => browser.newPage()))\n  for (const page of pages) {\n    const ua = await page.evaluate(() => window.navigator.userAgent)\n    t.true(ua.includes('Windows NT 10.0'))\n    t.true(!ua.includes('HeadlessChrome'))\n  }\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will remove headless from the user-agent on many incognito pages', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  // Requires puppeteer@next currrently\n  if (browser.createIncognitoBrowserContext) {\n    const contexts = await Promise.all(\n      [...Array(30)].map(slot => browser.createIncognitoBrowserContext())\n    )\n    for (const context of contexts) {\n      const page = await context.newPage()\n      const ua = await page.evaluate(() => window.navigator.userAgent)\n      t.true(ua.includes('Windows NT 10.0'))\n      t.true(!ua.includes('HeadlessChrome'))\n    }\n  }\n\n  await browser.close()\n  t.true(true)\n})\n\ntest('will remove headless from the user-agent on many pages in parallel', async t => {\n  const puppeteer = require('puppeteer-extra')\n  puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')())\n  const browser = await puppeteer.launch({ args: PUPPETEER_ARGS })\n\n  const testCase = async () => {\n    const page = await browser.newPage()\n    const ua = await page.evaluate(() => window.navigator.userAgent)\n    t.true(ua.includes('Windows NT 10.0'))\n    t.true(!ua.includes('HeadlessChrome'))\n  }\n  await Promise.all([...Array(30)].map(slot => testCase()))\n\n  await browser.close()\n  t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/example.js",
    "content": "'use strict'\n\n//\n// With debug logs:\n// DEBUG=puppeteer-extra,puppeteer-extra-plugin,puppeteer-extra-plugin:* node example.js\n//\n\n// const puppeteer = require('puppeteer-extra')\n// puppeteer.use(require('puppeteer-extra-plugin-block-resources')({\n//   blockedTypes: new Set(['image', 'stylesheet'])\n// }))\n// ;(async () => {\n//   const browser = await puppeteer.launch({ headless: false })\n//   const page = await browser.newPage()\n//   await page.goto('http://www.msn.com/', {waitUntil: 'domcontentloaded'})\n//   console.log('all done')\n// })()\n\nconst { DEFAULT_INTERCEPT_RESOLUTION_PRIORITY } = require('puppeteer')\nconst puppeteer = require('puppeteer-extra')\nconst blockResourcesPlugin = require('puppeteer-extra-plugin-block-resources')({\n  // Optionally enable Cooperative Mode for several request interceptors\n  interceptResolutionPriority: DEFAULT_INTERCEPT_RESOLUTION_PRIORITY\n})\n\npuppeteer.use(blockResourcesPlugin)\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n\n  blockResourcesPlugin.blockedTypes.add('image')\n  await page.goto('http://www.msn.com/', { waitUntil: 'domcontentloaded' })\n\n  blockResourcesPlugin.blockedTypes.add('stylesheet')\n  blockResourcesPlugin.blockedTypes.add('other') // e.g. favicon\n  await page.goto('http://news.ycombinator.com', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  blockResourcesPlugin.blockedTypes.delete('stylesheet')\n  blockResourcesPlugin.blockedTypes.delete('other')\n  blockResourcesPlugin.blockedTypes.add('media')\n  blockResourcesPlugin.blockedTypes.add('script')\n  await page.goto('http://www.youtube.com', { waitUntil: 'domcontentloaded' })\n\n  console.log('all done')\n})()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/index.d.ts",
    "content": "import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'\nimport { ResourceType } from 'puppeteer'\n\ndeclare interface PluginOptions {\n  availableTypes?: Set<ResourceType>\n  blockedTypes?: Set<ResourceType>\n  interceptResolutionPriority?: number\n}\n\ndeclare class Plugin extends PuppeteerExtraPlugin {\n  constructor(opts: Partial<PluginOptions>)\n  get name(): string\n  get defaults(): PluginOptions\n  get engineCacheFile(): string\n  get availableTypes(): Set<ResourceType>\n  get blockedTypes(): Set<ResourceType>\n  get interceptResolutionPriority(): number\n}\n\ndeclare const _default: (options?: Partial<PluginOptions>) => Plugin\n\nexport default _default\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Block resources (images, media, css, etc.) in puppeteer.\n *\n * Supports all resource types, blocking can be toggled dynamically.\n *\n * @param {Object} opts - Options\n * @param {Set<string>} [opts.blockedTypes] - Specify which resourceTypes to block (by default none)\n *\n * @example\n * const { DEFAULT_INTERCEPT_RESOLUTION_PRIORITY } = require('puppeteer')\n * puppeteer.use(require('puppeteer-extra-plugin-block-resources')({\n *   blockedTypes: new Set(['image', 'stylesheet']),\n *   // Optionally enable Cooperative Mode for several request interceptors\n *   interceptResolutionPriority: DEFAULT_INTERCEPT_RESOLUTION_PRIORITY\n * }))\n *\n * //\n * // and/or dynamically:\n * //\n *\n * const blockResourcesPlugin = require('puppeteer-extra-plugin-block-resources')()\n * puppeteer.use(blockResourcesPlugin)\n *\n * const browser = await puppeteer.launch({ headless: false })\n * const page = await browser.newPage()\n *\n * blockResourcesPlugin.blockedTypes.add('image')\n * await page.goto('http://www.msn.com/', {waitUntil: 'domcontentloaded'})\n *\n * blockResourcesPlugin.blockedTypes.add('stylesheet')\n * blockResourcesPlugin.blockedTypes.add('other') // e.g. favicon\n * await page.goto('http://news.ycombinator.com', {waitUntil: 'domcontentloaded'})\n *\n * blockResourcesPlugin.blockedTypes.delete('stylesheet')\n * blockResourcesPlugin.blockedTypes.delete('other')\n * blockResourcesPlugin.blockedTypes.add('media')\n * blockResourcesPlugin.blockedTypes.add('script')\n * await page.goto('http://www.youtube.com', {waitUntil: 'domcontentloaded'})\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'block-resources'\n  }\n\n  get defaults() {\n    return {\n      availableTypes: new Set([\n        'document',\n        'stylesheet',\n        'image',\n        'media',\n        'font',\n        'script',\n        'texttrack',\n        'xhr',\n        'fetch',\n        'eventsource',\n        'websocket',\n        'manifest',\n        'other'\n      ]),\n      // Block nothing by default\n      blockedTypes: new Set([]),\n      interceptResolutionPriority: undefined\n    }\n  }\n\n  /**\n   * Get all available resource types.\n   *\n   * Resource type will be one of the following: `document`, `stylesheet`, `image`, `media`, `font`, `script`, `texttrack`, `xhr`, `fetch`, `eventsource`, `websocket`, `manifest`, `other`.\n   *\n   * @type {Set<string>} - A Set of all available resource types.\n   */\n  get availableTypes() {\n    return this.defaults.availableTypes\n  }\n\n  /**\n   * Get all blocked resource types.\n   *\n   * Blocked resource types can be configured either through `opts` or by modifying this property.\n   *\n   * @type {Set<string>} - A Set of all blocked resource types.\n   */\n  get blockedTypes() {\n    return this.opts.blockedTypes\n  }\n\n  /**\n   * Get the request interception resolution priority.\n   *\n   * Priority for Cooperative Intercept Mode can be configured either through `opts` or by modifying this property.\n   *\n   * @type {number} - A number for the request interception resolution priority.\n   */\n  get interceptResolutionPriority() {\n    return this.opts.interceptResolutionPriority\n  }\n\n  /**\n   * @private\n   */\n  onRequest(request) {\n    const type = request.resourceType()\n    const shouldBlock = this.blockedTypes.has(type)\n\n    // Requests are immediately handled if not using Cooperative Intercept Mode\n    const alreadyHandled = request.isInterceptResolutionHandled\n      ? request.isInterceptResolutionHandled()\n      : true\n\n    this.debug('onRequest', {\n      type,\n      shouldBlock,\n      alreadyHandled\n    })\n\n    if (alreadyHandled) return\n\n    if (shouldBlock) {\n      const abortArgs = request.abortErrorReason\n        ? ['blockedbyclient', this.interceptResolutionPriority]\n        : []\n\n      return request.abort(...abortArgs)\n    }\n\n    const continueArgs = request.continueRequestOverrides\n      ? [request.continueRequestOverrides(), this.interceptResolutionPriority]\n      : []\n\n    return request.continue(...continueArgs)\n  }\n\n  /**\n   * @private\n   */\n  async onPageCreated(page) {\n    this.debug('onPageCreated', { blockedTypes: this.blockedTypes })\n    await page.setRequestInterception(true)\n    page.on('request', this.onRequest.bind(this))\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/index.test.js",
    "content": "'use strict'\n\nconst PLUGIN_NAME = 'block-resources'\n\nconst test = require('ava')\n\nconst Plugin = require('.')\n\ntest('is a function', async t => {\n  t.is(typeof Plugin, 'function')\n})\n\ntest('should have the basic class members', async t => {\n  const instance = new Plugin()\n  t.is(instance.name, PLUGIN_NAME)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have the public child class members', async t => {\n  const instance = new Plugin()\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('name'))\n  t.true(childClassMembers.includes('defaults'))\n  t.true(childClassMembers.includes('availableTypes'))\n  t.true(childClassMembers.includes('blockedTypes'))\n  t.true(childClassMembers.includes('interceptResolutionPriority'))\n  t.true(childClassMembers.includes('onRequest'))\n  t.true(childClassMembers.includes('onPageCreated'))\n  t.true(childClassMembers.length === 8)\n})\n\ntest('should have opts with default values', async t => {\n  const instance = new Plugin()\n  t.deepEqual(instance.opts.blockedTypes, new Set([]))\n  t.is(instance.opts.availableTypes.size, 13)\n  t.is(instance.opts.interceptResolutionPriority, undefined)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-block-resources\",\n  \"version\": \"2.4.3\",\n  \"description\": \"Block resources (images, media, etc.) in puppeteer.\",\n  \"main\": \"index.js\",\n  \"types\": \"index.d.ts\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test:js\": \"ava -v\",\n    \"test\": \"run-p test:js lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"block-resources\",\n    \"datasaver\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-block-resources/readme.md",
    "content": "# puppeteer-extra-plugin-block-resources\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-block-resources\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n    -   [availableTypes](#availabletypes)\n    -   [blockedTypes](#blockedtypes)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-block-resources/index.js#L41-L104)\n\n**Extends: PuppeteerExtraPlugin**\n\nBlock resources (images, media, css, etc.) in puppeteer.\n\nSupports all resource types, blocking can be toggled dynamically.\n\nType: `function (opts)`\n\n-   `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n    -   `opts.blockedTypes` **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** Specify which resourceTypes to block (by default none)\n\nExample:\n\n```javascript\nconst { DEFAULT_INTERCEPT_RESOLUTION_PRIORITY } = require('puppeteer')\npuppeteer.use(require('puppeteer-extra-plugin-block-resources')({\n  blockedTypes: new Set(['image', 'stylesheet']),\n  // Optionally enable Cooperative Mode for several request interceptors\n  interceptResolutionPriority: DEFAULT_INTERCEPT_RESOLUTION_PRIORITY\n}))\n\n//\n// and/or dynamically:\n//\n\nconst blockResourcesPlugin = require('puppeteer-extra-plugin-block-resources')()\npuppeteer.use(blockResourcesPlugin)\n\nconst browser = await puppeteer.launch({ headless: false })\nconst page = await browser.newPage()\n\nblockResourcesPlugin.blockedTypes.add('image')\nawait page.goto('http://www.msn.com/', {waitUntil: 'domcontentloaded'})\n\nblockResourcesPlugin.blockedTypes.add('stylesheet')\nblockResourcesPlugin.blockedTypes.add('other') // e.g. favicon\nawait page.goto('http://news.ycombinator.com', {waitUntil: 'domcontentloaded'})\n\nblockResourcesPlugin.blockedTypes.delete('stylesheet')\nblockResourcesPlugin.blockedTypes.delete('other')\nblockResourcesPlugin.blockedTypes.add('media')\nblockResourcesPlugin.blockedTypes.add('script')\nawait page.goto('http://www.youtube.com', {waitUntil: 'domcontentloaded'})\n```\n\n* * *\n\n#### [availableTypes](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-block-resources/index.js#L75-L75)\n\nGet all available resource types.\n\nResource type will be one of the following: `document`, `stylesheet`, `image`, `media`, `font`, `script`, `texttrack`, `xhr`, `fetch`, `eventsource`, `websocket`, `manifest`, `other`.\n\nType: [Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>\n\n* * *\n\n#### [blockedTypes](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-block-resources/index.js#L84-L84)\n\nGet all blocked resource types.\n\nBlocked resource types can be configured either through `opts` or by modifying this property.\n\nType: [Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-click-and-wait/example.js",
    "content": "'use strict'\n\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-click-and-wait')())\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  await page.goto('https://example.com/', { waitUntil: 'domcontentloaded' })\n  console.log('clicking on first link')\n  await page.clickAndWaitForNavigation('a')\n  console.log('all done')\n})()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-click-and-wait/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Convenience function to wait for navigation to complete after clicking on an element.\n *\n * Adds a new `page.clickAndWaitForNavigation(selector, clickOptions, waitOptions)` method.\n *\n * See this issue for more context: https://github.com/GoogleChrome/puppeteer/issues/1421\n *\n * > Note: Be wary of ajax powered pages where the navigation event is not triggered.\n *\n * @example\n * await page.clickAndWaitForNavigation('input#submitData')\n *\n * // as opposed to:\n *\n * await Promise.all([\n *   page.waitForNavigation(waitOptions),\n *   page.click('input#submitData', clickOptions),\n * ])\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'click-and-wait'\n  }\n\n  async clickAndWaitForNavigation(selector, clickOptions, waitOptions) {\n    return Promise.all([\n      this.waitForNavigation(waitOptions),\n      this.click(selector, clickOptions)\n    ]).then(values => {\n      return values[0]\n    })\n  }\n\n  async onPageCreated(page) {\n    page.clickAndWaitForNavigation = this.clickAndWaitForNavigation.bind(page)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-click-and-wait/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-click-and-wait\",\n  \"version\": \"2.3.3\",\n  \"description\": \"Convenience function to wait for navigation to complete after clicking on an element.\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"clickAndWaitForNavigation\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-click-and-wait/readme.md",
    "content": "# puppeteer-extra-plugin-click-and-wait\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-click-and-wait\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-click-and-wait/index.js#L24-L39)\n\n**Extends: PuppeteerExtraPlugin**\n\nConvenience function to wait for navigation to complete after clicking on an element.\n\nAdds a new `page.clickAndWaitForNavigation(selector, clickOptions, waitOptions)` method.\n\nSee this issue for more context: <https://github.com/GoogleChrome/puppeteer/issues/1421>\n\n> Note: Be wary of ajax powered pages where the navigation event is not triggered.\n\nType: `function (opts)`\n\n-   `opts`   (optional, default `{}`)\n\nExample:\n\n```javascript\nawait page.clickAndWaitForNavigation('input#submitData')\n\n// as opposed to:\n\nawait Promise.all([\n  page.waitForNavigation(waitOptions),\n  page.click('input#submitData', clickOptions),\n])\n```\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\nconst RemoteDevTools = require('./lib/RemoteDevTools')\nconst ow = require('ow')\n\n/**\n * As the tunnel page is public the plugin will require basic auth.\n *\n * You can set your own credentials using `opts` or `setAuthCredentials()`.\n *\n * If you don't specify basic auth credentials the plugin will\n * generate a password and print it to STDOUT.\n *\n * **opts**\n * @param {Object} opts - Options\n * @param {Object} [opts.auth] - Basic auth credentials for the public page\n * @param {string} [opts.auth.user] - Username (default: 'user')\n * @param {string} [opts.auth.pass] - Password (will be generated if not provided)\n * @param {Object} [opts.prefix] - The prefix to use for the localtunnel.me subdomain (default: 'devtools-tunnel')\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * const devtools = require('puppeteer-extra-plugin-devtools')({\n *   auth: { user: 'francis', pass: 'president' }\n * })\n * puppeteer.use(devtools)\n *\n * puppeteer.launch().then(async browser => {\n *   console.log('tunnel url:', (await devtools.createTunnel(browser)).url)\n *   // => tunnel url: https://devtools-tunnel-n9aogqwx3d.localtunnel.me\n * })\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n\n    // To store a wsEndpoint (= browser instance) > tunnel reference\n    this._browserSessions = {}\n  }\n\n  get name() {\n    return 'devtools'\n  }\n\n  get defaults() {\n    return {\n      prefix: 'devtools-tunnel',\n      auth: {\n        user: 'user',\n        pass: require('crypto')\n          .randomBytes(20)\n          .toString('hex')\n      }\n    }\n  }\n\n  /**\n   * Create a new public tunnel.\n   *\n   * Supports multiple browser instances (will create a new tunnel for each).\n   *\n   * @param  {Puppeteer.Browser} browser - The browser to create the tunnel for (there can be multiple)\n   * @return {Tunnel} The {@link Tunnel} instance\n   *\n   * @example\n   * const puppeteer = require('puppeteer-extra')\n   * const devtools = require('puppeteer-extra-plugin-devtools')()\n   * devtools.setAuthCredentials('bob', 'swordfish')\n   * puppeteer.use(devtools)\n   *\n   * ;(async () => {\n   *   const browserFleet = await Promise.all(\n   *     [...Array(3)].map(slot => puppeteer.launch())\n   *   )\n   *   for (const [index, browser] of browserFleet.entries()) {\n   *     const {url} = await devtools.createTunnel(browser)\n   *     console.info(`Browser ${index}'s devtools frontend can be found at: ${url}`)\n   *   }\n   * })()\n   * // =>\n   * // Browser 0's devtools frontend can be found at: https://devtools-tunnel-fzenb4zuav.localtunnel.me\n   * // Browser 1's devtools frontend can be found at: https://devtools-tunnel-qe2t5rghme.localtunnel.me\n   * // Browser 2's devtools frontend can be found at: https://devtools-tunnel-pp83sdi4jo.localtunnel.me\n   */\n  async createTunnel(browser) {\n    ow(browser, ow.object.hasKeys('wsEndpoint'))\n\n    const wsEndpoint = browser.wsEndpoint()\n    if (!this._browserSessions[wsEndpoint]) {\n      this._browserSessions[wsEndpoint] = await new Tunnel(\n        wsEndpoint,\n        this.opts\n      ).create()\n    }\n\n    this._printGeneratedPasswordWhenNotOverridden(\n      this._browserSessions[wsEndpoint].url\n    )\n    this.debug('createTunnel', {\n      wsEndpoint,\n      sessions: Object.keys(this._browserSessions)\n    })\n    return this._browserSessions[wsEndpoint]\n  }\n\n  /**\n   * Set the basic auth credentials for the public tunnel page.\n   *\n   * Alternatively the credentials can be defined when instantiating the plugin.\n   *\n   * @param {string} user - Username\n   * @param {string} pass - Password\n   *\n   * @example\n   * const puppeteer = require('puppeteer-extra')\n   * const devtools = require('puppeteer-extra-plugin-devtools')()\n   * puppeteer.use(devtools)\n   *\n   * puppeteer.launch().then(async browser => {\n   *   devtools.setAuthCredentials('bob', 'swordfish')\n   *   const tunnel = await devtools.createTunnel(browser)\n   * })\n   */\n  setAuthCredentials(user, pass) {\n    ow(user, ow.string.nonEmpty)\n    ow(pass, ow.string.nonEmpty)\n    this.opts.auth = { user, pass }\n    this.debug('updated credentials', this.opts.auth)\n    return this\n  }\n\n  /**\n   * Convenience function to get the local devtools frontend URL.\n   *\n   * @param  {Puppeteer.Browser} browser\n   * @return {string}\n   *\n   * @example\n   * const puppeteer = require('puppeteer-extra')\n   * const devtools = require('puppeteer-extra-plugin-devtools')()\n   * puppeteer.use(devtools)\n   *\n   * puppeteer.launch().then(async browser => {\n   *   console.log(devtools.getLocalDevToolsUrl(browser))\n   *   // => http://localhost:55952\n   * })\n   */\n  getLocalDevToolsUrl(browser) {\n    ow(browser, ow.object.hasKeys('wsEndpoint'))\n\n    const wsEndpoint = browser.wsEndpoint()\n    return new RemoteDevTools.DevToolsLocal(wsEndpoint).url\n  }\n\n  /**\n   * Prints the generated auth credentials, when not overriden by the user.\n   *\n   * As the tunnel is public we make basic auth a requirement,\n   * without forcing the user to specify their own credentials.\n   *\n   * @ignore\n   */\n  _printGeneratedPasswordWhenNotOverridden(url) {\n    if (this.opts.auth.pass.length !== 40) {\n      return\n    }\n    console.info(`\n      DevTools Tunnel: You haven't specified basic auth credentials.\n\n      Here are the generated ones, for your convenience:\n\n        - user: 'user'\n        - pass: '${this.opts.auth.pass}'\n\n      Public Url: ${url}\n\n      You can specify your own auth credentials when instantiating the plugin,\n      or by using the plugin.setAuthCredentials(user, pass) method.\n    `)\n  }\n}\n\n/**\n * The devtools tunnel for a browser instance.\n *\n */\nclass Tunnel extends RemoteDevTools.DevToolsTunnel {\n  constructor(wsEndpoint, opts = {}) {\n    super(wsEndpoint, opts)\n  }\n\n  /**\n   * Get the public devtools frontend url.\n   *\n   * @return {string} - url\n   *\n   * @example\n   * const tunnel = await devtools.createTunnel(browser)\n   * console.log(tunnel.url)\n   * // => https://devtools-tunnel-sdoqqj95vg.localtunnel.me\n   */\n  get url() {\n    return super.url\n  }\n\n  /**\n   * Get the devtools frontend deep link for a specific page.\n   *\n   * @param  {Puppeteer.Page} page\n   * @return {string} - url\n   *\n   * @example\n   * const page = await browser.newPage()\n   * const tunnel = await devtools.createTunnel(browser)\n   * console.log(tunnel.getUrlForPage(page))\n   * // => https://devtools-tunnel-bmkjg26zmr.localtunnel.me/devtools/inspector.html?ws(...)\n   */\n  getUrlForPage(page) {\n    ow(page, ow.object.hasKeys('_target._targetInfo.targetId'))\n    const pageId = page._target._targetInfo.targetId\n    return super.getUrlForPageId(pageId)\n  }\n\n  /**\n   * Close the tunnel.\n   *\n   * The tunnel will automatically stop when your script exits.\n   *\n   * @example\n   * const tunnel = await devtools.createTunnel(browser)\n   * tunnel.close()\n   */\n  close() {\n    return super.close()\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/index.test.js",
    "content": "'use strict'\n\nconst PLUGIN_NAME = 'devtools'\n\nconst test = require('ava')\n\nconst Plugin = require('.')\n\ntest('is a function', async t => {\n  t.is(typeof Plugin, 'function')\n})\n\ntest('should have the basic class members', async t => {\n  const instance = new Plugin()\n  t.is(instance.name, PLUGIN_NAME)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have opts with default values', async t => {\n  const instance = new Plugin()\n  t.is(instance.opts.prefix, 'devtools-tunnel')\n  t.is(instance.opts.auth.user, 'user')\n  t.is(instance.opts.auth.pass.length, 40)\n})\n\ntest('will throw without browser when creating a tunnel', async t => {\n  const instance = new Plugin()\n  let error = null\n  try {\n    await instance.createTunnel()\n  } catch (err) {\n    error = err\n  }\n  t.is(error.name, `ArgumentError`)\n})\n\n// test('will accept a browser when creating a tunnel', async t => {\n//   const instance = new Plugin({ auth: { user: 'bob', pass: 'yup' } })\n//   const fakeBrowser = { wsEndpoint: () => 'ws://foobar:1337' }\n//   await instance.createTunnel(fakeBrowser)\n//   t.is(true, true)\n// })\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/lib/RemoteDevTools.js",
    "content": "'use strict'\n\nconst debug = require('debug')('remote-devtools')\nconst ow = require('ow')\n\nconst got = require('got')\nconst http = require('http')\nconst httpProxy = require('http-proxy')\nconst localtunnel = require('localtunnel')\n\nconst httpAuth = require('http-auth')\nconst modifyResponse = require('http-proxy-response-rewrite')\nconst getPort = require('get-port')\nconst randomstring = require('randomstring')\nconst urlParse = require('url-parse')\n\n/**\n * Base class handling common stuff\n *\n * @ignore\n */\nclass DevToolsCommon {\n  constructor(webSocketDebuggerUrl, opts = {}) {\n    ow(webSocketDebuggerUrl, ow.string)\n    ow(webSocketDebuggerUrl, ow.string.includes('ws://'))\n    ow(opts, ow.object.plain)\n    this.opts = opts\n\n    this.wsUrl = webSocketDebuggerUrl\n    const wsUrlParts = urlParse(this.wsUrl)\n    this.wsHost =\n      wsUrlParts.hostname === '127.0.0.1' ? 'localhost' : wsUrlParts.hostname\n    this.wsPort = wsUrlParts.port\n  }\n\n  async fetchVersion() {\n    const { body } = await got(\n      `http://${this.wsHost}:${this.wsPort}/json/version`,\n      {\n        json: true\n      }\n    )\n    return body\n  }\n\n  async fetchList() {\n    const { body } = await got(\n      `http://${this.wsHost}:${this.wsPort}/json/list`,\n      { json: true }\n    )\n    return body\n  }\n}\n\n/**\n * Convenience functions for local remote debugging sessions.\n *\n * @ignore\n */\nclass DevToolsLocal extends DevToolsCommon {\n  constructor(webSocketDebuggerUrl, opts = {}) {\n    super(webSocketDebuggerUrl, opts)\n  }\n\n  get url() {\n    return `http://${this.wsHost}:${this.wsPort}`\n  }\n\n  getUrlForPageId(pageId) {\n    return `${this.url}/devtools/inspector.html?ws=${this.wsHost}:${this.wsPort}/devtools/page/${pageId}`\n  }\n}\n\n/**\n * Create a proxy + tunnel to make a local devTools session accessible from the internet.\n *\n * - These devtools pages support screencasting the browser screen\n * - Proxy supports both http and websockets\n * - Proxy patches Host header to bypass devtools bug preventing non-localhost/ip access\n * - Proxy rewrites URLs, so links on the devtools index page will work\n * - Has a convenience function to return a deep link to a debug a specific page\n * - Supports basic auth ;-)\n *\n * @todo No idea how long-living a tunnel connection is yet, we might want to add keep-alive/reconnect capabilities\n *\n * @ignore\n */\nclass DevToolsTunnel extends DevToolsCommon {\n  constructor(webSocketDebuggerUrl, opts = {}) {\n    super(webSocketDebuggerUrl, opts)\n\n    this.server = null\n    this.tunnel = {}\n    this.tunnelHost = null\n\n    this.opts = Object.assign(this.defaults, opts)\n  }\n\n  get defaults() {\n    return {\n      prefix: 'devtools-tunnel',\n      subdomain: null,\n      auth: { user: null, pass: null },\n      localtunnel: {}\n    }\n  }\n\n  get url() {\n    return this.tunnel.url\n  }\n\n  getUrlForPageId(pageId) {\n    return `https://${this.tunnelHost}/devtools/inspector.html?wss=${this.tunnelHost}/devtools/page/${pageId}`\n  }\n\n  async create() {\n    const subdomain =\n      this.opts.subdomain || this._generateSubdomain(this.opts.prefix)\n    const basicAuth = this.opts.auth.user\n      ? this._createBasicAuth(this.opts.auth.user, this.opts.auth.pass)\n      : null\n    const serverPort = await getPort() // only preference, will return an available one\n\n    this.proxyServer = this._createProxyServer(this.wsHost, this.wsPort)\n    this.server = await this._createServer(serverPort, basicAuth)\n    this.tunnel = await this._createTunnel({\n      local_host: this.wsHost,\n      port: serverPort,\n      subdomain,\n      ...this.opts.localtunnel\n    })\n\n    this.tunnelHost = urlParse(this.tunnel.url).hostname\n\n    debug(\n      'tunnel created.',\n      `\n      local:  http://${this.wsHost}:${this.wsPort}\n      proxy:  http://localhost:${serverPort}\n      tunnel: ${this.tunnel.url}\n    `\n    )\n    return this\n  }\n\n  close() {\n    this.tunnel.close()\n    this.server.close()\n    this.proxyServer.close()\n    debug('all closed')\n    return this\n  }\n\n  _generateSubdomain(prefix) {\n    const rand = randomstring.generate({\n      length: 10,\n      readable: true,\n      capitalization: 'lowercase'\n    })\n    return `${prefix}-${rand}`\n  }\n\n  _createBasicAuth(user, pass) {\n    const basicAuth = httpAuth.basic({}, (username, password, callback) => {\n      const isValid = username === user && password === pass\n      return callback(isValid)\n    })\n    basicAuth.on('fail', (result, req) => {\n      debug(`User authentication failed: ${result.user}`)\n    })\n    basicAuth.on('error', (error, req) => {\n      debug(`Authentication error: ${error.code + ' - ' + error.message}`)\n    })\n    return basicAuth\n  }\n\n  /**\n   * `fetch` used by the index page doesn't include credentials by default.\n   *\n   *           LOVELY\n   *           THANKS\n   *             <3\n   *\n   * @ignore\n   */\n  _modifyFetchToIncludeCredentials(body) {\n    if (!body) {\n      return\n    }\n    body = body.replace(`fetch(url).`, `fetch(url, {credentials: 'include'}).`)\n\n    // Fix for headless index pages that use weird client-side JS to modify the devtoolsFrontendUrl to something not working for us\n    // https://github.com/berstend/puppeteer-extra/issues/566\n    body = body.replace(\n      'link.href = `https://chrome-devtools-frontend.appspot.com',\n      'link.href = item.devtoolsFrontendUrl; // '\n    )\n\n    debug('fetch:after', body)\n    return body\n  }\n\n  _modifyJSONResponse(body) {\n    if (!body) {\n      return\n    }\n    debug('list body:before', body)\n    body = body.replace(new RegExp(this.wsHost, 'g'), `${this.tunnelHost}`)\n    body = body.replace(new RegExp('ws=', 'g'), 'wss=')\n    body = body.replace(new RegExp('ws://', 'g'), 'wss://')\n    debug('list body:after', body)\n    return body\n  }\n  _createProxyServer(targetHost = 'localhost', targetPort) {\n    // eslint-disable-next-line\n    const proxyServer = new httpProxy.createProxyServer({\n      // eslint-disable-line\n      target: { host: targetHost, port: parseInt(targetPort) }\n    })\n    proxyServer.on('proxyReq', (proxyReq, req, res, options) => {\n      debug('proxyReq', req.url)\n      // https://github.com/GoogleChrome/puppeteer/issues/2242\n      proxyReq.setHeader('Host', 'localhost')\n    })\n    proxyServer.on('proxyRes', (proxyRes, req, res, options) => {\n      debug('proxyRes', req.url)\n      if (req.url === '/') {\n        delete proxyRes.headers['content-length']\n        modifyResponse(\n          res,\n          proxyRes.headers['content-encoding'],\n          this._modifyFetchToIncludeCredentials.bind(this)\n        )\n      }\n      if (['/json/list', '/json/version'].includes(req.url)) {\n        delete proxyRes.headers['content-length']\n        modifyResponse(\n          res,\n          proxyRes.headers['content-encoding'],\n          this._modifyJSONResponse.bind(this)\n        )\n      }\n    })\n    return proxyServer\n  }\n\n  async _createServer(port, auth = null) {\n    const server = http.createServer(auth, (req, res) => {\n      this.proxyServer.web(req, res)\n    })\n    server.on('upgrade', (req, socket, head) => {\n      debug('upgrade request', req.url)\n      this.proxyServer.ws(req, socket, head)\n    })\n    server.listen(port)\n    return server\n  }\n\n  async _createTunnel(options) {\n    const tunnel = await localtunnel(options)\n\n    tunnel.on('close', () => {\n      // todo: add keep-alive?\n      debug('tunnel:close')\n    })\n\n    tunnel.on('error', err => {\n      console.log('tunnel error', err)\n    })\n\n    debug('tunnel:created', tunnel.url)\n    return tunnel\n  }\n}\n\nmodule.exports = { DevToolsCommon, DevToolsLocal, DevToolsTunnel }\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/lib/RemoteDevTools.test.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst {\n  DevToolsCommon,\n  DevToolsLocal,\n  DevToolsTunnel\n} = require('./RemoteDevTools')\n\nconst webSocketDebuggerUrl =\n  'ws://127.0.0.1:9222/devtools/browser/ec78d039-2f19-4c6f-a08e-bcaf88e34b69'\n\ntest('is a function', async t => {\n  t.is(typeof DevToolsCommon, 'function')\n  t.is(typeof DevToolsLocal, 'function')\n  t.is(typeof DevToolsTunnel, 'function')\n})\n\ntest('will throw when missing webSocketDebuggerUrl', async t => {\n  const error = await t.throws(() => new DevToolsCommon())\n  t.is(\n    error.message,\n    'Expected argument to be of type `string` but received type `undefined`'\n  ) // eslint-disable-line\n})\n\ntest('DevToolsLocal: has basic functionality', async t => {\n  const instance = new DevToolsLocal(webSocketDebuggerUrl)\n  t.is(instance.url, 'http://localhost:9222')\n  t.is(\n    instance.getUrlForPageId('foobar'),\n    'http://localhost:9222/devtools/inspector.html?ws=localhost:9222/devtools/page/foobar'\n  )\n})\n\ntest('DevToolsTunnel: has basic functionality', async t => {\n  const instance = new DevToolsTunnel(webSocketDebuggerUrl)\n  instance.tunnel = { url: 'https://faketunnel.com' }\n  instance.tunnelHost = 'faketunnel.com'\n  t.is(instance.url, instance.tunnel.url)\n  t.is(\n    instance.getUrlForPageId('foobar'),\n    'https://faketunnel.com/devtools/inspector.html?wss=faketunnel.com/devtools/page/foobar'\n  )\n})\n\ntest('DevToolsTunnel: has defaults', async t => {\n  const instance = new DevToolsTunnel(webSocketDebuggerUrl)\n\n  t.is(instance.opts.prefix, 'devtools-tunnel')\n  t.is(instance.opts.subdomain, null)\n  t.deepEqual(instance.opts.auth, { user: null, pass: null })\n})\n\ntest('DevToolsTunnel: has public members', async t => {\n  const instance = new DevToolsTunnel(webSocketDebuggerUrl)\n\n  t.true(instance.create instanceof Function)\n  t.true(instance.close instanceof Function)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-devtools\",\n  \"version\": \"2.4.6\",\n  \"description\": \"Make puppeteer browser debugging possible from anywhere (devtools with screencasting on the internet).\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test-ava\": \"ava --fail-fast -v\",\n    \"test\": \"run-p lint test-ava\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"devtools\",\n    \"devtools-tunnel\",\n    \"localtunnel\",\n    \"remote-debugging\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\",\n    \"puppeteer-extra\": \"^3.3.6\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"get-port\": \"^3.2.0\",\n    \"got\": \"^8.3.1\",\n    \"http-auth\": \"^3.2.3\",\n    \"http-proxy\": \"^1.17.0\",\n    \"http-proxy-response-rewrite\": \"^0.0.1\",\n    \"localtunnel\": \"^2.0.0\",\n    \"ow\": \"^0.4.0\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"randomstring\": \"^1.1.5\",\n    \"url-parse\": \"^1.5.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/readme.md",
    "content": "# puppeteer-extra-plugin-devtools\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n## Installation\n\n```bash\nyarn add puppeteer-extra-plugin-devtools\n```\n\n## Purpose\n\n**Make puppeteer browser debugging possible from anywhere.**\n\n- Creates a secure tunnel to make the devtools frontend (**incl. screencasting**) accessible from the public internet\n- Works for both headless and headful puppeteer instances, as well as within docker containers\n- Uses the already existing DevTools Protocol websocket connection from puppeteer\n- Features some convenience functions for using the devtools frontend locally\n\n## Magic\n\n![screenshot](https://i.imgur.com/dYvsKfJ.png)\n\n## Quickstart\n\n```es6\nconst puppeteer = require('puppeteer-extra')\nconst devtools = require('puppeteer-extra-plugin-devtools')()\npuppeteer.use(devtools)\n\npuppeteer\n  .launch({ headless: true, defaultViewport: null })\n  .then(async browser => {\n    console.log('Start')\n    const tunnel = await devtools.createTunnel(browser)\n    console.log(tunnel.url)\n\n    const page = await browser.newPage()\n    await page.goto('https://example.com')\n    console.log('All setup.')\n  })\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [Plugin](#plugin)\n  - [createTunnel](#createtunnel)\n  - [setAuthCredentials](#setauthcredentials)\n  - [getLocalDevToolsUrl](#getlocaldevtoolsurl)\n- [Tunnel](#tunnel)\n  - [url](#url)\n  - [getUrlForPage](#geturlforpage)\n  - [close](#close)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L34-L168)\n\n**Extends: PuppeteerExtraPlugin**\n\nAs the tunnel page is public the plugin will require basic auth.\n\nYou can set your own credentials using `opts` or `setAuthCredentials()`.\n\nIf you don't specify basic auth credentials the plugin will\ngenerate a password and print it to STDOUT.\n\n**opts**\n\nType: `function (opts)`\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n  - `opts.auth` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Basic auth credentials for the public page\n    - `opts.auth.user` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Username (default: 'user')\n    - `opts.auth.pass` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Password (will be generated if not provided)\n  - `opts.prefix` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** The prefix to use for the localtunnel.me subdomain (default: 'devtools-tunnel')\n  - `opts.localtunnel` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Advanced options to pass to [localtunnel](https://github.com/localtunnel/localtunnel#options)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\nconst devtools = require('puppeteer-extra-plugin-devtools')({\n  auth: { user: 'francis', pass: 'president' }\n})\npuppeteer.use(devtools)\n\npuppeteer.launch().then(async browser => {\n  console.log('tunnel url:', (await devtools.createTunnel(browser)).url)\n  // => tunnel url: https://devtools-tunnel-n9aogqwx3d.localtunnel.me\n})\n```\n\n---\n\n#### [createTunnel](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L82-L93)\n\nCreate a new public tunnel.\n\nSupports multiple browser instances (will create a new tunnel for each).\n\nType: `function (browser): Tunnel`\n\n- `browser` **Puppeteer.Browser** The browser to create the tunnel for (there can be multiple)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\nconst devtools = require('puppeteer-extra-plugin-devtools')()\ndevtools.setAuthCredentials('bob', 'swordfish')\npuppeteer.use(devtools)\n;(async () => {\n  const browserFleet = await Promise.all(\n    [...Array(3)].map(slot => puppeteer.launch())\n  )\n  for (const [index, browser] of browserFleet.entries()) {\n    const { url } = await devtools.createTunnel(browser)\n    console.info(`Browser ${index}'s devtools frontend can be found at: ${url}`)\n  }\n})()\n// =>\n// Browser 0's devtools frontend can be found at: https://devtools-tunnel-fzenb4zuav.localtunnel.me\n// Browser 1's devtools frontend can be found at: https://devtools-tunnel-qe2t5rghme.localtunnel.me\n// Browser 2's devtools frontend can be found at: https://devtools-tunnel-pp83sdi4jo.localtunnel.me\n```\n\n---\n\n#### [setAuthCredentials](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L113-L119)\n\nSet the basic auth credentials for the public tunnel page.\n\nAlternatively the credentials can be defined when instantiating the plugin.\n\nType: `function (user, pass)`\n\n- `user` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Username\n- `pass` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Password\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\nconst devtools = require('puppeteer-extra-plugin-devtools')()\npuppeteer.use(devtools)\n\npuppeteer.launch().then(async browser => {\n  devtools.setAuthCredentials('bob', 'swordfish')\n  const tunnel = await devtools.createTunnel(browser)\n})\n```\n\n---\n\n#### [getLocalDevToolsUrl](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L137-L142)\n\nConvenience function to get the local devtools frontend URL.\n\nType: `function (browser): string`\n\n- `browser` **Puppeteer.Browser**\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\nconst devtools = require('puppeteer-extra-plugin-devtools')()\npuppeteer.use(devtools)\n\npuppeteer.launch().then(async browser => {\n  console.log(devtools.getLocalDevToolsUrl(browser))\n  // => http://localhost:55952\n})\n```\n\n---\n\n### [Tunnel](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L174-L217)\n\n**Extends: RemoteDevTools.DevToolsTunnel**\n\nThe devtools tunnel for a browser instance.\n\nType: `function (wsEndpoint, opts)`\n\n- `wsEndpoint`\n- `opts` (optional, default `{}`)\n\n---\n\n#### [url](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L187-L187)\n\nGet the public devtools frontend url.\n\nType: `function (): string`\n\nExample:\n\n```javascript\nconst tunnel = await devtools.createTunnel(browser)\nconsole.log(tunnel.url)\n// => https://devtools-tunnel-sdoqqj95vg.localtunnel.me\n```\n\n---\n\n#### [getUrlForPage](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L201-L205)\n\nGet the devtools frontend deep link for a specific page.\n\nType: `function (page): string`\n\n- `page` **Puppeteer.Page**\n\nExample:\n\n```javascript\nconst page = await browser.newPage()\nconst tunnel = await devtools.createTunnel(browser)\nconsole.log(tunnel.getUrlForPage(page))\n// => https://devtools-tunnel-bmkjg26zmr.localtunnel.me/devtools/inspector.html?ws(...)\n```\n\n---\n\n#### [close](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-devtools/index.js#L216-L216)\n\nClose the tunnel.\n\nThe tunnel will automatically stop when your script exits.\n\nType: `function ()`\n\nExample:\n\n```javascript\nconst tunnel = await devtools.createTunnel(browser)\ntunnel.close()\n```\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-devtools/test/headless.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\n// const PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\n// test.beforeEach(t => {\n//   // Make sure we work with pristine modules\n//   delete require.cache[require.resolve('puppeteer-extra')]\n//   delete require.cache[require.resolve('puppeteer-extra-plugin-devtools')]\n// })\n\ntest('will create a tunnel', async t => {\n  // const puppeteer = require('puppeteer-extra')\n  // const devtools = require('puppeteer-extra-plugin-devtools')()\n  // puppeteer.use(devtools)\n  // devtools.setAuthCredentials('bob', 'swordfish')\n\n  // await puppeteer.launch({ args: PUPPETEER_ARGS }).then(async browser => {\n  //   const tunnel = await devtools.createTunnel(browser)\n  //   t.true(tunnel.url.includes('https://devtools-tunnel-'))\n  //   await browser.close()\n  // })\n  t.true(true)\n})\n\n// Note: https://tunnel.datahub.at is gone and I don't have an alternative currently\n// test('will create a tunnel with custom localtunnel options', async t => {\n//   const puppeteer = require('puppeteer-extra')\n//   const devtools = require('puppeteer-extra-plugin-devtools')({\n//     auth: { user: 'francis', pass: 'president' },\n//     localtunnel: {\n//       host: 'https://tunnel.datahub.at'\n//     }\n//   })\n//   puppeteer.use(devtools)\n\n//   await puppeteer.launch({ args: PUPPETEER_ARGS }).then(async browser => {\n//     const tunnel = await devtools.createTunnel(browser)\n//     t.true(tunnel.url.includes('.tunnel.datahub.at'))\n//     browser.close()\n//   })\n//   t.true(true)\n// })\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-flash/example.js",
    "content": "'use strict'\n\nconst puppeteer = require('puppeteer-extra')\n\n// This might not be the flashPath you're looking for. ;-)\nconst userName = require('os').userInfo().username\nconst pluginPath = `\n  /Users/${userName}/Library/Application Support/Google/Chrome/PepperFlash/29.0.0.171/PepperFlashPlayer.plugin\n`.trim()\nconst pluginVersion = '29.0.0.171'\n\n// Will implicitely require 'user-preferences' which will require 'user-data-dir'\n// When using default Chromium the pluginPath/pluginVersion need to be specified\npuppeteer.use(\n  require('puppeteer-extra-plugin-flash')({\n    pluginPath,\n    pluginVersion\n  })\n)\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  await page.goto('http://ultrasounds.com', { waitUntil: 'domcontentloaded' })\n})()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-flash/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Allow flash on all sites without user interaction.\n *\n * Note: The flash plugin is not working in headless mode.\n *\n * Note: When using the default Chromium browser\n * `pluginPath` and `pluginVersion` must be specified.\n *\n * Note: Unfortunately this doesn't seem to enable flash on incognito pages,\n * see [this gist] for a workaround using management policies.\n * [this gist]: https://gist.github.com/berstend/bcd64a4a2db28afbd6486daf69f4e787\n *\n * @param {Object} opts - Options\n * @param {boolean} [opts.allowFlash=true] - Whether to allow flash content or not\n * @param {boolean} [opts.pluginPath=null] - Flash plugin path\n * @param {boolean} [opts.pluginVersion=9000] - Flash plugin version (9000 is high enough for Chrome not to complain)\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('puppeteer-extra-plugin-flash')())\n * ;(async () => {\n *   const browser = await puppeteer.launch({headless: false})\n *   const page = await browser.newPage()\n *   await page.goto('http://ultrasounds.com', {waitUntil: 'domcontentloaded'})\n * })()\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'flash'\n  }\n\n  get defaults() {\n    return {\n      allowFlash: true,\n      pluginPath: null,\n      pluginVersion: 9000\n    }\n  }\n\n  get requirements() {\n    return new Set(['launch', 'headful'])\n  }\n\n  get dependencies() {\n    return new Set(['user-preferences'])\n  }\n\n  async beforeLaunch(options) {\n    if (this.opts.allowFlash === false) {\n      return\n    }\n\n    if (this.opts.pluginPath) {\n      options.args.push(`--ppapi-flash-path=${this.opts.pluginPath}`)\n    }\n    if (this.opts.pluginVersion) {\n      options.args.push(`--ppapi-flash-version=${this.opts.pluginVersion}`)\n    }\n  }\n\n  get data() {\n    if (this.opts.allowFlash === false) {\n      return\n    }\n    const allowFlashPreferences = {\n      profile: {\n        managed_default_content_settings: {\n          plugins: 1\n        },\n        managed_plugins_allowed_for_urls: ['https://*', 'http://*']\n      }\n    }\n    return [\n      {\n        name: 'userPreferences',\n        value: allowFlashPreferences\n      }\n    ]\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-flash/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-flash\",\n  \"version\": \"2.3.3\",\n  \"description\": \"Allow flash on all sites without user interaction.\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"chrome\",\n    \"flash\",\n    \"allow-flash\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-flash/readme.md",
    "content": "# puppeteer-extra-plugin-flash\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n## Install\n\n```bash\nyarn add puppeteer-extra-plugin-flash\n```\n\n## Changelog\n\n#### `v2.2.5`\n\n- Improved: Fixes flash content in newer Chrome versions (76+) ([#133](https://github.com/berstend/puppeteer-extra/pull/133), thanks [@Niek](https://github.com/Niek))\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-flash/index.js#L31-L100)\n\n**Extends: PuppeteerExtraPlugin**\n\nAllow flash on all sites without user interaction.\n\nNote: The flash plugin is not working in headless mode.\n\nNote: When using the default Chromium browser\n`pluginPath` and `pluginVersion` must be specified (stated in `chrome://version/`).\n\nNote: Unfortunately this doesn't seem to enable flash on incognito pages,\nsee [this gist] for a workaround using management policies.\n\n[this gist]: https://gist.github.com/berstend/bcd64a4a2db28afbd6486daf69f4e787\n\nType: `function (opts)`\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n  - `opts.allowFlash` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to allow flash content or not (optional, default `true`)\n  - `opts.pluginPath` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Flash plugin path (optional, default `null`)\n  - `opts.pluginVersion` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Flash plugin version (9000 is high enough for Chrome not to complain) (optional, default `9000`)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-flash')())\n;(async () => {\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  await page.goto('http://ultrasounds.com', { waitUntil: 'domcontentloaded' })\n})()\n```\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-font-size/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Modify/increase the default font size in puppeteer.\n *\n * @param {Object} opts - Options\n * @param {Number} [opts.defaultFontSize=20] - Default browser font size\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('puppeteer-extra-plugin-font-size')())\n * // or\n * puppeteer.use(require('puppeteer-extra-plugin-font-size')({defaultFontSize: 18}))\n * const browser = await puppeteer.launch()\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'font-size'\n  }\n\n  get defaults() {\n    return { defaultFontSize: 20 }\n  }\n\n  get requirements() {\n    return new Set(['launch', 'headful'])\n  }\n\n  get dependencies() {\n    return new Set(['user-preferences'])\n  }\n\n  get data() {\n    const userPreferences = {\n      webkit: {\n        webprefs: {\n          default_font_size: this.opts.defaultFontSize\n        }\n      }\n    }\n    return [\n      {\n        name: 'userPreferences',\n        value: userPreferences\n      }\n    ]\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-font-size/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-font-size\",\n  \"version\": \"2.3.3\",\n  \"description\": \"Adjust font sizes in puppeteer.\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-font-size/readme.md",
    "content": "# puppeteer-extra-plugin-font-size\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-font-size\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-font-size/index.js#L18-L44)\n\n**Extends: PuppeteerExtraPlugin**\n\nModify/increase the default font size in puppeteer.\n\nType: `function (opts)`\n\n-   `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n    -   `opts.defaultFontSize` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Default browser font size (optional, default `20`)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-font-size')())\n// or\npuppeteer.use(require('puppeteer-extra-plugin-font-size')({defaultFontSize: 18}))\nconst browser = await puppeteer.launch()\n```\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/ava.config-ts.js",
    "content": "export default {\n  compileEnhancements: false,\n  environmentVariables: {\n    TS_NODE_COMPILER_OPTIONS: '{\"module\":\"commonjs\"}'\n  },\n  files: ['src/**.test.ts'],\n  extensions: ['ts'],\n  require: ['ts-node/register']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/ava.config.js",
    "content": "export default {\n  files: ['dist/*.test.js']\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-recaptcha\",\n  \"version\": \"3.6.8\",\n  \"description\": \"A puppeteer-extra plugin to solve reCAPTCHAs and hCaptchas automatically.\",\n  \"main\": \"dist/index.cjs.js\",\n  \"module\": \"dist/index.esm.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-recaptcha\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\",\n    \"tscheck\": \"tsc --pretty --noEmit\",\n    \"prebuild\": \"run-s clean\",\n    \"build\": \"run-s build:tsc build:rollup ambient-dts\",\n    \"build:tsc\": \"tsc --project tsconfig.json --module commonjs\",\n    \"build:rollup\": \"rollup -c rollup.config.ts\",\n    \"docs\": \"node -e 0\",\n    \"predocs2\": \"rimraf docs/*\",\n    \"docs2\": \"typedoc --module commonjs --readme none --target ES6 --theme markdown --excludeNotExported --excludeExternals --excludePrivate --out docs --mode file src/index.ts\",\n    \"test\": \"ava -v --config ava.config-ts.js\",\n    \"pretest-ci\": \"run-s build\",\n    \"test-ci\": \"ava --fail-fast --concurrency 2 -v\",\n    \"ambient-dts\": \"run-s ambient-dts-copy ambient-dts-fix-path\",\n    \"ambient-dts-copy\": \"copyfiles -u 1 \\\"src/**/*.d.ts\\\" dist\",\n    \"ambient-dts-fix-path\": \"replace-in-files --string='/// <reference path=\\\"../src/' --replacement='/// <reference path=\\\"../dist/' 'dist/**/*.d.ts'\"\n  },\n  \"engines\": {\n    \"node\": \">=9.11.2\"\n  },\n  \"prettier\": {\n    \"printWidth\": 80,\n    \"semi\": false,\n    \"singleQuote\": true\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"recaptcha\",\n    \"hcaptcha\",\n    \"captcha\",\n    \"2captcha\"\n  ],\n  \"devDependencies\": {\n    \"@types/debug\": \"^4.1.5\",\n    \"@types/node\": \"14.17.6\",\n    \"@types/puppeteer\": \"*\",\n    \"ava\": \"2.4.0\",\n    \"copyfiles\": \"^2.1.1\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"9\",\n    \"puppeteer-extra\": \"^3.3.6\",\n    \"replace-in-files-cli\": \"^0.3.1\",\n    \"rimraf\": \"^3.0.0\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-typescript2\": \"^0.25.2\",\n    \"ts-node\": \"^8.5.4\",\n    \"tslint\": \"^5.20.1\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-config-standard\": \"^9.0.0\",\n    \"typescript\": \"4.4.3\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"merge-deep\": \"^3.0.2\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/readme.md",
    "content": "# puppeteer-extra-plugin-recaptcha [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/dt/puppeteer-extra-plugin-recaptcha.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin-recaptcha) [![npm](https://img.shields.io/npm/v/puppeteer-extra-plugin-recaptcha.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin-recaptcha)\n\n> A [puppeteer-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra) and [playwright-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra) plugin to solve reCAPTCHAs and hCaptchas automatically.\n\n![](https://i.imgur.com/SWrIQw0.gif)\n\n## Install\n\n```bash\nyarn add puppeteer-extra-plugin-recaptcha\n# - or -\nnpm install puppeteer-extra-plugin-recaptcha\n```\n\nIf this is your first [puppeteer-extra](https://github.com/berstend/puppeteer-extra) plugin here's everything you need:\n\n```bash\nyarn add puppeteer puppeteer-extra puppeteer-extra-plugin-recaptcha\n# - or -\nnpm install puppeteer puppeteer-extra puppeteer-extra-plugin-recaptcha\n```\n\n<details>\n <summary><strong>Changelog</strong></summary>\n\n##### Latest\n\n> 🎁 **Note:** Until we've automated changelog updates in markdown files please follow the `#announcements` channel in our [discord server](https://extra.community/) for the latest updates and changelog info.\n\n_Older changelog:_\n\n##### `3.1.9`\n\n- Support reCAPTCHAs not in forms ([#57](https://github.com/berstend/puppeteer-extra/issues/57))\n- Make script detection more fuzzy ([#48](https://github.com/berstend/puppeteer-extra/issues/48))\n\n##### `3.1.6`\n\n- We'll now add our custom methods to any existing pages and frames in the browser instance.\n- Fixed reference import path for our ambient declarations.\n\n##### `3.1.5`\n\n- Solving reCAPTCHAs in frames is now supported as well, if need be:\n\n```js\nfor (const frame of page.mainFrame().childFrames()) {\n  await frame.solveRecaptchas()\n}\n```\n\n##### `3.1.4`\n\n- Improved TypeScript experience: I found a way to make your TypeScript compiler automatically aware of the additions to the `Page` and `Frame` object (e.g. `page.solveRecaptchas()`).\n- We now print a warning if the provider throws an error (e.g. invalid api key)\n\n</details>\n\n## Usage\n\nThe plugin essentially provides a mighty `page.solveRecaptchas()` method that does everything needed automagically.\n\n```js\n// puppeteer-extra is a drop-in replacement for puppeteer,\n// it augments the installed puppeteer with plugin functionality\nconst puppeteer = require('puppeteer-extra')\n\n// add recaptcha plugin and provide it your 2captcha token (= their apiKey)\n// 2captcha is the builtin solution provider but others would work as well.\n// Please note: You need to add funds to your 2captcha account for this to work\nconst RecaptchaPlugin = require('puppeteer-extra-plugin-recaptcha')\npuppeteer.use(\n  RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: 'XXXXXXX' // REPLACE THIS WITH YOUR OWN 2CAPTCHA API KEY ⚡\n    },\n    visualFeedback: true // colorize reCAPTCHAs (violet = detected, green = solved)\n  })\n)\n\n// puppeteer usage as normal\npuppeteer.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n  await page.goto('https://www.google.com/recaptcha/api2/demo')\n\n  // That's it, a single line of code to solve reCAPTCHAs 🎉\n  await page.solveRecaptchas()\n\n  await Promise.all([\n    page.waitForNavigation(),\n    page.click(`#recaptcha-demo-submit`)\n  ])\n  await page.screenshot({ path: 'response.png', fullPage: true })\n  await browser.close()\n})\n```\n\n<details>\n <summary><strong>TypeScript usage</strong></summary>\n\n```ts\n// `puppeteer-extra` and the recaptcha plugin are written in TS,\n// hence you get perfect type support out of the box :)\n\nimport puppeteer from 'puppeteer-extra'\nimport RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha'\n\npuppeteer.use(\n  RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: 'ENTER_YOUR_2CAPTCHA_API_KEY_HERE'\n    }\n  })\n)\n\n// Puppeteer usage as normal (headless is \"false\" just for this demo)\npuppeteer.launch({ headless: false }).then(async browser => {\n  const page = await browser.newPage()\n  await page.goto('https://www.google.com/recaptcha/api2/demo')\n\n  // Even this `Puppeteer.Page` extension is recognized and fully type safe 🎉\n  await page.solveRecaptchas()\n\n  await Promise.all([\n    page.waitForNavigation(),\n    page.click(`#recaptcha-demo-submit`)\n  ])\n  await page.screenshot({ path: 'response.png', fullPage: true })\n  await browser.close()\n})\n```\n\n</details><br>\n\nIf you'd like to see debug output just run your script like so:\n\n```bash\nDEBUG=puppeteer-extra,puppeteer-extra-plugin:* node myscript.js\n```\n\n_**Tip:** The recaptcha plugin works really well together with the [stealth plugin](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth)._\n\n## Motivation 🏴\n\nThese days [captchas](https://en.wikipedia.org/wiki/CAPTCHA) are unfortunately everywhere, with [reCAPTCHA](https://developers.google.com/recaptcha/) having the biggest \"market share\" in that space (> 80%). The situation got really bad, with privacy minded users (tracking blocker, VPNs) being penalized heavily and having to solve a lot of reCAPTCHA challenges constantly while browsing the web.\n\nThe stated reasons for this omnipresent captcha plague vary from site owners having to protect themselves against increasingly malicious actors to some believing that we're essentially forced into free labour to train Google's various machine learning endeavours.\n\nIn any case I strongly feel that captchas in their current form have failed. They're a much bigger obstacle and annoyance to humans than to robots, which renders them useless. My anarchist contribution to this discussion is to demonstrate this absurdity, with a plugin for robots with which **a single line of code is all it takes to bypass reCAPTCHAs on any site**.\n\n> Note: Since `v3.3.0` the plugin will solve [hCaptchas](https://www.hcaptcha.com/) as well, as they've gained significant marketshare through their Cloudflare partnership.\n\n## Provider\n\nI thought about having the plugin solve captchas directly (e.g. using the [audio challenge](https://github.com/dessant/buster) and speech-to-text APIs), but external solution providers are so cheap and reliable that there is really no benefit in doing that. ¯\\\\\\_(ツ)\\_/¯\n\n_Please note:_ You need a provider configured for this plugin to do it's magic. If you decide to use the built-in 2captcha provider you need to add funds to your 2captcha account.\n\n### 2captcha\n\nCurrently the only builtin solution provider as it's the cheapest and most reliable, from my experience. If you'd like to throw some free captcha credit my way feel free to [signup here](https://2captcha.com?from=6690177) (referral link, allows me to write automated tests against their API).\n\n- Cost: 1000 reCAPTCHAs (and hCaptchas) for 3 USD\n- Delay: Solving a reCAPTCHA takes between 10 to 60 seconds\n- Error rate (incorrect solutions): Very rare\n\n#### Other providers\n\nYou can easily use your own provider as well, by providing the plugin a function instead of 2captcha credentials (explained in the API docs). PRs for new providers are welcome as well.\n\n## Q&A\n\n### How does this work?\n\n- When summoned with `page.solveRecaptchas()` the plugin will attempt to find any active reCAPTCHAs & hCaptchas, extract their configuration, pass that on to the specified solutions provider, take the solutions and put them back into the page (triggering any callback that might be required).\n\n### Is this production ready?\n\n- Yes, the plugin is actively maintained, has been battle-hardened over several years and is used in high workload production setups.\n\n### How do reCAPTCHAs work?\n\n- reCAPTCHAs use a per-site `sitekey`. Interestingly enough the response token after solving a challenge is (currently) not tied to a specific session or IP and can be passed on to others (until they expire). This is how the external solutions provider work: They're being given a `sitekey` and URL, solve the challenge and respond with a response token.\n\n- This plugin automates all these steps in a generic and robust way (detecting captchas, extracting their config and `sitekey`) as well as triggering the (optional) response callback the site owner might have specified.\n\n### Are ordinary image captchas supported as well?\n\n- No. This plugin focusses on reCAPTCHAs and hCaptchas exclusively, with the benefit of being fully automatic. 🔮\n\n### What about invisible reCAPTCHAs?\n\n- [Invisible reCAPTCHAs](https://developers.google.com/recaptcha/docs/invisible) are supported. They're basically used to compute a score of how likely the user is a bot. Based on that score the site owner can block access to resources or (most often) present the user with a reCAPTCHA challenge (which this plugin can solve). The [stealth plugin](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth) might be of interest here, as it masks the usage of puppeteer.\n- Technically speaking the plugin supports: reCAPTCHA v2, reCAPTCHA v3, invisible reCAPTCHA, hCaptcha, invisible hCaptcha. All of those (multiple as well) are solved when `page.solveRecaptchas()` is called.\n\n### When should I call `page.solveRecaptchas()`?\n\n- reCAPTCHAs will be solved automatically whenever they **are visible** (_aka their \"I'm not a robot\" iframe in the DOM_). It's your responsibility to do any required actions to trigger the captcha being shown, if needed.\n  - Note about \"invisible\" versions of reCAPTCHA/hCaptchas: They don't feature a visible checkbox iframe, the plugin will then solve any open challenge popups instead. :-)\n- If you summon the plugin immediately after navigating to a page it's got your back and will wait automatically until the reCAPTCHA script (if any) has been loaded and initialized.\n- If you call `page.solveRecaptchas()` on a page that has no reCAPTCHAs nothing bad will happen (😄) but the promise will resolve and the rest of your code executes as normal.\n- After solving the reCAPTCHAs the plugin will automatically detect and trigger their [optional callback](https://developers.google.com/recaptcha/docs/display#render_param). This might result in forms being submitted and page navigations to occur, depending on how the site owner implemented the reCAPTCHA.\n\n## Debug\n\n```bash\nDEBUG=puppeteer-extra,puppeteer-extra-plugin:* node myscript.js\n```\n\n## Fine grained control\n\n### Defaults\n\nBy default the plugin will never throw, but return any errors silently in the `{ error }` property of the result object. You can change that behaviour by passing `throwOnError: true` to the initializier and use `try/catch` blocks to catch errors.\n\nFor convenience and because it looks cool the plugin will \"colorize\" reCAPTCHAs depending on their state (violet = detected and being solved, green = solved). You can turn that feature off by passing `visualFeedback: false` to the plugin initializer.\n\n### Options\n\n```ts\ninterface PluginOptions {\n  /** Visualize reCAPTCHAs based on their state */\n  visualFeedback: boolean // default: true\n  /** Throw on errors instead of returning them in the error property */\n  throwOnError: boolean // default: false\n  /** Only solve captchas and challenges visible in the browser viewport */\n  solveInViewportOnly: boolean // default: false\n  /** Solve scored based captchas with no challenge (e.g. reCAPTCHA v3) */\n  solveScoreBased: boolean // default: false\n  /** Solve invisible captchas that have no active challenge */\n  solveInactiveChallenges: boolean // default: false\n}\n```\n\n### Result object\n\n```js\nconst {\n  captchas,\n  filtered,\n  solutions,\n  solved,\n  error\n} = await page.solveRecaptchas()\n```\n\n- `captchas` is an array of captchas found in the page\n- `filtered` is an array of captchas that have been detected but are ignored due to plugin options\n- `solutions` is an array of solutions returned from the provider\n- `solved` is an array of \"solved\" (= solution entered) captchas on the page\n\n### Manual control flow\n\n`page.solveRecaptchas()` is a convenience method that wraps the following steps:\n\n```js\nlet { captchas, filtered, error } = await page.findRecaptchas()\nlet { solutions, error } = await page.getRecaptchaSolutions(captchas)\nlet { solved, error } = await page.enterRecaptchaSolutions(solutions)\n```\n\n### Proxies\n\nIf you wish for 2captcha to use a specific proxy (= IP address) while solving the captcha you can set the enviroment variables `2CAPTCHA_PROXY_TYPE` and `2CAPTCHA_PROXY_ADDRESS`.\n\n## Troubleshooting\n\n### Solving captchas in iframes\n\nBy default the plugin will only solve reCAPTCHAs showing up on the immediate page. In case you encounter captchas in frames the plugin extends the `Puppeteer.Frame` object with custom methods as well:\n\n```js\n// Loop over all potential frames on that page\nfor (const frame of page.mainFrame().childFrames()) {\n  // Attempt to solve any potential captchas in those frames\n  await frame.solveRecaptchas()\n}\n```\n\nIn addition you might want to disable site isolation, so puppeteer is able to access [cross-origin iframes](https://github.com/puppeteer/puppeteer/issues/2548):\n\n```js\npuppeteer.launch({\n  args: [\n    '--disable-features=IsolateOrigins,site-per-process,SitePerProcess',\n    '--flag-switches-begin --disable-site-isolation-trials --flag-switches-end'\n  ]\n})\n```\n\n### Solving captchas in pre-existing browser pages\n\nIn case you're not using `browser.newPage()` but re-use the existing `about:blank` tab (which is not recommended for various reasons) you will experience a `page.solveRecaptchas is not a function` error, as the plugin hasn't hooked into this page yet. As a workaround you can manually add existing pages to the lifecycle methods of the plugin:\n\n```js\nconst recaptcha = RecaptchaPlugin()\nconst pages = await browser.pages()\nfor (const page in pages) {\n  // Add plugin methods to existing pages\n  await recaptcha.onPageCreated(page)\n}\n```\n\n### Tips\n\n- Make sure to use debug logging if something is not working right or when reporting issues.\n- Check for ignored captchas in the filtered array in case a captcha you intend to solve is being ignored, filtered captchas will state the reason why they have been ignored (or better: which plugin option is responsible)\n- Keep in mind that by default the plugin will only solve \"active\" captchas (the means a visible checkbox or an active challenge popup). In extreme cases (like a very weird or super slow loading site) you can help the plugin by making sure the captcha you intend to solve is there before calling `page.solveRecaptchas`:\n\n```js\nawait page.waitForSelector('iframe[src*=\"recaptcha/\"]')\nawait page.solveRecaptchas()\n```\n\n---\n\n## License\n\nCopyright © 2018 - 2023, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](https://github.com/berstend). Released under the MIT License.\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/rollup.config.ts",
    "content": "import resolve from 'rollup-plugin-node-resolve'\nimport sourceMaps from 'rollup-plugin-sourcemaps'\nimport typescript from 'rollup-plugin-typescript2'\n\nconst pkg = require('./package.json')\n\nconst entryFile = 'index'\nconst banner = `\n/*!\n * ${pkg.name} v${pkg.version} by ${pkg.author}\n * ${pkg.homepage || `https://github.com/${pkg.repository}`}\n * @license ${pkg.license}\n */\n`.trim()\n\nconst defaultExportOutro = `\n  module.exports = exports.default || {}\n  Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value })\n`\n\nexport default {\n  input: `src/${entryFile}.ts`,\n  output: [\n    {\n      file: pkg.main,\n      format: 'cjs',\n      sourcemap: true,\n      exports: 'named',\n      outro: defaultExportOutro,\n      banner\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      sourcemap: true,\n      exports: 'named',\n      banner\n    }\n  ],\n  // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')\n  external: [\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ],\n  watch: {\n    include: 'src/**'\n  },\n  plugins: [\n    // Compile TypeScript files\n    typescript({ useTsconfigDeclarationDir: true }),\n    // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)\n    // commonjs(),\n    // Allow node_modules resolution, so you can use 'external' to control\n    // which external modules to include in the bundle\n    // https://github.com/rollup/rollup-plugin-node-resolve#usage\n    resolve(),\n    // Resolve source maps to the original source\n    sourceMaps()\n  ]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/ambient.d.ts",
    "content": "export {}\n\n// https://github.com/sindresorhus/type-fest/issues/19\ndeclare global {\n  interface SymbolConstructor {\n    readonly observable: symbol\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/content-hcaptcha.ts",
    "content": "import * as types from './types'\n\nexport const ContentScriptDefaultOpts: types.ContentScriptOpts = {\n  visualFeedback: true\n}\n\nexport const ContentScriptDefaultData: types.ContentScriptData = {\n  solutions: []\n}\n\n/**\n * Content script for Hcaptcha handling (runs in browser context)\n * @note External modules are not supported here (due to content script isolation)\n */\nexport class HcaptchaContentScript {\n  private opts: types.ContentScriptOpts\n  private data: types.ContentScriptData\n\n  private baseUrls = [\n    'assets.hcaptcha.com/captcha/v1/',\n    'newassets.hcaptcha.com/captcha/v1/',\n  ]\n\n  constructor(\n    opts = ContentScriptDefaultOpts,\n    data = ContentScriptDefaultData\n  ) {\n    // Workaround for https://github.com/esbuild-kit/tsx/issues/113\n    if (typeof globalThis.__name === 'undefined') {\n      globalThis.__defProp = Object.defineProperty\n      globalThis.__name = (target, value) =>\n        globalThis.__defProp(target, 'name', { value, configurable: true })\n    }\n\n    this.opts = opts\n    this.data = data\n  }\n\n  private async _waitUntilDocumentReady() {\n    return new Promise(function(resolve) {\n      if (!document || !window) return resolve(null)\n      const loadedAlready = /^loaded|^i|^c/.test(document.readyState)\n      if (loadedAlready) return resolve(null)\n\n      function onReady() {\n        resolve(null)\n        document.removeEventListener('DOMContentLoaded', onReady)\n        window.removeEventListener('load', onReady)\n      }\n\n      document.addEventListener('DOMContentLoaded', onReady)\n      window.addEventListener('load', onReady)\n    })\n  }\n\n  private _paintCaptchaBusy($iframe: HTMLIFrameElement) {\n    try {\n      if (this.opts.visualFeedback) {\n        $iframe.style.filter = `opacity(60%) hue-rotate(400deg)` // violet\n      }\n    } catch (error) {\n      // noop\n    }\n    return $iframe\n  }\n\n  /** Regular checkboxes */\n  private _findRegularCheckboxes() {\n    const nodeList = document.querySelectorAll<HTMLIFrameElement>(\n      this.baseUrls.map(url => `iframe[src*='${url}'][data-hcaptcha-widget-id]:not([src*='invisible'])`).join(',')\n    )\n    return Array.from(nodeList)\n  }\n\n  /** Find active challenges from invisible hcaptchas */\n  private _findActiveChallenges() {\n    const nodeList = document.querySelectorAll<HTMLIFrameElement>(\n      this.baseUrls.map(url => `div[style*='visible'] iframe[src*='${url}'][src*='hcaptcha.html']`).join(',')\n    )\n    return Array.from(nodeList)\n  }\n\n  private _extractInfoFromIframes(iframes: HTMLIFrameElement[]) {\n    return iframes\n      .map(el => el.src.replace('.html#', '.html?'))\n      .map(url => {\n        const { searchParams } = new URL(url)\n        const result: types.CaptchaInfo = {\n          _vendor: 'hcaptcha',\n          url: document.location.href,\n          id: searchParams.get('id'),\n          sitekey: searchParams.get('sitekey'),\n          display: {\n            size: searchParams.get('size') || 'normal'\n          }\n        }\n        return result\n      })\n  }\n\n  public async findRecaptchas() {\n    const result = {\n      captchas: [] as types.CaptchaInfo[],\n      error: null as null | Error\n    }\n    try {\n      await this._waitUntilDocumentReady()\n      const iframes = [\n        ...this._findRegularCheckboxes(),\n        ...this._findActiveChallenges()\n      ]\n      if (!iframes.length) {\n        return result\n      }\n      result.captchas = this._extractInfoFromIframes(iframes)\n      iframes.forEach(el => {\n        this._paintCaptchaBusy(el)\n      })\n    } catch (error) {\n      result.error = error\n      return result\n    }\n    return result\n  }\n\n  public async enterRecaptchaSolutions() {\n    const result = {\n      solved: [] as types.CaptchaSolved[],\n      error: null as any\n    }\n    try {\n      await this._waitUntilDocumentReady()\n\n      const solutions = this.data.solutions\n      if (!solutions || !solutions.length) {\n        result.error = 'No solutions provided'\n        return result\n      }\n      result.solved = solutions\n        .filter(solution => solution._vendor === 'hcaptcha')\n        .filter(solution => solution.hasSolution === true)\n        .map(solution => {\n          window.postMessage(\n            JSON.stringify({\n              id: solution.id,\n              label: 'challenge-closed',\n              source: 'hcaptcha',\n              contents: {\n                event: 'challenge-passed',\n                expiration: 120,\n                response: solution.text\n              }\n            }),\n            '*'\n          )\n          return {\n            _vendor: solution._vendor,\n            id: solution.id,\n            isSolved: true,\n            solvedAt: new Date()\n          }\n        })\n    } catch (error) {\n      result.error = error\n      return result\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/content.ts",
    "content": "import * as types from './types'\n\nexport const ContentScriptDefaultOpts: types.ContentScriptOpts = {\n  visualFeedback: true,\n  debugBinding: undefined\n}\n\nexport const ContentScriptDefaultData: types.ContentScriptData = {\n  solutions: []\n}\n\ninterface FrameSources {\n  anchor: string[]\n  bframe: string[]\n}\n\n/**\n * Content script for Recaptcha handling (runs in browser context)\n * @note External modules are not supported here (due to content script isolation)\n */\nexport class RecaptchaContentScript {\n  private opts: types.ContentScriptOpts\n  private data: types.ContentScriptData\n  private frameSources: FrameSources\n\n  constructor(\n    opts = ContentScriptDefaultOpts,\n    data = ContentScriptDefaultData\n  ) {\n    // Workaround for https://github.com/esbuild-kit/tsx/issues/113\n    if (typeof globalThis.__name === 'undefined') {\n      globalThis.__defProp = Object.defineProperty\n      globalThis.__name = (target, value) =>\n        globalThis.__defProp(target, 'name', { value, configurable: true })\n    }\n\n    this.opts = opts\n    this.data = data\n    this.frameSources = this._generateFrameSources()\n    this.log('Intialized', { url: document.location.href, opts: this.opts })\n  }\n\n  /** Log using debug binding if available */\n  private log = (message: string, data?: any) => {\n    if (this.opts.debugBinding && window.top[this.opts.debugBinding]) {\n      window.top[this.opts.debugBinding](message, JSON.stringify(data))\n    }\n  }\n\n  // Poor mans _.pluck\n  private _pick = (props: any[]) => (o: any) =>\n    props.reduce((a, e) => ({ ...a, [e]: o[e] }), {})\n\n  // make sure the element is visible - this is equivalent to jquery's is(':visible')\n  private _isVisible = (elem: any) =>\n    !!(\n      elem.offsetWidth ||\n      elem.offsetHeight ||\n      (typeof elem.getClientRects === 'function' &&\n        elem.getClientRects().length)\n    )\n\n  /** Check if an element is in the current viewport */\n  private _isInViewport(elem: any) {\n    const rect = elem.getBoundingClientRect()\n    return (\n      rect.top >= 0 &&\n      rect.left >= 0 &&\n      rect.bottom <=\n        (window.innerHeight ||\n          (document.documentElement.clientHeight &&\n            rect.right <=\n              (window.innerWidth || document.documentElement.clientWidth)))\n    )\n  }\n\n  // Recaptcha client is a nested, circular object with object keys that seem generated\n  // We flatten that object a couple of levels deep for easy access to certain keys we're interested in.\n  private _flattenObject(item: any, levels = 2, ignoreHTML = true) {\n    const isObject = (x: any) => x && typeof x === 'object'\n    const isHTML = (x: any) => x && x instanceof HTMLElement\n    let newObj = {} as any\n    for (let i = 0; i < levels; i++) {\n      item = Object.keys(newObj).length ? newObj : item\n      Object.keys(item).forEach(key => {\n        if (ignoreHTML && isHTML(item[key])) return\n        if (isObject(item[key])) {\n          Object.keys(item[key]).forEach(innerKey => {\n            if (ignoreHTML && isHTML(item[key][innerKey])) return\n            const keyName = isObject(item[key][innerKey])\n              ? `obj_${key}_${innerKey}`\n              : `${innerKey}`\n            newObj[keyName] = item[key][innerKey]\n          })\n        } else {\n          newObj[key] = item[key]\n        }\n      })\n    }\n    return newObj\n  }\n\n  // Helper function to return an object based on a well known value\n  private _getKeyByValue(object: any, value: any) {\n    return Object.keys(object).find(key => object[key] === value)\n  }\n\n  private async _waitUntilDocumentReady() {\n    return new Promise(function(resolve) {\n      if (!document || !window) {\n        return resolve(null)\n      }\n      const loadedAlready = /^loaded|^i|^c/.test(document.readyState)\n      if (loadedAlready) {\n        return resolve(null)\n      }\n\n      function onReady() {\n        resolve(null)\n        document.removeEventListener('DOMContentLoaded', onReady)\n        window.removeEventListener('load', onReady)\n      }\n\n      document.addEventListener('DOMContentLoaded', onReady)\n      window.addEventListener('load', onReady)\n    })\n  }\n\n  private _paintCaptchaBusy($iframe: HTMLIFrameElement) {\n    try {\n      if (this.opts.visualFeedback) {\n        $iframe.style.filter = `opacity(60%) hue-rotate(400deg)` // violet\n      }\n    } catch (error) {\n      // noop\n    }\n    return $iframe\n  }\n\n  private _paintCaptchaSolved($iframe: HTMLIFrameElement) {\n    try {\n      if (this.opts.visualFeedback) {\n        $iframe.style.filter = `opacity(60%) hue-rotate(230deg)` // green\n      }\n    } catch (error) {\n      // noop\n    }\n    return $iframe\n  }\n\n  private _findVisibleIframeNodes() {\n    return Array.from(\n      document.querySelectorAll<HTMLIFrameElement>(\n        this.getFrameSelectorForId('anchor', '') // intentionally blank\n      )\n    )\n  }\n  private _findVisibleIframeNodeById(id?: string) {\n    return document.querySelector<HTMLIFrameElement>(\n      this.getFrameSelectorForId('anchor', id)\n    )\n  }\n\n  private _hideChallengeWindowIfPresent(id: string = '') {\n    let frame: HTMLElement | null = document.querySelector<HTMLIFrameElement>(\n      this.getFrameSelectorForId('bframe', id)\n    )\n    this.log(' - _hideChallengeWindowIfPresent', { id, hasFrame: !!frame })\n    if (!frame) {\n      return\n    }\n    while (\n      frame &&\n      frame.parentElement &&\n      frame.parentElement !== document.body\n    ) {\n      frame = frame.parentElement\n    }\n    if (frame) {\n      frame.style.visibility = 'hidden'\n    }\n  }\n\n  // There's so many different possible deployments URLs that we better generate them\n  private _generateFrameSources(): FrameSources {\n    const protos = ['http', 'https']\n    const hosts = [\n      'google.com',\n      'www.google.com',\n      'recaptcha.net',\n      'www.recaptcha.net'\n    ]\n    const origins = protos.flatMap(proto =>\n      hosts.map(host => `${proto}://${host}`)\n    )\n    const paths = {\n      anchor: ['/recaptcha/api2/anchor', '/recaptcha/enterprise/anchor'],\n      bframe: ['/recaptcha/api2/bframe', '/recaptcha/enterprise/bframe']\n    }\n    return {\n      anchor: origins.flatMap(origin =>\n        paths.anchor.map(path => `${origin}${path}`)\n      ),\n      bframe: origins.flatMap(origin =>\n        paths.bframe.map(path => `${origin}${path}`)\n      )\n    }\n  }\n\n  private getFrameSelectorForId(type: 'anchor' | 'bframe' = 'anchor', id = '') {\n    const namePrefix = type === 'anchor' ? 'a' : 'c'\n    return this.frameSources[type]\n      .map(src => `iframe[src^='${src}'][name^=\"${namePrefix}-${id}\"]`)\n      .join(',')\n  }\n\n  private getClients() {\n    // Bail out early if there's no indication of recaptchas\n    if (!window || !window.__google_recaptcha_client) return\n    if (!window.___grecaptcha_cfg || !window.___grecaptcha_cfg.clients) {\n      return\n    }\n    if (!Object.keys(window.___grecaptcha_cfg.clients).length) return\n    return window.___grecaptcha_cfg.clients\n  }\n\n  private getVisibleIframesIds() {\n    // Find all regular visible recaptcha boxes through their iframes\n    const result = this._findVisibleIframeNodes()\n      .filter($f => this._isVisible($f))\n      .map($f => this._paintCaptchaBusy($f))\n      .filter($f => $f && $f.getAttribute('name'))\n      .map($f => $f.getAttribute('name') || '') // a-841543e13666\n      .map(\n        rawId => rawId.split('-').slice(-1)[0] // a-841543e13666 => 841543e13666\n      )\n      .filter(id => id)\n    this.log('getVisibleIframesIds', result)\n    return result\n  }\n\n  // TODO: Obsolete with recent changes\n  private getInvisibleIframesIds() {\n    // Find all invisible recaptcha boxes through their iframes (only the ones with an active challenge window)\n    const result = this._findVisibleIframeNodes()\n      .filter($f => $f && $f.getAttribute('name'))\n      .map($f => $f.getAttribute('name') || '') // a-841543e13666\n      .map(\n        rawId => rawId.split('-').slice(-1)[0] // a-841543e13666 => 841543e13666\n      )\n      .filter(id => id)\n      .filter(\n        id =>\n          document.querySelectorAll(this.getFrameSelectorForId('bframe', id))\n            .length\n      )\n    this.log('getInvisibleIframesIds', result)\n    return result\n  }\n\n  private getIframesIds() {\n    // Find all recaptcha boxes through their iframes, check for invisible ones as fallback\n    const results = [\n      ...this.getVisibleIframesIds(),\n      ...this.getInvisibleIframesIds()\n    ]\n    this.log('getIframesIds', results)\n    // Deduplicate results by using the unique id as key\n    const dedup = Array.from(new Set(results))\n    this.log('getIframesIds - dedup', dedup)\n    return dedup\n  }\n\n  private isEnterpriseCaptcha(id?: string) {\n    if (!id) return false\n    // The only way to determine if a captcha is an enterprise one is by looking at their iframes\n    const prefix = 'iframe[src*=\"/recaptcha/\"][src*=\"/enterprise/\"]'\n    const nameSelectors = [`[name^=\"a-${id}\"]`, `[name^=\"c-${id}\"]`]\n    const fullSelector = nameSelectors.map(name => prefix + name).join(',')\n    return document.querySelectorAll(fullSelector).length > 0\n  }\n\n  private isInvisible(id?: string) {\n    if (!id) return false\n    const selector = `iframe[src*=\"/recaptcha/\"][src*=\"/anchor\"][name=\"a-${id}\"][src*=\"&size=invisible\"]`\n    return document.querySelectorAll(selector).length > 0\n  }\n\n  /** Whether an active challenge popup is open */\n  private hasActiveChallengePopup(id?: string) {\n    if (!id) return false\n    const selector = `iframe[src*=\"/recaptcha/\"][src*=\"/bframe\"][name=\"c-${id}\"]`\n    const elem = document.querySelector(selector)\n    if (!elem) {\n      return false\n    }\n    return this._isInViewport(elem) // note: _isVisible doesn't work here as the outer div is hidden, not the iframe itself\n  }\n\n  /** Whether an (invisible) captcha has a challenge bframe - otherwise it's a score based captcha */\n  private hasChallengeFrame(id?: string) {\n    if (!id) return false\n    return (\n      document.querySelectorAll(this.getFrameSelectorForId('bframe', id))\n        .length > 0\n    )\n  }\n\n  private isInViewport(id?: string) {\n    if (!id) return\n    const prefix = 'iframe[src*=\"recaptcha\"]'\n    const nameSelectors = [`[name^=\"a-${id}\"]`, `[name^=\"c-${id}\"]`]\n    const fullSelector = nameSelectors.map(name => prefix + name).join(',')\n    const elem = document.querySelector(fullSelector)\n    if (!elem) {\n      return false\n    }\n    return this._isInViewport(elem)\n  }\n\n  private getResponseInputById(id?: string) {\n    if (!id) return\n    const $iframe = this._findVisibleIframeNodeById(id)\n    if (!$iframe) return\n    const $parentForm = $iframe.closest(`form`)\n    if ($parentForm) {\n      return $parentForm.querySelector(`[name='g-recaptcha-response']`)\n    }\n    // Not all reCAPTCHAs are in forms\n    // https://github.com/berstend/puppeteer-extra/issues/57\n    if (document && document.body) {\n      return document.body.querySelector(`[name='g-recaptcha-response']`)\n    }\n  }\n\n  private getClientById(id?: string) {\n    if (!id) return\n    const clients = this.getClients()\n    // Lookup captcha \"client\" info using extracted id\n    let client: any = Object.values(clients || {})\n      .filter(obj => this._getKeyByValue(obj, id))\n      .shift() // returns first entry in array or undefined\n    this.log(' - getClientById:client', { id, hasClient: !!client })\n    if (!client) return\n    try {\n      client = this._flattenObject(client) as any\n      client.widgetId = client.id\n      client.id = id\n      this.log(' - getClientById:client:flatten', {\n        id,\n        hasClient: !!client\n      })\n    } catch (err) {\n      this.log(' - getClientById:client ERROR', err.toString())\n    }\n    return client\n  }\n\n  private extractInfoFromClient(client?: any) {\n    if (!client) return\n    const info: types.CaptchaInfo = this._pick(['sitekey', 'callback'])(client)\n    if (!info.sitekey) return\n    info._vendor = 'recaptcha'\n    info.id = client.id\n    info.s = client.s // google site specific\n    info.widgetId = client.widgetId\n    info.display = this._pick([\n      'size',\n      'top',\n      'left',\n      'width',\n      'height',\n      'theme'\n    ])(client)\n    if (client && client.action) {\n      info.action = client.action\n    }\n    // callbacks can be strings or funtion refs\n    if (info.callback && typeof info.callback === 'function') {\n      info.callback = info.callback.name || 'anonymous'\n    }\n    if (document && document.location) info.url = document.location.href\n    return info\n  }\n\n  public async findRecaptchas() {\n    const result = {\n      captchas: [] as (types.CaptchaInfo | undefined)[],\n      error: null as any\n    }\n    try {\n      await this._waitUntilDocumentReady()\n      const clients = this.getClients()\n      this.log('findRecaptchas', {\n        url: document.location.href,\n        hasClients: !!clients\n      })\n      if (!clients) return result\n      result.captchas = this.getIframesIds()\n        .map(id => this.getClientById(id))\n        .map(client => this.extractInfoFromClient(client))\n        .map(info => {\n          this.log(' - captchas:info', info)\n          if (!info) return\n          const $input = this.getResponseInputById(info.id)\n          info.hasResponseElement = !!$input\n          return info\n        })\n        .filter(info => !!info && !!info.sitekey)\n        .map(info => {\n          info.sitekey = info.sitekey.trim()\n          info.isEnterprise = this.isEnterpriseCaptcha(info.id)\n          info.isInViewport = this.isInViewport(info.id)\n          info.isInvisible = this.isInvisible(info.id)\n          info._type = 'checkbox'\n          if (info.isInvisible) {\n            info._type = 'invisible'\n            info.hasActiveChallengePopup = this.hasActiveChallengePopup(info.id)\n            info.hasChallengeFrame = this.hasChallengeFrame(info.id)\n            if (!info.hasChallengeFrame) {\n              info._type = 'score'\n            }\n          }\n          return info\n        })\n    } catch (error) {\n      result.error = error\n      return result\n    }\n    this.log('findRecaptchas - result', {\n      captchaNum: result.captchas.length,\n      result\n    })\n    return result\n  }\n\n  public async enterRecaptchaSolutions() {\n    const result = {\n      solved: [] as (types.CaptchaSolved | undefined)[],\n      error: null as any\n    }\n    try {\n      await this._waitUntilDocumentReady()\n      const clients = this.getClients()\n      this.log('enterRecaptchaSolutions', {\n        url: document.location.href,\n        hasClients: !!clients,\n        solutionNum: this.data.solutions.length\n      })\n\n      if (!clients) {\n        result.error = 'No recaptchas found'\n        return result\n      }\n      const solutions = this.data.solutions\n      if (!solutions || !solutions.length) {\n        result.error = 'No solutions provided'\n        return result\n      }\n\n      result.solved = this.data.solutions.map(solution => {\n        const client = this.getClientById(solution.id)\n        this.log(' - client', !!client)\n        const solved: types.CaptchaSolved = {\n          _vendor: 'recaptcha',\n          id: client.id,\n          responseElement: false,\n          responseCallback: false\n        }\n        const $iframe = this._findVisibleIframeNodeById(solved.id)\n        this.log(' - $iframe', !!$iframe)\n        if (!$iframe) {\n          solved.error = `Iframe not found for id '${solved.id}'`\n          return solved\n        }\n\n        if (this.hasActiveChallengePopup(solved.id)) {\n          // Hide if present challenge window\n          this._hideChallengeWindowIfPresent(solved.id)\n        }\n\n        // Enter solution in response textarea\n        const $input = this.getResponseInputById(solved.id)\n        this.log(' - $input', !!$input)\n        if ($input) {\n          $input.innerHTML = solution.text\n          solved.responseElement = true\n        }\n        // Enter solution in optional callback\n        this.log(' - callback', !!client.callback)\n        if (client.callback) {\n          try {\n            this.log(' - callback - type', {\n              typeof: typeof client.callback,\n              value: '' + client.callback\n            })\n            if (typeof client.callback === 'function') {\n              client.callback.call(window, solution.text)\n            } else {\n              eval(client.callback).call(window, solution.text) // tslint:disable-line\n              this.log(' - callback - aftereval')\n            }\n            solved.responseCallback = true\n          } catch (error) {\n            solved.error = error\n          }\n        }\n        // Finishing up\n        solved.isSolved = solved.responseCallback || solved.responseElement\n        solved.solvedAt = new Date()\n        this._paintCaptchaSolved($iframe)\n        this.log(' - solved', solved)\n        return solved\n      })\n    } catch (error) {\n      result.error = error\n      return result\n    }\n    this.log('enterRecaptchaSolutions - finished', result)\n    return result\n  }\n}\n\n/*\n// Example data\n\n{\n    \"captchas\": [{\n        \"sitekey\": \"6LdAUwoUAAAAAH44X453L0tUWOvx11XXXXXXXX\",\n        \"id\": \"lnfy52r0cccc\",\n        \"widgetId\": 0,\n        \"display\": {\n            \"size\": null,\n            \"top\": 23,\n            \"left\": 13,\n            \"width\": 28,\n            \"height\": 28,\n            \"theme\": null\n        },\n        \"url\": \"https://example.com\",\n        \"hasResponseElement\": true\n    }],\n    \"error\": null\n}\n\n{\n    \"solutions\": [{\n        \"id\": \"lnfy52r0cccc\",\n        \"provider\": \"2captcha\",\n        \"providerCaptchaId\": \"61109548000\",\n        \"text\": \"03AF6jDqVSOVODT-wLKZ47U0UXz...\",\n        \"requestAt\": \"2019-02-09T18:30:43.587Z\",\n        \"responseAt\": \"2019-02-09T18:30:57.937Z\"\n    }]\n    \"error\": null\n}\n\n{\n    \"solved\": [{\n        \"id\": \"lnfy52r0cccc\",\n        \"responseElement\": true,\n        \"responseCallback\": false,\n        \"isSolved\": true,\n        \"solvedAt\": {}\n    }]\n    \"error\": null\n}\n*/\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/detection.test.ts",
    "content": "import test from 'ava'\n\nimport RecaptchaPlugin from './index'\n\nimport { addExtra } from 'puppeteer-extra'\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\nconst getBrowser = async (url = '', opts = {}) => {\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin(opts)\n  puppeteer.use(recaptchaPlugin)\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true,\n    defaultViewport: null\n  })\n  const page = await browser.newPage()\n  await page.goto(url, { waitUntil: 'networkidle0' })\n  return { browser, page }\n}\n\ntest('will correctly detect v2-checkbox-auto.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-checkbox-auto.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c._type, 'checkbox')\n  t.is(c.url, url)\n\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n  t.is(c.callback, undefined)\n\n  t.is(c.hasResponseElement, true)\n  t.is(c.isEnterprise, false)\n  t.is(c.isInViewport, true)\n  t.is(c.isInvisible, false)\n\n  await browser.close()\n})\n\ntest('will correctly detect v2-checkbox-auto-nowww.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-checkbox-auto-nowww.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, undefined)\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n\n  await browser.close()\n})\n\ntest('will correctly detect v2-checkbox-auto-recaptchadotnet.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-checkbox-auto-recaptchadotnet.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, undefined)\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n\n  await browser.close()\n})\n\ntest('will correctly detect enterprise-checkbox-auto.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/enterprise-checkbox-auto.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, undefined)\n  t.is(c.isEnterprise, true)\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n\n  await browser.close()\n})\n\ntest('will correctly detect enterprise-checkbox-auto-recaptchadotnet.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/enterprise-checkbox-auto-recaptchadotnet.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, undefined)\n  t.is(c.isEnterprise, true)\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n\n  await browser.close()\n})\n\ntest('will correctly detect enterprise-checkbox-explicit.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/enterprise-checkbox-explicit.html'\n  const { browser, page } = await getBrowser(url)\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, undefined)\n  t.is(c.action, 'homepage') // NOTE\n  t.is(c.isEnterprise, true)\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.is(c.widgetId, 0)\n  t.not(c.display, undefined)\n\n  await browser.close()\n})\n\ntest('will correctly detect v2-invisible-explicit-isolated.html', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-invisible-explicit-isolated.html'\n  const { browser, page } = await getBrowser(url, {\n    solveInactiveChallenges: true\n  })\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.not(c.display, undefined)\n  t.not(c.id, undefined)\n\n  delete c.url\n  delete c.sitekey\n  delete c.display\n  delete c.id\n\n  t.deepEqual(c, {\n    callback: 'onSubmit',\n    _vendor: 'recaptcha',\n    s: null,\n    widgetId: 100000,\n    hasResponseElement: true,\n    isEnterprise: false,\n    isInViewport: true,\n    isInvisible: true,\n    _type: 'invisible',\n    hasActiveChallengePopup: false,\n    hasChallengeFrame: true\n  })\n\n  await browser.close()\n})\n\ntest('will correctly detect v2-invisible-auto.html - active challenge', async t => {\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-invisible-explicit.html'\n  const { browser, page } = await getBrowser('about:blank')\n  await page.setUserAgent('BOT') // we want to trigger the invisible recaptcha challenge window\n  await page.goto(url, { waitUntil: 'networkidle2' })\n\n  if (page.waitForTimeout) {\n    await page.waitForTimeout(1000)\n  } else {\n    await page.waitFor(1000)\n  }\n\n  await page.click('#submit')\n\n  if (page.waitForTimeout) {\n    await page.waitForTimeout(1000)\n  } else {\n    await page.waitFor(1000)\n  }\n\n  if (page.url() !== url) {\n    // we didn't get a challenge\n    t.truthy('foo')\n    return\n  }\n\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.not(c.display, undefined)\n  t.not(c.id, undefined)\n\n  delete c.url\n  delete c.sitekey\n  delete c.display\n  delete c.id\n\n  t.deepEqual(c, {\n    callback: 'onSubmit',\n    _vendor: 'recaptcha',\n    s: null,\n    widgetId: 0,\n    hasResponseElement: true,\n    isEnterprise: false,\n    isInViewport: true,\n    isInvisible: true,\n    _type: 'invisible',\n    hasActiveChallengePopup: true, // the important bit\n    hasChallengeFrame: true\n  })\n\n  await browser.close()\n})\n\ntest('will correctly detect v3-programmatic.html with solveScoreBased:false and filter captcha', async t => {\n  const url = 'https://berstend.github.io/static/recaptcha/v3-programmatic.html'\n  const { browser, page } = await getBrowser(url, {\n    solveScoreBased: false\n  })\n  const { captchas, filtered, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 0)\n  t.is(filtered.length, 1)\n\n  const c = filtered[0]\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.not(c.display, undefined)\n  t.not(c.id, undefined)\n\n  delete c.url\n  delete c.sitekey\n  delete c.display\n  delete c.id\n\n  t.deepEqual(c, {\n    _vendor: 'recaptcha',\n    s: null,\n    widgetId: 100000,\n    hasResponseElement: true,\n    isEnterprise: false,\n    isInViewport: true,\n    isInvisible: true,\n    _type: 'score',\n    hasActiveChallengePopup: false,\n    hasChallengeFrame: false, // important\n    filtered: true, // important\n    filteredReason: 'solveScoreBased' // important\n  })\n\n  await browser.close()\n})\n\ntest('will correctly detect v3-programmatic.html with solveScoreBased:true', async t => {\n  const url = 'https://berstend.github.io/static/recaptcha/v3-programmatic.html'\n  const { browser, page } = await getBrowser(url, {\n    solveScoreBased: true\n  })\n  const { captchas, filtered, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n  t.is(filtered.length, 0)\n\n  const c = captchas[0]\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n  t.not(c.display, undefined)\n  t.not(c.id, undefined)\n\n  delete c.url\n  delete c.sitekey\n  delete c.display\n  delete c.id\n\n  t.deepEqual(c, {\n    _vendor: 'recaptcha',\n    s: null,\n    widgetId: 100000,\n    hasResponseElement: true,\n    isEnterprise: false,\n    isInViewport: true,\n    isInvisible: true,\n    _type: 'score',\n    hasActiveChallengePopup: false,\n    hasChallengeFrame: false // important\n  })\n\n  await browser.close()\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/index.test.ts",
    "content": "import test from 'ava'\n\nimport RecaptchaPlugin from './index'\n// import * as types from './types'\n\n// import { Puppeteer } from './puppeteer-mods'\n\nimport { addExtra } from 'puppeteer-extra'\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest('will detect reCAPTCHAs', async t => {\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin()\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url = 'https://www.google.com/recaptcha/api2/demo'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 1)\n\n  const c = captchas[0]\n  t.is(c._vendor, 'recaptcha')\n  t.is(c.callback, 'onSuccess')\n  t.is(c.hasResponseElement, true)\n  t.is(c.url, url)\n  t.true(c.sitekey && c.sitekey.length > 5)\n\n  await browser.close()\n})\n\ntest('will detect hCAPTCHAs', async t => {\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin()\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const urls = [\n    'https://accounts.hcaptcha.com/demo',\n    'https://democaptcha.com/demo-form-eng/hcaptcha.html'\n  ]\n\n  for (const url of urls) {\n    await page.goto(url, { waitUntil: 'networkidle0' })\n\n    const { captchas, error } = await (page as any).findRecaptchas()\n    t.is(error, null)\n    t.is(captchas.length, 1)\n\n    const c = captchas[0]\n    t.is(c._vendor, 'hcaptcha')\n    t.is(c.url, url)\n    t.true(c.sitekey && c.sitekey.length > 5)\n  }\n\n  await browser.close()\n})\n\ntest('will detect active hCAPTCHA challenges', async t => {\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin()\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const urls = [\n    'https://accounts.hcaptcha.com/demo',\n    'https://democaptcha.com/demo-form-eng/hcaptcha.html'\n  ]\n\n  for (const url of urls) {\n    await page.goto(url, { waitUntil: 'networkidle0' })\n    await page.evaluate(() => (window as any).hcaptcha.execute()) // trigger challenge popup\n    await page.waitForTimeout(2 * 1000)\n    await page.evaluate(() =>\n      document\n        .querySelector(`[data-hcaptcha-widget-id]:not([src*='invisible'])`)\n        .remove()\n    ) // remove regular checkbox so we definitely test against the popup\n\n    const { captchas, error } = await (page as any).findRecaptchas()\n    t.is(error, null)\n    t.is(captchas.length, 1)\n\n    const c = captchas[0]\n    t.is(c._vendor, 'hcaptcha')\n    t.is(c.url, url)\n    t.true(c.sitekey && c.sitekey.length > 5)\n  }\n\n  await browser.close()\n})\n\ntest('will not throw when no captchas are found', async t => {\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin()\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url = 'https://www.example.com'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const { captchas, error } = await (page as any).findRecaptchas()\n  t.is(error, null)\n  t.is(captchas.length, 0)\n\n  await browser.close()\n})\n\n// TODO: test/mock the rest\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/index.ts",
    "content": "import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'\n\nimport { Browser, Frame, Page } from 'puppeteer'\n\nimport * as types from './types'\n\nimport { RecaptchaContentScript } from './content'\nimport { HcaptchaContentScript } from './content-hcaptcha'\nimport * as TwoCaptcha from './provider/2captcha'\n\nexport const BuiltinSolutionProviders: types.SolutionProvider[] = [\n  {\n    id: TwoCaptcha.PROVIDER_ID,\n    fn: TwoCaptcha.getSolutions\n  }\n]\n\n/**\n * A puppeteer-extra plugin to automatically detect and solve reCAPTCHAs.\n * @noInheritDoc\n */\nexport class PuppeteerExtraPluginRecaptcha extends PuppeteerExtraPlugin {\n  private contentScriptDebug: debug.Debugger\n\n  constructor(opts: Partial<types.PluginOptions>) {\n    super(opts)\n    this.debug('Initialized', this.opts)\n\n    this.contentScriptDebug = this.debug.extend('cs')\n  }\n\n  get name() {\n    return 'recaptcha'\n  }\n\n  get defaults(): types.PluginOptions {\n    return {\n      visualFeedback: true,\n      throwOnError: false,\n      solveInViewportOnly: false,\n      solveScoreBased: false,\n      solveInactiveChallenges: false\n    }\n  }\n\n  get opts(): types.PluginOptions {\n    return super.opts as any\n  }\n\n  get contentScriptOpts(): types.ContentScriptOpts {\n    const { visualFeedback } = this.opts\n    return {\n      visualFeedback,\n      debugBinding: this.contentScriptDebug.enabled\n        ? this.debugBindingName\n        : undefined\n    }\n  }\n\n  /** An optional global window object we use for contentscript debug logging */\n  private debugBindingName = '___pepr_cs'\n\n  private _generateContentScript(\n    vendor: types.CaptchaVendor,\n    fn: 'findRecaptchas' | 'enterRecaptchaSolutions',\n    data?: any\n  ) {\n    this.debug('_generateContentScript', vendor, fn, data)\n    let scriptSource = RecaptchaContentScript.toString()\n    let scriptName = 'RecaptchaContentScript'\n    if (vendor === 'hcaptcha') {\n      scriptSource = HcaptchaContentScript.toString()\n      scriptName = 'HcaptchaContentScript'\n    }\n    // Some bundlers transform classes to anonymous classes that are assigned to\n    // vars (e.g. esbuild). In such cases, `unexpected token '{'` errors are thrown\n    // once the script is executed. Let's bring class name back to script in such\n    // cases!\n    scriptSource = scriptSource.replace(/class \\{/, `class ${scriptName} {`)\n    return `(async() => {\n      const DATA = ${JSON.stringify(data || null)}\n      const OPTS = ${JSON.stringify(this.contentScriptOpts)}\n\n      ${scriptSource}\n      const script = new ${scriptName}(OPTS, DATA)\n      return script.${fn}()\n    })()`\n  }\n\n  /** Based on the user defined options we may want to filter out certain captchas (inactive, etc) */\n  private _filterRecaptchas(recaptchas: types.CaptchaInfo[] = []) {\n    const results = recaptchas.map((c: types.FilteredCaptcha) => {\n      if (\n        c._type === 'invisible' &&\n        !c.hasActiveChallengePopup &&\n        !this.opts.solveInactiveChallenges\n      ) {\n        c.filtered = true\n        c.filteredReason = 'solveInactiveChallenges'\n      }\n      if (c._type === 'score' && !this.opts.solveScoreBased) {\n        c.filtered = true\n        c.filteredReason = 'solveScoreBased'\n      }\n      if (\n        c._type === 'checkbox' &&\n        !c.isInViewport &&\n        this.opts.solveInViewportOnly\n      ) {\n        c.filtered = true\n        c.filteredReason = 'solveInViewportOnly'\n      }\n      if (c.filtered) {\n        this.debug('Filtered out captcha based on provided options', {\n          id: c.id,\n          reason: c.filteredReason,\n          captcha: c\n        })\n      }\n      return c\n    })\n    return {\n      captchas: results.filter(c => !c.filtered) as types.CaptchaInfo[],\n      filtered: results.filter(c => c.filtered)\n    }\n  }\n\n  async findRecaptchas(page: Page | Frame) {\n    this.debug('findRecaptchas')\n    // As this might be called very early while recaptcha is still loading\n    // we add some extra waiting logic for developer convenience.\n    const hasRecaptchaScriptTag = await page.$(\n      `script[src*=\"/recaptcha/api.js\"], script[src*=\"/recaptcha/enterprise.js\"]`\n    )\n    this.debug('hasRecaptchaScriptTag', !!hasRecaptchaScriptTag)\n    if (hasRecaptchaScriptTag) {\n      this.debug('waitForRecaptchaClient - start', new Date())\n      await page\n        .waitForFunction(\n          `\n        (function() {\n          return Object.keys((window.___grecaptcha_cfg || {}).clients || {}).length\n        })()\n      `,\n          { polling: 200, timeout: 10 * 1000 }\n        )\n        .catch(this.debug)\n      this.debug('waitForRecaptchaClient - end', new Date()) // used as timer\n    }\n    const hasHcaptchaScriptTag = await page.$(\n      `script[src*=\"hcaptcha.com/1/api.js\"]`\n    )\n    this.debug('hasHcaptchaScriptTag', !!hasHcaptchaScriptTag)\n    if (hasHcaptchaScriptTag) {\n      this.debug('wait:hasHcaptchaScriptTag - start', new Date())\n      await page.waitForFunction(\n        `\n        (function() {\n          return window.hcaptcha\n        })()\n      `,\n        { polling: 200, timeout: 10 * 1000 }\n      )\n      this.debug('wait:hasHcaptchaScriptTag - end', new Date()) // used as timer\n    }\n\n    const onDebugBindingCalled = (message: string, data: any) => {\n      this.contentScriptDebug(message, data)\n    }\n\n    if (this.contentScriptDebug.enabled) {\n      if ('exposeFunction' in page) {\n        await page.exposeFunction(this.debugBindingName, onDebugBindingCalled)\n      }\n    }\n    // Even without a recaptcha script tag we're trying, just in case.\n    const resultRecaptcha: types.FindRecaptchasResult = (await page.evaluate(\n      this._generateContentScript('recaptcha', 'findRecaptchas')\n    )) as any\n    const resultHcaptcha: types.FindRecaptchasResult = (await page.evaluate(\n      this._generateContentScript('hcaptcha', 'findRecaptchas')\n    )) as any\n\n    const filterResults = this._filterRecaptchas(resultRecaptcha.captchas)\n    this.debug(\n      `Filter results: ${filterResults.filtered.length} of ${filterResults.captchas.length} captchas filtered from results.`\n    )\n\n    const response: types.FindRecaptchasResult = {\n      captchas: [...filterResults.captchas, ...resultHcaptcha.captchas],\n      filtered: filterResults.filtered,\n      error: resultRecaptcha.error || resultHcaptcha.error\n    }\n    this.debug('findRecaptchas', response)\n    if (this.opts.throwOnError && response.error) {\n      throw new Error(response.error)\n    }\n    return response\n  }\n\n  async getRecaptchaSolutions(\n    captchas: types.CaptchaInfo[],\n    provider?: types.SolutionProvider\n  ) {\n    this.debug('getRecaptchaSolutions', { captchaNum: captchas.length })\n    provider = provider || this.opts.provider\n    if (\n      !provider ||\n      (!provider.token && !provider.fn) ||\n      (provider.token && provider.token === 'XXXXXXX' && !provider.fn)\n    ) {\n      throw new Error('Please provide a solution provider to the plugin.')\n    }\n    let fn = provider.fn\n    if (!fn) {\n      const builtinProvider = BuiltinSolutionProviders.find(\n        p => p.id === (provider || {}).id\n      )\n      if (!builtinProvider || !builtinProvider.fn) {\n        throw new Error(\n          `Cannot find builtin provider with id '${provider.id}'.`\n        )\n      }\n      fn = builtinProvider.fn\n    }\n    const response = await fn.call(\n      this,\n      captchas,\n      provider.token,\n      provider.opts || {}\n    )\n    response.error =\n      response.error ||\n      response.solutions.find((s: types.CaptchaSolution) => !!s.error)\n    this.debug('getRecaptchaSolutions', response)\n    if (response && response.error) {\n      console.warn(\n        'PuppeteerExtraPluginRecaptcha: An error occured during \"getRecaptchaSolutions\":',\n        response.error\n      )\n    }\n    if (this.opts.throwOnError && response.error) {\n      throw new Error(response.error)\n    }\n    return response\n  }\n\n  async enterRecaptchaSolutions(\n    page: Page | Frame,\n    solutions: types.CaptchaSolution[]\n  ) {\n    this.debug('enterRecaptchaSolutions', { solutions })\n\n    const hasRecaptcha = !!solutions.find(s => s._vendor === 'recaptcha')\n    const solvedRecaptcha: types.EnterRecaptchaSolutionsResult = hasRecaptcha\n      ? ((await page.evaluate(\n          this._generateContentScript('recaptcha', 'enterRecaptchaSolutions', {\n            solutions\n          })\n        )) as any)\n      : { solved: [] }\n    const hasHcaptcha = !!solutions.find(s => s._vendor === 'hcaptcha')\n    const solvedHcaptcha: types.EnterRecaptchaSolutionsResult = hasHcaptcha\n      ? ((await page.evaluate(\n          this._generateContentScript('hcaptcha', 'enterRecaptchaSolutions', {\n            solutions\n          })\n        )) as any)\n      : { solved: [] }\n\n    const response: types.EnterRecaptchaSolutionsResult = {\n      solved: [...solvedRecaptcha.solved, ...solvedHcaptcha.solved],\n      error: solvedRecaptcha.error || solvedHcaptcha.error\n    }\n    response.error = response.error || response.solved.find(s => !!s.error)\n    this.debug('enterRecaptchaSolutions', response)\n    if (this.opts.throwOnError && response.error) {\n      throw new Error(response.error)\n    }\n    return response\n  }\n\n  async solveRecaptchas(\n    page: Page | Frame\n  ): Promise<types.SolveRecaptchasResult> {\n    this.debug('solveRecaptchas')\n    const response: types.SolveRecaptchasResult = {\n      captchas: [],\n      filtered: [],\n      solutions: [],\n      solved: [],\n      error: null\n    }\n    try {\n      // If `this.opts.throwOnError` is set any of the\n      // following will throw and abort execution.\n      const {\n        captchas,\n        filtered,\n        error: captchasError\n      } = await this.findRecaptchas(page)\n      response.captchas = captchas\n      response.filtered = filtered\n\n      if (captchas.length) {\n        const {\n          solutions,\n          error: solutionsError\n        } = await this.getRecaptchaSolutions(response.captchas)\n        response.solutions = solutions\n\n        const {\n          solved,\n          error: solvedError\n        } = await this.enterRecaptchaSolutions(page, response.solutions)\n        response.solved = solved\n\n        response.error = captchasError || solutionsError || solvedError\n      }\n    } catch (error) {\n      response.error = error.toString()\n    }\n    this.debug('solveRecaptchas', response)\n    if (this.opts.throwOnError && response.error) {\n      throw new Error(response.error)\n    }\n    return response\n  }\n\n  private _addCustomMethods(prop: Page | Frame) {\n    prop.findRecaptchas = async () => this.findRecaptchas(prop)\n    prop.getRecaptchaSolutions = async (\n      captchas: types.CaptchaInfo[],\n      provider?: types.SolutionProvider\n    ) => this.getRecaptchaSolutions(captchas, provider)\n    prop.enterRecaptchaSolutions = async (solutions: types.CaptchaSolution[]) =>\n      this.enterRecaptchaSolutions(prop, solutions)\n    // Add convenience methods that wraps all others\n    prop.solveRecaptchas = async () => this.solveRecaptchas(prop)\n  }\n\n  async onPageCreated(page: Page) {\n    this.debug('onPageCreated', page.url())\n    // Make sure we can run our content script\n    await page.setBypassCSP(true)\n\n    // Add custom page methods\n    this._addCustomMethods(page)\n\n    // Add custom methods to potential frames as well\n    page.on('frameattached', frame => {\n      if (!frame) return\n      this._addCustomMethods(frame)\n    })\n  }\n\n  /** Add additions to already existing pages and frames */\n  async onBrowser(browser: Browser) {\n    const pages = await browser.pages()\n    for (const page of pages) {\n      this._addCustomMethods(page)\n      for (const frame of page.mainFrame().childFrames()) {\n        this._addCustomMethods(frame)\n      }\n    }\n  }\n}\n\n/** Default export, PuppeteerExtraPluginRecaptcha  */\nconst defaultExport = (options?: Partial<types.PluginOptions>) => {\n  return new PuppeteerExtraPluginRecaptcha(options || {})\n}\n\nexport default defaultExport\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/playwright-mods.d.ts",
    "content": "// Extend Playwright interfaces transparently to the end user.\nimport {} from 'playwright-core'\n\nimport { RecaptchaPluginPageAdditions } from './types'\n\ndeclare module 'playwright-core' {\n  interface Page extends RecaptchaPluginPageAdditions {}\n  interface Frame extends RecaptchaPluginPageAdditions {}\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/provider/2captcha-api.ts",
    "content": "// https://github.com/bochkarev-artem/2captcha/blob/master/index.js\n// TODO: Create our own API wrapper\n\nvar https = require('https')\nvar url = require('url')\nvar querystring = require('querystring')\n\nvar apiKey\nvar apiInUrl = 'https://2captcha.com/in.php'\nvar apiResUrl = 'https://2captcha.com/res.php'\nvar apiMethod = 'base64'\nvar SOFT_ID = '2589'\n\nvar defaultOptions = {\n  pollingInterval: 2000,\n  retries: 3\n}\n\nfunction pollCaptcha(captchaId, options, invalid, callback) {\n  invalid = invalid.bind({ options: options, captchaId: captchaId })\n  var intervalId = setInterval(function() {\n    var httpsRequestOptions = url.parse(\n      apiResUrl +\n        '?action=get&soft_id=' +\n        SOFT_ID +\n        '&key=' +\n        apiKey +\n        '&id=' +\n        captchaId\n    )\n    var request = https.request(httpsRequestOptions, function(response) {\n      var body = ''\n\n      response.on('data', function(chunk) {\n        body += chunk\n      })\n\n      response.on('end', function() {\n        if (body === 'CAPCHA_NOT_READY') {\n          return\n        }\n\n        clearInterval(intervalId)\n\n        var result = body.split('|')\n        if (result[0] !== 'OK') {\n          callback(result[0]) //error\n        } else {\n          callback(\n            null,\n            {\n              id: captchaId,\n              text: result[1]\n            },\n            invalid\n          )\n        }\n        callback = function() {} // prevent the callback from being called more than once, if multiple https requests are open at the same time.\n      })\n    })\n    request.on('error', function(e) {\n      request.destroy()\n      callback(e)\n    })\n    request.end()\n  }, options.pollingInterval || defaultOptions.pollingInterval)\n}\n\nexport const setApiKey = function(key) {\n  apiKey = key\n}\n\nexport const decode = function(base64, options, callback) {\n  if (!callback) {\n    callback = options\n    options = defaultOptions\n  }\n  var httpsRequestOptions = url.parse(apiInUrl)\n  httpsRequestOptions.method = 'POST'\n\n  var postData = {\n    method: apiMethod,\n    key: apiKey,\n    soft_id: SOFT_ID,\n    body: base64\n  }\n\n  postData = querystring.stringify(postData)\n\n  var request = https.request(httpsRequestOptions, function(response) {\n    var body = ''\n\n    response.on('data', function(chunk) {\n      body += chunk\n    })\n\n    response.on('end', function() {\n      var result = body.split('|')\n      if (result[0] !== 'OK') {\n        return callback(result[0])\n      }\n\n      pollCaptcha(\n        result[1],\n        options,\n        function(error) {\n          var callbackToInitialCallback = callback\n\n          report(this.captchaId)\n\n          if (error) {\n            return callbackToInitialCallback('CAPTCHA_FAILED')\n          }\n\n          if (!this.options.retries) {\n            this.options.retries = defaultOptions.retries\n          }\n          if (this.options.retries > 1) {\n            this.options.retries = this.options.retries - 1\n            decode(base64, this.options, callback)\n          } else {\n            callbackToInitialCallback('CAPTCHA_FAILED_TOO_MANY_TIMES')\n          }\n        },\n        callback\n      )\n    })\n  })\n  request.on('error', function(e) {\n    request.destroy()\n    callback(e)\n  })\n\n  request.write(postData)\n  request.end()\n}\n\nexport const decodeReCaptcha = function(\n  captchaMethod,\n  captcha,\n  pageUrl,\n  extraData,\n  options,\n  callback\n) {\n  if (!callback) {\n    callback = options\n    options = defaultOptions\n  }\n  var httpsRequestOptions = url.parse(apiInUrl)\n  httpsRequestOptions.method = 'POST'\n\n  var postData = {\n    method: captchaMethod,\n    key: apiKey,\n    soft_id: SOFT_ID,\n    // googlekey: captcha,\n    pageurl: pageUrl,\n    ...extraData\n  }\n  if (captchaMethod === 'userrecaptcha') {\n    postData.googlekey = captcha\n  }\n  if (captchaMethod === 'hcaptcha') {\n    postData.sitekey = captcha\n  }\n\n  postData = querystring.stringify(postData)\n\n  var request = https.request(httpsRequestOptions, function(response) {\n    var body = ''\n\n    response.on('data', function(chunk) {\n      body += chunk\n    })\n\n    response.on('end', function() {\n      var result = body.split('|')\n      if (result[0] !== 'OK') {\n        return callback(result[0])\n      }\n\n      pollCaptcha(\n        result[1],\n        options,\n        function(error) {\n          var callbackToInitialCallback = callback\n\n          report(this.captchaId)\n\n          if (error) {\n            return callbackToInitialCallback('CAPTCHA_FAILED')\n          }\n\n          if (!this.options.retries) {\n            this.options.retries = defaultOptions.retries\n          }\n          if (this.options.retries > 1) {\n            this.options.retries = this.options.retries - 1\n            decodeReCaptcha(\n              captchaMethod,\n              captcha,\n              pageUrl,\n              extraData,\n              this.options,\n              callback\n            )\n          } else {\n            callbackToInitialCallback('CAPTCHA_FAILED_TOO_MANY_TIMES')\n          }\n        },\n        callback\n      )\n    })\n  })\n  request.on('error', function(e) {\n    request.destroy()\n    callback(e)\n  })\n  request.write(postData)\n  request.end()\n}\n\nexport const decodeUrl = function(uri, options, callback) {\n  if (!callback) {\n    callback = options\n    options = defaultOptions\n  }\n\n  var options = url.parse(uri)\n\n  var request = https.request(options, function(response) {\n    var body = ''\n    response.setEncoding('base64')\n\n    response.on('data', function(chunk) {\n      body += chunk\n    })\n\n    response.on('end', function() {\n      decode(body, options, callback)\n    })\n  })\n  request.on('error', function(e) {\n    request.destroy()\n    callback(e)\n  })\n  request.end()\n}\n\nexport const solveRecaptchaFromHtml = function(html, options, callback) {\n  if (!callback) {\n    callback = options\n    options = defaultOptions\n  }\n  var googleUrl = html.split('/challenge?k=')\n  if (googleUrl.length < 2) return callback('No captcha found in html')\n  googleUrl = googleUrl[1]\n  googleUrl = googleUrl.split('\"')[0]\n  googleUrl = googleUrl.split(\"'\")[0]\n  googleUrl = 'https://www.google.com/recaptcha/api/challenge?k=' + googleUrl\n\n  var httpsRequestOptions = url.parse(googleUrl)\n\n  var request = https.request(httpsRequestOptions, function(response) {\n    var body = ''\n    response.on('data', function(chunk) {\n      body += chunk\n    })\n\n    response.on('end', function() {\n      var challengeArr = body.split(\"'\")\n      if (!challengeArr[1]) return callback('Parsing captcha failed')\n      var challenge = challengeArr[1]\n      if (challenge.length === 0) return callback('Parsing captcha failed')\n\n      decodeUrl(\n        'https://www.google.com/recaptcha/api/image?c=' + challenge,\n        options,\n        function(error, result, invalid) {\n          if (result) {\n            result.challenge = challenge\n          }\n          callback(error, result, invalid)\n        }\n      )\n    })\n  })\n  request.end()\n}\n\nexport const report = function(captchaId) {\n  var reportUrl =\n    apiResUrl +\n    '?action=reportbad&soft_id=' +\n    SOFT_ID +\n    '&key=' +\n    apiKey +\n    '&id=' +\n    captchaId\n  var options = url.parse(reportUrl)\n\n  var request = https.request(options, function(response) {\n    // var body = ''\n    // response.on('data', function(chunk) {\n    //   body += chunk\n    // })\n    // response.on('end', function() {})\n  })\n  request.end()\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/provider/2captcha.ts",
    "content": "export const PROVIDER_ID = '2captcha'\nimport * as types from '../types'\n\nimport Debug from 'debug'\nconst debug = Debug(`puppeteer-extra-plugin:recaptcha:${PROVIDER_ID}`)\n\n// const solver = require('./2captcha-api')\nimport * as solver from './2captcha-api'\n\nconst secondsBetweenDates = (before: Date, after: Date) =>\n  (after.getTime() - before.getTime()) / 1000\n\nexport interface DecodeRecaptchaAsyncResult {\n  err?: any\n  result?: any\n  invalid?: any\n}\n\nexport interface TwoCaptchaProviderOpts {\n  useEnterpriseFlag?: boolean\n  useActionValue?: boolean\n}\n\nconst providerOptsDefaults: TwoCaptchaProviderOpts = {\n  useEnterpriseFlag: false, // Seems to make solving chance worse?\n  useActionValue: true\n}\n\nasync function decodeRecaptchaAsync(\n  token: string,\n  vendor: types.CaptchaVendor,\n  sitekey: string,\n  url: string,\n  extraData: any,\n  opts = { pollingInterval: 2000 }\n): Promise<DecodeRecaptchaAsyncResult> {\n  return new Promise(resolve => {\n    const cb = (err: any, result: any, invalid: any) =>\n      resolve({ err, result, invalid })\n    try {\n      solver.setApiKey(token)\n\n      let method = 'userrecaptcha'\n      if (vendor === 'hcaptcha') {\n        method = 'hcaptcha'\n      }\n      solver.decodeReCaptcha(method, sitekey, url, extraData, opts, cb)\n    } catch (error) {\n      return resolve({ err: error })\n    }\n  })\n}\n\nexport async function getSolutions(\n  captchas: types.CaptchaInfo[] = [],\n  token: string = '',\n  opts: TwoCaptchaProviderOpts = {}\n): Promise<types.GetSolutionsResult> {\n  opts = { ...providerOptsDefaults, ...opts }\n  const solutions = await Promise.all(\n    captchas.map(c => getSolution(c, token, opts))\n  )\n  return { solutions, error: solutions.find(s => !!s.error) }\n}\n\nasync function getSolution(\n  captcha: types.CaptchaInfo,\n  token: string,\n  opts: TwoCaptchaProviderOpts\n): Promise<types.CaptchaSolution> {\n  const solution: types.CaptchaSolution = {\n    _vendor: captcha._vendor,\n    provider: PROVIDER_ID\n  }\n  try {\n    if (!captcha || !captcha.sitekey || !captcha.url || !captcha.id) {\n      throw new Error('Missing data in captcha')\n    }\n    solution.id = captcha.id\n    solution.requestAt = new Date()\n    debug('Requesting solution..', solution)\n    const extraData = {}\n    if (captcha.s) {\n      extraData['data-s'] = captcha.s // google site specific property\n    }\n    if (opts.useActionValue && captcha.action) {\n      extraData['action'] = captcha.action // Optional v3/enterprise action\n    }\n    if (opts.useEnterpriseFlag && captcha.isEnterprise) {\n      extraData['enterprise'] = 1\n    }\n    \n    if (process.env['2CAPTCHA_PROXY_TYPE'] && process.env['2CAPTCHA_PROXY_ADDRESS']) {\n         extraData['proxytype'] = process.env['2CAPTCHA_PROXY_TYPE'].toUpperCase()\n         extraData['proxy'] = process.env['2CAPTCHA_PROXY_ADDRESS']\n    }\n      \n    const { err, result, invalid } = await decodeRecaptchaAsync(\n      token,\n      captcha._vendor,\n      captcha.sitekey,\n      captcha.url,\n      extraData\n    )\n    debug('Got response', { err, result, invalid })\n    if (err) throw new Error(`${PROVIDER_ID} error: ${err}`)\n    if (!result || !result.text || !result.id) {\n      throw new Error(`${PROVIDER_ID} error: Missing response data: ${result}`)\n    }\n    solution.providerCaptchaId = result.id\n    solution.text = result.text\n    solution.responseAt = new Date()\n    solution.hasSolution = !!solution.text\n    solution.duration = secondsBetweenDates(\n      solution.requestAt,\n      solution.responseAt\n    )\n  } catch (error) {\n    debug('Error', error)\n    solution.error = error.toString()\n  }\n  return solution\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/puppeteer-mods.d.ts",
    "content": "// Extend Puppeteer interfaces transparently to the end user.\n\n// Note, we need to manually copy this file into the build dir (yarn ambient-dts): https://stackoverflow.com/questions/56018167\n// Note2: It's not sufficient to just copy over this d.ts file, it needs to be referenced by another .ts file!\n// Note3: To make it even more urgh the TS compiler will change the reference import path, hence we need to fix that in the end as well\n\n// This import statement is important for all this to work, otherwise we don't extend but replace the puppeteer module definition.\n// https://github.com/microsoft/TypeScript/issues/10859\nimport {} from 'puppeteer'\n\nimport { RecaptchaPluginPageAdditions } from './types'\n\ndeclare module 'puppeteer' {\n  interface Page extends RecaptchaPluginPageAdditions {}\n  interface Frame extends RecaptchaPluginPageAdditions {}\n}\n\ndeclare module 'puppeteer-core' {\n  interface Page extends RecaptchaPluginPageAdditions {}\n  interface Frame extends RecaptchaPluginPageAdditions {}\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/solve.test.ts",
    "content": "import test from 'ava'\n\nimport RecaptchaPlugin from './index'\n\nimport { addExtra } from 'puppeteer-extra'\n\nconst PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest('will solve reCAPTCHAs', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url = 'https://www.google.com/recaptcha/api2/demo'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const result = await (page as any).solveRecaptchas()\n\n  const { captchas, solutions, solved, error } = result\n  t.falsy(error)\n\n  t.is(captchas.length, 1)\n  t.is(solutions.length, 1)\n  t.is(solved.length, 1)\n  t.is(solved[0]._vendor, 'recaptcha')\n  t.is(solved[0].isSolved, true)\n\n  await browser.close()\n})\n\ntest('will solve hCAPTCHAs', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const urls = [\n    'https://accounts.hcaptcha.com/demo',\n    'http://democaptcha.com/demo-form-eng/hcaptcha.html',\n  ]\n\n  for (const url of urls) {\n    await page.goto(url, { waitUntil: 'networkidle0' })\n\n    const result = await (page as any).solveRecaptchas()\n    const { captchas, solutions, solved, error } = result\n    t.falsy(error)\n\n    t.is(captchas.length, 1)\n    t.is(solutions.length, 1)\n    t.is(solved.length, 1)\n    t.is(solved[0]._vendor, 'hcaptcha')\n    t.is(solved[0].isSolved, true)\n  }\n\n  await browser.close()\n})\n\ntest('will solve reCAPTCHA enterprise', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN,\n      opts: {\n        useEnterpriseFlag: false // Not sure but using the enterprise flag makes it worse\n      }\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url =\n    'https://berstend.github.io/static/recaptcha/enterprise-checkbox-explicit.html'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const result = await (page as any).solveRecaptchas()\n\n  const { captchas, solutions, solved, error } = result\n  t.falsy(error)\n\n  t.is(captchas.length, 1)\n  t.is(solutions.length, 1)\n  t.is(solved.length, 1)\n  t.is(solved[0]._vendor, 'recaptcha')\n  t.is(solved[0].isSolved, true)\n\n  await browser.close()\n})\n\ntest('will solve multiple reCAPTCHAs', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN,\n      opts: {\n        useEnterpriseFlag: false // Not sure but using the enterprise flag makes it worse\n      }\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-checkbox-explicit-multi.html'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  page.on('dialog', async dialog => {\n    dialog.dismiss() // the test page has blocking `alert`s\n  })\n\n  const result = await (page as any).solveRecaptchas()\n\n  const { captchas, solutions, solved, error } = result\n  t.falsy(error)\n\n  t.is(captchas.length, 3)\n  t.is(solutions.length, 3)\n  t.is(solved.length, 3)\n  t.is(solved[0]._vendor, 'recaptcha')\n  t.is(solved[0].isSolved, true)\n\n  await browser.close()\n})\n\ntest('will not solve inactive invisible reCAPTCHAs by default', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url =\n    'https://berstend.github.io/static/recaptcha/v2-invisible-auto.html'\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const result = await (page as any).solveRecaptchas()\n\n  const { captchas, solutions, solved, error } = result\n  t.falsy(error)\n\n  t.is(captchas.length, 0)\n  t.is(solutions.length, 0)\n  t.is(solved.length, 0)\n\n  await browser.close()\n})\n\ntest('will not solve score based reCAPTCHAs by default', async t => {\n  if (!process.env.TWOCAPTCHA_TOKEN) {\n    t.truthy('foo')\n    console.log('TWOCAPTCHA_TOKEN not set, skipping test.')\n    return\n  }\n\n  const puppeteer = addExtra(require('puppeteer'))\n  const recaptchaPlugin = RecaptchaPlugin({\n    provider: {\n      id: '2captcha',\n      token: process.env.TWOCAPTCHA_TOKEN\n    }\n  })\n  puppeteer.use(recaptchaPlugin)\n\n  const browser = await puppeteer.launch({\n    args: PUPPETEER_ARGS,\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  const url = 'https://berstend.github.io/static/recaptcha/v3-programmatic.html'\n\n  await page.goto(url, { waitUntil: 'networkidle0' })\n\n  const result = await (page as any).solveRecaptchas()\n\n  const { captchas, solutions, solved, error } = result\n  t.falsy(error)\n\n  t.is(captchas.length, 0)\n  t.is(solutions.length, 0)\n  t.is(solved.length, 0)\n\n  await browser.close()\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/src/types.ts",
    "content": "/// <reference path=\"./puppeteer-mods.d.ts\" />\n/// <reference path=\"./playwright-mods.d.ts\" />\n// Warn: The above is EXTREMELY important for our custom page mods to be recognized by the end users typescript!\n\n/**\n * Extend window object with recaptcha things\n */\ndeclare global {\n  interface Window {\n    __google_recaptcha_client?: boolean\n    ___grecaptcha_cfg?: {\n      clients?: any\n    }\n  }\n}\n\nexport type RecaptchaPluginPageAdditions = {\n  /** Attempt to find all reCAPTCHAs on this page. */\n  findRecaptchas: () => Promise<FindRecaptchasResult>\n\n  getRecaptchaSolutions: (\n    captchas: CaptchaInfo[],\n    provider?: SolutionProvider\n  ) => Promise<GetSolutionsResult>\n\n  enterRecaptchaSolutions: (\n    solutions: CaptchaSolution[]\n  ) => Promise<EnterRecaptchaSolutionsResult>\n\n  /** Attempt to detect and solve reCAPTCHAs on this page automatically. 🔮 */\n  solveRecaptchas: () => Promise<SolveRecaptchasResult>\n}\n\nexport interface SolutionProvider<TOpts = any> {\n  id?: string\n  token?: string\n  fn?: (captchas: CaptchaInfo[], token?: string) => Promise<GetSolutionsResult>\n  opts?: TOpts // Optional options ;-)\n}\n\nexport interface FindRecaptchasResult {\n  captchas: CaptchaInfo[]\n  filtered: FilteredCaptcha[]\n  error?: any\n}\nexport interface EnterRecaptchaSolutionsResult {\n  solved: CaptchaSolved[]\n  error?: any\n}\nexport interface GetSolutionsResult {\n  solutions: CaptchaSolution[]\n  error?: any\n}\n\nexport type SolveRecaptchasResult = FindRecaptchasResult &\n  EnterRecaptchaSolutionsResult &\n  GetSolutionsResult\n\nexport type CaptchaVendor = 'recaptcha' | 'hcaptcha'\n\nexport type CaptchaType = 'checkbox' | 'invisible' | 'score'\n\nexport interface CaptchaInfo {\n  _vendor: CaptchaVendor\n  id?: string // captcha id\n  widgetId?: number\n  sitekey?: string\n  s?: string // new google site specific property\n  isEnterprise?: boolean\n  isInViewport?: boolean\n  /** Is captcha invisible */\n  isInvisible?: boolean\n  /** Invisible recaptchas: Does the captcha have an active challenge popup */\n  hasActiveChallengePopup?: boolean\n  /** Invisible recaptchas: Can the captcha trigger a challenge or is it purely score based (v3) */\n  hasChallengeFrame?: boolean\n  _type?: CaptchaType\n  action?: string // Optional action (v3/enterprise): https://developers.google.com/recaptcha/docs/v3#actions\n  callback?: string | Function\n  hasResponseElement?: boolean\n  url?: string\n  display?: {\n    size?: string\n    theme?: string\n    top?: string\n    left?: string\n    width?: string\n    height?: string\n  }\n}\n\nexport type FilteredCaptcha = CaptchaInfo & {\n  filtered: boolean\n  filteredReason:\n    | 'solveInViewportOnly'\n    | 'solveScoreBased'\n    | 'solveInactiveChallenges'\n}\n\nexport interface CaptchaSolution {\n  _vendor: CaptchaVendor\n  id?: string // captcha id\n  provider?: string\n  providerCaptchaId?: string\n  text?: string // the solution\n  requestAt?: Date\n  responseAt?: Date\n  duration?: number\n  error?: string | Error\n  hasSolution?: boolean\n}\n\nexport interface CaptchaSolved {\n  _vendor: CaptchaVendor\n  id?: string // captcha id\n  responseElement?: boolean\n  responseCallback?: boolean\n  solvedAt?: Date\n  error?: string | Error\n  isSolved?: boolean\n}\n\nexport interface PluginOptions {\n  /** Visualize reCAPTCHAs based on their state */\n  visualFeedback: boolean\n  /** Throw on errors instead of returning them in the error property */\n  throwOnError: boolean\n\n  /** Only solve captchas and challenges visible in the viewport */\n  solveInViewportOnly: boolean\n  /** Solve invisible captchas used to acquire a score and not present a challenge (e.g. reCAPTCHA v3) */\n  solveScoreBased: boolean\n  /** Solve invisible captchas that have no active challenge */\n  solveInactiveChallenges: boolean\n\n  provider?: SolutionProvider\n}\n\nexport interface ContentScriptOpts {\n  visualFeedback: boolean\n  debugBinding?: string\n}\n\nexport interface ContentScriptData {\n  solutions?: CaptchaSolution[]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"es2019\", \"dom\"],\n    // \"noResolve\": true, // Important: Otherwise TS would rewrite our ambient d.ts file locations (see: yarn copy-dts) :(\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"strict\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": false,\n    \"pretty\": true,\n    \"stripInternal\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"./src/**/*.tsx\",\n    \"./src/**/*.ts\",\n    \"./src/**/*.d.ts\",\n    \"./src/**/*.test.ts\",\n    \"./test/**/*.ts\",\n    \"src/provider/2captcha-api.js\"\n  ],\n  \"exclude\": [\"node_modules\", \"dist\", \"./test/**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-recaptcha/tslint.json",
    "content": "{\n  \"extends\": [\"tslint-config-standard\", \"tslint-config-prettier\"],\n  \"rules\": {\n    \"ordered-imports\": true\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/index.d.ts",
    "content": "/// <reference types=\"node\" />\n\nimport { EventEmitter } from 'events';\nimport 'puppeteer';\nimport { PuppeteerExtraPlugin, PluginOptions } from 'puppeteer-extra-plugin';\n\n// augment repl() for Page/Browser\n\ndeclare module 'puppeteer' {\n    export interface Page extends EventEmitter, FrameBase {\n        repl(): Promise<void>;\n    }\n\n    export interface Browser extends EventEmitter, TargetAwaiter {\n        repl(): Promise<void>;\n    }\n}\n\n/**\n * Create an interactive REPL for the provided object.\n * Uses an extended (colorized) readline interface under the hood. Will resolve the returned Promise when the readline interface is closed.\n * If opts.addToPuppeteerClass is true (default) then page.repl()/browser.repl() will point to this method, for convenience.\n * Can be used standalone as well, to inspect an arbitrary class instance or object.\n */\ndeclare function repl(config?: Options): Plugin;\n\ndeclare interface Options extends DefaultOptions, PluginOptions {}\n\ndeclare interface DefaultOptions {\n    /**\n     * If a .repl() method should be attached to Puppeteer Page and Browser instances\n     * @default true\n     */\n    addToPuppeteerClass?: boolean;\n}\n\ndeclare class Plugin extends PuppeteerExtraPlugin {\n    get name(): 'repl';\n    get defaults(): DefaultOptions;\n    repl(obj: any): Promise<void>;\n}\n\nexport = repl;\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\nconst REPLSession = require('./lib/REPLSession')\n\n/**\n * Interrupt your puppeteer code with an interactive REPL.\n *\n * Features tab auto-completion for the given object properties and a colorized prompt.\n *\n * Works with arbitrary objects ands class instances, though `Page` & `Browser` make the most sense. :-)\n *\n * **opts**\n * @param {Object} opts - Options\n * @param {boolean} [opts.addToPuppeteerClass] - If a `.repl()` method should be attached to Puppeteer `Page` and `Browser` instances (default: true).\n *\n * @todo enumerate instance members differently, so e.g. clickAndWaitForNavigation shows up.\n *\n * @example\n * // In this example we don't extend the native puppeteer classes\n *\n * const puppeteer = require('puppeteer-extra')\n * const repl = require('puppeteer-extra-plugin-repl')({ addToPuppeteerClass: false })\n * puppeteer.use(repl)\n *\n * puppeteer.launch({ headless: true }).then(async browser => {\n *   const page = await browser.newPage()\n *   await page.goto('https://example.com')\n *\n *   // Start an interactive REPL here with the `page` instance.\n *   await repl.repl(page)\n *   // Afterwards start REPL with the `repl` instance itself. 🐴\n *   await repl.repl(repl)\n *\n *   await browser.close()\n * })\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'repl'\n  }\n\n  get defaults() {\n    return { addToPuppeteerClass: true }\n  }\n\n  /**\n   * Run last so other plugins can extend e.g. Page :-)\n   *\n   * @ignore\n   */\n  get requirements() {\n    return new Set(['runLast'])\n  }\n\n  /**\n   * Create an interactive REPL for the provided object.\n   *\n   * Uses an extended (colorized) readline interface under the hood.\n   * Will resolve the returned Promise when the readline interface is closed.\n   *\n   * If `opts.addToPuppeteerClass` is true (default) then `page.repl()`/`browser.repl()`\n   * will point to this method, for convenience.\n   *\n   * Can be used standalone as well, to inspect an arbitrary class instance or object.\n   *\n   * @param  {Object} obj - An object or class instance to use in the repl (e.g. `page`, `browser`)\n   * @return {Promise}\n   *\n   * @example\n   * const repl = require('puppeteer-extra-plugin-repl')()\n   * await repl.repl(<object or class instance to inspect>)\n   */\n  async repl(obj) {\n    return new REPLSession({ obj }).start()\n  }\n\n  /**\n   * Conditionally add a .repl() method to `page` and `browser` instances.\n   *\n   * @ignore\n   */\n  async onPageCreated(page) {\n    if (!this.opts.addToPuppeteerClass) {\n      return\n    }\n    page.repl = () => this.repl(page)\n    const browser = page.browser()\n    browser.repl = () => this.repl(browser)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/index.test.js",
    "content": "'use strict'\n\nconst PLUGIN_NAME = 'repl'\n\nconst test = require('ava')\n\nconst Plugin = require('.')\n\ntest('is a function', async t => {\n  t.is(typeof Plugin, 'function')\n})\n\ntest('should have the basic class members', async t => {\n  const instance = new Plugin()\n  t.is(instance.name, PLUGIN_NAME)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have the public child class members', async t => {\n  const instance = new Plugin()\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('name'))\n  t.true(childClassMembers.includes('defaults'))\n  t.true(childClassMembers.includes('requirements'))\n  t.true(childClassMembers.includes('repl'))\n  t.true(childClassMembers.includes('onPageCreated'))\n  t.true(childClassMembers.length === 6)\n})\n\ntest('should have opts with default values', async t => {\n  const instance = new Plugin()\n  const opts = instance.opts\n\n  t.is(opts.addToPuppeteerClass, true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/lib/REPLSession.js",
    "content": "const ow = require('ow')\nconst readline = require('./super-readline')\n\nclass REPLSession {\n  constructor(opts) {\n    ow(opts, ow.object.hasKeys('obj'))\n    ow(opts.obj, ow.object.hasKeys('constructor'))\n\n    this._obj = opts.obj\n    this._meta = {\n      type: typeof this._obj,\n      name: this._obj.constructor.name,\n      members:\n        Object.getOwnPropertyNames(Object.getPrototypeOf(this._obj)) || []\n    }\n    this._completions = [].concat(this.extraMethods, this._meta.members)\n  }\n\n  get extraMethods() {\n    return ['inspect', 'exit']\n  }\n\n  async start() {\n    this._createInterface()\n    this._showIntro()\n    this._rl.prompt()\n    return this._closePromise\n  }\n\n  _createInterface() {\n    this._rl = readline.createInterface({\n      input: process.stdin,\n      output: process.stdout,\n      prompt: this._meta.name ? `> ${this._meta.name.toLowerCase()}.` : `> `,\n      completer: readline.defaultCompleter(this._completions),\n      colors: {\n        prompt: readline.chalk.cyan,\n        completer: readline.chalk.yellow\n      }\n    })\n    this._rl.on('line', this._onLineInput.bind(this))\n    this._closePromise = new Promise(resolve =>\n      this._rl.once('close', () => resolve())\n    )\n  }\n\n  _showIntro() {\n    console.log(`\n      Started puppeteer-extra repl for ${this._meta.type} '${this._meta.name}' with ${this._meta.members.length} properties.\n\n        - Type 'inspect' to return the current ${this._meta.type}.\n        - Type 'exit' to leave the repl.\n\n      Tab auto-completion available:\n    `)\n    this._rl.showTabCompletions()\n  }\n\n  async _onLineInput(line) {\n    if (!line) {\n      return this._rl.prompt()\n    }\n    if (line === 'exit') {\n      return this._rl.close()\n    }\n\n    const cmd = line === 'inspect' ? this._obj : `this._obj.${line}`\n    await this._evalAsync(cmd)\n    this._rl.prompt()\n  }\n\n  async _evalAsync(cmd) {\n    try {\n      // eslint-disable-next-line no-eval\n      const out = await eval(cmd)\n      console.log(out)\n    } catch (err) {\n      console.warn(err)\n    }\n  }\n}\n\nmodule.exports = REPLSession\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/lib/REPLSession.test.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst REPLSession = require('./REPLSession')\n\ntest('is a function', async t => {\n  t.is(typeof REPLSession, 'function')\n})\n\ntest('is a class', async t => {\n  t.is(REPLSession.constructor.name, 'Function')\n})\n\ntest('will throw without opts', async t => {\n  const error = await t.throws(() => new REPLSession())\n  t.is(\n    error.message,\n    'Expected argument to be of type `object` but received type `undefined`'\n  )\n})\n\ntest('will throw when opts.obj is not a class derivative', async t => {\n  const error = await t.throws(() => new REPLSession({ obj: 'foobar' }))\n  t.is(\n    error.message,\n    'Expected argument to be of type `object` but received type `string`'\n  )\n})\n\ntest('should have the expected class members', async t => {\n  const FakeClass = class Foo {}\n  const opts = { obj: new FakeClass() }\n  const instance = new REPLSession(opts)\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('extraMethods'))\n  t.true(childClassMembers.includes('start'))\n  t.true(childClassMembers.includes('_createInterface'))\n  t.true(childClassMembers.includes('_showIntro'))\n  t.true(childClassMembers.includes('_onLineInput'))\n  t.true(childClassMembers.includes('_evalAsync'))\n  t.true(childClassMembers.length === 7)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/lib/super-readline.js",
    "content": "const chalk = require('chalk')\n\nconst {\n  Interface,\n  clearLine,\n  clearScreenDown,\n  cursorTo,\n  emitKeypressEvents,\n  moveCursor\n} = require('readline')\n\n/**\n * Extends the native readline interface with color support.\n *\n * A drop-in replacement for `readline`.\n *\n * Additionally accepts an options.color object with chalk colors\n * for `prompt` and `completer`.\n *\n * @todo this could be enhanced with auto complete hints in grey.\n * @todo similar to this: https://github.com/aantthony/node-color-readline\n *\n * @ignore\n *\n * @example\n * const readline = require('./super-readline')\n *\n * const rl = readline.createInterface({\n *   input: process.stdin,\n *   output: process.stdout,\n *   prompt: '> ',\n *   completer: readline.defaultCompleter([ 'bob', 'yolk' ]),\n *   colors: {\n *     prompt: readline.chalk.cyan,\n *     completer: readline.chalk.yellow\n *   }\n * })\n *\n * rl.prompt()\n */\nclass SuperInterface extends Interface {\n  constructor(options) {\n    super(options)\n    this._colors = options.colors || {}\n    this._writingTabComplete = false\n  }\n\n  _tabComplete(lastKeypressWasTab) {\n    this._writingTabComplete = true\n    super._tabComplete(lastKeypressWasTab)\n    this._writingTabComplete = false\n  }\n\n  showTabCompletions() {\n    this._tabComplete(true)\n  }\n\n  _writeToOutput(stringToWrite) {\n    // colorize prompt itself\n    const startsWithPrompt = stringToWrite.startsWith(this._prompt)\n    if (this._colors.prompt && startsWithPrompt) {\n      stringToWrite = `${this._colors.prompt(\n        this._prompt\n      )}${stringToWrite.replace(this._prompt, '')}`\n      return super._writeToOutput(stringToWrite)\n    }\n    // colorize completer output\n    if (this._colors.completer && this._writingTabComplete) {\n      return super._writeToOutput(this._colors.completer(stringToWrite))\n    }\n    // anything else\n    super._writeToOutput(stringToWrite)\n  }\n}\n\nconst createSuperInterface = function(options) {\n  return new SuperInterface(options)\n}\n\n/**\n * A typical default completer that can be used, for convenience.\n *\n * @ignore\n */\nconst defaultCompleter = completions => line => {\n  const hits = completions.filter(c => c.startsWith(line))\n  // show all completions if none found\n  const arr = hits.length ? hits : completions\n  return [arr, line]\n}\n\nmodule.exports = {\n  // customized exports:\n  chalk,\n  Interface: SuperInterface,\n  createInterface: createSuperInterface,\n  defaultCompleter,\n\n  // default readline exports:\n  clearLine,\n  clearScreenDown,\n  cursorTo,\n  emitKeypressEvents,\n  moveCursor\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/lib/super-readline.test.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\nconst readline = require('./super-readline')\n\ntest('is an object', async t => {\n  t.is(typeof readline, 'object')\n})\n\ntest('should have the expected number of exports', async t => {\n  const exportedKeys = Object.keys(readline)\n\n  t.true(exportedKeys.includes('chalk'))\n  t.true(exportedKeys.includes('Interface'))\n  t.true(exportedKeys.includes('createInterface'))\n  t.true(exportedKeys.includes('defaultCompleter'))\n  t.true(exportedKeys.includes('clearLine'))\n  t.true(exportedKeys.includes('clearScreenDown'))\n  t.true(exportedKeys.includes('cursorTo'))\n  t.true(exportedKeys.includes('emitKeypressEvents'))\n  t.true(exportedKeys.includes('moveCursor'))\n  t.is(exportedKeys.length, 9)\n})\n\ntest('can create an interface', async t => {\n  const instance = readline.createInterface({\n    input: process.stdin,\n    output: process.stdout,\n    prompt: '> ',\n    completer: readline.defaultCompleter(['bob', 'yolk']),\n    colors: {\n      prompt: readline.chalk.cyan,\n      completer: readline.chalk.yellow\n    }\n  })\n  t.is(instance.constructor.name, 'SuperInterface')\n  t.is(typeof instance, 'object')\n})\n\ntest('should have the extended class members', async t => {\n  const instance = readline.createInterface({\n    input: process.stdin,\n    output: process.stdout,\n    prompt: '> ',\n    completer: readline.defaultCompleter(['bob', 'yolk']),\n    colors: {\n      prompt: readline.chalk.cyan,\n      completer: readline.chalk.yellow\n    }\n  })\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('_tabComplete'))\n  t.true(childClassMembers.includes('_writeToOutput'))\n  t.true(childClassMembers.includes('showTabCompletions'))\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-repl\",\n  \"version\": \"2.3.3\",\n  \"description\": \"Start an interactive REPL in your puppeteer code.\",\n  \"main\": \"index.js\",\n  \"types\": \"index.d.ts\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"nswbmw & berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"repl\",\n    \"debug\",\n    \"interactive\",\n    \"puppeteer-debug\",\n    \"puppeteer-repl\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"mock-stdin\": \"^0.3.1\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"chalk\": \"^3.0.0\",\n    \"debug\": \"^4.1.1\",\n    \"ow\": \"^0.4.0\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\"\n  },\n  \"peerDependencies\": {\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/readme.md",
    "content": "# puppeteer-extra-plugin-repl\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n## Installation\n\n```bash\nyarn add puppeteer-extra-plugin-repl\n```\n\n## Purpose\n\n**Make quick puppeteer debugging and exploration fun with an interactive REPL.**\n\n-   Can interrupt your code at anytime to start an interactive [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) in your console.\n-   Adds convenience `.repl()` methods to `Page` and `Browser` instances.\n-   Supports inspecting arbitrary objects and instances.\n-   Features tab auto-completion for the available object properties and a colorized prompt.\n\n#### Kudos\n\n-   Inspired by [puppeteer-debug](https://github.com/nswbmw/puppeteer-debug) from [nswbmw](https://github.com/nswbmw), thanks!\n\n## REPL\n\n![repl](https://i.imgur.com/xeP7hEc.gif)\n\n## Quickstart\n\n```es6\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-repl')())\n\npuppeteer.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n  await page.goto('https://example.com')\n\n  // Start an interactive REPL here with the `page` instance.\n  await page.repl()\n  // Afterwards start REPL with the `browser` instance.\n  await browser.repl()\n\n  await browser.close()\n})\n```\n\nIn the REPL session (hit `tab` two times to see all available properties):\n\n```es6\n> page.url()\n// => https://example.com\n> page.click('a')\n> page.url()\n// => https://www.iana.org/domains/reserved\n> page.content()\n// => <!DOCTYPE html><html><head> ...\n> page.goto('https://google.com')\n> page.type('input', 'what is the answer to life the universe and everything')\n> page.click('input[type=submit]')\n> page.url()\n// => https://www.google.com/search?source=hp&ei=u9oXW5HpO8a ...\n> page.evaluate(() => document.querySelector('h3 a').textContent)\n// => Question 42 (The Impossible Quiz) - The Impossible Quiz Wiki - Fandom\n```\n\n-   Type `inspect` to return the current object.\n-   Type `exit` (or hit ctrl+c) to leave the repl.\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n    -   [repl](#repl)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-repl/index.js#L38-L83)\n\n**Extends: PuppeteerExtraPlugin**\n\nInterrupt your puppeteer code with an interactive REPL.\n\nFeatures tab auto-completion for the given object properties and a colorized prompt.\n\nWorks with arbitrary objects ands class instances, though `Page` & `Browser` make the most sense. :-)\n\n**opts**\n\nType: `function (opts)`\n\n-   `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n    -   `opts.addToPuppeteerClass` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** If a `.repl()` method should be attached to Puppeteer `Page` and `Browser` instances (default: true).\n\nExample:\n\n```javascript\n// In this example we don't extend the native puppeteer classes\n\nconst puppeteer = require('puppeteer-extra')\nconst repl = require('puppeteer-extra-plugin-repl')({ addToPuppeteerClass: false })\npuppeteer.use(repl)\n\npuppeteer.launch({ headless: true }).then(async browser => {\n  const page = await browser.newPage()\n  await page.goto('https://example.com')\n\n  // Start an interactive REPL here with the `page` instance.\n  await repl.repl(page)\n  // Afterwards start REPL with the `repl` instance itself. 🐴\n  await repl.repl(repl)\n\n  await browser.close()\n})\n```\n\n* * *\n\n#### [repl](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-repl/index.js#L70-L70)\n\nCreate an interactive REPL for the provided object.\n\nUses an extended (colorized) readline interface under the hood.\nWill resolve the returned Promise when the readline interface is closed.\n\nIf `opts.addToPuppeteerClass` is true (default) then `page.repl()`/`browser.repl()`\nwill point to this method, for convenience.\n\nCan be used standalone as well, to inspect an arbitrary class instance or object.\n\nType: `function (obj): Promise`\n\n-   `obj` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object or class instance to use in the repl (e.g. `page`, `browser`)\n\nExample:\n\n```javascript\nconst repl = require('puppeteer-extra-plugin-repl')()\nawait repl.repl(<object or class instance to inspect>)\n```\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-repl/test/headless.js",
    "content": "'use strict'\n\nconst test = require('ava')\n\n// const PUPPETEER_ARGS = ['--no-sandbox', '--disable-setuid-sandbox']\n\ntest.beforeEach(t => {\n  // Make sure we work with pristine modules\n  // delete require.cache[require.resolve('puppeteer-extra')]\n  // delete require.cache[require.resolve('puppeteer-extra-plugin-repl')]\n})\n\ntest('will create a repl', async t => {\n  t.pass()\n  // @TODO: This test is a little brittle and fails in CI sometimes.\n\n  // const stdin = require('mock-stdin').stdin()\n\n  // const puppeteer = require('puppeteer-extra')\n  // const repl = require('puppeteer-extra-plugin-repl')()\n  // puppeteer.use(repl)\n\n  // await puppeteer.launch({ args: PUPPETEER_ARGS }).then(async browser => {\n  //   const page = await browser.newPage()\n\n  //   // Mock stdout, there might be cleaner ways to do this :-)\n  //   let stdoutOutput = ''\n  //   const origStdout = process.stdout.write\n  //   process.stdout.write = (string, encoding, fd) => { stdoutOutput += string }\n  //   await Promise.all([\n  //     page.repl(),\n  //     stdin.send('url()'),\n  //     stdin.end()\n  //   ])\n  //   process.stdout.write = origStdout\n\n  //   t.true(stdoutOutput.includes(`Started puppeteer-extra repl for object 'Page' with`))\n  //   t.true(stdoutOutput.includes(`> page.`))\n  //   t.true(stdoutOutput.includes(`url()`))\n  //   t.true(stdoutOutput.includes(`about:blank`))\n\n  //   browser.close()\n  // })\n  // t.true(true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/.npmignore",
    "content": "stealthtests/\nrunall_stealthtests.sh"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_template/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Minimal stealth plugin template, not being used. :-)\n *\n * Feel free to copy this folder as the basis for additional detection evasion plugins.\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/_template'\n  }\n\n  async onPageCreated(page) {\n    await page.evaluateOnNewDocument(() => {\n      console.debug('hello world')\n    })\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_template/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_template/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_template/index.js#L10-L24)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nMinimal stealth plugin template, not being used. :-)\n\nFeel free to copy this folder as the basis for additional detection evasion plugins.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js",
    "content": "/**\n * A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces.\n *\n * Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser).\n *\n * Note: If for whatever reason you need to use this outside of `puppeteer-extra`:\n * Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context.\n *\n * Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities.\n *\n */\nconst utils = {}\n\nutils.init = () => {\n  utils.preloadCache()\n}\n\n/**\n * Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw.\n *\n * The presence of a JS Proxy can be revealed as it shows up in error stack traces.\n *\n * @param {object} handler - The JS Proxy handler to wrap\n */\nutils.stripProxyFromErrors = (handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(`at `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply\n            `at Object.${trap} `, // e.g. Object.get or Object.apply\n            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}\n\n/**\n * Strip error lines from stack traces until (and including) a known line the stack.\n *\n * @param {object} err - The error to sanitize\n * @param {string} anchor - The string the anchor line starts with\n */\nutils.stripErrorWithAnchor = (err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}\n\n/**\n * Replace the property of an object in a stealthy way.\n *\n * Note: You also want to work on the prototype of an object most often,\n * as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)).\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty\n *\n * @example\n * replaceProperty(WebGLRenderingContext.prototype, 'getParameter', { value: \"alice\" })\n * // or\n * replaceProperty(Object.getPrototypeOf(navigator), 'languages', { get: () => ['en-US', 'en'] })\n *\n * @param {object} obj - The object which has the property to replace\n * @param {string} propName - The property name to replace\n * @param {object} descriptorOverrides - e.g. { value: \"alice\" }\n */\nutils.replaceProperty = (obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}\n\n/**\n * Preload a cache of function copies and data.\n *\n * For a determined enough observer it would be possible to overwrite and sniff usage of functions\n * we use in our internal Proxies, to combat that we use a cached copy of those functions.\n *\n * Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,\n * by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).\n *\n * This is evaluated once per execution context (e.g. window)\n */\nutils.preloadCache = () => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in `makeNativeString`\n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`\n  }\n}\n\n/**\n * Utility function to generate a cross-browser `toString` result representing native code.\n *\n * There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings.\n * To future-proof this we use an existing native toString result as the basis.\n *\n * The only advantage we have over the other team is that our JS runs first, hence we cache the result\n * of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it.\n *\n * @example\n * makeNativeString('foobar') // => `function foobar() { [native code] }`\n *\n * @param {string} [name] - Optional function name\n */\nutils.makeNativeString = (name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}\n\n/**\n * Helper function to modify the `toString()` result of the provided object.\n *\n * Note: Use `utils.redirectToString` instead when possible.\n *\n * There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object.\n * If no string is provided we will generate a `[native code]` thing based on the name of the property object.\n *\n * @example\n * patchToString(WebGLRenderingContext.prototype.getParameter, 'function getParameter() { [native code] }')\n *\n * @param {object} obj - The object for which to modify the `toString()` representation\n * @param {string} str - Optional string used as a return value\n */\nutils.patchToString = (obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // `toString` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}\n\n/**\n * Make all nested functions of an object native.\n *\n * @param {object} obj\n */\nutils.patchToStringNested = (obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}\n\n/**\n * Redirect toString requests from one object to another.\n *\n * @param {object} proxyObj - The object that toString will be called on\n * @param {object} originalObj - The object which toString result we wan to return\n */\nutils.redirectToString = (proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + \"\"`\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // `toString` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}\n\n/**\n * All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps.\n *\n * Will stealthify these aspects (strip error stack traces, redirect toString, etc).\n * Note: This is meant to modify native Browser APIs and works best with prototype objects.\n *\n * @example\n * replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler)\n *\n * @param {object} obj - The object which has the property to replace\n * @param {string} propName - The name of the property to replace\n * @param {object} handler - The JS Proxy handler to use\n */\nutils.replaceWithProxy = (obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}\n/**\n * All-in-one method to replace a getter with a JS Proxy using the provided Proxy handler with traps.\n *\n * @example\n * replaceGetterWithProxy(Object.getPrototypeOf(navigator), 'vendor', proxyHandler)\n *\n * @param {object} obj - The object which has the property to replace\n * @param {string} propName - The name of the property to replace\n * @param {object} handler - The JS Proxy handler to use\n */\nutils.replaceGetterWithProxy = (obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}\n\n/**\n * All-in-one method to replace a getter and/or setter. Functions get and set\n * of handler have one more argument that contains the native function.\n *\n * @example\n * replaceGetterSetter(HTMLIFrameElement.prototype, 'contentWindow', handler)\n *\n * @param {object} obj - The object which has the property to replace\n * @param {string} propName - The name of the property to replace\n * @param {object} handlerGetterSetter - The handler with get and/or set\n *                                     functions\n * @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description\n */\nutils.replaceGetterSetter = (obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}\n\n/**\n * All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps.\n *\n * Will stealthify these aspects (strip error stack traces, redirect toString, etc).\n *\n * @example\n * mockWithProxy(chrome.runtime, 'sendMessage', function sendMessage() {}, proxyHandler)\n *\n * @param {object} obj - The object which has the property to replace\n * @param {string} propName - The name of the property to replace or create\n * @param {object} pseudoTarget - The JS Proxy target to use as a basis\n * @param {object} handler - The JS Proxy handler to use\n */\nutils.mockWithProxy = (obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}\n\n/**\n * All-in-one method to create a new JS Proxy with stealth tweaks.\n *\n * This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property.\n *\n * Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc).\n *\n * @example\n * createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy\n *\n * @param {object} pseudoTarget - The JS Proxy target to use as a basis\n * @param {object} handler - The JS Proxy handler to use\n */\nutils.createProxy = (pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}\n\n/**\n * Helper function to split a full path to an Object into the first part and property.\n *\n * @example\n * splitObjPath(`HTMLMediaElement.prototype.canPlayType`)\n * // => {objName: \"HTMLMediaElement.prototype\", propName: \"canPlayType\"}\n *\n * @param {string} objPath - The full path to an object as dot notation string\n */\nutils.splitObjPath = objPath => ({\n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> `canPlayType`\n  propName: objPath.split('.').slice(-1)[0]\n})\n\n/**\n * Convenience method to replace a property with a JS Proxy using the provided objPath.\n *\n * Supports a full path (dot notation) to the object as string here, in case that makes it easier.\n *\n * @example\n * replaceObjPathWithProxy('WebGLRenderingContext.prototype.getParameter', proxyHandler)\n *\n * @param {string} objPath - The full path to an object (dot notation string) to replace\n * @param {object} handler - The JS Proxy handler to use\n */\nutils.replaceObjPathWithProxy = (objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}\n\n/**\n * Traverse nested properties of an object recursively and apply the given function on a whitelist of value types.\n *\n * @param {object} obj\n * @param {array} typeFilter - e.g. `['function']`\n * @param {Function} fn - e.g. `utils.patchToString`\n */\nutils.execRecursively = (obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}\n\n/**\n * Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one.\n * That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter.\n *\n * Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process.\n * This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings.\n *\n * We use this to pass down our utility functions as well as any other functions (to be able to split up code better).\n *\n * @see utils.materializeFns\n *\n * @param {object} fnObj - An object containing functions as properties\n */\nutils.stringifyFns = (fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}\n\n/**\n * Utility function to reverse the process of `utils.stringifyFns`.\n * Will materialize an object with stringified functions (supports classic and fat arrow functions).\n *\n * @param {object} fnStrObj - An object containing stringified functions as properties\n */\nutils.materializeFns = (fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}\n\n// Proxy handler templates for re-usability\nutils.makeHandler = () => ({\n  // Used by simple `navigator` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like `navigator.__proto__.vendor` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})\n\n/**\n * Compare two arrays.\n *\n * @param {array} array1 - First array\n * @param {array} array2 - Second array\n */\nutils.arrayEquals = (array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}\n\n/**\n * Cache the method return according to its arguments.\n *\n * @param {Function} fn - A function that will be cached\n */\nutils.memoize = fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}\n\n// --\n// Stuff starting below this line is NodeJS specific.\n// --\nmodule.exports = utils\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer } = require('../../test/util')\n\nconst utils = require('.')\nconst withUtils = require('./withUtils')\n\n/* global HTMLMediaElement WebGLRenderingContext */\n\ntest('splitObjPath: will do what it says', async t => {\n  const { objName, propName } = utils.splitObjPath(\n    'HTMLMediaElement.prototype.canPlayType'\n  )\n  t.is(objName, 'HTMLMediaElement.prototype')\n  t.is(propName, 'canPlayType')\n})\n\ntest('makeNativeString: will do what it says', async t => {\n  utils.init()\n  t.is(utils.makeNativeString('bob'), 'function bob() { [native code] }')\n  t.is(\n    utils.makeNativeString('toString'),\n    'function toString() { [native code] }'\n  )\n  t.is(utils.makeNativeString(), 'function () { [native code] }')\n})\n\ntest('replaceWithProxy: will work correctly', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await withUtils(page).evaluate(utils => {\n    const dummyProxyHandler = {\n      get(target, param) {\n        if (param && param === 'ping') {\n          return 'pong'\n        }\n        return utils.cache.Reflect.get(...(arguments || []))\n      },\n      apply() {\n        return utils.cache.Reflect.apply(...arguments)\n      }\n    }\n    utils.replaceWithProxy(\n      HTMLMediaElement.prototype,\n      'canPlayType',\n      dummyProxyHandler\n    )\n    return {\n      toString: HTMLMediaElement.prototype.canPlayType.toString(),\n      ping: HTMLMediaElement.prototype.canPlayType.ping\n    }\n  })\n  t.deepEqual(test1, {\n    toString: 'function canPlayType() { [native code] }',\n    ping: 'pong'\n  })\n})\n\ntest('replaceObjPathWithProxy: will work correctly', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await withUtils(page).evaluate(utils => {\n    const dummyProxyHandler = {\n      get(target, param) {\n        if (param && param === 'ping') {\n          return 'pong'\n        }\n        return utils.cache.Reflect.get(...(arguments || []))\n      },\n      apply() {\n        return utils.cache.Reflect.apply(...arguments)\n      }\n    }\n    utils.replaceObjPathWithProxy(\n      'HTMLMediaElement.prototype.canPlayType',\n      dummyProxyHandler\n    )\n    return {\n      toString: HTMLMediaElement.prototype.canPlayType.toString(),\n      ping: HTMLMediaElement.prototype.canPlayType.ping\n    }\n  })\n  t.deepEqual(test1, {\n    toString: 'function canPlayType() { [native code] }',\n    ping: 'pong'\n  })\n})\n\ntest('redirectToString: is battle hardened', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // Patch all documents including iframes\n  await withUtils(page).evaluateOnNewDocument(utils => {\n    // We redirect toString calls targeted at `canPlayType` to `getParameter`,\n    // so if everything works correctly we expect `getParameter` as response.\n    const proxyObj = HTMLMediaElement.prototype.canPlayType\n    const originalObj = WebGLRenderingContext.prototype.getParameter\n\n    utils.redirectToString(proxyObj, originalObj)\n  })\n  await page.goto('about:blank')\n\n  const result = await withUtils(page).evaluate(utils => {\n    const iframe = document.createElement('iframe')\n    document.body.appendChild(iframe)\n\n    return {\n      target: {\n        raw: HTMLMediaElement.prototype.canPlayType + '',\n        rawiframe:\n          iframe.contentWindow.HTMLMediaElement.prototype.canPlayType + '',\n        raw2: HTMLMediaElement.prototype.canPlayType.toString(),\n        rawiframe2:\n          iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString(),\n        direct: Function.prototype.toString.call(\n          HTMLMediaElement.prototype.canPlayType\n        ),\n        directWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n          HTMLMediaElement.prototype.canPlayType\n        ),\n        iframeWithdirect: Function.prototype.toString.call(\n          iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n        ),\n        iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n          iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n        )\n      },\n      toString: {\n        obj: HTMLMediaElement.prototype.canPlayType.toString + '',\n        objiframe:\n          iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString +\n          '',\n        raw: Function.prototype.toString + '',\n        rawiframe: iframe.contentWindow.Function.prototype.toString + '',\n        direct: Function.prototype.toString.call(Function.prototype.toString),\n        directWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n          Function.prototype.toString\n        ),\n        iframeWithdirect: Function.prototype.toString.call(\n          iframe.contentWindow.Function.prototype.toString\n        ),\n        iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n          iframe.contentWindow.Function.prototype.toString\n        )\n      }\n    }\n  })\n  t.deepEqual(result, {\n    target: {\n      raw: 'function getParameter() { [native code] }',\n      raw2: 'function getParameter() { [native code] }',\n      rawiframe: 'function getParameter() { [native code] }',\n      rawiframe2: 'function getParameter() { [native code] }',\n      direct: 'function getParameter() { [native code] }',\n      directWithiframe: 'function getParameter() { [native code] }',\n      iframeWithdirect: 'function getParameter() { [native code] }',\n      iframeWithiframe: 'function getParameter() { [native code] }'\n    },\n    toString: {\n      obj: 'function toString() { [native code] }',\n      objiframe: 'function toString() { [native code] }',\n      raw: 'function toString() { [native code] }',\n      rawiframe: 'function toString() { [native code] }',\n      direct: 'function toString() { [native code] }',\n      directWithiframe: 'function toString() { [native code] }',\n      iframeWithdirect: 'function toString() { [native code] }',\n      iframeWithiframe: 'function toString() { [native code] }'\n    }\n  })\n})\n\ntest('redirectToString: has proper errors', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // Patch all documents including iframes\n  await withUtils(page).evaluateOnNewDocument(utils => {\n    // We redirect toString calls targeted at `canPlayType` to `getParameter`,\n    // so if everything works correctly we expect `getParameter` as response.\n    const proxyObj = HTMLMediaElement.prototype.canPlayType\n    const originalObj = WebGLRenderingContext.prototype.getParameter\n\n    utils.redirectToString(proxyObj, originalObj)\n  })\n  await page.goto('about:blank')\n\n  const result = await withUtils(page).evaluate(utils => {\n    const evalErr = (str = '') => {\n      try {\n        // eslint-disable-next-line no-eval\n        return eval(str)\n      } catch (err) {\n        return err.toString()\n      }\n    }\n\n    return {\n      blank: evalErr(`Function.prototype.toString.apply()`),\n      null: evalErr(`Function.prototype.toString.apply(null)`),\n      undef: evalErr(`Function.prototype.toString.apply(undefined)`),\n      emptyObject: evalErr(`Function.prototype.toString.apply({})`)\n    }\n  })\n  t.deepEqual(result, {\n    blank:\n      \"TypeError: Function.prototype.toString requires that 'this' be a Function\",\n    null: \"TypeError: Function.prototype.toString requires that 'this' be a Function\",\n    undef:\n      \"TypeError: Function.prototype.toString requires that 'this' be a Function\",\n    emptyObject:\n      \"TypeError: Function.prototype.toString requires that 'this' be a Function\"\n  })\n})\n\ntest('patchToString: will work correctly', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // Test verbatim string replacement\n  const test1 = await withUtils(page).evaluate(utils => {\n    utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob')\n    return HTMLMediaElement.prototype.canPlayType.toString()\n  })\n  t.is(test1, 'bob')\n\n  // Test automatic mode derived from `.name`\n  const test2 = await withUtils(page).evaluate(utils => {\n    utils.patchToString(HTMLMediaElement.prototype.canPlayType)\n    return HTMLMediaElement.prototype.canPlayType.toString()\n  })\n  t.is(test2, 'function canPlayType() { [native code] }')\n\n  // Make sure automatic mode derived from `.name` works with proxies\n  const test3 = await withUtils(page).evaluate(utils => {\n    HTMLMediaElement.prototype.canPlayType = new Proxy(\n      HTMLMediaElement.prototype.canPlayType,\n      {}\n    )\n    utils.patchToString(HTMLMediaElement.prototype.canPlayType)\n    return HTMLMediaElement.prototype.canPlayType.toString()\n  })\n  t.is(test3, 'function canPlayType() { [native code] }')\n\n  // Actually verify there's an issue when using vanilla Proxies\n  const test4 = await withUtils(page).evaluate(utils => {\n    HTMLMediaElement.prototype.canPlayType = new Proxy(\n      HTMLMediaElement.prototype.canPlayType,\n      {}\n    )\n    return HTMLMediaElement.prototype.canPlayType.toString()\n  })\n  t.is(test4, 'function () { [native code] }')\n})\n\nfunction toStringTest(obj) {\n  obj = eval(obj) // eslint-disable-line no-eval\n  return `\n- obj.toString(): ${obj.toString()}\n- obj.name: ${obj.name}\n- obj.toString + \"\": ${obj.toString + ''}\n- obj.toString.name: ${obj.toString.name}\n- obj.valueOf + \"\": ${obj.valueOf + ''}\n- obj.valueOf().name: ${obj.valueOf().name}\n- Object.prototype.toString.apply(obj): ${Object.prototype.toString.apply(obj)}\n- Function.prototype.toString.call(obj): ${Function.prototype.toString.call(\n    obj\n  )}\n- Function.prototype.valueOf.call(obj) + \"\": ${\n    Function.prototype.valueOf.call(obj) + ''\n  }\n- obj.toString === Function.prototype.toString: ${\n    obj.toString === Function.prototype.toString\n  }\n`.trim()\n}\n\ntest('patchToString: passes all toString tests', async t => {\n  const toStringVanilla = await (async function () {\n    const browser = await vanillaPuppeteer.launch({ headless: true })\n    const page = await browser.newPage()\n    return page.evaluate(toStringTest, 'HTMLMediaElement.prototype.canPlayType')\n  })()\n  const toStringStealth = await (async function () {\n    const browser = await vanillaPuppeteer.launch({ headless: true })\n    const page = await browser.newPage()\n    await withUtils(page).evaluate(utils => {\n      HTMLMediaElement.prototype.canPlayType = function canPlayType() {}\n      utils.patchToString(HTMLMediaElement.prototype.canPlayType)\n    })\n    return page.evaluate(toStringTest, 'HTMLMediaElement.prototype.canPlayType')\n  })()\n\n  // Check that the unmodified results are as expected\n  t.is(\n    toStringVanilla,\n    `\n- obj.toString(): function canPlayType() { [native code] }\n- obj.name: canPlayType\n- obj.toString + \"\": function toString() { [native code] }\n- obj.toString.name: toString\n- obj.valueOf + \"\": function valueOf() { [native code] }\n- obj.valueOf().name: canPlayType\n- Object.prototype.toString.apply(obj): [object Function]\n- Function.prototype.toString.call(obj): function canPlayType() { [native code] }\n- Function.prototype.valueOf.call(obj) + \"\": function canPlayType() { [native code] }\n- obj.toString === Function.prototype.toString: true\n`.trim()\n  )\n\n  // Make sure our customizations leave no trace\n  t.is(toStringVanilla, toStringStealth)\n})\n\ntest('patchToString: passes stack trace tests', async t => {\n  const toStringStackTrace = () => {\n    try {\n      Object.create(\n        Object.getOwnPropertyDescriptor(Function.prototype, 'toString').get\n      ).toString()\n    } catch (err) {\n      return err.stack.split('\\n').slice(0, 2).join('|')\n    }\n    return 'error not thrown'\n  }\n\n  const toStringVanilla = await (async function () {\n    const browser = await vanillaPuppeteer.launch({ headless: true })\n    const page = await browser.newPage()\n    return page.evaluate(toStringStackTrace)\n  })()\n  const toStringStealth = await (async function () {\n    const browser = await vanillaPuppeteer.launch({ headless: true })\n    const page = await browser.newPage()\n    await withUtils(page).evaluate(utils => {\n      HTMLMediaElement.prototype.canPlayType = function canPlayType() {}\n      utils.patchToString(HTMLMediaElement.prototype.canPlayType)\n    })\n    return page.evaluate(toStringStackTrace)\n  })()\n\n  // Check that the unmodified results are as expected\n  t.is(\n    toStringVanilla,\n    `TypeError: Object prototype may only be an Object or null: undefined|    at Function.create (<anonymous>)`.trim()\n  )\n\n  // Make sure our customizations leave no trace\n  t.is(toStringVanilla, toStringStealth)\n})\n\ntest('patchToString: vanilla has iframe issues', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // Only patch the main window\n  const result = await withUtils(page).evaluate(utils => {\n    utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob')\n\n    const iframe = document.createElement('iframe')\n    document.body.appendChild(iframe)\n    return {\n      direct: Function.prototype.toString.call(\n        HTMLMediaElement.prototype.canPlayType\n      ),\n      directWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n        HTMLMediaElement.prototype.canPlayType\n      ),\n      iframeWithdirect: Function.prototype.toString.call(\n        iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n      ),\n      iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n        iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n      )\n    }\n  })\n  t.deepEqual(result, {\n    direct: 'bob',\n    directWithiframe: 'function canPlayType() { [native code] }',\n    iframeWithdirect: 'function canPlayType() { [native code] }',\n    iframeWithiframe: 'function canPlayType() { [native code] }'\n  })\n})\n\ntest('patchToString: stealth has no iframe issues', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // Patch all documents including iframes\n  await withUtils(page).evaluateOnNewDocument(utils => {\n    utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'alice')\n  })\n  await page.goto('about:blank')\n\n  const result = await withUtils(page).evaluate(utils => {\n    const iframe = document.createElement('iframe')\n    document.body.appendChild(iframe)\n    return {\n      direct: Function.prototype.toString.call(\n        HTMLMediaElement.prototype.canPlayType\n      ),\n      directWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n        HTMLMediaElement.prototype.canPlayType\n      ),\n      iframeWithdirect: Function.prototype.toString.call(\n        iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n      ),\n      iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(\n        iframe.contentWindow.HTMLMediaElement.prototype.canPlayType\n      )\n    }\n  })\n  t.deepEqual(result, {\n    direct: 'alice',\n    directWithiframe: 'alice',\n    iframeWithdirect: 'alice',\n    iframeWithiframe: 'alice'\n  })\n})\n\ntest('stripProxyFromErrors: will work correctly', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await withUtils(page).evaluate(utils => {\n    const getStack = prop => {\n      try {\n        prop.caller() // Will throw (HTMLMediaElement.prototype.canPlayType.caller)\n        return false\n      } catch (err) {\n        return err.stack\n      }\n    }\n    /** We need traps to show up in the error stack */\n    const dummyProxyHandler = {\n      get() {\n        return utils.cache.Reflect.get(...(arguments || []))\n      },\n      apply() {\n        return utils.cache.Reflect.apply(...arguments)\n      }\n    }\n    const vanillaProxy = new Proxy(\n      HTMLMediaElement.prototype.canPlayType,\n      dummyProxyHandler\n    )\n    const stealthProxy = new Proxy(\n      HTMLMediaElement.prototype.canPlayType,\n      utils.stripProxyFromErrors(dummyProxyHandler)\n    )\n\n    const stacks = {\n      vanilla: getStack(HTMLMediaElement.prototype.canPlayType),\n      vanillaProxy: getStack(vanillaProxy),\n      stealthProxy: getStack(stealthProxy)\n    }\n    return stacks\n  })\n\n  // Check that the untouched stuff behaves as expected\n  t.true(results.vanilla.includes(`TypeError: 'caller'`))\n  t.false(results.vanilla.includes(`at Object.get`))\n\n  // Regression test: Make sure vanilla JS Proxies leak the stack trace\n  t.true(results.vanillaProxy.includes(`TypeError: 'caller'`))\n  t.true(results.vanillaProxy.includes(`at Object.get`))\n\n  // Stealth tests\n  t.true(results.stealthProxy.includes(`TypeError: 'caller'`))\n  t.false(results.stealthProxy.includes(`at Object.get`))\n})\n\ntest('replaceProperty: will work without traces', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await withUtils(page).evaluate(utils => {\n    utils.replaceProperty(Object.getPrototypeOf(navigator), 'languages', {\n      get: () => ['de-DE']\n    })\n    return {\n      propNames: Object.getOwnPropertyNames(navigator)\n    }\n  })\n  t.false(results.propNames.includes('languages'))\n})\n\ntest('cache: will prevent leaks through overriding methods', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await withUtils(page).evaluate(utils => {\n    const sniffResults = {\n      vanilla: false,\n      stealth: false\n    }\n\n    const vanillaProxy = new Proxy(\n      {},\n      {\n        get() {\n          return Reflect.get(...arguments)\n        }\n      }\n    )\n    Reflect.get = () => (sniffResults.vanilla = true)\n    // trigger get trap\n    vanillaProxy.foo // eslint-disable-line\n\n    const stealthProxy = new Proxy(\n      {},\n      {\n        get() {\n          return utils.cache.Reflect.get(...arguments) // using cached copy\n        }\n      }\n    )\n    Reflect.get = () => (sniffResults.stealth = true)\n    // trigger get trap\n    stealthProxy.foo // eslint-disable-line\n\n    return sniffResults\n  })\n\n  t.deepEqual(results, {\n    vanilla: true,\n    stealth: false\n  })\n})\n\ntest('replaceWithProxy: will throw prototype errors', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('about:blank')\n\n  const result = await withUtils(page).evaluate(utils => {\n    utils.replaceWithProxy(HTMLMediaElement.prototype, 'canPlayType', {})\n\n    const evalErr = (str = '') => {\n      try {\n        // eslint-disable-next-line no-eval\n        return eval(str)\n      } catch (err) {\n        return err.toString()\n      }\n    }\n\n    return {\n      same: evalErr(\n        `Object.setPrototypeOf(HTMLMediaElement.prototype.canPlayType, HTMLMediaElement.prototype.canPlayType) + \"\"`\n      ),\n      sameString: evalErr(\n        `Object.setPrototypeOf(Function.prototype.toString, Function.prototype.toString) + \"\"`\n      ),\n      null: evalErr(\n        `Object.setPrototypeOf(Function.prototype.toString, null) + \"\"`\n      ),\n      undef: evalErr(\n        `Object.setPrototypeOf(Function.prototype.toString, undefined) + \"\"`\n      ),\n      none: evalErr(`Object.setPrototypeOf(Function.prototype.toString) + \"\"`)\n    }\n  })\n  t.deepEqual(result, {\n    same: 'TypeError: Cyclic __proto__ value',\n    sameString: 'TypeError: Cyclic __proto__ value',\n    null: 'TypeError: Cannot convert object to primitive value',\n    undef:\n      'TypeError: Object prototype may only be an Object or null: undefined',\n    none: 'TypeError: Object prototype may only be an Object or null: undefined'\n  })\n})\n\ntest('replaceGetterSetter', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('about:blank')\n\n  const results = await withUtils(page).evaluate(utils => {\n    const getDetails = a => ({\n      href: a.href,\n      typeof: typeof a.href,\n      in: 'href' in a,\n      keys: Object.keys(a),\n      // eslint-disable-next-line no-undef\n      prototypeKeys: Object.keys(HTMLAnchorElement.prototype),\n      getOwnPropertyNames: Object.getOwnPropertyNames(a),\n      prototypeGetOwnPropertyNames: Object.getOwnPropertyNames(\n        // eslint-disable-next-line no-undef\n        HTMLAnchorElement.prototype\n      ),\n      ownPropertyDescriptor:\n        undefined === Object.getOwnPropertyDescriptor(a, 'href'),\n      prototypeOwnPropertyDescriptor: Object.getOwnPropertyDescriptor(\n        // eslint-disable-next-line no-undef\n        HTMLAnchorElement.prototype,\n        'href'\n      ),\n      ownPropertyDescriptors: Object.getOwnPropertyDescriptors(a, 'href'),\n      prototypeOwnPropertyDescriptors: Object.getOwnPropertyDescriptors(\n        // eslint-disable-next-line no-undef\n        HTMLAnchorElement.prototype,\n        'href'\n      ),\n      getToString: Object.getOwnPropertyDescriptor(\n        // eslint-disable-next-line no-undef\n        HTMLAnchorElement.prototype,\n        'href'\n      ).get.toString(),\n      setToString: Object.getOwnPropertyDescriptor(\n        // eslint-disable-next-line no-undef\n        HTMLAnchorElement.prototype,\n        'href'\n      ).set.toString()\n    })\n\n    // Use native a.href.\n    const a1 = document.createElement('a')\n    a1.href = 'http://foo.com/'\n    const details1 = getDetails(a1)\n\n    // Override a.href.\n    let href = ''\n    // eslint-disable-next-line no-undef\n    utils.replaceGetterSetter(HTMLAnchorElement.prototype, 'href', {\n      get: function() {\n        return href\n      },\n      set: function(newValue) {\n        href = newValue\n      }\n    })\n\n    // Use overrided a.href.\n    const a2 = document.createElement('a')\n    a2.href = 'http://foo.com/'\n    const details2 = getDetails(a2)\n\n    return [details1, details2]\n  })\n\n  t.deepEqual(results[1], results[0])\n})\n\ntest('arrayEquals', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('about:blank')\n\n  const results = await withUtils(page).evaluate(utils => {\n    const obj = { foo: 'bar' }\n    return {\n      a: utils.arrayEquals(['a', 'Alpha'], ['a', 'Alpha']),\n      b: !utils.arrayEquals(['b', 'Beta'], ['b', 'Blue']),\n      c: !utils.arrayEquals(['c', { foo: 'bar' }], ['c', { foo: 'bar' }]),\n      d: utils.arrayEquals(['d', obj], ['d', obj]),\n      e: utils.arrayEquals([null], [null]),\n      f: utils.arrayEquals([undefined], [undefined]),\n      g: utils.arrayEquals([false], [false])\n    }\n  })\n\n  t.deepEqual(results, {\n    a: true,\n    b: true,\n    c: true,\n    d: true,\n    e: true,\n    f: true,\n    g: true\n  })\n})\n\ntest('memoize', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('about:blank')\n\n  const results = await withUtils(page).evaluate(utils => {\n    const objectify = utils.memoize((valueAdded, valueIgnored) => {\n      return { valueAdded }\n    })\n\n    const obj = { foo: 'bar' }\n    /* eslint-disable no-self-compare */\n    return {\n      a: objectify('a', 'Alpha') === objectify('a', 'Alpha'),\n      b: objectify('b', 'Beta') !== objectify('b', 'Blue'),\n      c: objectify('c', { foo: 'bar' }) !== objectify('c', { foo: 'bar' }),\n      d: objectify('d', obj) === objectify('d', obj),\n      e: objectify(null) === objectify(null),\n      f: objectify(undefined) === objectify(undefined),\n      g: objectify(false) === objectify(false)\n    }\n    /* eslint-enable no-self-compare */\n  })\n\n  t.deepEqual(results, {\n    a: true,\n    b: true,\n    c: true,\n    d: true,\n    e: true,\n    f: true,\n    g: true\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_utils/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [utils()](#utils)\n  - [.stripProxyFromErrors(handler)](#stripproxyfromerrorshandler)\n  - [.stripErrorWithAnchor(err, anchor)](#striperrorwithanchorerr-anchor)\n  - [.replaceProperty(obj, propName, descriptorOverrides)](#replacepropertyobj-propname-descriptoroverrides)\n  - [.preloadCache()](#preloadcache)\n  - [.makeNativeString(name?)](#makenativestringname)\n  - [.patchToString(obj, str)](#patchtostringobj-str)\n  - [.patchToStringNested(obj)](#patchtostringnestedobj)\n  - [.redirectToString(proxyObj, originalObj)](#redirecttostringproxyobj-originalobj)\n  - [.replaceWithProxy(obj, propName, handler)](#replacewithproxyobj-propname-handler)\n  - [.mockWithProxy(obj, propName, pseudoTarget, handler)](#mockwithproxyobj-propname-pseudotarget-handler)\n  - [.createProxy(pseudoTarget, handler)](#createproxypseudotarget-handler)\n  - [.splitObjPath(objPath)](#splitobjpathobjpath)\n  - [.replaceObjPathWithProxy(objPath, handler)](#replaceobjpathwithproxyobjpath-handler)\n  - [.execRecursively(obj, typeFilter, fn)](#execrecursivelyobj-typefilter-fn)\n  - [.stringifyFns(fnObj)](#stringifyfnsfnobj)\n  - [.materializeFns(fnStrObj)](#materializefnsfnstrobj)\n\n### [utils()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L12-L12)\n\nA set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces.\n\nMeant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser).\n\nNote: If for whatever reason you need to use this outside of `puppeteer-extra`:\nJust remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context.\n\nAlternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities.\n\n---\n\n#### .[stripProxyFromErrors(handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L21-L82)\n\n- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to wrap (optional, default `{}`)\n\nWraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw.\n\nThe presence of a JS Proxy can be revealed as it shows up in error stack traces.\n\n---\n\n#### .[stripErrorWithAnchor(err, anchor)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L90-L101)\n\n- `err` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The error to sanitize\n- `anchor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The string the anchor line starts with\n\nStrip error lines from stack traces until (and including) a known line the stack.\n\n---\n\n#### .[replaceProperty(obj, propName, descriptorOverrides)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L120-L127)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace\n- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The property name to replace\n- `descriptorOverrides` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** e.g. { value: \"alice\" } (optional, default `{}`)\n\nReplace the property of an object in a stealthy way.\n\nNote: You also want to work on the prototype of an object most often,\nas you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)).\n\nExample:\n\n```javascript\nreplaceProperty(WebGLRenderingContext.prototype, 'getParameter', {\n  value: 'alice'\n})\n// or\nreplaceProperty(Object.getPrototypeOf(navigator), 'languages', {\n  get: () => ['en-US', 'en']\n})\n```\n\n- **See: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>**\n\n---\n\n#### .[preloadCache()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L137-L150)\n\nPreload a cache of function copies and data.\n\nFor a determined enough observer it would be possible to overwrite and sniff usage of functions\nwe use in our internal Proxies, to combat that we use a cached copy of those functions.\n\nThis is evaluated once per execution context (e.g. window)\n\n---\n\n#### .[makeNativeString(name?)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L169-L173)\n\n- `name` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Optional function name (optional, default `''`)\n\nUtility function to generate a cross-browser `toString` result representing native code.\n\nThere's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings.\nTo future-proof this we use an existing native toString result as the basis.\n\nThe only advantage we have over the other team is that our JS runs first, hence we cache the result\nof the native toString result once, so they cannot spoof it afterwards and reveal that we're using it.\n\nNote: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,\nby executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).\n\nExample:\n\n```javascript\nmakeNativeString('foobar') // => `function foobar() { [native code] }`\n```\n\n---\n\n#### .[patchToString(obj, str)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L189-L218)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object for which to modify the `toString()` representation\n- `str` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Optional string used as a return value (optional, default `''`)\n\nHelper function to modify the `toString()` result of the provided object.\n\nNote: Use `utils.redirectToString` instead when possible.\n\nThere's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object.\nIf no string is provided we will generate a `[native code]` thing based on the name of the property object.\n\nExample:\n\n```javascript\npatchToString(\n  WebGLRenderingContext.prototype.getParameter,\n  'function getParameter() { [native code] }'\n)\n```\n\n---\n\n#### .[patchToStringNested(obj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L225-L227)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)\n\nMake all nested functions of an object native.\n\n---\n\n#### .[redirectToString(proxyObj, originalObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L235-L272)\n\n- `proxyObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object that toString will be called on\n- `originalObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which toString result we wan to return\n\nRedirect toString requests from one object to another.\n\n---\n\n#### .[replaceWithProxy(obj, propName, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L287-L296)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace\n- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the property to replace\n- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use\n\nAll-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps.\n\nWill stealthify these aspects (strip error stack traces, redirect toString, etc).\nNote: This is meant to modify native Browser APIs and works best with prototype objects.\n\nExample:\n\n```javascript\nreplaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler)\n```\n\n---\n\n#### .[mockWithProxy(obj, propName, pseudoTarget, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L311-L319)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace\n- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the property to replace or create\n- `pseudoTarget` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy target to use as a basis\n- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use\n\nAll-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps.\n\nWill stealthify these aspects (strip error stack traces, redirect toString, etc).\n\nExample:\n\n```javascript\nmockWithProxy(\n  chrome.runtime,\n  'sendMessage',\n  function sendMessage() {},\n  proxyHandler\n)\n```\n\n---\n\n#### .[createProxy(pseudoTarget, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L334-L340)\n\n- `pseudoTarget` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy target to use as a basis\n- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use\n\nAll-in-one method to create a new JS Proxy with stealth tweaks.\n\nThis is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property.\n\nWill stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc).\n\nExample:\n\n```javascript\ncreateProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy\n```\n\n---\n\n#### .[splitObjPath(objPath)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L351-L359)\n\n- `objPath` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The full path to an object as dot notation string\n\nHelper function to split a full path to an Object into the first part and property.\n\nExample:\n\n```javascript\nsplitObjPath(`HTMLMediaElement.prototype.canPlayType`)\n// => {objName: \"HTMLMediaElement.prototype\", propName: \"canPlayType\"}\n```\n\n---\n\n#### .[replaceObjPathWithProxy(objPath, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L372-L376)\n\n- `objPath` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The full path to an object (dot notation string) to replace\n- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use\n\nConvenience method to replace a property with a JS Proxy using the provided objPath.\n\nSupports a full path (dot notation) to the object as string here, in case that makes it easier.\n\nExample:\n\n```javascript\nreplaceObjPathWithProxy(\n  'WebGLRenderingContext.prototype.getParameter',\n  proxyHandler\n)\n```\n\n---\n\n#### .[execRecursively(obj, typeFilter, fn)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L385-L402)\n\n- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)\n- `typeFilter` **[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** e.g. `['function']` (optional, default `[]`)\n- `fn` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** e.g. `utils.patchToString`\n\nTraverse nested properties of an object recursively and apply the given function on a whitelist of value types.\n\n---\n\n#### .[stringifyFns(fnObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L417-L431)\n\n- `fnObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object containing functions as properties (optional, default `{hello:()=>'world'}`)\n\nEverything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one.\nThat means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter.\n\nUnfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process.\nThis utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings.\n\nWe use this to pass down our utility functions as well as any other functions (to be able to split up code better).\n\n- **See: utils.materializeFns**\n\n---\n\n#### .[materializeFns(fnStrObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L439-L451)\n\n- `fnStrObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object containing stringified functions as properties (optional, default `{hello:\"() => 'world'\"}`)\n\nUtility function to reverse the process of `utils.stringifyFns`.\nWill materialize an object with stringified functions (supports classic and fat arrow functions).\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/_utils/withUtils.js",
    "content": "const utils = require('./index')\n\n/**\n * Wrap a page with utilities.\n *\n * @param {Puppeteer.Page} page\n */\nmodule.exports = page => ({\n  /**\n   * Simple `page.evaluate` replacement to preload utils\n   */\n  evaluate: async function (mainFunction, ...args) {\n    return page.evaluate(\n      ({ _utilsFns, _mainFunction, _args }) => {\n        // Add this point we cannot use our utililty functions as they're just strings, we need to materialize them first\n        const utils = Object.fromEntries(\n          Object.entries(_utilsFns).map(([key, value]) => [key, eval(value)]) // eslint-disable-line no-eval\n        )\n        utils.init()\n        return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval\n      },\n      {\n        _utilsFns: utils.stringifyFns(utils),\n        _mainFunction: mainFunction.toString(),\n        _args: args || []\n      }\n    )\n  },\n  /**\n   * Simple `page.evaluateOnNewDocument` replacement to preload utils\n   */\n  evaluateOnNewDocument: async function (mainFunction, ...args) {\n    return page.evaluateOnNewDocument(\n      ({ _utilsFns, _mainFunction, _args }) => {\n        // Add this point we cannot use our utililty functions as they're just strings, we need to materialize them first\n        const utils = Object.fromEntries(\n          Object.entries(_utilsFns).map(([key, value]) => [key, eval(value)]) // eslint-disable-line no-eval\n        )\n        utils.init()\n        return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval\n      },\n      {\n        _utilsFns: utils.stringifyFns(utils),\n        _mainFunction: mainFunction.toString(),\n        _args: args || []\n      }\n    )\n  }\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.app/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Mock the `chrome.app` object if not available (e.g. when running headless).\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/chrome.app'\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(utils => {\n      if (!window.chrome) {\n        // Use the exact property descriptor found in headful Chrome\n        // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n        Object.defineProperty(window, 'chrome', {\n          writable: true,\n          enumerable: true,\n          configurable: false, // note!\n          value: {} // We'll extend that later\n        })\n      }\n\n      // That means we're running headful and don't need to mock anything\n      if ('app' in window.chrome) {\n        return // Nothing to do here\n      }\n\n      const makeError = {\n        ErrorInInvocation: fn => {\n          const err = new TypeError(`Error in invocation of app.${fn}()`)\n          return utils.stripErrorWithAnchor(\n            err,\n            `at ${fn} (eval at <anonymous>`\n          )\n        }\n      }\n\n      // There's a some static data in that property which doesn't seem to change,\n      // we should periodically check for updates: `JSON.stringify(window.app, null, 2)`\n      const STATIC_DATA = JSON.parse(\n        `\n{\n  \"isInstalled\": false,\n  \"InstallState\": {\n    \"DISABLED\": \"disabled\",\n    \"INSTALLED\": \"installed\",\n    \"NOT_INSTALLED\": \"not_installed\"\n  },\n  \"RunningState\": {\n    \"CANNOT_RUN\": \"cannot_run\",\n    \"READY_TO_RUN\": \"ready_to_run\",\n    \"RUNNING\": \"running\"\n  }\n}\n        `.trim()\n      )\n\n      window.chrome.app = {\n        ...STATIC_DATA,\n\n        get isInstalled() {\n          return false\n        },\n\n        getDetails: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`getDetails`)\n          }\n          return null\n        },\n        getIsInstalled: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`getIsInstalled`)\n          }\n          return false\n        },\n        runningState: function getDetails() {\n          if (arguments.length) {\n            throw makeError.ErrorInInvocation(`runningState`)\n          }\n          return 'cannot_run'\n        }\n      }\n      utils.patchToStringNested(window.chrome.app)\n    })\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.app/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\n/* global chrome */\n\ntest('stealth: will add convincing chrome.app object', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin({}))\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const catchErr = (fn, ...args) => {\n      try {\n        return fn.apply(this, args)\n      } catch ({ name, message, stack }) {\n        return { name, message, stack }\n      }\n    }\n\n    return {\n      app: {\n        exists: window.chrome && 'app' in window.chrome,\n        toString: chrome.app.toString(),\n        deepToString: chrome.app.runningState.toString()\n      },\n      data: {\n        getIsInstalled: chrome.app.getIsInstalled(),\n        runningState: chrome.app.runningState(),\n        getDetails: chrome.app.getDetails(),\n        InstallState: chrome.app.InstallState,\n        RunningState: chrome.app.RunningState\n      },\n      errors: {\n        getIsInstalled: catchErr(chrome.app.getDetails, 'foo').message,\n        stackOK: !catchErr(chrome.app.getDetails, 'foo').stack.includes(\n          'at getDetails'\n        )\n      }\n    }\n  })\n\n  t.deepEqual(results, {\n    app: {\n      exists: true,\n      toString: '[object Object]',\n      deepToString: 'function getDetails() { [native code] }'\n    },\n    data: {\n      InstallState: {\n        DISABLED: 'disabled',\n        INSTALLED: 'installed',\n        NOT_INSTALLED: 'not_installed'\n      },\n      RunningState: {\n        CANNOT_RUN: 'cannot_run',\n        READY_TO_RUN: 'ready_to_run',\n        RUNNING: 'running'\n      },\n      getDetails: null,\n      getIsInstalled: false,\n      runningState: 'cannot_run'\n    },\n    errors: {\n      getIsInstalled: 'Error in invocation of app.getDetails()',\n      stackOK: true\n    }\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.app/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.app/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.app/index.js#L11-L97)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nMock the `chrome.app` object if not available (e.g. when running headless).\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.csi/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Mock the `chrome.csi` function if not available (e.g. when running headless).\n * It's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings.\n *\n * Internally chromium switched the implementation to use the WebPerformance API,\n * so we can do the same to create a fully functional mock. :-)\n *\n * Note: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.\n *\n * @see https://bugs.chromium.org/p/chromium/issues/detail?id=113048\n * @see https://codereview.chromium.org/2456293003/\n * @see https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated\n * @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming\n * @see https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium\n * @see `chrome.loadTimes` evasion\n *\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/chrome.csi'\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(utils => {\n      if (!window.chrome) {\n        // Use the exact property descriptor found in headful Chrome\n        // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n        Object.defineProperty(window, 'chrome', {\n          writable: true,\n          enumerable: true,\n          configurable: false, // note!\n          value: {} // We'll extend that later\n        })\n      }\n\n      // That means we're running headful and don't need to mock anything\n      if ('csi' in window.chrome) {\n        return // Nothing to do here\n      }\n\n      // Check that the Navigation Timing API v1 is available, we need that\n      if (!window.performance || !window.performance.timing) {\n        return\n      }\n\n      const { timing } = window.performance\n\n      window.chrome.csi = function() {\n        return {\n          onloadT: timing.domContentLoadedEventEnd,\n          startE: timing.navigationStart,\n          pageT: Date.now() - timing.navigationStart,\n          tran: 15 // Transition type or something\n        }\n      }\n      utils.patchToString(window.chrome.csi)\n    })\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.csi/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\n/* global chrome */\n\ntest('stealth: will add functional chrome.csi function mock', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      runOnInsecureOrigins: true // for testing\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const { timing } = window.performance\n    const csi = window.chrome.csi()\n\n    return {\n      csi: {\n        exists: window.chrome && 'csi' in window.chrome,\n        toString: chrome.csi.toString()\n      },\n      dataOK: {\n        onloadT: csi.onloadT === timing.domContentLoadedEventEnd,\n        startE: csi.startE === timing.navigationStart,\n        pageT: Number.isInteger(csi.pageT),\n        tran: Number.isInteger(csi.tran)\n      }\n    }\n  })\n\n  t.deepEqual(results, {\n    csi: {\n      exists: true,\n      toString: 'function () { [native code] }'\n    },\n    dataOK: {\n      onloadT: true,\n      pageT: true,\n      startE: true,\n      tran: true\n    }\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.csi/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.csi/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.csi/index.js#L25-L70)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nMock the `chrome.csi` function if not available (e.g. when running headless).\nIt's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings.\n\nInternally chromium switched the implementation to use the WebPerformance API,\nso we can do the same to create a fully functional mock. :-)\n\nNote: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.\n\n- **See: <https://bugs.chromium.org/p/chromium/issues/detail?id=113048>**\n- **See: <https://codereview.chromium.org/2456293003/>**\n- **See: <https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated>**\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming>**\n- **See: <https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium>**\n- **See: `chrome.loadTimes` evasion**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Mock the `chrome.loadTimes` function if not available (e.g. when running headless).\n * It's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings and connection info.\n *\n * Internally chromium switched the implementation to use the WebPerformance API,\n * so we can do the same to create a fully functional mock. :-)\n *\n * Note: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.\n *\n * @see https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated\n * @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming\n * @see https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium\n * @see `chrome.csi` evasion\n *\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/chrome.loadTimes'\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { opts }) => {\n        if (!window.chrome) {\n          // Use the exact property descriptor found in headful Chrome\n          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n          Object.defineProperty(window, 'chrome', {\n            writable: true,\n            enumerable: true,\n            configurable: false, // note!\n            value: {} // We'll extend that later\n          })\n        }\n\n        // That means we're running headful and don't need to mock anything\n        if ('loadTimes' in window.chrome) {\n          return // Nothing to do here\n        }\n\n        // Check that the Navigation Timing API v1 + v2 is available, we need that\n        if (\n          !window.performance ||\n          !window.performance.timing ||\n          !window.PerformancePaintTiming\n        ) {\n          return\n        }\n\n        const { performance } = window\n\n        // Some stuff is not available on about:blank as it requires a navigation to occur,\n        // let's harden the code to not fail then:\n        const ntEntryFallback = {\n          nextHopProtocol: 'h2',\n          type: 'other'\n        }\n\n        // The API exposes some funky info regarding the connection\n        const protocolInfo = {\n          get connectionInfo() {\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ntEntry.nextHopProtocol\n          },\n          get npnNegotiatedProtocol() {\n            // NPN is deprecated in favor of ALPN, but this implementation returns the\n            // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n              ? ntEntry.nextHopProtocol\n              : 'unknown'\n          },\n          get navigationType() {\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ntEntry.type\n          },\n          get wasAlternateProtocolAvailable() {\n            // The Alternate-Protocol header is deprecated in favor of Alt-Svc\n            // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this\n            // should always return false.\n            return false\n          },\n          get wasFetchedViaSpdy() {\n            // SPDY is deprecated in favor of HTTP/2, but this implementation returns\n            // true for HTTP/2 or HTTP2+QUIC/39 as well.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n          },\n          get wasNpnNegotiated() {\n            // NPN is deprecated in favor of ALPN, but this implementation returns true\n            // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n            const ntEntry =\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n          }\n        }\n\n        const { timing } = window.performance\n\n        // Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3\n        function toFixed(num, fixed) {\n          var re = new RegExp('^-?\\\\d+(?:.\\\\d{0,' + (fixed || -1) + '})?')\n          return num.toString().match(re)[0]\n        }\n\n        const timingInfo = {\n          get firstPaintAfterLoadTime() {\n            // This was never actually implemented and always returns 0.\n            return 0\n          },\n          get requestTime() {\n            return timing.navigationStart / 1000\n          },\n          get startLoadTime() {\n            return timing.navigationStart / 1000\n          },\n          get commitLoadTime() {\n            return timing.responseStart / 1000\n          },\n          get finishDocumentLoadTime() {\n            return timing.domContentLoadedEventEnd / 1000\n          },\n          get finishLoadTime() {\n            return timing.loadEventEnd / 1000\n          },\n          get firstPaintTime() {\n            const fpEntry = performance.getEntriesByType('paint')[0] || {\n              startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)\n            }\n            return toFixed(\n              (fpEntry.startTime + performance.timeOrigin) / 1000,\n              3\n            )\n          }\n        }\n\n        window.chrome.loadTimes = function() {\n          return {\n            ...protocolInfo,\n            ...timingInfo\n          }\n        }\n        utils.patchToString(window.chrome.loadTimes)\n      },\n      {\n        opts: this.opts\n      }\n    )\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\n/* global chrome */\n\ntest('stealth: will add functional chrome.loadTimes function mock', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin({}))\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const loadTimes = window.chrome.loadTimes()\n\n    return {\n      loadTimes: {\n        exists: window.chrome && 'loadTimes' in window.chrome,\n        toString: chrome.loadTimes.toString()\n      },\n      dataOK: {\n        connectionInfo: 'connectionInfo' in loadTimes,\n        npnNegotiatedProtocol: 'npnNegotiatedProtocol' in loadTimes,\n        navigationType: 'navigationType' in loadTimes,\n        wasAlternateProtocolAvailable:\n          'wasAlternateProtocolAvailable' in loadTimes,\n        wasFetchedViaSpdy: 'wasFetchedViaSpdy' in loadTimes,\n        wasNpnNegotiated: 'wasNpnNegotiated' in loadTimes,\n\n        firstPaintAfterLoadTime: 'firstPaintAfterLoadTime' in loadTimes,\n        requestTime: 'requestTime' in loadTimes,\n        startLoadTime: 'startLoadTime' in loadTimes,\n        commitLoadTime: 'commitLoadTime' in loadTimes,\n        finishDocumentLoadTime: 'finishDocumentLoadTime' in loadTimes,\n        finishLoadTime: 'finishLoadTime' in loadTimes,\n        firstPaintTime: 'firstPaintTime' in loadTimes\n      }\n    }\n  })\n\n  t.deepEqual(results, {\n    loadTimes: {\n      exists: true,\n      toString: 'function () { [native code] }'\n    },\n    dataOK: {\n      commitLoadTime: true,\n      connectionInfo: true,\n      finishDocumentLoadTime: true,\n      finishLoadTime: true,\n      firstPaintAfterLoadTime: true,\n      firstPaintTime: true,\n      navigationType: true,\n      npnNegotiatedProtocol: true,\n      requestTime: true,\n      startLoadTime: true,\n      wasAlternateProtocolAvailable: true,\n      wasFetchedViaSpdy: true,\n      wasNpnNegotiated: true\n    }\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/index.js#L23-L164)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nMock the `chrome.loadTimes` function if not available (e.g. when running headless).\nIt's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings and connection info.\n\nInternally chromium switched the implementation to use the WebPerformance API,\nso we can do the same to create a fully functional mock. :-)\n\nNote: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.\n\n- **See: <https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated>**\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming>**\n- **See: <https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium>**\n- **See: `chrome.csi` evasion**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\nconst STATIC_DATA = require('./staticData.json')\n\n/**\n * Mock the `chrome.runtime` object if not available (e.g. when running headless) and on a secure site.\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/chrome.runtime'\n  }\n\n  get defaults() {\n    return { runOnInsecureOrigins: false } // Override for testing\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { opts, STATIC_DATA }) => {\n        if (!window.chrome) {\n          // Use the exact property descriptor found in headful Chrome\n          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`\n          Object.defineProperty(window, 'chrome', {\n            writable: true,\n            enumerable: true,\n            configurable: false, // note!\n            value: {} // We'll extend that later\n          })\n        }\n\n        // That means we're running headful and don't need to mock anything\n        const existsAlready = 'runtime' in window.chrome\n        // `chrome.runtime` is only exposed on secure origins\n        const isNotSecure = !window.location.protocol.startsWith('https')\n        if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {\n          return // Nothing to do here\n        }\n\n        window.chrome.runtime = {\n          // There's a bunch of static data in that property which doesn't seem to change,\n          // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)`\n          ...STATIC_DATA,\n          // `chrome.runtime.id` is extension related and returns undefined in Chrome\n          get id() {\n            return undefined\n          },\n          // These two require more sophisticated mocks\n          connect: null,\n          sendMessage: null\n        }\n\n        const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({\n          NoMatchingSignature: new TypeError(\n            preamble + `No matching signature.`\n          ),\n          MustSpecifyExtensionID: new TypeError(\n            preamble +\n              `${method} called from a webpage must specify an Extension ID (string) for its first argument.`\n          ),\n          InvalidExtensionID: new TypeError(\n            preamble + `Invalid extension id: '${extensionId}'`\n          )\n        })\n\n        // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`:\n        // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90\n        const isValidExtensionID = str =>\n          str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)\n\n        /** Mock `chrome.runtime.sendMessage` */\n        const sendMessageHandler = {\n          apply: function(target, ctx, args) {\n            const [extensionId, options, responseCallback] = args || []\n\n            // Define custom errors\n            const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): `\n            const Errors = makeCustomRuntimeErrors(\n              errorPreamble,\n              `chrome.runtime.sendMessage()`,\n              extensionId\n            )\n\n            // Check if the call signature looks ok\n            const noArguments = args.length === 0\n            const tooManyArguments = args.length > 4\n            const incorrectOptions = options && typeof options !== 'object'\n            const incorrectResponseCallback =\n              responseCallback && typeof responseCallback !== 'function'\n            if (\n              noArguments ||\n              tooManyArguments ||\n              incorrectOptions ||\n              incorrectResponseCallback\n            ) {\n              throw Errors.NoMatchingSignature\n            }\n\n            // At least 2 arguments are required before we even validate the extension ID\n            if (args.length < 2) {\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            // Now let's make sure we got a string as extension ID\n            if (typeof extensionId !== 'string') {\n              throw Errors.NoMatchingSignature\n            }\n\n            if (!isValidExtensionID(extensionId)) {\n              throw Errors.InvalidExtensionID\n            }\n\n            return undefined // Normal behavior\n          }\n        }\n        utils.mockWithProxy(\n          window.chrome.runtime,\n          'sendMessage',\n          function sendMessage() {},\n          sendMessageHandler\n        )\n\n        /**\n         * Mock `chrome.runtime.connect`\n         *\n         * @see https://developer.chrome.com/apps/runtime#method-connect\n         */\n        const connectHandler = {\n          apply: function(target, ctx, args) {\n            const [extensionId, connectInfo] = args || []\n\n            // Define custom errors\n            const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): `\n            const Errors = makeCustomRuntimeErrors(\n              errorPreamble,\n              `chrome.runtime.connect()`,\n              extensionId\n            )\n\n            // Behavior differs a bit from sendMessage:\n            const noArguments = args.length === 0\n            const emptyStringArgument = args.length === 1 && extensionId === ''\n            if (noArguments || emptyStringArgument) {\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            const tooManyArguments = args.length > 2\n            const incorrectConnectInfoType =\n              connectInfo && typeof connectInfo !== 'object'\n\n            if (tooManyArguments || incorrectConnectInfoType) {\n              throw Errors.NoMatchingSignature\n            }\n\n            const extensionIdIsString = typeof extensionId === 'string'\n            if (extensionIdIsString && extensionId === '') {\n              throw Errors.MustSpecifyExtensionID\n            }\n            if (extensionIdIsString && !isValidExtensionID(extensionId)) {\n              throw Errors.InvalidExtensionID\n            }\n\n            // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate\n            const validateConnectInfo = ci => {\n              // More than a first param connectInfo as been provided\n              if (args.length > 1) {\n                throw Errors.NoMatchingSignature\n              }\n              // An empty connectInfo has been provided\n              if (Object.keys(ci).length === 0) {\n                throw Errors.MustSpecifyExtensionID\n              }\n              // Loop over all connectInfo props an check them\n              Object.entries(ci).forEach(([k, v]) => {\n                const isExpected = ['name', 'includeTlsChannelId'].includes(k)\n                if (!isExpected) {\n                  throw new TypeError(\n                    errorPreamble + `Unexpected property: '${k}'.`\n                  )\n                }\n                const MismatchError = (propName, expected, found) =>\n                  TypeError(\n                    errorPreamble +\n                      `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.`\n                  )\n                if (k === 'name' && typeof v !== 'string') {\n                  throw MismatchError(k, 'string', typeof v)\n                }\n                if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {\n                  throw MismatchError(k, 'boolean', typeof v)\n                }\n              })\n            }\n            if (typeof extensionId === 'object') {\n              validateConnectInfo(extensionId)\n              throw Errors.MustSpecifyExtensionID\n            }\n\n            // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well\n            return utils.patchToStringNested(makeConnectResponse())\n          }\n        }\n        utils.mockWithProxy(\n          window.chrome.runtime,\n          'connect',\n          function connect() {},\n          connectHandler\n        )\n\n        function makeConnectResponse() {\n          const onSomething = () => ({\n            addListener: function addListener() {},\n            dispatch: function dispatch() {},\n            hasListener: function hasListener() {},\n            hasListeners: function hasListeners() {\n              return false\n            },\n            removeListener: function removeListener() {}\n          })\n\n          const response = {\n            name: '',\n            sender: undefined,\n            disconnect: function disconnect() {},\n            onDisconnect: onSomething(),\n            onMessage: onSomething(),\n            postMessage: function postMessage() {\n              if (!arguments.length) {\n                throw new TypeError(`Insufficient number of arguments.`)\n              }\n              throw new Error(`Attempting to use a disconnected port object`)\n            }\n          }\n          return response\n        }\n      },\n      {\n        opts: this.opts,\n        STATIC_DATA\n      }\n    )\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\nconst STATIC_DATA = require('./staticData.json')\n\n/* global chrome */\n\ntest('vanilla: is chrome false', async t => {\n  const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line\n  const { pageFnResult: chrome, hasChrome } = await getVanillaFingerPrint(\n    pageFn\n  )\n  t.is(hasChrome, false)\n  t.false(chrome instanceof Object)\n  t.is(chrome, undefined)\n})\n\ntest('stealth: is chrome true', async t => {\n  const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line\n  const { pageFnResult: chrome, hasChrome } = await getStealthFingerPrint(\n    Plugin,\n    pageFn\n  )\n  t.is(hasChrome, true)\n  t.true(chrome instanceof Object)\n})\n\ntest('stealth: will add convincing chrome.runtime object', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      runOnInsecureOrigins: true // for testing\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  //\n\n  const results = await page.evaluate(() => {\n    const catchErr = (fn, ...args) => {\n      try {\n        return fn.apply(this, args)\n      } catch (err) {\n        return err.toString()\n      }\n    }\n\n    return {\n      runtime: {\n        exists: window.chrome && 'runtime' in window.chrome,\n        toString: chrome.runtime.toString()\n      },\n      staticData: {\n        OnInstalledReason: chrome.runtime.OnInstalledReason,\n        OnRestartRequiredReason: chrome.runtime.OnRestartRequiredReason,\n        PlatformArch: chrome.runtime.PlatformArch,\n        PlatformNaclArch: chrome.runtime.PlatformNaclArch,\n        PlatformOs: chrome.runtime.PlatformOs,\n        RequestUpdateCheckStatus: chrome.runtime.RequestUpdateCheckStatus\n      },\n      id: {\n        exists: 'id' in chrome.runtime,\n        undefined: chrome.runtime.id === undefined\n      },\n      sendMessage: {\n        exists: 'sendMessage' in chrome.runtime,\n        name: chrome.runtime.sendMessage.name,\n        toString1: chrome.runtime.sendMessage + '',\n        toString2: chrome.runtime.sendMessage.toString(),\n        validIdWorks:\n          chrome.runtime.sendMessage('nckgahadagoaajjgafhacjanaoiihapd', '') ===\n          undefined\n      },\n      sendMessageErrors: {\n        noArg: catchErr(chrome.runtime.sendMessage),\n        singleArg: catchErr(chrome.runtime.sendMessage, ''),\n        tooManyArg: catchErr(\n          chrome.runtime.sendMessage,\n          '',\n          '',\n          '',\n          '',\n          '',\n          ''\n        ),\n        incorrectArg: catchErr(chrome.runtime.sendMessage, '', '', {}, ''),\n        noValidID: catchErr(chrome.runtime.sendMessage, 'foo', '')\n      }\n    }\n  })\n\n  const bla = `TypeError: Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback)`\n  t.deepEqual(results, {\n    runtime: {\n      exists: true,\n      toString: '[object Object]'\n    },\n    staticData: STATIC_DATA,\n    id: {\n      exists: true,\n      undefined: true\n    },\n    sendMessage: {\n      exists: true,\n      name: 'sendMessage',\n      toString1: 'function sendMessage() { [native code] }',\n      toString2: 'function sendMessage() { [native code] }',\n      validIdWorks: true\n    },\n    sendMessageErrors: {\n      noArg: `${bla}: No matching signature.`,\n      singleArg: `${bla}: chrome.runtime.sendMessage() called from a webpage must specify an Extension ID (string) for its first argument.`,\n      tooManyArg: `${bla}: No matching signature.`,\n      incorrectArg: `${bla}: No matching signature.`,\n      noValidID: `${bla}: Invalid extension id: 'foo'`\n    }\n  })\n})\n\ntest('stealth: will add convincing chrome.runtime.connect', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      runOnInsecureOrigins: true // for testing\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const catchErr = (fn, ...args) => {\n      try {\n        return fn.apply(this, args)\n      } catch (err) {\n        return err.toString()\n      }\n    }\n\n    return {\n      connect: {\n        exists: 'connect' in chrome.runtime,\n        name: chrome.runtime.connect.name,\n        toString1: chrome.runtime.connect + '',\n        toString2: chrome.runtime.connect.toString(),\n        validIdWorks:\n          chrome.runtime.connect('nckgahadagoaajjgafhacjanaoiihapd') !==\n          undefined\n      },\n      connectErrors: {\n        noArg: catchErr(chrome.runtime.connect),\n        singleArg: catchErr(chrome.runtime.connect, ''),\n        tooManyArg: catchErr(chrome.runtime.connect, '', '', '', '', '', ''),\n        incorrectArg: catchErr(chrome.runtime.connect, '', '', {}, ''),\n        noValidID: catchErr(chrome.runtime.connect, 'foo', ''),\n        connectInfoFirst: {\n          emptyObject: catchErr(chrome.runtime.connect, {}),\n          tooManyArg: catchErr(chrome.runtime.connect, {}, {}),\n          unexpectedProp: catchErr(chrome.runtime.connect, { wtf: true }),\n          invalidName: catchErr(chrome.runtime.connect, { name: 666 }),\n          invalidTLS: catchErr(chrome.runtime.connect, {\n            includeTlsChannelId: 777\n          }),\n          invalidBoth: catchErr(chrome.runtime.connect, {\n            name: 666,\n            includeTlsChannelId: 777\n          }),\n          validName: catchErr(chrome.runtime.connect, { name: 'foo' }),\n          missingExtensionId: catchErr(chrome.runtime.connect, {\n            name: 'bob',\n            includeTlsChannelId: false\n          })\n        }\n      }\n    }\n  })\n\n  const bla = `TypeError: Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo)`\n  t.deepEqual(results, {\n    connect: {\n      exists: true,\n      name: 'connect',\n      toString1: 'function connect() { [native code] }',\n      toString2: 'function connect() { [native code] }',\n      validIdWorks: true\n    },\n    connectErrors: {\n      noArg: `${bla}: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.`,\n      singleArg: `${bla}: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.`,\n      tooManyArg: `${bla}: No matching signature.`,\n      incorrectArg: `${bla}: No matching signature.`,\n      noValidID: `${bla}: Invalid extension id: 'foo'`,\n      connectInfoFirst: {\n        emptyObject: `${bla}: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.`,\n        tooManyArg: `${bla}: No matching signature.`,\n        unexpectedProp: `${bla}: Unexpected property: 'wtf'.`,\n        invalidName: `${bla}: Error at property 'name': Invalid type: expected string, found number.`,\n        invalidTLS: `${bla}: Error at property 'includeTlsChannelId': Invalid type: expected boolean, found number.`,\n        invalidBoth: `${bla}: Error at property 'name': Invalid type: expected string, found number.`,\n        validName: `${bla}: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.`,\n        missingExtensionId: `${bla}: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.`\n      }\n    }\n  })\n})\n\ntest('stealth: will add convincing chrome.runtime.connect response', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      runOnInsecureOrigins: true // for testing\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const connectResponse = chrome.runtime.connect(\n      'nckgahadagoaajjgafhacjanaoiihapd'\n    )\n\n    return {\n      connectResponse: {\n        exists: !!connectResponse,\n        toString1: connectResponse + '',\n        toString2: connectResponse.toString(),\n        nestedToString: connectResponse.onDisconnect.addListener + ''\n      },\n      disconnect: {\n        toString: connectResponse.disconnect + '',\n        noReturn: connectResponse.disconnect() === undefined\n      }\n    }\n  })\n\n  t.deepEqual(results, {\n    connectResponse: {\n      exists: true,\n      toString1: '[object Object]',\n      toString2: '[object Object]',\n      nestedToString: `function addListener() { [native code] }`\n    },\n    disconnect: {\n      toString: `function disconnect() { [native code] }`,\n      noReturn: true\n    }\n  })\n})\n\n// FIXME: This changed in more recent chrome versions\n// test('stealth: error stack is fine', async t => {\n//   const puppeteer = addExtra(vanillaPuppeteer).use(\n//     Plugin({\n//       runOnInsecureOrigins: true // for testing\n//     })\n//   )\n//   const browser = await puppeteer.launch({ headless: true })\n//   const page = await browser.newPage()\n\n//   const result = await page.evaluate(() => {\n//     const catchErr = (fn, ...args) => {\n//       try {\n//         return fn.apply(this, args)\n//       } catch ({ name, message, stack }) {\n//         return {\n//           name,\n//           message,\n//           stack\n//         }\n//       }\n//     }\n//     return catchErr(chrome.runtime.connect, '').stack\n//   })\n\n//   /**\n//    * OK:\n// TypeError: Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.␊\n//   -       at catchErr (__puppeteer_evaluation_script__:4:19)␊\n//   -       at __puppeteer_evaluation_script__:18:12\n//    */\n//   t.is(result.split('\\n').length, 3)\n// })\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n- [sendMessageHandler()](#sendmessagehandler)\n- [connectHandler()](#connecthandler)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js#L13-L251)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nMock the `chrome.runtime` object if not available (e.g. when running headless) and on a secure site.\n\n---\n\n### [sendMessageHandler()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js#L80-L123)\n\nMock `chrome.runtime.sendMessage`\n\n---\n\n### [connectHandler()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js#L136-L210)\n\nMock `chrome.runtime.connect`\n\n- **See: <https://developer.chrome.com/apps/runtime#method-connect>**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/staticData.json",
    "content": "{\n  \"OnInstalledReason\": {\n    \"CHROME_UPDATE\": \"chrome_update\",\n    \"INSTALL\": \"install\",\n    \"SHARED_MODULE_UPDATE\": \"shared_module_update\",\n    \"UPDATE\": \"update\"\n  },\n  \"OnRestartRequiredReason\": {\n    \"APP_UPDATE\": \"app_update\",\n    \"OS_UPDATE\": \"os_update\",\n    \"PERIODIC\": \"periodic\"\n  },\n  \"PlatformArch\": {\n    \"ARM\": \"arm\",\n    \"ARM64\": \"arm64\",\n    \"MIPS\": \"mips\",\n    \"MIPS64\": \"mips64\",\n    \"X86_32\": \"x86-32\",\n    \"X86_64\": \"x86-64\"\n  },\n  \"PlatformNaclArch\": {\n    \"ARM\": \"arm\",\n    \"MIPS\": \"mips\",\n    \"MIPS64\": \"mips64\",\n    \"X86_32\": \"x86-32\",\n    \"X86_64\": \"x86-64\"\n  },\n  \"PlatformOs\": {\n    \"ANDROID\": \"android\",\n    \"CROS\": \"cros\",\n    \"LINUX\": \"linux\",\n    \"MAC\": \"mac\",\n    \"OPENBSD\": \"openbsd\",\n    \"WIN\": \"win\"\n  },\n  \"RequestUpdateCheckStatus\": {\n    \"NO_UPDATE\": \"no_update\",\n    \"THROTTLED\": \"throttled\",\n    \"UPDATE_AVAILABLE\": \"update_available\"\n  }\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/defaultArgs/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst argsToIgnore = [\n  '--disable-extensions',\n  '--disable-default-apps',\n  '--disable-component-extensions-with-background-pages'\n]\n\n/**\n * A CDP driver like puppeteer can make use of various browser launch arguments that are\n * adversarial to mimicking a regular browser and need to be stripped when launching the browser.\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/defaultArgs'\n  }\n\n  get requirements() {\n    return new Set(['runLast']) // So other plugins can modify launch options before\n  }\n\n  async beforeLaunch(options = {}) {\n    options.ignoreDefaultArgs = options.ignoreDefaultArgs || []\n    if (options.ignoreDefaultArgs === true) {\n      // that means the user explicitly wants to disable all default arguments\n      return\n    }\n    argsToIgnore.forEach(arg => {\n      if (options.ignoreDefaultArgs.includes(arg)) {\n        return\n      }\n      options.ignoreDefaultArgs.push(arg)\n    })\n  }\n}\n\nmodule.exports = function (pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n\nmodule.exports.argsToIgnore = argsToIgnore\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/defaultArgs/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\nconst Plugin = require('.')\nconst { argsToIgnore } = require('.')\n\ntest('vanilla: uses args to ignore', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  const client =\n    typeof page._client === 'function' ? page._client() : page._client\n  const { arguments: launchArgs } = await client.send(\n    'Browser.getBrowserCommandLine'\n  )\n  const ok = argsToIgnore.every(arg => launchArgs.includes(arg))\n  if (!ok) {\n    console.log({ argsToIgnore, launchArgs })\n  }\n  t.is(ok, true)\n})\n\ntest('stealth: does not use args to ignore', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  const client =\n    typeof page._client === 'function' ? page._client() : page._client\n  const { arguments: launchArgs } = await client.send(\n    'Browser.getBrowserCommandLine'\n  )\n  const ok = argsToIgnore.every(arg => !launchArgs.includes(arg))\n  if (!ok) {\n    console.log({ argsToIgnore, launchArgs })\n  }\n  t.is(ok, true)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/defaultArgs/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/defaultArgs/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/358246d5cc56bbb8800624128503482b8d7b426a/packages/puppeteer-extra-plugin-stealth/evasions/defaultArgs/index.js#L15-L41)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nA CDP driver like puppeteer can make use of various browser launch arguments that are\nadversarial to mimicking a regular browser and need to be stripped when launching the browser.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/iframe.contentWindow/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Fix for the HEADCHR_IFRAME detection (iframe.contentWindow.chrome), hopefully this time without breaking iframes.\n * Note: Only `srcdoc` powered iframes cause issues due to a chromium bug:\n *\n * https://github.com/puppeteer/puppeteer/issues/1106\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/iframe.contentWindow'\n  }\n\n  get requirements() {\n    // Make sure `chrome.runtime` has ran, we use data defined by it (e.g. `window.chrome`)\n    return new Set(['runLast'])\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument((utils, opts) => {\n      try {\n        // Adds a contentWindow proxy to the provided iframe element\n        const addContentWindowProxy = iframe => {\n          const contentWindowProxy = {\n            get(target, key) {\n              // Now to the interesting part:\n              // We actually make this thing behave like a regular iframe window,\n              // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :)\n              // That makes it possible for these assertions to be correct:\n              // iframe.contentWindow.self === window.top // must be false\n              if (key === 'self') {\n                return this\n              }\n              // iframe.contentWindow.frameElement === iframe // must be true\n              if (key === 'frameElement') {\n                return iframe\n              }\n              // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.\n              if (key === '0') {\n                return undefined\n              }\n              return Reflect.get(target, key)\n            }\n          }\n\n          if (!iframe.contentWindow) {\n            const proxy = new Proxy(window, contentWindowProxy)\n            Object.defineProperty(iframe, 'contentWindow', {\n              get() {\n                return proxy\n              },\n              set(newValue) {\n                return newValue // contentWindow is immutable\n              },\n              enumerable: true,\n              configurable: false\n            })\n          }\n        }\n\n        // Handles iframe element creation, augments `srcdoc` property so we can intercept further\n        const handleIframeCreation = (target, thisArg, args) => {\n          const iframe = target.apply(thisArg, args)\n\n          // We need to keep the originals around\n          const _iframe = iframe\n          const _srcdoc = _iframe.srcdoc\n\n          // Add hook for the srcdoc property\n          // We need to be very surgical here to not break other iframes by accident\n          Object.defineProperty(iframe, 'srcdoc', {\n            configurable: true, // Important, so we can reset this later\n            get: function() {\n              return _srcdoc\n            },\n            set: function(newValue) {\n              addContentWindowProxy(this)\n              // Reset property, the hook is only needed once\n              Object.defineProperty(iframe, 'srcdoc', {\n                configurable: false,\n                writable: false,\n                value: _srcdoc\n              })\n              _iframe.srcdoc = newValue\n            }\n          })\n          return iframe\n        }\n\n        // Adds a hook to intercept iframe creation events\n        const addIframeCreationSniffer = () => {\n          /* global document */\n          const createElementHandler = {\n            // Make toString() native\n            get(target, key) {\n              return Reflect.get(target, key)\n            },\n            apply: function(target, thisArg, args) {\n              const isIframe =\n                args && args.length && `${args[0]}`.toLowerCase() === 'iframe'\n              if (!isIframe) {\n                // Everything as usual\n                return target.apply(thisArg, args)\n              } else {\n                return handleIframeCreation(target, thisArg, args)\n              }\n            }\n          }\n          // All this just due to iframes with srcdoc bug\n          utils.replaceWithProxy(\n            document,\n            'createElement',\n            createElementHandler\n          )\n        }\n\n        // Let's go\n        addIframeCreationSniffer()\n      } catch (err) {\n        // console.warn(err)\n      }\n    })\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/iframe.contentWindow/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint,\n  dummyHTMLPath,\n  vanillaPuppeteer,\n  addExtra\n} = require('../../test/util')\n// const Plugin = require('.')\n// NOTE: We're using the full plugin for testing here as `iframe.contentWindow` uses data set by `chrome.runtime`\nconst Plugin = require('puppeteer-extra-plugin-stealth')\n\n// Fix CI issues with old versions\nconst isOldPuppeteerVersion = () => {\n  const version = process.env.PUPPETEER_VERSION\n  const isOld = version && (version === '1.9.0' || version === '1.6.2')\n  return isOld\n}\n\ntest('vanilla: will be undefined', async t => {\n  const { iframeChrome } = await getVanillaFingerPrint()\n  t.is(iframeChrome, 'undefined')\n})\n\ntest('stealth: will be object', async t => {\n  const { iframeChrome } = await getStealthFingerPrint(Plugin)\n  t.is(iframeChrome, 'object')\n})\n\ntest('stealth: will not break iframes', async t => {\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  const testFuncReturnValue = 'TESTSTRING'\n  await page.evaluate(returnValue => {\n    const { document } = window // eslint-disable-line\n    const body = document.querySelector('body')\n    const iframe = document.createElement('iframe')\n    body.srcdoc = 'foobar'\n    body.appendChild(iframe)\n    iframe.contentWindow.mySuperFunction = () => returnValue\n  }, testFuncReturnValue)\n  const realReturn = await page.evaluate(\n    () => document.querySelector('iframe').contentWindow.mySuperFunction() // eslint-disable-line\n  )\n  await browser.close()\n\n  t.is(realReturn, 'TESTSTRING')\n})\n\ntest('vanilla: will not have contentWindow[0]', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const zero = await page.evaluate(returnValue => {\n    const { document } = window // eslint-disable-line\n    const body = document.querySelector('body')\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'foobar'\n    body.appendChild(iframe)\n    return typeof iframe.contentWindow[0]\n  })\n  await browser.close()\n\n  t.is(zero, 'undefined')\n})\n\ntest('stealth: will not have contentWindow[0]', async t => {\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  const zero = await page.evaluate(returnValue => {\n    const { document } = window // eslint-disable-line\n    const body = document.querySelector('body')\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'foobar'\n    body.appendChild(iframe)\n    return typeof iframe.contentWindow[0]\n  })\n  await browser.close()\n\n  t.is(zero, 'undefined')\n})\n\ntest('vanilla: will not have chrome runtine in any frame', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  await page.goto('file://' + dummyHTMLPath)\n\n  const basiciframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const sandboxSOiframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.setAttribute('sandbox', 'allow-same-origin')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const sandboxSOASiframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.setAttribute('sandbox', 'allow-same-origin allow-scripts')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const srcdociframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.srcdoc = 'blank page, boys.'\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  // console.log('basic iframe', basiciframe)\n  // console.log('sandbox same-origin iframe', sandboxSOiframe)\n  // console.log('sandbox same-origin&scripts iframe', sandboxSOASiframe)\n  // console.log('srcdoc iframe', srcdociframe)\n\n  await browser.close()\n\n  t.is(typeof basiciframe, 'undefined')\n  t.is(typeof sandboxSOiframe, 'undefined')\n  t.is(typeof sandboxSOASiframe, 'undefined')\n  t.is(typeof srcdociframe, 'undefined')\n})\n\ntest('stealth: it will cover all frames including srcdoc', async t => {\n  // const browser = await vanillaPuppeteer.launch({ headless: false })\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  await page.goto('file://' + dummyHTMLPath)\n\n  const basiciframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const sandboxSOiframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.setAttribute('sandbox', 'allow-same-origin')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const sandboxSOASiframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.setAttribute('sandbox', 'allow-same-origin allow-scripts')\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  const srcdociframe = await page.evaluate(() => {\n    const el = document.createElement('iframe')\n    el.srcdoc = 'blank page, boys.'\n    document.body.appendChild(el)\n    return el.contentWindow.chrome\n  })\n\n  // console.log('basic iframe', basiciframe)\n  // console.log('sandbox same-origin iframe', sandboxSOiframe)\n  // console.log('sandbox same-origin&scripts iframe', sandboxSOASiframe)\n  // console.log('srcdoc iframe', srcdociframe)\n\n  await browser.close()\n\n  if (isOldPuppeteerVersion()) {\n    t.is(typeof basiciframe, 'object')\n  } else {\n    t.is(typeof basiciframe, 'object')\n    t.is(typeof sandboxSOiframe, 'object')\n    t.is(typeof sandboxSOASiframe, 'object')\n    t.is(typeof srcdociframe, 'object')\n  }\n})\n\ntest('vanilla: will allow to define property contentWindow', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const iframe = await page.evaluate(() => {\n    const { document } = window // eslint-disable-line\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'foobar'\n    return Object.defineProperty(iframe, 'contentWindow', { value: 'baz' })\n  })\n  await browser.close()\n\n  t.is(typeof iframe, 'object')\n})\n\n// test('stealth: will allow to define property contentWindow', async t => {\n//   const browser = await addExtra(vanillaPuppeteer)\n//     .use(Plugin())\n//     .launch({ headless: true })\n//   const page = await browser.newPage()\n\n//   const iframe = await page.evaluate(() => {\n//     const { document } = window // eslint-disable-line\n//     const iframe = document.createElement('iframe')\n//     iframe.srcdoc = 'foobar'\n//     return Object.defineProperty(iframe, 'contentWindow', { value: 'baz' })\n//   })\n//   await browser.close()\n\n//   t.is(typeof iframe, 'object')\n// })\n\ntest('vanilla: will return undefined for getOwnPropertyDescriptor of contentWindow', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const iframe = await page.evaluate(() => {\n    const { document } = window // eslint-disable-line\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'foobar'\n    return Object.getOwnPropertyDescriptor(iframe, 'contentWindow')\n  })\n  await browser.close()\n\n  t.is(iframe, undefined)\n})\n\n// test('stealth: will return undefined for getOwnPropertyDescriptor of contentWindow', async t => {\n//   const browser = await addExtra(vanillaPuppeteer)\n//     .use(Plugin())\n//     .launch({ headless: true })\n//   const page = await browser.newPage()\n\n//   const iframe = await page.evaluate(() => {\n//     const { document } = window // eslint-disable-line\n//     const iframe = document.createElement('iframe')\n//     iframe.srcdoc = 'foobar'\n//     return Object.getOwnPropertyDescriptor(iframe, 'contentWindow')\n//   })\n//   await browser.close()\n\n//   t.is(iframe, undefined)\n// })\n\n/* global HTMLIFrameElement */\ntest('stealth: it will emulate advanved contentWindow features correctly', async t => {\n  // const browser = await vanillaPuppeteer.launch({ headless: false })\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  await page.goto('file://' + dummyHTMLPath)\n\n  // page.on('console', msg => {\n  //   console.log('Page console: ', msg.text())\n  // })\n\n  const results = await page.evaluate(() => {\n    const results = {}\n\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'page intentionally left blank' // Note: srcdoc\n    document.body.appendChild(iframe)\n\n    const basicIframe = document.createElement('iframe')\n    basicIframe.src = 'data:text/plain;charset=utf-8,foobar'\n    document.body.appendChild(iframe)\n\n    results.descriptors = (() => {\n      // Verify iframe prototype isn't touched\n      const descriptors = Object.getOwnPropertyDescriptors(\n        HTMLIFrameElement.prototype\n      )\n      return descriptors.contentWindow.get.toString()\n    })()\n\n    results.noProxySignature = (() => {\n      return iframe.srcdoc.toString.hasOwnProperty('[[IsRevoked]]') // eslint-disable-line\n    })()\n\n    results.doesExist = (() => {\n      // Verify iframe isn't remapped to main window\n      return !!iframe.contentWindow\n    })()\n\n    results.isNotAClone = (() => {\n      // Verify iframe isn't remapped to main window\n      return iframe.contentWindow !== window\n    })()\n\n    results.hasPlugins = (() => {\n      return iframe.contentWindow.navigator.plugins.length > 0\n    })()\n\n    results.hasSameNumberOfPlugins = (() => {\n      return (\n        window.navigator.plugins.length ===\n        iframe.contentWindow.navigator.plugins.length\n      )\n    })()\n\n    results.SelfIsNotWindow = (() => {\n      return iframe.contentWindow.self !== window\n    })()\n\n    results.SelfIsNotWindowTop = (() => {\n      return iframe.contentWindow.self !== window.top\n    })()\n\n    results.TopIsNotSame = (() => {\n      return iframe.contentWindow.top !== iframe.contentWindow\n    })()\n\n    results.FrameElementMatches = (() => {\n      return iframe.contentWindow.frameElement === iframe\n    })()\n\n    results.StackTraces = (() => {\n      try {\n        // eslint-disable-next-line\n        document['createElement'](0)\n      } catch (e) {\n        return e.stack\n      }\n      return false\n    })()\n\n    return results\n  })\n\n  await browser.close()\n\n  if (isOldPuppeteerVersion()) {\n    t.true(true)\n    return\n  }\n\n  t.is(results.descriptors, 'function get contentWindow() { [native code] }')\n  t.true(results.doesExist)\n  t.true(results.isNotAClone)\n  t.true(results.hasPlugins)\n  t.true(results.hasSameNumberOfPlugins)\n  t.true(results.SelfIsNotWindow)\n  t.true(results.SelfIsNotWindowTop)\n  t.true(results.TopIsNotSame)\n  t.false(results.StackTraces.includes(`at Object.apply`))\n})\n\ntest('regression: new method will not break hcaptcha', async t => {\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  page.waitForTimeout = page.waitForTimeout || page.waitFor\n\n  await page.goto('https://democaptcha.com/demo-form-eng/hcaptcha.html', {\n    waitUntil: 'networkidle2'\n  })\n  await page.evaluate(() => {\n    window.hcaptcha.execute()\n  })\n  await page.waitForTimeout(2 * 1000)\n  const { hasChallengePopup } = await page.evaluate(() => {\n    const hasChallengePopup = !!document.querySelectorAll(\n      `div[style*='visible'] iframe[title*='hCaptcha challenge']`\n    ).length\n    return { hasChallengePopup }\n  })\n  await browser.close()\n  t.true(hasChallengePopup)\n})\n\ntest('regression: new method will not break recaptcha popup', async t => {\n  // const browser = await vanillaPuppeteer.launch({ headless: false })\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n\n  page.waitForTimeout = page.waitForTimeout || page.waitFor\n\n  await page.goto('https://www.fbdemo.com/invisible-captcha/index.html', {\n    waitUntil: 'networkidle2'\n  })\n\n  await page.type('#tswname', 'foo')\n  await page.type('#tswemail', 'foo@foo.foo')\n  await page.type(\n    '#tswcomments',\n    'In the depth of winter, I finally learned that within me there lay an invincible summer.'\n  )\n  await page.click('#tswsubmit')\n  await page.waitForTimeout(1000)\n  const { hasRecaptchaPopup } = await page.evaluate(() => {\n    const hasRecaptchaPopup = !!document.querySelectorAll(\n      `iframe[title*=\"recaptcha challenge\"]`\n    ).length\n    return { hasRecaptchaPopup }\n  })\n  await browser.close()\n  t.true(hasRecaptchaPopup)\n})\n\ntest('regression: old method indeed did break recaptcha popup', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  page.waitForTimeout = page.waitForTimeout || page.waitFor\n  // Old method\n  await page.evaluateOnNewDocument(() => {\n    // eslint-disable-next-line\n    Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {\n      get: function() {\n        return window\n      }\n    })\n  })\n  await page.goto('https://www.fbdemo.com/invisible-captcha/index.html', {\n    waitUntil: 'networkidle2'\n  })\n  await page.type('#tswname', 'foo')\n  await page.type('#tswemail', 'foo@foo.foo')\n  await page.type(\n    '#tswcomments',\n    'In the depth of winter, I finally learned that within me there lay an invincible summer.'\n  )\n  await page.click('#tswsubmit')\n  await page.waitForTimeout(1000)\n\n  const { hasRecaptchaPopup } = await page.evaluate(() => {\n    const hasRecaptchaPopup = !!document.querySelectorAll(\n      `iframe[title*=\"recaptcha challenge\"]`\n    ).length\n    return { hasRecaptchaPopup }\n  })\n  await browser.close()\n  t.false(hasRecaptchaPopup)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/iframe.contentWindow/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/iframe.contentWindow/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/iframe.contentWindow/index.js#L11-L125)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nFix for the HEADCHR_IFRAME detection (iframe.contentWindow.chrome), hopefully this time without breaking iframes.\nNote: Only `srcdoc` powered iframes cause issues due to a chromium bug:\n\n<https://github.com/puppeteer/puppeteer/issues/1106>\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Fix Chromium not reporting \"probably\" to codecs like `videoEl.canPlayType('video/mp4; codecs=\"avc1.42E01E\"')`.\n * (Chromium doesn't support proprietary codecs, only Chrome does)\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/media.codecs'\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(utils => {\n      /**\n       * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.\n       *\n       * @example\n       * video/webm; codecs=\"vp8, vorbis\"\n       * video/mp4; codecs=\"avc1.42E01E\"\n       * audio/x-m4a;\n       * audio/ogg; codecs=\"vorbis\"\n       * @param {String} arg\n       */\n      const parseInput = arg => {\n        const [mime, codecStr] = arg.trim().split(';')\n        let codecs = []\n        if (codecStr && codecStr.includes('codecs=\"')) {\n          codecs = codecStr\n            .trim()\n            .replace(`codecs=\"`, '')\n            .replace(`\"`, '')\n            .trim()\n            .split(',')\n            .filter(x => !!x)\n            .map(x => x.trim())\n        }\n        return {\n          mime,\n          codecStr,\n          codecs\n        }\n      }\n\n      const canPlayType = {\n        // Intercept certain requests\n        apply: function(target, ctx, args) {\n          if (!args || !args.length) {\n            return target.apply(ctx, args)\n          }\n          const { mime, codecs } = parseInput(args[0])\n          // This specific mp4 codec is missing in Chromium\n          if (mime === 'video/mp4') {\n            if (codecs.includes('avc1.42E01E')) {\n              return 'probably'\n            }\n          }\n          // This mimetype is only supported if no codecs are specified\n          if (mime === 'audio/x-m4a' && !codecs.length) {\n            return 'maybe'\n          }\n\n          // This mimetype is only supported if no codecs are specified\n          if (mime === 'audio/aac' && !codecs.length) {\n            return 'probably'\n          }\n          // Everything else as usual\n          return target.apply(ctx, args)\n        }\n      }\n\n      /* global HTMLMediaElement */\n      utils.replaceWithProxy(\n        HTMLMediaElement.prototype,\n        'canPlayType',\n        canPlayType\n      )\n    })\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\ntest('vanilla: doesnt support proprietary codecs', async t => {\n  const { videoCodecs, audioCodecs } = await getVanillaFingerPrint()\n  t.deepEqual(videoCodecs, { ogg: 'probably', h264: '', webm: 'probably' })\n  t.deepEqual(audioCodecs, {\n    ogg: 'probably',\n    mp3: 'probably',\n    wav: 'probably',\n    m4a: '',\n    aac: ''\n  })\n})\n\ntest('vanilla: will not have modifications', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // https://datadome.co/bot-detection/client-side-detection-is-essential-for-bot-protection/\n  const test1 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.toString()\n  })\n  t.is(test1, 'function canPlayType() { [native code] }')\n\n  const test2 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.name\n  })\n  t.is(test2, 'canPlayType')\n})\n\ntest('stealth: supports proprietary codecs', async t => {\n  const { videoCodecs, audioCodecs } = await getStealthFingerPrint(Plugin)\n  t.deepEqual(videoCodecs, {\n    ogg: 'probably',\n    h264: 'probably',\n    webm: 'probably'\n  })\n  t.deepEqual(audioCodecs, {\n    ogg: 'probably',\n    mp3: 'probably',\n    wav: 'probably',\n    m4a: 'maybe',\n    aac: 'probably'\n  })\n})\n\ntest('stealth: will not leak modifications', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  // https://datadome.co/bot-detection/client-side-detection-is-essential-for-bot-protection/\n  const test1 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.toString()\n  })\n  t.is(test1, 'function canPlayType() { [native code] }')\n\n  const test2 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.name\n  })\n  t.is(test2, 'canPlayType')\n\n  // Double check the plugin is active and spoofing e.g. the aac codec results\n  const isWorkingTest = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType('audio/aac') === 'probably' // empty in Chromium without stealth plugin\n  })\n  t.true(isWorkingTest)\n})\n\ntest('vanilla: normal toString stuff', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.toString + ''\n  })\n  t.is(test1, 'function toString() { [native code] }')\n})\n\ntest('stealth: will not leak toString stuff', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => {\n    const audioElt = document.createElement('audio')\n    return audioElt.canPlayType.toString + ''\n  })\n  t.is(test1, 'function toString() { [native code] }') // returns function () { [native code] }\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n- [parseInput(arg)](#parseinputarg)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/index.js#L12-L88)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nFix Chromium not reporting \"probably\" to codecs like `videoEl.canPlayType('video/mp4; codecs=\"avc1.42E01E\"')`.\n(Chromium doesn't support proprietary codecs, only Chrome does)\n\n---\n\n### [parseInput(arg)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/media.codecs/index.js#L33-L51)\n\n- `arg` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**\n\nInput might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.\n\nExample:\n\n```javascript\nvideo / webm\ncodecs = 'vp8, vorbis'\nvideo / mp4\ncodecs = 'avc1.42E01E'\naudio / x - m4a\naudio / ogg\ncodecs = 'vorbis'\n```\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Set the hardwareConcurrency to 4 (optionally configurable with `hardwareConcurrency`)\n *\n * @see https://arh.antoinevastel.com/reports/stats/osName_hardwareConcurrency_report.html\n *\n * @param {Object} [opts] - Options\n * @param {number} [opts.hardwareConcurrency] - The value to use in `navigator.hardwareConcurrency` (default: `4`)\n */\n\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.hardwareConcurrency'\n  }\n\n  get defaults() {\n    return {\n      hardwareConcurrency: 4\n    }\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { opts }) => {\n        utils.replaceGetterWithProxy(\n          Object.getPrototypeOf(navigator),\n          'hardwareConcurrency',\n          utils.makeHandler().getterValue(opts.hardwareConcurrency)\n        )\n      },\n      {\n        opts: this.opts\n      }\n    )\n  }\n}\n\nmodule.exports = function (pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency/index.test.js",
    "content": "const test = require('ava')\nconst os = require('os')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst Plugin = require('.')\n\nconst fingerprintFn = page => page.evaluate('navigator.hardwareConcurrency')\n\ntest('vanilla: matches real core count', async t => {\n  const { pageFnResult } = await getVanillaFingerPrint(fingerprintFn)\n  t.is(pageFnResult, os.cpus().length)\n})\n\ntest('stealth: default is set to 4', async t => {\n  const { pageFnResult } = await getStealthFingerPrint(Plugin, fingerprintFn)\n  t.is(pageFnResult, 4)\n})\n\ntest('stealth: will override value correctly', async t => {\n  const { pageFnResult } = await getStealthFingerPrint(Plugin, fingerprintFn, {\n    hardwareConcurrency: 8\n  })\n  t.is(pageFnResult, 8)\n})\n\ntest('stealth: does patch getters properly', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const hasInvocationError = (() => {\n      try {\n        // eslint-disable-next-line dot-notation\n        Object['seal'](Object.getPrototypeOf(navigator)['hardwareConcurrency'])\n        return false\n      } catch (err) {\n        return true\n      }\n    })()\n    return {\n      hasInvocationError,\n      toString: Object.getOwnPropertyDescriptor(\n        Object.getPrototypeOf(navigator),\n        'hardwareConcurrency'\n      ).get.toString()\n    }\n  })\n\n  t.deepEqual(results, {\n    hasInvocationError: true,\n    toString: 'function get hardwareConcurrency() { [native code] }'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/9534845cc95088e65c2d53bfb029263976fc9add/packages/puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency/index.js#L16-L37)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.hardwareConcurrency` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** The value to use in `navigator.hardwareConcurrency` (default: `4`)\n\n**Extends: PuppeteerExtraPlugin**\n\nSet the hardwareConcurrency to 4 (optionally configurable with `hardwareConcurrency`)\n\n- **See: <https://arh.antoinevastel.com/reports/stats/osName_hardwareConcurrency_report.html>**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Pass the Languages Test. Allows setting custom languages.\n *\n * @param {Object} [opts] - Options\n * @param {Array<string>} [opts.languages] - The languages to use (default: `['en-US', 'en']`)\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.languages'\n  }\n\n  get defaults() {\n    return {\n      languages: [] // Empty default, otherwise this would be merged with user defined array override\n    }\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { opts }) => {\n        const languages = opts.languages.length\n          ? opts.languages\n          : ['en-US', 'en']\n        utils.replaceGetterWithProxy(\n          Object.getPrototypeOf(navigator),\n          'languages',\n          utils.makeHandler().getterValue(Object.freeze([...languages]))\n        )\n      },\n      {\n        opts: this.opts\n      }\n    )\n  }\n}\n\nmodule.exports = function (pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\n// TODO: Vanilla seems fine, evasion obsolete?\n// Note: We keep it around for now, as we will need this method in a fingerprinting plugin later anyway\ntest('vanilla: is array with en-US', async t => {\n  const { languages } = await getVanillaFingerPrint()\n  t.is(Array.isArray(languages), true)\n  t.is(languages[0], 'en-US')\n})\n\ntest('vanilla: will not have modifications', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(\n    () => Object.getOwnPropertyDescriptor(navigator, 'languages') // Must be undefined if native\n  )\n  t.is(test1, undefined)\n\n  const test2 = await page.evaluate(\n    () => Object.getOwnPropertyNames(navigator) // Must be an empty array if native\n  )\n  t.false(test2.includes('languages'))\n})\n\ntest('stealth: is array with en-US', async t => {\n  const { languages } = await getStealthFingerPrint(Plugin)\n  t.is(Array.isArray(languages), true)\n  t.is(languages[0], 'en-US')\n})\n\ntest('stealth: customized value', async t => {\n  const { languages } = await getStealthFingerPrint(Plugin, null, {\n    languages: ['foo', 'bar']\n  })\n  t.deepEqual(languages, ['foo', 'bar'])\n})\n\ntest('stealth: will not leak modifications', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(\n    () => Object.getOwnPropertyDescriptor(navigator, 'languages') // Must be undefined if native\n  )\n  t.is(test1, undefined)\n\n  const test2 = await page.evaluate(\n    () => Object.getOwnPropertyNames(navigator) // Must be an empty array if native\n  )\n  t.false(test2.includes('languages'))\n})\n\ntest('stealth: does patch getters properly', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const hasInvocationError = (() => {\n      try {\n        // eslint-disable-next-line dot-notation\n        Object['seal'](Object.getPrototypeOf(navigator)['languages'])\n        return false\n      } catch (err) {\n        return true\n      }\n    })()\n    const hasPushError = (() => {\n      try {\n        // eslint-disable-next-line dot-notation\n        navigator.languages.push(null)\n        return false\n      } catch (err) {\n        return true\n      }\n    })()\n    return {\n      hasInvocationError,\n      hasPushError,\n      toString: Object.getOwnPropertyDescriptor(\n        Object.getPrototypeOf(navigator),\n        'languages'\n      ).get.toString()\n    }\n  })\n\n  t.deepEqual(results, {\n    hasInvocationError: true,\n    hasPushError: true,\n    toString: 'function get languages() { [native code] }'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/index.js#L11-L28)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.languages` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** The languages to use (default: `['en-US', 'en']`)\n\n**Extends: PuppeteerExtraPlugin**\n\nPass the Languages Test. Allows setting custom languages.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Fix `Notification.permission` behaving weirdly in headless mode\n *\n * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1052332\n */\n\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.permissions'\n  }\n\n  /* global Notification Permissions PermissionStatus */\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument((utils, opts) => {\n      const isSecure = document.location.protocol.startsWith('https')\n\n      // In headful on secure origins the permission should be \"default\", not \"denied\"\n      if (isSecure) {\n        utils.replaceGetterWithProxy(Notification, 'permission', {\n          apply() {\n            return 'default'\n          }\n        })\n      }\n\n      // Another weird behavior:\n      // On insecure origins in headful the state is \"denied\",\n      // whereas in headless it's \"prompt\"\n      if (!isSecure) {\n        const handler = {\n          apply(target, ctx, args) {\n            const param = (args || [])[0]\n\n            const isNotifications =\n              param && param.name && param.name === 'notifications'\n            if (!isNotifications) {\n              return utils.cache.Reflect.apply(...arguments)\n            }\n\n            return Promise.resolve(\n              Object.setPrototypeOf(\n                {\n                  state: 'denied',\n                  onchange: null\n                },\n                PermissionStatus.prototype\n              )\n            )\n          }\n        }\n        // Note: Don't use `Object.getPrototypeOf` here\n        utils.replaceWithProxy(Permissions.prototype, 'query', handler)\n      }\n    }, this.opts)\n  }\n}\n\nmodule.exports = function (pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/index.test.js",
    "content": "/* global Notification */\nconst test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\ntest('vanilla: is prompt', async t => {\n  const { permissions } = await getVanillaFingerPrint()\n  t.deepEqual(permissions, {\n    permission: 'denied',\n    state: 'prompt' // this is WRONG behavior, it's \"denied\" in headful!\n  })\n})\n\ntest('stealth: is denied', async t => {\n  const { permissions } = await getStealthFingerPrint(Plugin)\n  t.deepEqual(permissions, {\n    permission: 'denied',\n    state: 'denied' // this is FIXED behavior, it's \"denied\" in headful!\n  })\n})\n\nasync function getNotificationPermission() {\n  const { state, onchange } = await navigator.permissions.query({\n    name: 'notifications'\n  })\n  return {\n    state,\n    onchange,\n    permission: Notification.permission\n  }\n}\n\ntest('vanilla headful: as expected', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer)\n  const browser = await puppeteer.launch({ headless: false })\n  const page = await browser.newPage()\n  const result = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result, {\n    state: 'denied',\n    onchange: null,\n    permission: 'denied'\n  })\n\n  await page.goto('https://example.com', {\n    waitUntil: 'domcontentloaded'\n  })\n  const result2 = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result2, {\n    state: 'prompt',\n    onchange: null,\n    permission: 'default'\n  })\n})\n\ntest('vanilla headless: as expected', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer)\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  const result = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result, {\n    state: 'prompt', // should be denied\n    onchange: null,\n    permission: 'denied'\n  })\n\n  await page.goto('https://example.com', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  const result2 = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result2, {\n    state: 'prompt',\n    onchange: null,\n    permission: 'denied' // should be default\n  })\n})\n\ntest('stealth headless: as vanilla headful', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  const result = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result, {\n    state: 'denied',\n    onchange: null,\n    permission: 'denied'\n  })\n\n  await page.goto('https://example.com', {\n    waitUntil: 'domcontentloaded'\n  })\n\n  const result2 = await page.evaluate(getNotificationPermission)\n  t.deepEqual(result2, {\n    state: 'prompt',\n    onchange: null,\n    permission: 'default'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/index.js#L12-L45)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nPass the Permissions Test.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/data.json",
    "content": "{\n  \"mimeTypes\": [\n    {\n      \"type\": \"application/pdf\",\n      \"suffixes\": \"pdf\",\n      \"description\": \"\",\n      \"__pluginName\": \"Chrome PDF Viewer\"\n    },\n    {\n      \"type\": \"application/x-google-chrome-pdf\",\n      \"suffixes\": \"pdf\",\n      \"description\": \"Portable Document Format\",\n      \"__pluginName\": \"Chrome PDF Plugin\"\n    },\n    {\n      \"type\": \"application/x-nacl\",\n      \"suffixes\": \"\",\n      \"description\": \"Native Client Executable\",\n      \"__pluginName\": \"Native Client\"\n    },\n    {\n      \"type\": \"application/x-pnacl\",\n      \"suffixes\": \"\",\n      \"description\": \"Portable Native Client Executable\",\n      \"__pluginName\": \"Native Client\"\n    }\n  ],\n  \"plugins\": [\n    {\n      \"name\": \"Chrome PDF Plugin\",\n      \"filename\": \"internal-pdf-viewer\",\n      \"description\": \"Portable Document Format\",\n      \"__mimeTypes\": [\"application/x-google-chrome-pdf\"]\n    },\n    {\n      \"name\": \"Chrome PDF Viewer\",\n      \"filename\": \"mhjfbmdgcfjbbpaeojofohoefgiehjai\",\n      \"description\": \"\",\n      \"__mimeTypes\": [\"application/pdf\"]\n    },\n    {\n      \"name\": \"Native Client\",\n      \"filename\": \"internal-nacl-plugin\",\n      \"description\": \"\",\n      \"__mimeTypes\": [\"application/x-nacl\", \"application/x-pnacl\"]\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/functionMocks.js",
    "content": "/**\n * `navigator.{plugins,mimeTypes}` share similar custom functions to look up properties\n *\n * Note: This is meant to be run in the context of the page.\n */\nmodule.exports.generateFunctionMocks = utils => (\n  proto,\n  itemMainProp,\n  dataArray\n) => ({\n  /** Returns the MimeType object with the specified index. */\n  item: utils.createProxy(proto.item, {\n    apply(target, ctx, args) {\n      if (!args.length) {\n        throw new TypeError(\n          `Failed to execute 'item' on '${\n            proto[Symbol.toStringTag]\n          }': 1 argument required, but only 0 present.`\n        )\n      }\n      // Special behavior alert:\n      // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup\n      // - If anything else than an integer (including as string) is provided it will return the first entry\n      const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer\n      // Note: Vanilla never returns `undefined`\n      return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null\n    }\n  }),\n  /** Returns the MimeType object with the specified name. */\n  namedItem: utils.createProxy(proto.namedItem, {\n    apply(target, ctx, args) {\n      if (!args.length) {\n        throw new TypeError(\n          `Failed to execute 'namedItem' on '${\n            proto[Symbol.toStringTag]\n          }': 1 argument required, but only 0 present.`\n        )\n      }\n      return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`!\n    }\n  }),\n  /** Does nothing and shall return nothing */\n  refresh: proto.refresh\n    ? utils.createProxy(proto.refresh, {\n        apply(target, ctx, args) {\n          return undefined\n        }\n      })\n    : undefined\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst utils = require('../_utils')\nconst withUtils = require('../_utils/withUtils')\n\nconst { generateMimeTypeArray } = require('./mimeTypes')\nconst { generatePluginArray } = require('./plugins')\nconst { generateMagicArray } = require('./magicArray')\nconst { generateFunctionMocks } = require('./functionMocks')\n\nconst data = require('./data.json')\n\n/**\n * In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.\n * This plugin emulates both of these with functional mocks to match regular headful Chrome.\n *\n * Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes\n * @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray\n * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins\n * @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.plugins'\n  }\n\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { fns, data }) => {\n        fns = utils.materializeFns(fns)\n\n        // That means we're running headful\n        const hasPlugins = 'plugins' in navigator && navigator.plugins.length\n        if (hasPlugins) {\n          return // nothing to do here\n        }\n\n        const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)\n        const plugins = fns.generatePluginArray(utils, fns)(data.plugins)\n\n        // Plugin and MimeType cross-reference each other, let's do that now\n        // Note: We're looping through `data.plugins` here, not the generated `plugins`\n        for (const pluginData of data.plugins) {\n          pluginData.__mimeTypes.forEach((type, index) => {\n            plugins[pluginData.name][index] = mimeTypes[type]\n\n            Object.defineProperty(plugins[pluginData.name], type, {\n              value: mimeTypes[type],\n              writable: false,\n              enumerable: false, // Not enumerable\n              configurable: true\n            })\n            Object.defineProperty(mimeTypes[type], 'enabledPlugin', {\n              value:\n                type === 'application/x-pnacl'\n                  ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks\n                  : new Proxy(plugins[pluginData.name], {}), // Prevent circular references\n              writable: false,\n              enumerable: false, // Important: `JSON.stringify(navigator.plugins)`\n              configurable: true\n            })\n          })\n        }\n\n        const patchNavigator = (name, value) =>\n          utils.replaceProperty(Object.getPrototypeOf(navigator), name, {\n            get() {\n              return value\n            }\n          })\n\n        patchNavigator('mimeTypes', mimeTypes)\n        patchNavigator('plugins', plugins)\n\n        // All done\n      },\n      {\n        // We pass some functions to evaluate to structure the code more nicely\n        fns: utils.stringifyFns({\n          generateMimeTypeArray,\n          generatePluginArray,\n          generateMagicArray,\n          generateFunctionMocks\n        }),\n        data\n      }\n    )\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\ntest('vanilla: empty plugins, empty mimetypes', async t => {\n  const { plugins, mimeTypes } = await getVanillaFingerPrint()\n  t.is(plugins.length, 0)\n  t.is(mimeTypes.length, 0)\n})\n\ntest('vanilla: will not have modifications', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => ({\n    mimeTypes: Object.getOwnPropertyDescriptor(navigator, 'mimeTypes'), // Must be undefined if native\n    plugins: Object.getOwnPropertyDescriptor(navigator, 'plugins') // Must be undefined if native\n  }))\n  t.is(test1.mimeTypes, undefined)\n  t.is(test1.plugins, undefined)\n\n  const test2 = await page.evaluate(\n    () => Object.getOwnPropertyNames(navigator) // Must be an empty array if native\n  )\n  t.false(test2.includes('plugins'))\n})\n\ntest('stealth: has plugin, has mimetypes', async t => {\n  const { plugins, mimeTypes } = await getStealthFingerPrint(Plugin)\n  t.is(plugins.length, 3)\n  t.is(mimeTypes.length, 4)\n})\n\ntest('stealth: will not leak modifications', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => ({\n    mimeTypes: Object.getOwnPropertyDescriptor(navigator, 'mimeTypes'), // Must be undefined if native\n    plugins: Object.getOwnPropertyDescriptor(navigator, 'plugins') // Must be undefined if native\n  }))\n  t.is(test1.mimeTypes, undefined)\n  t.is(test1.plugins, undefined)\n\n  const test2 = await page.evaluate(\n    () => Object.getOwnPropertyNames(navigator) // Must be an empty array if native\n  )\n  t.false(test2.includes('plugins'))\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/magicArray.js",
    "content": "/* global MimeType MimeTypeArray Plugin PluginArray  */\n\n/**\n * Generate a convincing and functional MimeType or Plugin array from scratch.\n * They're so similar that it makes sense to use a single generator here.\n *\n * Note: This is meant to be run in the context of the page.\n */\nmodule.exports.generateMagicArray = (utils, fns) =>\n  function(\n    dataArray = [],\n    proto = MimeTypeArray.prototype,\n    itemProto = MimeType.prototype,\n    itemMainProp = 'type'\n  ) {\n    // Quick helper to set props with the same descriptors vanilla is using\n    const defineProp = (obj, prop, value) =>\n      Object.defineProperty(obj, prop, {\n        value,\n        writable: false,\n        enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`\n        configurable: true\n      })\n\n    // Loop over our fake data and construct items\n    const makeItem = data => {\n      const item = {}\n      for (const prop of Object.keys(data)) {\n        if (prop.startsWith('__')) {\n          continue\n        }\n        defineProp(item, prop, data[prop])\n      }\n      return patchItem(item, data)\n    }\n\n    const patchItem = (item, data) => {\n      let descriptor = Object.getOwnPropertyDescriptors(item)\n\n      // Special case: Plugins have a magic length property which is not enumerable\n      // e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypes\n      if (itemProto === Plugin.prototype) {\n        descriptor = {\n          ...descriptor,\n          length: {\n            value: data.__mimeTypes.length,\n            writable: false,\n            enumerable: false,\n            configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`\n          }\n        }\n      }\n\n      // We need to spoof a specific `MimeType` or `Plugin` object\n      const obj = Object.create(itemProto, descriptor)\n\n      // Virtually all property keys are not enumerable in vanilla\n      const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']\n      return new Proxy(obj, {\n        ownKeys(target) {\n          return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))\n        },\n        getOwnPropertyDescriptor(target, prop) {\n          if (blacklist.includes(prop)) {\n            return undefined\n          }\n          return Reflect.getOwnPropertyDescriptor(target, prop)\n        }\n      })\n    }\n\n    const magicArray = []\n\n    // Loop through our fake data and use that to create convincing entities\n    dataArray.forEach(data => {\n      magicArray.push(makeItem(data))\n    })\n\n    // Add direct property access  based on types (e.g. `obj['application/pdf']`) afterwards\n    magicArray.forEach(entry => {\n      defineProp(magicArray, entry[itemMainProp], entry)\n    })\n\n    // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`\n    const magicArrayObj = Object.create(proto, {\n      ...Object.getOwnPropertyDescriptors(magicArray),\n\n      // There's one ugly quirk we unfortunately need to take care of:\n      // The `MimeTypeArray` prototype has an enumerable `length` property,\n      // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.\n      // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.\n      length: {\n        value: magicArray.length,\n        writable: false,\n        enumerable: false,\n        configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`\n      }\n    })\n\n    // Generate our functional function mocks :-)\n    const functionMocks = fns.generateFunctionMocks(utils)(\n      proto,\n      itemMainProp,\n      magicArray\n    )\n\n    // We need to overlay our custom object with a JS Proxy\n    const magicArrayObjProxy = new Proxy(magicArrayObj, {\n      get(target, key = '') {\n        // Redirect function calls to our custom proxied versions mocking the vanilla behavior\n        if (key === 'item') {\n          return functionMocks.item\n        }\n        if (key === 'namedItem') {\n          return functionMocks.namedItem\n        }\n        if (proto === PluginArray.prototype && key === 'refresh') {\n          return functionMocks.refresh\n        }\n        // Everything else can pass through as normal\n        return utils.cache.Reflect.get(...arguments)\n      },\n      ownKeys(target) {\n        // There are a couple of quirks where the original property demonstrates \"magical\" behavior that makes no sense\n        // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`\n        // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly\n        // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing\n        // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing\n        const keys = []\n        const typeProps = magicArray.map(mt => mt[itemMainProp])\n        typeProps.forEach((_, i) => keys.push(`${i}`))\n        typeProps.forEach(propName => keys.push(propName))\n        return keys\n      },\n      getOwnPropertyDescriptor(target, prop) {\n        if (prop === 'length') {\n          return undefined\n        }\n        return Reflect.getOwnPropertyDescriptor(target, prop)\n      }\n    })\n\n    return magicArrayObjProxy\n  }\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.js",
    "content": "/* global MimeType MimeTypeArray */\n\n/**\n * Generate a convincing and functional MimeTypeArray (with mime types) from scratch.\n *\n * Note: This is meant to be run in the context of the page.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes\n * @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray\n */\nmodule.exports.generateMimeTypeArray = (utils, fns) => mimeTypesData => {\n  return fns.generateMagicArray(utils, fns)(\n    mimeTypesData,\n    MimeTypeArray.prototype,\n    MimeType.prototype,\n    'type'\n  )\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\ntest('stealth: will have convincing mimeTypes', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    // We need to help serializing the error or it won't survive being sent back from `page.evaluate`\n    const catchErr = function(fn, ...args) {\n      try {\n        return fn.apply(this, args)\n      } catch ({ name, message, stack }) {\n        return { name, message, stack, str: stack.split('\\n')[0] }\n      }\n    }\n\n    return {\n      mimeTypes: {\n        exists: 'mimeTypes' in navigator,\n        isArray: Array.isArray(navigator.mimeTypes),\n        length: navigator.mimeTypes.length,\n        // value: navigator.mimeTypes,\n        toString: navigator.mimeTypes.toString(),\n        toStringProto: navigator.mimeTypes.__proto__.toString(), // eslint-disable-line no-proto\n        protoSymbol: navigator.mimeTypes.__proto__[Symbol.toStringTag], // eslint-disable-line no-proto\n        // valueOf: navigator.mimeTypes.valueOf(),\n        valueOfSame: navigator.mimeTypes.valueOf() === navigator.mimeTypes,\n        json: JSON.stringify(navigator.mimeTypes),\n        hasPropPush: 'push' in navigator.mimeTypes,\n        hasPropLength: 'length' in navigator.mimeTypes,\n        hasLengthDescriptor: !!Object.getOwnPropertyDescriptor(\n          navigator.mimeTypes,\n          'length'\n        ),\n        propertyNames: JSON.stringify(\n          Object.getOwnPropertyNames(navigator.mimeTypes)\n        ),\n        lengthInProps: Object.getOwnPropertyNames(navigator.mimeTypes).includes(\n          'length'\n        ),\n        keys: JSON.stringify(Object.keys(navigator.mimeTypes)),\n        namedPropsAuthentic: (function() {\n          navigator.mimeTypes.alice = 'bob'\n          return navigator.mimeTypes.namedItem('alice') === null // true on chrome\n        })(),\n        loopResult: (function() {\n          let res = ''\n          for (var bK = 0; bK < window.navigator.mimeTypes.length; bK++)\n            bK === window.navigator.mimeTypes.length - 1\n              ? (res += window.navigator.mimeTypes[bK].type)\n              : (res += window.navigator.mimeTypes[bK].type + ',')\n          return res\n        })()\n      },\n      namedItem: {\n        exists: 'namedItem' in navigator.mimeTypes,\n        toString: navigator.mimeTypes.namedItem.toString(),\n        resultNotFound: navigator.mimeTypes.namedItem('foo'),\n        resultFound: navigator.mimeTypes // eslint-disable-line no-proto\n          .namedItem('application/pdf')\n          .__proto__.toString(),\n        errors: {\n          // For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.mimeTypes`\n          noArgs: catchErr.bind(navigator.mimeTypes)(\n            navigator.mimeTypes.namedItem\n          ).str,\n          noStackLeaks: !catchErr\n            .bind(navigator.mimeTypes)(navigator.mimeTypes.namedItem)\n            .stack.includes(`.apply`),\n          protoCall: catchErr.bind(navigator.mimeTypes)(\n            navigator.mimeTypes.__proto__.namedItem // eslint-disable-line no-proto\n          ).str\n        }\n      },\n      item: {\n        exists: 'item' in navigator.mimeTypes,\n        toString: navigator.mimeTypes.item.toString(),\n        resultNotFound: navigator.mimeTypes.item('madness').type,\n        resultNotFoundNumberString: navigator.mimeTypes.item('777'),\n        resultEmptyString: navigator.mimeTypes.item('').type,\n        resultByNumberString: navigator.mimeTypes.item('2').type,\n        resultByNumberStringZero: navigator.mimeTypes.item('0').type,\n        resultByNumber: navigator.mimeTypes.item(2).type,\n        resultNull: navigator.mimeTypes.item(null).type,\n        resultFound: navigator.mimeTypes.item('application/x-nacl').type,\n        resultBrackets: navigator.mimeTypes['application/x-pnacl'].type,\n        errors: {\n          // For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.mimeTypes`\n          noArgs: catchErr.bind(navigator.mimeTypes)(navigator.mimeTypes.item)\n            .str,\n          noStackLeaks: !catchErr\n            .bind(navigator.mimeTypes)(navigator.mimeTypes.item)\n            .stack.includes(`.apply`),\n          protoCall: catchErr.bind(navigator.mimeTypes)(\n            navigator.mimeTypes.__proto__.item // eslint-disable-line no-proto\n          ).str\n        }\n      }\n    }\n  })\n\n  t.deepEqual(results.mimeTypes, {\n    exists: true,\n    hasPropPush: false,\n    hasPropLength: true,\n    hasLengthDescriptor: false,\n    isArray: false,\n    json: `{\"0\":{},\"1\":{},\"2\":{},\"3\":{}}`,\n    keys: `[\"0\",\"1\",\"2\",\"3\"]`,\n    length: 4,\n    lengthInProps: false,\n    loopResult:\n      'application/pdf,application/x-google-chrome-pdf,application/x-nacl,application/x-pnacl',\n    namedPropsAuthentic: true,\n    propertyNames: `[\"0\",\"1\",\"2\",\"3\",\"application/pdf\",\"application/x-google-chrome-pdf\",\"application/x-nacl\",\"application/x-pnacl\"]`,\n    protoSymbol: 'MimeTypeArray',\n    toString: '[object MimeTypeArray]',\n    toStringProto: '[object MimeTypeArray]',\n    valueOfSame: true\n  })\n\n  t.deepEqual(results.namedItem, {\n    exists: true,\n    toString: 'function namedItem() { [native code] }',\n    resultFound: '[object MimeType]',\n    resultNotFound: null,\n\n    errors: {\n      noArgs:\n        \"TypeError: Failed to execute 'namedItem' on 'MimeTypeArray': 1 argument required, but only 0 present.\",\n      noStackLeaks: true,\n      protoCall: 'TypeError: Illegal invocation'\n    }\n  })\n\n  t.deepEqual(results.item, {\n    exists: true,\n    resultBrackets: 'application/x-pnacl',\n    resultByNumber: 'application/x-nacl',\n    resultByNumberString: 'application/x-nacl',\n    resultByNumberStringZero: 'application/pdf',\n    resultEmptyString: 'application/pdf',\n    resultFound: 'application/pdf',\n    resultNotFound: 'application/pdf',\n    resultNotFoundNumberString: null,\n    resultNull: 'application/pdf',\n    toString: 'function item() { [native code] }',\n    errors: {\n      noArgs:\n        \"TypeError: Failed to execute 'item' on 'MimeTypeArray': 1 argument required, but only 0 present.\",\n      noStackLeaks: true,\n      protoCall: 'TypeError: Illegal invocation'\n    }\n  })\n})\n\ntest('stealth: will have convincing mimeType entry', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => ({\n    mimeType: {\n      exists: !!navigator.mimeTypes[0],\n      toString: navigator.mimeTypes[0].toString(),\n      toStringProto: navigator.mimeTypes[0].__proto__.toString(), // eslint-disable-line no-proto\n      protoSymbol: navigator.mimeTypes[0].__proto__[Symbol.toStringTag], // eslint-disable-line no-proto\n      enabledPlugin: !!navigator.mimeTypes[0].enabledPlugin, // should not throw\n      enabledPlugin2: !!navigator.mimeTypes['application/pdf'].enabledPlugin, // should not throw\n      enabledPlugins: !!navigator.mimeTypes[0].enabledPlugins, // regression: should not exist (anymore)\n      pdfPlugin: JSON.stringify(\n        navigator.mimeTypes['application/pdf'].enabledPlugin\n      ),\n      length: !!navigator.mimeTypes[0].length, // should not throw and return mimeTypes length\n      lengthDescriptor: !!Object.getOwnPropertyDescriptor(\n        navigator.mimeTypes[0],\n        'length'\n      ),\n      json: JSON.stringify(navigator.mimeTypes[0]),\n      propertyNames: JSON.stringify(\n        Object.getOwnPropertyNames(navigator.mimeTypes[0])\n      ),\n      nested:\n        navigator.mimeTypes['application/pdf'].enabledPlugin[0].enabledPlugin[0]\n          .enabledPlugin[0].enabledPlugin[0].enabledPlugin[0].suffixes\n    }\n  }))\n  t.deepEqual(results.mimeType, {\n    exists: true,\n    protoSymbol: 'MimeType',\n    toString: '[object MimeType]',\n    toStringProto: '[object MimeType]',\n    enabledPlugin: true,\n    enabledPlugin2: true,\n    enabledPlugins: false,\n    pdfPlugin: '{\"0\":{}}',\n    length: false,\n    lengthDescriptor: false,\n    json: '{}',\n    propertyNames: '[]',\n    nested: 'pdf'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.js",
    "content": "/* global Plugin PluginArray */\n\n/**\n * Generate a convincing and functional PluginArray (with plugins) from scratch.\n *\n * Note: This is meant to be run in the context of the page.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins\n * @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray\n */\nmodule.exports.generatePluginArray = (utils, fns) => pluginsData => {\n  return fns.generateMagicArray(utils, fns)(\n    pluginsData,\n    PluginArray.prototype,\n    Plugin.prototype,\n    'name'\n  )\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\n\ntest('stealth: will have convincing plugins', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    // We need to help serializing the error or it won't survive being sent back from `page.evaluate`\n    const catchErr = function(fn, ...args) {\n      try {\n        return fn.apply(this, args)\n      } catch ({ name, message, stack }) {\n        return { name, message, stack, str: stack.split('\\n')[0] }\n      }\n    }\n\n    return {\n      plugins: {\n        exists: 'plugins' in navigator,\n        isArray: Array.isArray(navigator.plugins),\n        length: navigator.plugins.length,\n        // value: navigator.plugins,\n        toString: navigator.plugins.toString(),\n        toStringProto: navigator.plugins.__proto__.toString(), // eslint-disable-line no-proto\n        protoSymbol: navigator.plugins.__proto__[Symbol.toStringTag], // eslint-disable-line no-proto\n        // valueOf: navigator.plugins.valueOf(),\n        valueOfSame: navigator.plugins.valueOf() === navigator.plugins,\n        json: JSON.stringify(navigator.plugins),\n        hasPropPush: 'push' in navigator.plugins,\n        hasPropLength: 'length' in navigator.plugins,\n        hasLengthDescriptor: !!Object.getOwnPropertyDescriptor(\n          navigator.plugins,\n          'length'\n        ),\n        propertyNames: JSON.stringify(\n          Object.getOwnPropertyNames(navigator.plugins)\n        ),\n        lengthInProps: Object.getOwnPropertyNames(navigator.plugins).includes(\n          'length'\n        ),\n        keys: JSON.stringify(Object.keys(navigator.plugins)),\n        loopResult: [...navigator.plugins].map(p => p.name).join(',')\n      },\n      namedItem: {\n        exists: 'namedItem' in navigator.plugins,\n        toString: navigator.plugins.namedItem.toString(),\n        resultNotFound: navigator.plugins.namedItem('foo'),\n        resultFound: navigator.plugins // eslint-disable-line no-proto\n          .namedItem('Chrome PDF Viewer')\n          .__proto__.toString(),\n        errors: {\n          // For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.plugins`\n          noArgs: catchErr.bind(navigator.plugins)(navigator.plugins.namedItem)\n            .str,\n          noStackLeaks: !catchErr\n            .bind(navigator.plugins)(navigator.plugins.namedItem)\n            .stack.includes(`.apply`),\n          protoCall: catchErr.bind(navigator.plugins)(\n            navigator.plugins.__proto__.namedItem // eslint-disable-line no-proto\n          ).str\n        }\n      },\n      item: {\n        exists: 'item' in navigator.plugins,\n        toString: navigator.plugins.item.toString(),\n        resultNotFound: navigator.plugins.item('madness').name,\n        resultNotFoundNumberString: navigator.plugins.item('777'),\n        resultEmptyString: navigator.plugins.item('').name,\n        resultByNumberString: navigator.plugins.item('2').name,\n        resultByNumberStringZero: navigator.plugins.item('0').name,\n        resultByNumber: navigator.plugins.item(2).name,\n        resultNull: navigator.plugins.item(null).name,\n        resultFound: navigator.plugins.item('application/x-nacl').name,\n        errors: {\n          // For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.plugins`\n          noArgs: catchErr.bind(navigator.plugins)(navigator.plugins.item).str,\n          noStackLeaks: !catchErr\n            .bind(navigator.plugins)(navigator.plugins.item)\n            .stack.includes(`.apply`),\n          protoCall: catchErr.bind(navigator.plugins)(\n            navigator.plugins.__proto__.item // eslint-disable-line no-proto\n          ).str\n        }\n      }\n    }\n  })\n\n  t.deepEqual(results.plugins, {\n    exists: true,\n    hasPropLength: true,\n    hasLengthDescriptor: false,\n    hasPropPush: false,\n    isArray: false,\n    json: `{\"0\":{\"0\":{}},\"1\":{\"0\":{}},\"2\":{\"0\":{},\"1\":{}}}`,\n    keys: `[\"0\",\"1\",\"2\"]`,\n    length: 3,\n    lengthInProps: false,\n    loopResult: 'Chrome PDF Plugin,Chrome PDF Viewer,Native Client',\n    propertyNames: `[\"0\",\"1\",\"2\",\"Chrome PDF Plugin\",\"Chrome PDF Viewer\",\"Native Client\"]`,\n    protoSymbol: 'PluginArray',\n    toString: '[object PluginArray]',\n    toStringProto: '[object PluginArray]',\n    valueOfSame: true\n  })\n\n  t.deepEqual(results.namedItem, {\n    exists: true,\n    toString: 'function namedItem() { [native code] }',\n    resultFound: '[object Plugin]',\n    resultNotFound: null,\n\n    errors: {\n      noArgs:\n        \"TypeError: Failed to execute 'namedItem' on 'PluginArray': 1 argument required, but only 0 present.\",\n      noStackLeaks: true,\n      protoCall: 'TypeError: Illegal invocation'\n    }\n  })\n\n  t.deepEqual(results.item, {\n    exists: true,\n    resultByNumber: 'Native Client',\n    resultByNumberString: 'Native Client',\n    resultByNumberStringZero: 'Chrome PDF Plugin',\n    resultEmptyString: 'Chrome PDF Plugin',\n    resultFound: 'Chrome PDF Plugin',\n    resultNotFound: 'Chrome PDF Plugin',\n    resultNotFoundNumberString: null,\n    resultNull: 'Chrome PDF Plugin',\n    toString: 'function item() { [native code] }',\n    errors: {\n      noArgs:\n        \"TypeError: Failed to execute 'item' on 'PluginArray': 1 argument required, but only 0 present.\",\n      noStackLeaks: true,\n      protoCall: 'TypeError: Illegal invocation'\n    }\n  })\n})\n\ntest('stealth: will have convincing plugin entry', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => ({\n    plugins: {\n      exists: !!navigator.plugins[0],\n      toString: navigator.plugins[0].toString(),\n      toStringProto: navigator.plugins[0].__proto__.toString(), // eslint-disable-line no-proto\n      protoSymbol: navigator.plugins[0].__proto__[Symbol.toStringTag], // eslint-disable-line no-proto\n      length: navigator.plugins[0].length, // should not throw and return mimeTypes length\n      lengthDescriptor: Object.getOwnPropertyDescriptor(\n        navigator.plugins[0],\n        'length'\n      )\n    },\n    plugin: {\n      mtIndex: !!navigator.plugins[0][0], // mimeType should be accessible through index\n      mtNamed: !!navigator.plugins[0]['application/x-google-chrome-pdf'], // mimeType should be accessible through name\n      json: JSON.stringify(navigator.plugins[0]),\n      propertyNames: JSON.stringify(\n        Object.getOwnPropertyNames(navigator.plugins[0])\n      )\n    }\n  }))\n  t.deepEqual(results.plugins, {\n    exists: true,\n    protoSymbol: 'Plugin',\n    toString: '[object Plugin]',\n    toStringProto: '[object Plugin]',\n    length: 1\n  })\n  t.deepEqual(results.plugin, {\n    mtIndex: true,\n    mtNamed: true,\n    json: '{\"0\":{}}',\n    propertyNames: '[\"0\",\"application/x-google-chrome-pdf\"]'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js#L26-L88)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nIn headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.\nThis plugin emulates both of these with functional mocks to match regular headful Chrome.\n\nNote: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.\n\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes>**\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray>**\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins>**\n- **See: <https://developer.mozilla.org/en-US/docs/Web/API/PluginArray>**\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * By default puppeteer will have a fixed `navigator.vendor` property.\n *\n * This plugin makes it possible to change this property.\n *\n * @example\n * const puppeteer = require(\"puppeteer-extra\")\n *\n * const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\")\n * const stealth = StealthPlugin()\n * // Remove this specific stealth plugin from the default set\n * stealth.enabledEvasions.delete(\"navigator.vendor\")\n * puppeteer.use(stealth)\n *\n * // Stealth plugins are just regular `puppeteer-extra` plugins and can be added as such\n * const NavigatorVendorPlugin = require(\"puppeteer-extra-plugin-stealth/evasions/navigator.vendor\")\n * const nvp = NavigatorVendorPlugin({ vendor: 'Apple Computer, Inc.' }) // Custom vendor\n * puppeteer.use(nvp)\n *\n * @param {Object} [opts] - Options\n * @param {string} [opts.vendor] - The vendor to use in `navigator.vendor` (default: `Google Inc.`)\n *\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.vendor'\n  }\n\n  get defaults() {\n    return {\n      vendor: 'Google Inc.'\n    }\n  }\n\n  async onPageCreated(page) {\n    this.debug('onPageCreated', {\n      opts: this.opts\n    })\n\n    await withUtils(page).evaluateOnNewDocument(\n      (utils, { opts }) => {\n        utils.replaceGetterWithProxy(\n          Object.getPrototypeOf(navigator),\n          'vendor',\n          utils.makeHandler().getterValue(opts.vendor)\n        )\n      },\n      {\n        opts: this.opts\n      }\n    )\n  } // onPageCreated\n}\n\nconst defaultExport = opts => new Plugin(opts)\nmodule.exports = defaultExport\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\nconst Plugin = require('.')\n\ntest('vanilla: navigator.vendor is always Google Inc.', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const vendor = await page.evaluate(() => navigator.vendor)\n  t.is(vendor, 'Google Inc.')\n})\n\ntest('stealth: navigator.vendor set to custom value', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ vendor: 'Apple Computer, Inc.' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const vendor = await page.evaluate(() => navigator.vendor)\n  t.is(vendor, 'Apple Computer, Inc.')\n})\n\ntest('stealth: will not leak modifications', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(\n    () => Object.getOwnPropertyDescriptor(navigator, 'vendor') // Must be undefined if native\n  )\n  t.is(test1, undefined)\n\n  const test2 = await page.evaluate(\n    () => Object.getOwnPropertyNames(navigator) // Must be an empty array if native\n  )\n  t.false(test2.includes('vendor'))\n})\n\ntest('stealth: does patch getters properly', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const results = await page.evaluate(() => {\n    const hasInvocationError = (() => {\n      try {\n        // eslint-disable-next-line dot-notation\n        Object['seal'](Object.getPrototypeOf(navigator)['vendor'])\n        return false\n      } catch (err) {\n        return true\n      }\n    })()\n    return {\n      hasInvocationError,\n      toString: Object.getOwnPropertyDescriptor(\n        Object.getPrototypeOf(navigator),\n        'vendor'\n      ).get.toString()\n    }\n  })\n\n  t.deepEqual(results, {\n    hasInvocationError: true,\n    toString: 'function get vendor() { [native code] }'\n  })\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/index.js#L28-L55)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.vendor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The vendor to use in `navigator.vendor` (default: `Google Inc.`)\n\n**Extends: PuppeteerExtraPlugin**\n\nBy default puppeteer will have a fixed `navigator.vendor` property.\n\nThis plugin makes it possible to change this property.\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\n\nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')\nconst stealth = StealthPlugin()\n// Remove this specific stealth plugin from the default set\nstealth.enabledEvasions.delete('navigator.vendor')\npuppeteer.use(stealth)\n\n// Stealth plugins are just regular `puppeteer-extra` plugins and can be added as such\nconst NavigatorVendorPlugin = require('puppeteer-extra-plugin-stealth/evasions/navigator.vendor')\nconst nvp = NavigatorVendorPlugin({ vendor: 'Apple Computer, Inc.' }) // Custom vendor\npuppeteer.use(nvp)\n```\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.webdriver/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Pass the Webdriver Test.\n * Will delete `navigator.webdriver` property.\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/navigator.webdriver'\n  }\n\n  async onPageCreated(page) {\n    await page.evaluateOnNewDocument(() => {\n      if (navigator.webdriver === false) {\n        // Post Chrome 89.0.4339.0 and already good\n      } else if (navigator.webdriver === undefined) {\n        // Pre Chrome 89.0.4339.0 and already good\n      } else {\n        // Pre Chrome 88.0.4291.0 and needs patching\n        delete Object.getPrototypeOf(navigator).webdriver\n      }\n    })\n  }\n\n  // Post Chrome 88.0.4291.0\n  // Note: this will add an infobar to Chrome with a warning that an unsupported flag is set\n  // To remove this bar on Linux, run: mkdir -p /etc/opt/chrome/policies/managed && echo '{ \"CommandLineFlagSecurityWarningsEnabled\": false }' > /etc/opt/chrome/policies/managed/managed_policies.json\n  async beforeLaunch(options) {\n    // If disable-blink-features is already passed, append the AutomationControlled switch\n    const idx = options.args.findIndex((arg) => arg.startsWith('--disable-blink-features='));\n    if (idx !== -1) {\n      const arg = options.args[idx];\n      options.args[idx] = `${arg},AutomationControlled`;\n    } else {\n      options.args.push('--disable-blink-features=AutomationControlled');\n    }\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.webdriver/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra, compareLooseVersionStrings } = require('../../test/util')\nconst Plugin = require('.')\n\nfunction getExpectedValue(looseVersionString) {\n  if (compareLooseVersionStrings(looseVersionString, '89.0.4339.0') >= 0) {\n    return false\n  } else {\n    return undefined\n  }\n}\n\ntest('vanilla: navigator.webdriver is defined', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const data = await page.evaluate(() => navigator.webdriver)\n  t.is(data, true)\n})\n\ntest('stealth: navigator.webdriver is undefined', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const data = await page.evaluate(() => navigator.webdriver)\n  // XXX: launch this test multiple times with browsers of different versions?\n  t.is(data, getExpectedValue(await browser.version()))\n})\n\n// https://github.com/berstend/puppeteer-extra/pull/130\ntest('stealth: regression: wont kill other navigator methods', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  try {\n    const data = await page.evaluate(() => navigator.javaEnabled())\n    t.is(data, false)\n  } catch (err) {\n    t.is(err, undefined)\n  }\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.webdriver/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/navigator.webdriver/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.webdriver/index.js#L9-L23)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nPass the Webdriver Test.\nWill delete `navigator.webdriver` property.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/readme.md",
    "content": "# puppeteer-extra-plugin-stealth/evasions\n\nVarious detection evasion plugins for `puppeteer-extra-plugin-stealth`.\n\nYou can bypass the main module and require specific evasion plugins yourself, if you wish to do so:\n\n```es6\npuppeteer.use(\n  require('puppeteer-extra-plugin-stealth/evasions/console.debug')()\n)\n```\n\nIf you want to add a new evasion technique I suggest you look at the [template](./_template/) to kickstart things.\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/_fixtures/test.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Page Title</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <body>\n    <h1 id=\"result\">Please use `document.querySelector`..</h1>\n\n    <script>\n      function test() {\n        const err = new Error('Test Error')\n        const isPptr = err.stack\n          .toString()\n          .includes('puppeteer_evaluation_script')\n\n        document.getElementById('result').innerHTML = isPptr ? 'FAIL' : 'PASS'\n        console.log({ err, isPptr })\n      }\n\n      function overrideFunction(item) {\n        item.obj[item.propName] = (function(orig) {\n          return function() {\n            test()\n            return orig.apply(this, arguments)\n          }\n        })(item.obj[item.propName])\n      }\n\n      overrideFunction({\n        propName: 'querySelector',\n        obj: document\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Strip sourceURL from scripts injected by puppeteer.\n * It can be used to identify the presence of pptr via stacktraces.\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/sourceurl'\n  }\n\n  async onPageCreated(page) {\n    const client =\n      page && typeof page._client === 'function' ? page._client() : page._client\n    if (!client) {\n      this.debug('Warning, missing properties to intercept CDP.', { page })\n      return\n    }\n\n    // Intercept CDP commands and strip identifying and unnecessary sourceURL\n    // https://github.com/puppeteer/puppeteer/blob/9b3005c105995cd267fdc7fb95b78aceab82cf0e/new-docs/puppeteer.cdpsession.md\n    const debug = this.debug\n    client.send = (function(originalMethod, context) {\n      return async function() {\n        const [method, paramArgs] = arguments || []\n        const next = async () => {\n          try {\n            return await originalMethod.apply(context, [method, paramArgs])\n          } catch (error) {\n            // This seems to happen sometimes when redirects cause other outstanding requests to be cut short\n            if (\n              error instanceof Error &&\n              error.message.includes(\n                `Protocol error (Network.getResponseBody): No resource with given identifier found`\n              )\n            ) {\n              debug(\n                `Caught and ignored an error about a missing network resource.`,\n                { error }\n              )\n            } else {\n              throw error\n            }\n          }\n        }\n\n        if (!method || !paramArgs) {\n          return next()\n        }\n\n        // To find the methods/props in question check `_evaluateInternal` at:\n        // https://github.com/puppeteer/puppeteer/blob/main/src/common/ExecutionContext.ts#L186\n        const methodsToPatch = {\n          'Runtime.evaluate': 'expression',\n          'Runtime.callFunctionOn': 'functionDeclaration'\n        }\n        const SOURCE_URL_SUFFIX =\n          '//# sourceURL=__puppeteer_evaluation_script__'\n\n        if (!methodsToPatch[method] || !paramArgs[methodsToPatch[method]]) {\n          return next()\n        }\n\n        debug('Stripping sourceURL', { method })\n        paramArgs[methodsToPatch[method]] = paramArgs[\n          methodsToPatch[method]\n        ].replace(SOURCE_URL_SUFFIX, '')\n\n        return next()\n      }\n    })(client.send, client)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\nconst Plugin = require('.')\n\nconst TEST_HTML_FILE = require('path').join(__dirname, './_fixtures/test.html')\n\ntest('vanilla: sourceurl is leaking', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('file://' + TEST_HTML_FILE, { waitUntil: 'load' })\n\n  // Trigger test\n  await page.$('title')\n\n  const result = await page.evaluate(\n    () => document.querySelector('#result').innerText\n  )\n  t.is(result, 'FAIL')\n\n  const result2 = await page.evaluate(() => {\n    try {\n      Function.prototype.toString.apply({})\n    } catch (err) {\n      return err.stack\n    }\n  })\n  t.true(result2.includes('__puppeteer_evaluation_script'))\n})\n\ntest('stealth: sourceurl is not leaking', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  await page.goto('file://' + TEST_HTML_FILE, { waitUntil: 'load' })\n\n  // Trigger test\n  await page.$('title')\n\n  const result = await page.evaluate(\n    () => document.querySelector('#result').innerText\n  )\n  t.is(result, 'PASS')\n\n  const result2 = await page.evaluate(() => {\n    try {\n      Function.prototype.toString.apply({})\n    } catch (err) {\n      return err.stack\n    }\n  })\n  t.false(result2.includes('__puppeteer_evaluation_script'))\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/sourceurl/index.js#L9-L58)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nStrip sourceURL from scripts injected by puppeteer.\nIt can be used to identify the presence of pptr via stacktraces.\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/user-agent-override/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Fixes the UserAgent info (composed of UA string, Accept-Language, Platform, and UA hints).\n *\n * If you don't provide any values this plugin will default to using the regular UserAgent string (while stripping the headless part).\n * Default language is set to \"en-US,en\", the other settings match the UserAgent string.\n * If you are running on Linux, it will mask the settins to look like Windows. This behavior can be disabled with the `maskLinux` option.\n *\n * By default puppeteer will not set a `Accept-Language` header in headless:\n * It's (theoretically) possible to fix that using either `page.setExtraHTTPHeaders` or a `--lang` launch arg.\n * Unfortunately `page.setExtraHTTPHeaders` will lowercase everything and launch args are not always available. :)\n *\n * In addition, the `navigator.platform` property is always set to the host value, e.g. `Linux` which makes detection very easy.\n *\n * Note: You cannot use the regular `page.setUserAgent()` puppeteer call in your code,\n * as it will reset the language and platform values you set with this plugin.\n *\n * @example\n * const puppeteer = require(\"puppeteer-extra\")\n *\n * const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\")\n * const stealth = StealthPlugin()\n * // Remove this specific stealth plugin from the default set\n * stealth.enabledEvasions.delete(\"user-agent-override\")\n * puppeteer.use(stealth)\n *\n * // Stealth plugins are just regular `puppeteer-extra` plugins and can be added as such\n * const UserAgentOverride = require(\"puppeteer-extra-plugin-stealth/evasions/user-agent-override\")\n * // Define custom UA and locale\n * const ua = UserAgentOverride({ userAgent: \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\", locale: \"de-DE,de\" })\n * puppeteer.use(ua)\n *\n * @param {Object} [opts] - Options\n * @param {string} [opts.userAgent] - The user agent to use (default: browser.userAgent())\n * @param {string} [opts.locale] - The locale to use in `Accept-Language` header and in `navigator.languages` (default: `en-US,en`)\n * @param {boolean} [opts.maskLinux] - Wether to hide Linux as platform in the user agent or not - true by default\n *\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n\n    this._headless = false\n  }\n\n  get name() {\n    return 'stealth/evasions/user-agent-override'\n  }\n\n  get dependencies() {\n    return new Set(['user-preferences'])\n  }\n\n  get defaults() {\n    return {\n      userAgent: null,\n      locale: 'en-US,en',\n      maskLinux: true\n    }\n  }\n\n  async onPageCreated(page) {\n    // Determine the full user agent string, strip the \"Headless\" part\n    let ua =\n      this.opts.userAgent ||\n      (await page.browser().userAgent()).replace('HeadlessChrome/', 'Chrome/')\n\n    if (\n      this.opts.maskLinux &&\n      ua.includes('Linux') &&\n      !ua.includes('Android') // Skip Android user agents since they also contain Linux\n    ) {\n      ua = ua.replace(/\\(([^)]+)\\)/, '(Windows NT 10.0; Win64; x64)') // Replace the first part in parentheses with Windows data\n    }\n\n    // Full version number from Chrome\n    const uaVersion = ua.includes('Chrome/')\n      ? ua.match(/Chrome\\/([\\d|.]+)/)[1]\n      : (await page.browser().version()).match(/\\/([\\d|.]+)/)[1]\n\n    // Get platform identifier (short or long version)\n    const _getPlatform = (extended = false) => {\n      if (ua.includes('Mac OS X')) {\n        return extended ? 'Mac OS X' : 'MacIntel'\n      } else if (ua.includes('Android')) {\n        return 'Android'\n      } else if (ua.includes('Linux')) {\n        return 'Linux'\n      } else {\n        return extended ? 'Windows' : 'Win32'\n      }\n    }\n\n    // Source in C++: https://source.chromium.org/chromium/chromium/src/+/master:components/embedder_support/user_agent_utils.cc;l=55-100\n    const _getBrands = () => {\n      const seed = uaVersion.split('.')[0] // the major version number of Chrome\n\n      const order = [\n        [0, 1, 2],\n        [0, 2, 1],\n        [1, 0, 2],\n        [1, 2, 0],\n        [2, 0, 1],\n        [2, 1, 0]\n      ][seed % 6]\n      const escapedChars = [' ', ' ', ';']\n\n      const greaseyBrand = `${escapedChars[order[0]]}Not${\n        escapedChars[order[1]]\n      }A${escapedChars[order[2]]}Brand`\n\n      const greasedBrandVersionList = []\n      greasedBrandVersionList[order[0]] = {\n        brand: greaseyBrand,\n        version: '99'\n      }\n      greasedBrandVersionList[order[1]] = {\n        brand: 'Chromium',\n        version: seed\n      }\n      greasedBrandVersionList[order[2]] = {\n        brand: 'Google Chrome',\n        version: seed\n      }\n\n      return greasedBrandVersionList\n    }\n\n    // Return OS version\n    const _getPlatformVersion = () => {\n      if (ua.includes('Mac OS X ')) {\n        return ua.match(/Mac OS X ([^)]+)/)[1]\n      } else if (ua.includes('Android ')) {\n        return ua.match(/Android ([^;]+)/)[1]\n      } else if (ua.includes('Windows ')) {\n        return ua.match(/Windows .*?([\\d|.]+);?/)[1]\n      } else {\n        return ''\n      }\n    }\n\n    // Get architecture, this seems to be empty on mobile and x86 on desktop\n    const _getPlatformArch = () => (_getMobile() ? '' : 'x86')\n\n    // Return the Android model, empty on desktop\n    const _getPlatformModel = () =>\n      _getMobile() ? ua.match(/Android.*?;\\s([^)]+)/)[1] : ''\n\n    const _getMobile = () => ua.includes('Android')\n\n    const override = {\n      userAgent: ua,\n      platform: _getPlatform(),\n      userAgentMetadata: {\n        brands: _getBrands(),\n        fullVersion: uaVersion,\n        platform: _getPlatform(true),\n        platformVersion: _getPlatformVersion(),\n        architecture: _getPlatformArch(),\n        model: _getPlatformModel(),\n        mobile: _getMobile()\n      }\n    }\n\n    // In case of headless, override the acceptLanguage in CDP.\n    // This is not preferred, as it messed up the header order.\n    // On headful, we set the user preference language setting instead.\n    if (this._headless) {\n      override.acceptLanguage = this.opts.locale || 'en-US,en'\n    }\n\n    this.debug('onPageCreated - Will set these user agent options', {\n      override,\n      opts: this.opts\n    })\n\n    const client =\n      typeof page._client === 'function' ? page._client() : page._client\n    client.send('Network.setUserAgentOverride', override)\n  }\n\n  async beforeLaunch(options) {\n    // Check if launched headless\n    this._headless = options.headless\n  }\n\n  async beforeConnect() {\n    // Treat browsers using connect() as headless browsers\n    this._headless = true\n  }\n\n  get data() {\n    return [\n      {\n        name: 'userPreferences',\n        value: {\n          intl: { accept_languages: this.opts.locale || 'en-US,en' }\n        }\n      }\n    ]\n  }\n}\n\nconst defaultExport = opts => new Plugin(opts)\nmodule.exports = defaultExport\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/user-agent-override/index.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\nconst Plugin = require('.')\n\n// Fixed since 2.1.1?\n// test('vanilla: Accept-Language header is missing', async t => {\n//   const browser = await vanillaPuppeteer.launch({ headless: true })\n//   const page = await browser.newPage()\n//   await page.goto('http://httpbin.org/headers')\n\n//   const content = await page.content()\n//   t.true(content.includes(`\"User-Agent\"`))\n//   t.false(content.includes(`\"Accept-Language\"`))\n// })\n\ntest('vanilla: User-Agent header contains HeadlessChrome', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('http://httpbin.org/headers')\n\n  const content = await page.content()\n  t.true(content.includes(`\"User-Agent\"`))\n  t.true(content.includes(`HeadlessChrome`))\n})\n\ntest('vanilla: navigator.languages is always en-US', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  const lang = await page.evaluate(() => navigator.languages)\n  t.true(lang.length === 1 && lang[0] === 'en-US')\n})\n\ntest('vanilla: navigator.platform set to host platform', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const platform = await page.evaluate(() => navigator.platform)\n  switch (process.platform) {\n    case 'linux':\n      t.true(platform.includes('Linux')) // TravisCI\n      break\n    case 'darwin':\n      t.true(platform === 'MacIntel')\n      break\n    case 'win32':\n      t.true(platform === 'Win32')\n      break\n    default:\n      t.true(platform === process.platform)\n  }\n})\n\ntest('stealth: Accept-Language header with default locale', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('http://httpbin.org/headers')\n\n  const content = await page.content()\n  t.true(content.includes(`\"User-Agent\"`))\n  t.true(content.includes(`\"Accept-Language\": \"en-US,en;q=0.9\"`))\n})\n\ntest('stealth: Accept-Language header with optional locale', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ locale: 'de-DE,de' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('http://httpbin.org/headers')\n\n  const content = await page.content()\n  t.true(content.includes(`\"User-Agent\"`))\n  t.true(content.includes(`\"Accept-Language\": \"de-DE,de;q=0.9\"`))\n})\n\ntest('stealth: User-Agent header does not contain HeadlessChrome', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('http://httpbin.org/headers')\n\n  const content = await page.content()\n  t.true(content.includes(`\"User-Agent\"`))\n  t.false(content.includes(`HeadlessChrome`))\n})\n\ntest('stealth: User-Agent header with custom userAgent', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ userAgent: 'MyFunkyUA/1.0' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('http://httpbin.org/headers')\n\n  const content = await page.content()\n  t.true(content.includes(`\"User-Agent\": \"MyFunkyUA/1.0\"`))\n})\n\ntest('stealth: navigator.languages with default locale', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const lang = await page.evaluate(() => navigator.languages)\n  t.true(lang.length === 2 && lang[0] === 'en-US' && lang[1] === 'en')\n})\n\ntest('stealth: navigator.languages with custom locale', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ locale: 'de-DE,de' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const langs = await page.evaluate(() => navigator.languages)\n  t.deepEqual(langs, ['de-DE', 'de'])\n  const lang = await page.evaluate(() => navigator.language)\n  t.deepEqual(lang, 'de-DE')\n})\n\ntest('stealth: navigator.platform with maskLinux true (default)', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      userAgent:\n        'Mozilla/5.0 (X11; Ubuntu; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.9.9999.99 Safari/537.36'\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const platform = await page.evaluate(() => navigator.platform)\n  t.true(platform === 'Win32')\n})\n\ntest('stealth: navigator.platform with maskLinux false', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      userAgent:\n        'Mozilla/5.0 (X11; Ubuntu; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.9.9999.99 Safari/537.36',\n      maskLinux: false\n    })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const platform = await page.evaluate(() => navigator.platform)\n  t.true(platform === 'Linux')\n})\n\nconst _testUAHint = async (userAgent, locale) => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ userAgent, locale })\n  )\n\n  const browser = await puppeteer.launch({\n    headless: false, // only works on headful\n    args: ['--enable-features=UserAgentClientHint']\n  })\n\n  const majorVersion = parseInt(\n    (await browser.version()).match(/\\/([^\\.]+)/)[1]\n  )\n  if (majorVersion < 88) {\n    return null // Skip test on browsers that don't support UA hints\n  }\n\n  const page = await browser.newPage()\n\n  await page.goto('https://headers.cf/headers/?format=raw')\n\n  return page\n}\n\ntest('stealth: test if UA hints are correctly set - Windows 10', async t => {\n  const page = await _testUAHint(\n    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.99 Safari/537.36',\n    'en-AU'\n  )\n  if (!page) {\n    t.true(true) // skip\n    return\n  }\n  const firstLoad = await page.content()\n  t.true(\n    firstLoad.includes(\n      `sec-ch-ua: \"Google Chrome\";v=\"99\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"99\"`\n    )\n  )\n  t.true(firstLoad.includes(`Accept-Language: en-AU`))\n\n  await page.reload()\n  const secondLoad = await page.content()\n  if (secondLoad.includes('sec-ch-ua-full-version')) {\n    t.true(secondLoad.includes('sec-ch-ua-mobile: ?0'))\n    t.true(secondLoad.includes('sec-ch-ua-full-version: \"99.0.9999.99\"'))\n    t.true(secondLoad.includes('sec-ch-ua-arch: \"x86\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform: \"Windows\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform-version: \"10.0\"'))\n    t.true(secondLoad.includes('sec-ch-ua-model: \"\"'))\n  }\n})\n\ntest('stealth: test if UA hints are correctly set - macOS 11', async t => {\n  const page = await _testUAHint(\n    'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.99 Safari/537.36',\n    'de-DE'\n  )\n  if (!page) {\n    t.true(true) // skip\n    return\n  }\n  const firstLoad = await page.content()\n  t.true(\n    firstLoad.includes(\n      `sec-ch-ua: \"Google Chrome\";v=\"99\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"99\"`\n    )\n  )\n  t.true(firstLoad.includes(`Accept-Language: de-DE`))\n\n  await page.reload()\n  const secondLoad = await page.content()\n  if (secondLoad.includes('sec-ch-ua-full-version')) {\n    t.true(secondLoad.includes('sec-ch-ua-mobile: ?0'))\n    t.true(secondLoad.includes('sec-ch-ua-full-version: \"99.0.9999.99\"'))\n    t.true(secondLoad.includes('sec-ch-ua-arch: \"x86\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform: \"Mac OS X\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform-version: \"11_1_0\"'))\n    t.true(secondLoad.includes('sec-ch-ua-model: \"\"'))\n  }\n})\n\ntest('stealth: test if UA hints are correctly set - Android 10', async t => {\n  const page = await _testUAHint(\n    'Mozilla/5.0 (Linux; Android 10; SM-P205) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.99 Safari/537.36',\n    'nl-NL'\n  )\n  if (!page) {\n    t.true(true) // skip\n    return\n  }\n  const firstLoad = await page.content()\n  t.true(\n    firstLoad.includes(\n      `sec-ch-ua: \"Google Chrome\";v=\"99\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"99\"`\n    )\n  )\n  t.true(firstLoad.includes(`Accept-Language: nl-NL`))\n\n  await page.reload()\n  const secondLoad = await page.content()\n\n  if (secondLoad.includes('sec-ch-ua-full-version')) {\n    t.true(secondLoad.includes('sec-ch-ua-mobile: ?1'))\n    t.true(secondLoad.includes('sec-ch-ua-full-version: \"99.0.9999.99\"'))\n    t.true(secondLoad.includes('sec-ch-ua-arch: \"\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform: \"Android\"'))\n    t.true(secondLoad.includes('sec-ch-ua-platform-version: \"10\"'))\n    t.true(secondLoad.includes('sec-ch-ua-model: \"SM-P205\"'))\n  }\n})\n\nasync function userAgentData() {\n  if (!('userAgentData' in navigator)) {\n    return undefined\n  }\n\n  // https://wicg.github.io/ua-client-hints/#getHighEntropyValues\n  const UADataProps = ['brands', 'mobile']\n  const UADataValues = [\n    'architecture', // \"arm\"\n    'bitness', // \"64\"\n    'model', // \"X644GTM\"\n    'platform', // \"PhoneOS\"\n    'platformVersion', // \"10A\"\n    'uaFullVersion' // \"73.32.AGX.5\"\n  ]\n\n  const highEntropy = await navigator.userAgentData.getHighEntropyValues(\n    UADataValues\n  )\n\n  const result = {\n    ...highEntropy,\n    ...Object.fromEntries(UADataProps.map(k => [k, navigator.userAgentData[k]]))\n  }\n  return result\n}\n\ntest('stealth: test if UA hints are correctly set - Windows 10 Generic', async t => {\n  const userAgent =\n    'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.99 Safari/537.36'\n  const locale = 'en-AU'\n\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({\n      userAgent,\n      locale\n    })\n  )\n  const browser = await puppeteer.launch({\n    headless: true\n  })\n\n  const majorVersion = parseInt(\n    (await browser.version()).match(/\\/([^\\.]+)/)[1]\n  )\n  if (majorVersion < 90) {\n    t.truthy('foo')\n    console.log('Skipping test, browser version too old', majorVersion)\n    return\n  }\n  const page = await browser.newPage()\n  await page.goto('https://example.com') // secure context\n\n  const results = await page.evaluate(userAgentData)\n  t.is(results.platform, 'Windows')\n  t.is(results.platformVersion, '10.0')\n  t.is(results.uaFullVersion, '99.0.9999.99')\n\n  const language = await page.evaluate(() => navigator.language)\n  t.is(language, locale)\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/user-agent-override/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/user-agent-override/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/ab0047d1af7dc38412744abdb61bcfc35c42dc34/packages/puppeteer-extra-plugin-stealth/evasions/user-agent-override/index.js#L42-L203)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.userAgent` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The user agent to use (default: browser.userAgent())\n  - `opts.locale` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The locale to use in `Accept-Language` header and in `navigator.languages` (default: `en-US,en`)\n  - `opts.maskLinux` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** Wether to hide Linux as platform in the user agent or not - true by default\n\n**Extends: PuppeteerExtraPlugin**\n\nFixes the UserAgent info (composed of UA string, Accept-Language, Platform, and UA hints).\n\nIf you don't provide any values this plugin will default to using the regular UserAgent string (while stripping the headless part).\nDefault language is set to \"en-US,en\", the other settings match the UserAgent string.\nIf you are running on Linux, it will mask the settins to look like Windows. This behavior can be disabled with the `maskLinux` option.\n\nBy default puppeteer will not set a `Accept-Language` header in headless:\nIt's (theoretically) possible to fix that using either `page.setExtraHTTPHeaders` or a `--lang` launch arg.\nUnfortunately `page.setExtraHTTPHeaders` will lowercase everything and launch args are not always available. :)\n\nIn addition, the `navigator.platform` property is always set to the host value, e.g. `Linux` which makes detection very easy.\n\nNote: You cannot use the regular `page.setUserAgent()` puppeteer call in your code,\nas it will reset the language and platform values you set with this plugin.\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\n\nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')\nconst stealth = StealthPlugin()\n// Remove this specific stealth plugin from the default set\nstealth.enabledEvasions.delete('user-agent-override')\npuppeteer.use(stealth)\n\n// Stealth plugins are just regular `puppeteer-extra` plugins and can be added as such\nconst UserAgentOverride = require('puppeteer-extra-plugin-stealth/evasions/user-agent-override')\n// Define custom UA and locale\nconst ua = UserAgentOverride({\n  userAgent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',\n  locale: 'de-DE,de'\n})\npuppeteer.use(ua)\n```\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/webgl.vendor/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\nconst withUtils = require('../_utils/withUtils')\n\n/**\n * Fix WebGL Vendor/Renderer being set to Google in headless mode\n *\n * Example data (Apple Retina MBP 13): {vendor: \"Intel Inc.\", renderer: \"Intel(R) Iris(TM) Graphics 6100\"}\n *\n * @param {Object} [opts] - Options\n * @param {string} [opts.vendor] - The vendor string to use (default: `Intel Inc.`)\n * @param {string} [opts.renderer] - The renderer string (default: `Intel Iris OpenGL Engine`)\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/webgl.vendor'\n  }\n\n  /* global WebGLRenderingContext WebGL2RenderingContext */\n  async onPageCreated(page) {\n    await withUtils(page).evaluateOnNewDocument((utils, opts) => {\n      const getParameterProxyHandler = {\n        apply: function(target, ctx, args) {\n          const param = (args || [])[0]\n          const result = utils.cache.Reflect.apply(target, ctx, args)\n          // UNMASKED_VENDOR_WEBGL\n          if (param === 37445) {\n            return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.\n          }\n          // UNMASKED_RENDERER_WEBGL\n          if (param === 37446) {\n            return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader\n          }\n          return result\n        }\n      }\n\n      // There's more than one WebGL rendering context\n      // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility\n      // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)\n      const addProxy = (obj, propName) => {\n        utils.replaceWithProxy(obj, propName, getParameterProxyHandler)\n      }\n      // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:\n      addProxy(WebGLRenderingContext.prototype, 'getParameter')\n      addProxy(WebGL2RenderingContext.prototype, 'getParameter')\n    }, this.opts)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/webgl.vendor/index.test.js",
    "content": "const test = require('ava')\n\nconst {\n  getVanillaFingerPrint,\n  getStealthFingerPrint\n} = require('../../test/util')\nconst { vanillaPuppeteer, addExtra } = require('../../test/util')\n\nconst Plugin = require('.')\nconst { errors } = require('puppeteer')\n\n// FIXME: This changed in more recent chrome versions\n// test('vanilla: videoCard is Google Inc', async t => {\n//   const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line\n//   const { videoCard } = await getVanillaFingerPrint(pageFn)\n//   t.deepEqual(videoCard, ['Google Inc.', 'Google SwiftShader'])\n// })\n\ntest('stealth: videoCard is Intel Inc', async t => {\n  const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line\n  const { videoCard } = await getStealthFingerPrint(Plugin, pageFn)\n  t.deepEqual(videoCard, ['Intel Inc.', 'Intel Iris OpenGL Engine'])\n})\n\ntest('stealth: customized values', async t => {\n  const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line\n  const { videoCard } = await getStealthFingerPrint(Plugin, pageFn, {\n    vendor: 'foo',\n    renderer: 'bar'\n  })\n  t.deepEqual(videoCard, ['foo', 'bar'])\n})\n\n/* global WebGLRenderingContext */\nasync function extendedTests() {\n  const results = {}\n\n  async function test(name, fn) {\n    const detectionPassed = await fn()\n    if (detectionPassed) console.log(`Chrome headless detected via ${name}`)\n    results[name] = detectionPassed\n  }\n\n  const canvas = document.createElement('canvas')\n  const context = canvas.getContext('webgl')\n\n  await test('descriptorsOK', _ => {\n    const descriptors = Object.getOwnPropertyDescriptors(\n      WebGLRenderingContext.prototype\n    )\n    const str = descriptors.getParameter.toString()\n    return str === `[object Object]`\n  })\n\n  await test('toStringOK', _ => {\n    const str = context.getParameter.toString()\n    return str === `function getParameter() { [native code] }`\n  })\n\n  await test('toStringOK2', _ => {\n    const str = WebGLRenderingContext.prototype.getParameter.toString()\n    return str === `function getParameter() { [native code] }`\n  })\n\n  // Make sure we not reveal our proxy through errors\n  await test('errorOK', _ => {\n    try {\n      return context.getParameter()\n    } catch (err) {\n      return !err.stack.includes(`at Object.apply`)\n    }\n  })\n\n  // Should not throw (that was old stealth behavior)\n  await test('elementOK', _ => {\n    try {\n      return context.getParameter(123) === null\n    } catch (_) {\n      return false\n    }\n  })\n\n  return results\n}\n\ntest('vanilla: webgl is native', async t => {\n  const pageFn = async page => {\n    // page.on('console', msg => {\n    //   console.log('Page console: ', msg.text())\n    // })\n    return await page.evaluate(extendedTests) // eslint-disable-line\n  }\n  const { pageFnResult: result } = await getVanillaFingerPrint(pageFn)\n\n  const wasHeadlessDetected = Object.values(result).some(e => e === false)\n  if (wasHeadlessDetected) {\n    console.log(result)\n  }\n  t.false(wasHeadlessDetected)\n})\n\ntest('stealth: webgl is native', async t => {\n  const pageFn = async page => await page.evaluate(extendedTests) // eslint-disable-line\n  const { pageFnResult: result } = await getStealthFingerPrint(Plugin, pageFn)\n\n  const wasHeadlessDetected = Object.values(result).some(e => e === false)\n  if (wasHeadlessDetected) {\n    console.log(result)\n  }\n  t.false(wasHeadlessDetected)\n})\n\n/**\n * A very simple method to retrieve the name of the default videocard of the system\n * using webgl.\n *\n * Example (Apple Retina MBP 13): {vendor: \"Intel Inc.\", renderer: \"Intel(R) Iris(TM) Graphics 6100\"}\n *\n * @see https://stackoverflow.com/questions/49267764/how-to-get-the-video-card-driver-name-using-javascript-browser-side\n * @returns {Object}\n */\nfunction getVideoCardInfo(context = 'webgl') {\n  const gl = document.createElement('canvas').getContext(context)\n  if (!gl) {\n    return {\n      error: 'no webgl'\n    }\n  }\n  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')\n  if (debugInfo) {\n    return {\n      vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),\n      renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)\n    }\n  }\n  return {\n    error: 'no WEBGL_debug_renderer_info'\n  }\n}\n\ntest('stealth: handles WebGLRenderingContext', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl')\n  t.is(videoCardInfo.error, undefined)\n  t.is(videoCardInfo.vendor, 'Intel Inc.')\n  t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine')\n})\n\ntest('stealth: handles WebGL2RenderingContext', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl2')\n  t.is(videoCardInfo.error, undefined)\n  t.is(videoCardInfo.vendor, 'Intel Inc.')\n  t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine')\n})\n\ntest('vanilla: normal toString stuff', async t => {\n  const browser = await vanillaPuppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => {\n    return WebGLRenderingContext.prototype.getParameter.toString + ''\n  })\n  t.is(test1, 'function toString() { [native code] }')\n\n  const test2 = await page.evaluate(() => {\n    return WebGLRenderingContext.prototype.getParameter.toString()\n  })\n  t.is(test2, 'function getParameter() { [native code] }')\n})\n\ntest('stealth: will not leak toString stuff', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const test1 = await page.evaluate(() => {\n    return WebGLRenderingContext.prototype.getParameter.toString + ''\n  })\n  t.is(test1, 'function toString() { [native code] }') // returns function () { [native code] }\n\n  const test2 = await page.evaluate(() => {\n    return WebGLRenderingContext.prototype.getParameter.toString()\n  })\n  t.is(test2, 'function getParameter() { [native code] }')\n})\n\ntest('stealth: sets user opts correctly', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ vendor: 'alice', renderer: 'bob' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl')\n  t.is(videoCardInfo.error, undefined)\n  t.is(videoCardInfo.vendor, 'alice')\n  t.is(videoCardInfo.renderer, 'bob')\n})\n\ntest('stealth: does not affect protoype', async t => {\n  const puppeteer = addExtra(vanillaPuppeteer).use(\n    Plugin({ vendor: 'alice', renderer: 'bob' })\n  )\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n\n  const result = await page.evaluate(() => {\n    try {\n      return WebGLRenderingContext.prototype.getParameter(37445)\n    } catch (err) {\n      return err.message\n    }\n  })\n  t.is(result, 'Illegal invocation')\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/webgl.vendor/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/webgl.vendor/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/webgl.vendor/index.js#L17-L55)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.vendor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The vendor string to use (default: `Intel Inc.`)\n  - `opts.renderer` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The renderer string (default: `Intel Iris OpenGL Engine`)\n\n**Extends: PuppeteerExtraPlugin**\n\nFix WebGL Vendor/Renderer being set to Google in headless mode\n\nExample data (Apple Retina MBP 13): {vendor: \"Intel Inc.\", renderer: \"Intel(R) Iris(TM) Graphics 6100\"}\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/window.outerdimensions/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Fix missing window.outerWidth/window.outerHeight in headless mode\n * Will also set the viewport to match window size, unless specified by user\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth/evasions/window.outerdimensions'\n  }\n\n  async onPageCreated(page) {\n    // Chrome returns undefined, Firefox false\n    await page.evaluateOnNewDocument(() => {\n      try {\n        if (window.outerWidth && window.outerHeight) {\n          return // nothing to do here\n        }\n        const windowFrame = 85 // probably OS and WM dependent\n        window.outerWidth = window.innerWidth\n        window.outerHeight = window.innerHeight + windowFrame\n      } catch (err) {}\n    })\n  }\n\n  async beforeLaunch(options) {\n    // Have viewport match window size, unless specified by user\n    // https://github.com/GoogleChrome/puppeteer/issues/3688\n    if (!('defaultViewport' in options)) {\n      options.defaultViewport = null\n    }\n    return options\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/window.outerdimensions/package.json",
    "content": "{\n  \"private\": true,\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/evasions/window.outerdimensions/readme.md",
    "content": "## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [class: Plugin](#class-plugin)\n\n### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/window.outerdimensions/index.js#L9-L40)\n\n- `opts` (optional, default `{}`)\n\n**Extends: PuppeteerExtraPlugin**\n\nFix missing window.outerWidth/window.outerHeight in headless mode\nWill also set the viewport to match window size, unless specified by user\n\n---\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/examples/detect-headless.js",
    "content": "'use strict'\n\n// taken from: https://github.com/paulirish/headless-cat-n-mouse/blob/master/detect-headless.js\n// initial detects from @antoinevastel\n//   http://antoinevastel.github.io/bot%20detection/2018/01/17/detect-chrome-headless-v2.html\n\nmodule.exports = async function() {\n  const results = {}\n\n  async function test(name, fn) {\n    const detectionPassed = await fn()\n    if (detectionPassed) {\n      console.log(`WARNING: Chrome headless detected via ${name}`)\n    } else {\n      console.log(`PASS: Chrome headless NOT detected via ${name}`)\n    }\n    results[name] = detectionPassed\n  }\n\n  await test('userAgent', _ => {\n    return /HeadlessChrome/.test(window.navigator.userAgent)\n  })\n\n  // Detects the --enable-automation || --headless flags\n  // Will return true in headful if --enable-automation is provided\n  await test('navigator.webdriver present', _ => {\n    return 'webdriver' in navigator\n  })\n\n  await test('window.chrome missing', _ => {\n    return /Chrome/.test(window.navigator.userAgent) && !window.chrome\n  })\n\n  await test('permissions API', async _ => {\n    const permissionStatus = await navigator.permissions.query({\n      name: 'notifications'\n    })\n    // eslint-disable-next-line\n    return (\n      Notification.permission === 'denied' && // eslint-disable-line no-undef\n      permissionStatus.state === 'prompt'\n    )\n  })\n\n  await test('permissions API overriden', _ => {\n    const permissions = window.navigator.permissions\n    if (permissions.query.toString() !== 'function query() { [native code] }')\n      return true\n    if (\n      permissions.query.toString.toString() !==\n      'function toString() { [native code] }'\n    )\n      return true\n    if (\n      permissions.query.toString.hasOwnProperty('[[Handler]]') && // eslint-disable-line no-prototype-builtins\n      permissions.query.toString.hasOwnProperty('[[Target]]') && // eslint-disable-line no-prototype-builtins\n      permissions.query.toString.hasOwnProperty('[[IsRevoked]]') // eslint-disable-line no-prototype-builtins\n    )\n      return true\n    if (permissions.hasOwnProperty('query')) return true // eslint-disable-line no-prototype-builtins\n  })\n\n  await test('navigator.plugins empty', _ => {\n    return navigator.plugins.length === 0\n  })\n\n  await test('navigator.languages blank', _ => {\n    return navigator.languages === ''\n  })\n\n  await test('iFrame for fresh window object', _ => {\n    // evaluateOnNewDocument scripts don't apply within [srcdoc] (or [sandbox]) iframes\n    // https://github.com/GoogleChrome/puppeteer/issues/1106#issuecomment-359313898\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'page intentionally left blank'\n    document.body.appendChild(iframe)\n\n    // Here we would need to rerun all tests with `iframe.contentWindow` as `window`\n    // Example:\n    return iframe.contentWindow.navigator.plugins.length === 0\n  })\n\n  // This detects that a devtools protocol agent is attached.\n  // So it will also pass true in headful Chrome if the devtools window is attached\n  await test('toString', _ => {\n    let gotYou = 0\n    const spooky = /./\n    spooky.toString = function() {\n      gotYou++\n      return 'spooky'\n    }\n    console.debug(spooky)\n    return gotYou > 1\n  })\n\n  return results\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/examples/test1.js",
    "content": "'use strict'\n\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-stealth')())\n\nconst detectHeadless = require('./detect-headless')\n\n;(async () => {\n  const browser = await puppeteer.launch({ args: ['--no-sandbox'] })\n  const page = await browser.newPage()\n  page.on('console', msg => {\n    console.log('Page console: ', msg.text())\n  })\n\n  await page.goto('about:blank')\n  const detectionResults = await page.evaluate(detectHeadless)\n  console.assert(\n    Object.keys(detectionResults).length,\n    'No detection results returned.'\n  )\n\n  await browser.close()\n})()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/examples/test2.js",
    "content": "'use strict'\n\nconst puppeteer = require('puppeteer-extra')\n// Enable stealth plugin\npuppeteer.use(require('puppeteer-extra-plugin-stealth')())\n;(async () => {\n  // Launch the browser in headless mode and set up a page.\n  const browser = await puppeteer.launch({\n    args: ['--no-sandbox'],\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  // Navigate to the page that will perform the tests.\n  const testUrl =\n    'https://intoli.com/blog/' +\n    'not-possible-to-block-chrome-headless/chrome-headless-test.html'\n  await page.goto(testUrl)\n\n  // Save a screenshot of the results.\n  const screenshotPath = '/tmp/headless-test-result.png'\n  await page.screenshot({ path: screenshotPath })\n  console.log('have a look at the screenshot:', screenshotPath)\n\n  await browser.close()\n})()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/index.d.ts",
    "content": "export = defaultExport;\ndeclare function defaultExport(opts?: {\n    enabledEvasions?: Set<string>;\n}): StealthPlugin;\ndeclare const StealthPlugin_base: typeof import(\"puppeteer-extra-plugin\").PuppeteerExtraPlugin;\n/**\n * Stealth mode: Applies various techniques to make detection of headless puppeteer harder. 💯\n *\n * ### Purpose\n * There are a couple of ways the use of puppeteer can easily be detected by a target website.\n * The addition of `HeadlessChrome` to the user-agent being only the most obvious one.\n *\n * The goal of this plugin is to be the definite companion to puppeteer to avoid\n * detection, applying new techniques as they surface.\n *\n * As this cat & mouse game is in it's infancy and fast-paced the plugin\n * is kept as flexibile as possible, to support quick testing and iterations.\n *\n * ### Modularity\n * This plugin uses `puppeteer-extra`'s dependency system to only require\n * code mods for evasions that have been enabled, to keep things modular and efficient.\n *\n * The `stealth` plugin is a convenience wrapper that requires multiple [evasion techniques](./evasions/)\n * automatically and comes with defaults. You could also bypass the main module and require\n * specific evasion plugins yourself, if you whish to do so (as they're standalone `puppeteer-extra` plugins):\n *\n * ```es6\n * // bypass main module and require a specific stealth plugin directly:\n * puppeteer.use(require('puppeteer-extra-plugin-stealth/evasions/console.debug')())\n * ```\n *\n * ### Contributing\n * PRs are welcome, if you want to add a new evasion technique I suggest you\n * look at the [template](./evasions/_template) to kickstart things.\n *\n * ### Kudos\n * Thanks to [Evan Sangaline](https://intoli.com/blog/not-possible-to-block-chrome-headless/) and [Paul Irish](https://github.com/paulirish/headless-cat-n-mouse) for kickstarting the discussion!\n *\n * ---\n *\n * @todo\n * - white-/blacklist with url globs (make this a generic plugin method?)\n * - dynamic whitelist based on function evaluation\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * // Enable stealth plugin with all evasions\n * puppeteer.use(require('puppeteer-extra-plugin-stealth')())\n *\n *\n * ;(async () => {\n *   // Launch the browser in headless mode and set up a page.\n *   const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true })\n *   const page = await browser.newPage()\n *\n *   // Navigate to the page that will perform the tests.\n *   const testUrl = 'https://intoli.com/blog/' +\n *     'not-possible-to-block-chrome-headless/chrome-headless-test.html'\n *   await page.goto(testUrl)\n *\n *   // Save a screenshot of the results.\n *   const screenshotPath = '/tmp/headless-test-result.png'\n *   await page.screenshot({path: screenshotPath})\n *   console.log('have a look at the screenshot:', screenshotPath)\n *\n *   await browser.close()\n * })()\n *\n * @param {Object} [opts] - Options\n * @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)\n *\n */\ndeclare class StealthPlugin extends StealthPlugin_base {\n    constructor(opts?: {});\n    get defaults(): {\n        availableEvasions: Set<string>;\n        enabledEvasions: Set<any>;\n    };\n    /**\n     * Get all available evasions.\n     *\n     * Please look into the [evasions directory](./evasions/) for an up to date list.\n     *\n     * @type {Set<string>} - A Set of all available evasions.\n     *\n     * @example\n     * const pluginStealth = require('puppeteer-extra-plugin-stealth')()\n     * console.log(pluginStealth.availableEvasions) // => Set { 'user-agent', 'console.debug' }\n     * puppeteer.use(pluginStealth)\n     */\n    get availableEvasions(): Set<string>;\n    /**\n     * @private\n     */\n    set enabledEvasions(arg: Set<string>);\n    /**\n     * Get all enabled evasions.\n     *\n     * Enabled evasions can be configured either through `opts` or by modifying this property.\n     *\n     * @type {Set<string>} - A Set of all enabled evasions.\n     *\n     * @example\n     * // Remove specific evasion from enabled ones dynamically\n     * const pluginStealth = require('puppeteer-extra-plugin-stealth')()\n     * pluginStealth.enabledEvasions.delete('console.debug')\n     * puppeteer.use(pluginStealth)\n     */\n    get enabledEvasions(): Set<string>;\n    onBrowser(browser: any): Promise<void>;\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/index.js",
    "content": "'use strict'\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Stealth mode: Applies various techniques to make detection of headless puppeteer harder. 💯\n *\n * ### Purpose\n * There are a couple of ways the use of puppeteer can easily be detected by a target website.\n * The addition of `HeadlessChrome` to the user-agent being only the most obvious one.\n *\n * The goal of this plugin is to be the definite companion to puppeteer to avoid\n * detection, applying new techniques as they surface.\n *\n * As this cat & mouse game is in it's infancy and fast-paced the plugin\n * is kept as flexibile as possible, to support quick testing and iterations.\n *\n * ### Modularity\n * This plugin uses `puppeteer-extra`'s dependency system to only require\n * code mods for evasions that have been enabled, to keep things modular and efficient.\n *\n * The `stealth` plugin is a convenience wrapper that requires multiple [evasion techniques](./evasions/)\n * automatically and comes with defaults. You could also bypass the main module and require\n * specific evasion plugins yourself, if you whish to do so (as they're standalone `puppeteer-extra` plugins):\n *\n * ```es6\n * // bypass main module and require a specific stealth plugin directly:\n * puppeteer.use(require('puppeteer-extra-plugin-stealth/evasions/console.debug')())\n * ```\n *\n * ### Contributing\n * PRs are welcome, if you want to add a new evasion technique I suggest you\n * look at the [template](./evasions/_template) to kickstart things.\n *\n * ### Kudos\n * Thanks to [Evan Sangaline](https://intoli.com/blog/not-possible-to-block-chrome-headless/) and [Paul Irish](https://github.com/paulirish/headless-cat-n-mouse) for kickstarting the discussion!\n *\n * ---\n *\n * @todo\n * - white-/blacklist with url globs (make this a generic plugin method?)\n * - dynamic whitelist based on function evaluation\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * // Enable stealth plugin with all evasions\n * puppeteer.use(require('puppeteer-extra-plugin-stealth')())\n *\n *\n * ;(async () => {\n *   // Launch the browser in headless mode and set up a page.\n *   const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true })\n *   const page = await browser.newPage()\n *\n *   // Navigate to the page that will perform the tests.\n *   const testUrl = 'https://intoli.com/blog/' +\n *     'not-possible-to-block-chrome-headless/chrome-headless-test.html'\n *   await page.goto(testUrl)\n *\n *   // Save a screenshot of the results.\n *   const screenshotPath = '/tmp/headless-test-result.png'\n *   await page.screenshot({path: screenshotPath})\n *   console.log('have a look at the screenshot:', screenshotPath)\n *\n *   await browser.close()\n * })()\n *\n * @param {Object} [opts] - Options\n * @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)\n *\n */\nclass StealthPlugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n  }\n\n  get name() {\n    return 'stealth'\n  }\n\n  get defaults() {\n    const availableEvasions = new Set([\n      'chrome.app',\n      'chrome.csi',\n      'chrome.loadTimes',\n      'chrome.runtime',\n      'defaultArgs',\n      'iframe.contentWindow',\n      'media.codecs',\n      'navigator.hardwareConcurrency',\n      'navigator.languages',\n      'navigator.permissions',\n      'navigator.plugins',\n      'navigator.webdriver',\n      'sourceurl',\n      'user-agent-override',\n      'webgl.vendor',\n      'window.outerdimensions'\n    ])\n    return {\n      availableEvasions,\n      // Enable all available evasions by default\n      enabledEvasions: new Set([...availableEvasions])\n    }\n  }\n\n  /**\n   * Requires evasion techniques dynamically based on configuration.\n   *\n   * @private\n   */\n  get dependencies() {\n    return new Set(\n      [...this.opts.enabledEvasions].map(e => `${this.name}/evasions/${e}`)\n    )\n  }\n\n  /**\n   * Get all available evasions.\n   *\n   * Please look into the [evasions directory](./evasions/) for an up to date list.\n   *\n   * @type {Set<string>} - A Set of all available evasions.\n   *\n   * @example\n   * const pluginStealth = require('puppeteer-extra-plugin-stealth')()\n   * console.log(pluginStealth.availableEvasions) // => Set { 'user-agent', 'console.debug' }\n   * puppeteer.use(pluginStealth)\n   */\n  get availableEvasions() {\n    return this.defaults.availableEvasions\n  }\n\n  /**\n   * Get all enabled evasions.\n   *\n   * Enabled evasions can be configured either through `opts` or by modifying this property.\n   *\n   * @type {Set<string>} - A Set of all enabled evasions.\n   *\n   * @example\n   * // Remove specific evasion from enabled ones dynamically\n   * const pluginStealth = require('puppeteer-extra-plugin-stealth')()\n   * pluginStealth.enabledEvasions.delete('console.debug')\n   * puppeteer.use(pluginStealth)\n   */\n  get enabledEvasions() {\n    return this.opts.enabledEvasions\n  }\n\n  /**\n   * @private\n   */\n  set enabledEvasions(evasions) {\n    this.opts.enabledEvasions = evasions\n  }\n\n  async onBrowser(browser) {\n    if (browser && browser.setMaxListeners) {\n      // Increase event emitter listeners to prevent MaxListenersExceededWarning\n      browser.setMaxListeners(30)\n    }\n  }\n}\n\n/**\n * Default export, PuppeteerExtraStealthPlugin\n *\n * @param {Object} [opts] - Options\n * @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)\n */\nconst defaultExport = opts => new StealthPlugin(opts)\nmodule.exports = defaultExport\n\n// const moduleExport = defaultExport\n// moduleExport.StealthPlugin = StealthPlugin\n// module.exports = moduleExport\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/index.test.js",
    "content": "'use strict'\n\nconst PLUGIN_NAME = 'stealth'\n\nconst test = require('ava')\n\nconst Plugin = require('.')\n\ntest('is a function', async t => {\n  t.is(typeof Plugin, 'function')\n})\n\ntest('should have the basic class members', async t => {\n  const instance = Plugin()\n  t.is(instance.name, PLUGIN_NAME)\n  t.true(instance._isPuppeteerExtraPlugin)\n})\n\ntest('should have the public child class members', async t => {\n  const instance = Plugin()\n  const prototype = Object.getPrototypeOf(instance)\n  const childClassMembers = Object.getOwnPropertyNames(prototype)\n\n  t.true(childClassMembers.includes('constructor'))\n  t.true(childClassMembers.includes('name'))\n  t.true(childClassMembers.includes('name'))\n  t.true(childClassMembers.includes('defaults'))\n  t.true(childClassMembers.includes('availableEvasions'))\n  t.true(childClassMembers.includes('enabledEvasions'))\n  t.is(childClassMembers.length, 7)\n})\n\ntest('should have opts with default values', async t => {\n  const instance = Plugin()\n  t.deepEqual(instance.opts.enabledEvasions, instance.availableEvasions)\n})\n\ntest('should add all dependencies dynamically', async t => {\n  const instance = Plugin()\n  const deps = new Set(\n    [...instance.opts.enabledEvasions].map(e => `${PLUGIN_NAME}/evasions/${e}`)\n  )\n  t.deepEqual(instance.dependencies, deps)\n})\n\ntest('should add all dependencies dynamically including changes', async t => {\n  const instance = Plugin()\n  const fakeDep = 'foobar'\n  instance.enabledEvasions = new Set([fakeDep])\n  t.deepEqual(\n    instance.dependencies,\n    new Set([`${PLUGIN_NAME}/evasions/${fakeDep}`])\n  )\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-stealth\",\n  \"version\": \"2.11.2\",\n  \"description\": \"Stealth mode: Applies various techniques to make detection of headless puppeteer harder.\",\n  \"main\": \"index.js\",\n  \"typings\": \"index.d.ts\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"homepage\": \"https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth#readme\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"run-s docs-for-plugin postdocs-for-plugin docs-for-evasions postdocs-for-evasions types\",\n    \"docs-for-plugin\": \"documentation readme --quiet --shallow --github --markdown-theme transitivebs --readme-file readme.md --section API index.js\",\n    \"postdocs-for-plugin\": \"npx prettier --write readme.md\",\n    \"docs-for-evasions\": \"cd ./evasions && loop \\\"documentation readme --quiet --shallow --github --markdown-theme transitivebs --readme-file readme.md --section API index.js\\\"\",\n    \"postdocs-for-evasions\": \"cd ./evasions && loop \\\"npx prettier --write readme.md\\\"\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test:js\": \"ava --concurrency 2 -v\",\n    \"test\": \"run-p test:js\",\n    \"test-ci\": \"run-s test:js\",\n    \"types\": \"npx --package typescript@3.7 tsc --emitDeclarationOnly --declaration --allowJs index.js\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"stealth\",\n    \"stealth-mode\",\n    \"detection-evasion\",\n    \"crawler\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"ava\": {\n    \"files\": [\n      \"!test/util.js\",\n      \"!test/fixtures/sw.js\"\n    ]\n  },\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"documentation-markdown-themes\": \"^12.1.5\",\n    \"fpcollect\": \"^1.0.4\",\n    \"fpscanner\": \"^0.1.5\",\n    \"loop\": \"^3.0.6\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"9\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"puppeteer-extra-plugin-user-preferences\": \"^2.4.1\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/readme.md",
    "content": "# puppeteer-extra-plugin-stealth [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/berstend/puppeteer-extra/test.yml?branch=master&event=push) [![Discord](https://img.shields.io/discord/737009125862408274)](https://extra.community) [![npm](https://img.shields.io/npm/v/puppeteer-extra-plugin-stealth.svg)](https://www.npmjs.com/package/puppeteer-extra-plugin-stealth)\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra) and [playwright-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra) to prevent detection.\n\n<p align=\"center\"><img src=\"https://i.imgur.com/q2xBjqH.png\" /></p>\n\n## Install\n\n```bash\nyarn add puppeteer-extra-plugin-stealth\n# - or -\nnpm install puppeteer-extra-plugin-stealth\n```\n\nIf this is your first [puppeteer-extra](https://github.com/berstend/puppeteer-extra) plugin here's everything you need:\n\n```bash\nyarn add puppeteer puppeteer-extra puppeteer-extra-plugin-stealth\n# - or -\nnpm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth\n```\n\n## Usage\n\n```js\n// puppeteer-extra is a drop-in replacement for puppeteer,\n// it augments the installed puppeteer with plugin functionality\nconst puppeteer = require('puppeteer-extra')\n\n// add stealth plugin and use defaults (all evasion techniques)\nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')\npuppeteer.use(StealthPlugin())\n\n// puppeteer usage as normal\npuppeteer.launch({ headless: true }).then(async browser => {\n  console.log('Running tests..')\n  const page = await browser.newPage()\n  await page.goto('https://bot.sannysoft.com')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: 'testresult.png', fullPage: true })\n  await browser.close()\n  console.log(`All done, check the screenshot. ✨`)\n})\n```\n\n<details>\n <summary><strong>TypeScript usage</strong></summary><br/>\n\n> `puppeteer-extra` and most plugins are written in TS,\n> so you get perfect type support out of the box. :)\n\n```ts\nimport puppeteer from 'puppeteer-extra'\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\n\npuppeteer\n  .use(StealthPlugin())\n  .launch({ headless: true })\n  .then(async browser => {\n    const page = await browser.newPage()\n    await page.goto('https://bot.sannysoft.com')\n    await page.waitForTimeout(5000)\n    await page.screenshot({ path: 'stealth.png', fullPage: true })\n    await browser.close()\n  })\n```\n\n> Please check this [wiki](https://github.com/berstend/puppeteer-extra/wiki/TypeScript-usage) entry in case you have TypeScript related import issues.\n\n</details><br>\n\n> Please check out the [main documentation](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra) to learn more about `puppeteer-extra` (Firefox usage, other Plugins, etc).\n\n## Status\n\n- ✅ **`puppeteer-extra` with stealth passes all public bot tests.**\n\nPlease note: I consider this a friendly competition in a rather interesting cat and mouse game. If the other team (👋) wants to detect headless chromium there are still ways to do that (at least I noticed a few, which I'll tackle in future updates).\n\nIt's probably impossible to prevent all ways to detect headless chromium, but it should be possible to make it so difficult that it becomes cost-prohibitive or triggers too many false-positives to be feasible.\n\nIf something new comes up or you experience a problem, please do your homework and create a PR in a respectful way (this is Github, not reddit) or I might not be motivated to help. :)\n\n## Changelog\n\n> 🎁 **Note:** Until we've automated changelog updates in markdown files please follow the `#announcements` channel in our [discord server](https://discord.gg/vz7PeKk) for the latest updates and changelog info.\n\n_Older changelog:_\n\n#### `v2.4.7`\n\n- New: `user-agent-override` - Used to set a stealthy UA string, language & platform. This also fixes issues with the prior method of setting the `Accept-Language` header through request interception ([#104](https://github.com/berstend/puppeteer-extra/pull/104), kudos to [@Niek](https://github.com/Niek))\n- New: `navigator.vendor` - Makes it possible to optionally override navigator.vendor ([#110](https://github.com/berstend/puppeteer-extra/pull/110), thanks [@Niek](https://github.com/Niek))\n- Improved: `navigator.webdriver`: Now uses ES6 Proxies to pass `instanceof` tests ([#117](https://github.com/berstend/puppeteer-extra/pull/117), thanks [@aabbccsmith](https://github.com/aabbccsmith))\n- Removed: `user-agent`, `accept-language` (now obsolete)\n\n#### `v2.4.2` / `v2.4.1`\n\n- Improved: `iframe.contentWindow` - We now proxy the original window object and smartly redirect calls that might reveal it's true identity, as opposed to mocking it like peasants :)\n- Improved: `accept-language` - More robust and it's now possible to [set a custom locale](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions/accept-language#readme) if needed.\n- ⭐️ Passes the [headless-cat-n-mouse](https://github.com/paulirish/headless-cat-n-mouse) test\n\n#### `v2.4.0`\n\nLet's ring the bell for round 2 in this cat and mouse fight 😄\n\n- New: All evasions now have a specific before and after test to make make this whole topic less voodoo\n- New: `media.codecs` - we spoof the presence of proprietary codecs in Chromium now\n- New & improved: `iframe.contentWindow` - Found a way to fix `srcdoc` frame based detection without breaking recaptcha inline popup & other iframes (please report any issues)\n- New: `accept-language` - Adds a missing `Accept-Language` header in headless (capitalized correctly, `page.setExtraHTTPHeaders` is all lowercase which can be detected)\n- Improved: `chrome.runtime` - More extensive mocking of the chrome object\n- ⭐️ All [fpscanner](https://antoinevastel.com/bots/) tests are now green, as well as all [intoli](https://bot.sannysoft.com) tests and the [`areyouheadless`](https://arh.antoinevastel.com/bots/areyouheadless) test\n\n<details>\n <summary><code>v2.1.2</code></summary><br/>\n\n- Improved: `navigator.plugins` - we fully emulate plugins/mimetypes in headless now 🎉\n- New: `webgl.vendor` - is otherwise set to \"Google\" in headless\n- New: `window.outerdimensions` - fix missing window.outerWidth/outerHeight and viewport\n- Fixed: `navigator.webdriver` now returns undefined instead of false\n\n</details>\n\n## Test results (red is bad)\n\n#### Vanilla puppeteer <strong>without stealth 😢</strong>\n\n<table class=\"image\">\n<tr>\n\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headless-chromium-vanilla.js.png\"><img src=\"./stealthtests/_results/_thumbs/headless-chromium-vanilla.js.png\"></a><figcaption>Chromium + headless</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headful-chromium-vanilla.js.png\"><img src=\"./stealthtests/_results/_thumbs/headful-chromium-vanilla.js.png\"></a><figcaption>Chromium + headful</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headless-chrome-vanilla.js.png\"><img src=\"./stealthtests/_results/_thumbs/headless-chrome-vanilla.js.png\"></a><figcaption>Chrome + headless</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headful-chrome-vanilla.js.png\"><img src=\"./stealthtests/_results/_thumbs/headful-chrome-vanilla.js.png\"></a><figcaption>Chrome + headful</figcaption></figure></td>\n\n</tr>\n</table>\n\n#### Puppeteer <strong>with stealth plugin 💯</strong>\n\n<table class=\"image\">\n<tr>\n\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headless-chromium-stealth.js.png\"><img src=\"./stealthtests/_results/_thumbs/headless-chromium-stealth.js.png\"></a><figcaption>Chromium + headless</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headful-chromium-stealth.js.png\"><img src=\"./stealthtests/_results/_thumbs/headful-chromium-stealth.js.png\"></a><figcaption>Chromium + headful</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headless-chrome-stealth.js.png\"><img src=\"./stealthtests/_results/_thumbs/headless-chrome-stealth.js.png\"></a><figcaption>Chrome + headless</figcaption></figure></td>\n  <td><figure class=\"image\"><a href=\"./stealthtests/_results/headful-chrome-stealth.js.png\"><img src=\"./stealthtests/_results/_thumbs/headful-chrome-stealth.js.png\"></a><figcaption>Chrome + headful</figcaption></figure></td>\n\n</tr>\n</table>\n\n> Note: The `MQ_SCREEN` test is broken on their page (will fail in regular Chrome as well).\n\nTests have been done using [this test site](https://bot.sannysoft.com/) and [these scripts](./stealthtests/).\n\n#### Improved reCAPTCHA v3 scores\n\nUsing stealth also seems to help with maintaining a normal [reCAPTCHA v3 score](https://developers.google.com/recaptcha/docs/v3#score).\n\n<table class=\"image\">\n<tr>\n\n  <td><figure class=\"image\"><figcaption><code>Regular Puppeteer</code></figcaption><br/><img src=\"https://i.imgur.com/rHEH69b.png\"></figure></td>\n  <td><figure class=\"image\"><figcaption><code>Stealth Puppeteer</code></figcaption><br/><img src=\"https://i.imgur.com/2if496Z.png\"></figure></td>\n\n</tr>\n</table>\n\nNote: The [official test](https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php) is to be taken with a grain of salt, as the score is calculated individually per site and multiple other factors (past behaviour, IP address, etc). Based on anecdotal observations it still seems to work as a rough indicator.\n\n_**Tip:** Have a look at the [recaptcha plugin](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-recaptcha) if you have issues with reCAPTCHAs._\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n- [puppeteer-extra-plugin-stealth \\[ ](#puppeteer-extra-plugin-stealth---)\n  - [Install](#install)\n  - [Usage](#usage)\n  - [Status](#status)\n  - [Changelog](#changelog)\n    - [`v2.4.7`](#v247)\n    - [`v2.4.2` / `v2.4.1`](#v242--v241)\n    - [`v2.4.0`](#v240)\n  - [Test results (red is bad)](#test-results-red-is-bad)\n    - [Vanilla puppeteer without stealth 😢](#vanilla-puppeteer-without-stealth-)\n    - [Puppeteer with stealth plugin 💯](#puppeteer-with-stealth-plugin-)\n    - [Improved reCAPTCHA v3 scores](#improved-recaptcha-v3-scores)\n  - [API](#api)\n    - [Table of Contents](#table-of-contents)\n    - [class: StealthPlugin](#class-stealthplugin)\n      - [Purpose](#purpose)\n      - [Modularity](#modularity)\n      - [Contributing](#contributing)\n      - [Kudos](#kudos)\n      - [.availableEvasions](#availableevasions)\n      - [.enabledEvasions](#enabledevasions)\n    - [defaultExport(opts?)](#defaultexportopts)\n  - [License](#license)\n\n### class: [StealthPlugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/index.js#L72-L162)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)\n  - `opts.enabledEvasions` **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** Specify which evasions to use (by default all)\n\n**Extends: PuppeteerExtraPlugin**\n\nStealth mode: Applies various techniques to make detection of headless puppeteer harder. 💯\n\n#### Purpose\n\nThere are a couple of ways the use of puppeteer can easily be detected by a target website.\nThe addition of `HeadlessChrome` to the user-agent being only the most obvious one.\n\nThe goal of this plugin is to be the definite companion to puppeteer to avoid\ndetection, applying new techniques as they surface.\n\nAs this cat & mouse game is in it's infancy and fast-paced the plugin\nis kept as flexibile as possible, to support quick testing and iterations.\n\n#### Modularity\n\nThis plugin uses `puppeteer-extra`'s dependency system to only require\ncode mods for evasions that have been enabled, to keep things modular and efficient.\n\nThe `stealth` plugin is a convenience wrapper that requires multiple [evasion techniques](./evasions/)\nautomatically and comes with defaults. You could also bypass the main module and require\nspecific evasion plugins yourself, if you whish to do so (as they're standalone `puppeteer-extra` plugins):\n\n```es6\n// bypass main module and require a specific stealth plugin directly:\npuppeteer.use(\n  require('puppeteer-extra-plugin-stealth/evasions/console.debug')()\n)\n```\n\n#### Contributing\n\nPRs are welcome, if you want to add a new evasion technique I suggest you\nlook at the [template](./evasions/_template) to kickstart things.\n\n#### Kudos\n\nThanks to [Evan Sangaline](https://intoli.com/blog/not-possible-to-block-chrome-headless/) and [Paul Irish](https://github.com/paulirish/headless-cat-n-mouse) for kickstarting the discussion!\n\n---\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\n// Enable stealth plugin with all evasions\npuppeteer.use(require('puppeteer-extra-plugin-stealth')())\n;(async () => {\n  // Launch the browser in headless mode and set up a page.\n  const browser = await puppeteer.launch({\n    args: ['--no-sandbox'],\n    headless: true\n  })\n  const page = await browser.newPage()\n\n  // Navigate to the page that will perform the tests.\n  const testUrl =\n    'https://intoli.com/blog/' +\n    'not-possible-to-block-chrome-headless/chrome-headless-test.html'\n  await page.goto(testUrl)\n\n  // Save a screenshot of the results.\n  const screenshotPath = '/tmp/headless-test-result.png'\n  await page.screenshot({ path: screenshotPath })\n  console.log('have a look at the screenshot:', screenshotPath)\n\n  await browser.close()\n})()\n```\n\n---\n\n#### .[availableEvasions](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/index.js#L128-L130)\n\nType: **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>**\n\nGet all available evasions.\n\nPlease look into the [evasions directory](./evasions/) for an up to date list.\n\nExample:\n\n```javascript\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')()\nconsole.log(pluginStealth.availableEvasions) // => Set { 'user-agent', 'console.debug' }\npuppeteer.use(pluginStealth)\n```\n\n---\n\n#### .[enabledEvasions](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/index.js#L145-L147)\n\nType: **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>**\n\nGet all enabled evasions.\n\nEnabled evasions can be configured either through `opts` or by modifying this property.\n\nExample:\n\n```javascript\n// Remove specific evasion from enabled ones dynamically\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')()\npluginStealth.enabledEvasions.delete('console.debug')\npuppeteer.use(pluginStealth)\n```\n\n---\n\n### [defaultExport(opts?)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/index.js#L170-L170)\n\n- `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options\n  - `opts.enabledEvasions` **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)&lt;[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** Specify which evasions to use (by default all)\n\nDefault export, PuppeteerExtraStealthPlugin\n\n---\n\n## License\n\nCopyright © 2018 - 2023, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](mailto:github@berstend.com?subject=[GitHub]%20PuppeteerExtra). Released under the MIT License.\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/runall_stealthtests.sh",
    "content": "#! /bin/bash\n\necho \"Cleanup..\"\nrm -f ./stealthtests/_results/*.png\nrm -f ./stealthtests/_results/_thumbs/*.png\n\necho \"Running scripts..\"\nFILES=`find ./stealthtests -type f -name '*.js'`\nfor file in $FILES\ndo\n  node $file\ndone\n\necho \"Making thumbnails..\"\ncp ./stealthtests/_results/*.png ./stealthtests/_results/_thumbs\n# Note: MacOS specific image resizing command\nsips -Z 640 ./stealthtests/_results/_thumbs/*.png\n\necho \"All done.\"\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headful-chrome-stealth.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer-extra')\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')()\npuppeteer.use(pluginStealth)\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({\n    headless: false,\n    executablePath: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` // MacOS\n  })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headful-chrome-vanilla.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer')\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({\n    headless: false,\n    executablePath: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` // MacOS\n  })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headful-chromium-stealth.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer-extra')\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')\n\nasync function main() {\n  puppeteer.use(pluginStealth())\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({ headless: false })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headful-chromium-vanilla.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer')\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({ headless: false })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headless-chrome-stealth.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer-extra')\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')()\npuppeteer.use(pluginStealth)\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({\n    headless: true,\n    executablePath: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` // MacOS\n  })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headless-chrome-vanilla.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer')\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({\n    headless: true,\n    executablePath: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` // MacOS\n  })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headless-chromium-stealth.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer-extra')\nconst pluginStealth = require('puppeteer-extra-plugin-stealth')()\npuppeteer.use(pluginStealth)\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({ headless: true })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/stealthtests/headless-chromium-vanilla.js",
    "content": "const path = require('path')\nconst scriptName = path.basename(__filename)\nconst screenshotPath = path.join(__dirname, '_results', `${scriptName}.png`)\n\nconst puppeteer = require('puppeteer')\n\nasync function main() {\n  console.log('start', scriptName)\n  const browser = await puppeteer.launch({ headless: true })\n\n  const page = await browser.newPage()\n  await page.setViewport({ width: 800, height: 600 })\n  await page.goto('https://bot.sannysoft.com/')\n  await page.waitForTimeout(5000)\n  await page.screenshot({ path: screenshotPath, fullPage: true })\n\n  await browser.close()\n  console.log('end', screenshotPath)\n}\nmain()\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/cat-and-mouse.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra, compareLooseVersionStrings } = require('./util')\nconst Plugin = require('..')\n\n// Fix CI issues with old versions\nconst isOldPuppeteerVersion = () => {\n  const version = process.env.PUPPETEER_VERSION\n  const isOld = version && (version === '1.9.0' || version === '1.6.2')\n  return isOld\n}\n\n/* global HTMLIFrameElement */\n/* global Notification */\ntest('stealth: will pass Paul Irish', async t => {\n  const browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  const page = await browser.newPage()\n  await page.exposeFunction('compareLooseVersionStrings', compareLooseVersionStrings)\n  const detectionResults = await page.evaluate(detectHeadless)\n  await browser.close()\n\n  if (isOldPuppeteerVersion()) {\n    t.true(true)\n    return\n  }\n\n  const wasHeadlessDetected = Object.values(detectionResults).some(Boolean)\n  if (wasHeadlessDetected) {\n    console.log(detectionResults)\n  }\n  t.false(wasHeadlessDetected)\n})\n\nasync function detectHeadless() {\n  const results = {}\n\n  async function test(name, fn) {\n    const detectionPassed = await fn()\n    if (detectionPassed) console.log(`Chrome headless detected via ${name}`)\n    results[name] = detectionPassed\n  }\n\n  await test('userAgent', _ => {\n    return /HeadlessChrome/.test(window.navigator.userAgent)\n  })\n\n  // navigator.webdriver behavior change since release 89.0.4339.0. See also #448\n  if (await compareLooseVersionStrings(navigator.userAgent, '89.0.4339.0') >= 0) {\n    await test('navigator.webdriver is not false', _ => {\n      return navigator.webdriver !== false\n    })\n  } else {\n    // Detects the --enable-automation || --headless flags\n    // Will return true in headful if --enable-automation is provided\n    await test('navigator.webdriver present', _ => {\n      return 'webdriver' in navigator\n    })\n\n    await test('navigator.webdriver not undefined', _ => {\n      return navigator.webdriver !== undefined\n    })\n\n    /* eslint-disable no-proto */\n    await test('navigator.webdriver property overridden', _ => {\n      return (\n        Object.getOwnPropertyDescriptor(navigator.__proto__, 'webdriver') !==\n        undefined\n      )\n    })\n\n    await test('navigator.webdriver prop detected', _ => {\n      for (const prop in navigator) {\n        if (prop === 'webdriver') {\n          return true\n        }\n      }\n      return false\n    })\n  }\n\n  await test('window.chrome missing', _ => {\n    return /Chrome/.test(window.navigator.userAgent) && !window.chrome\n  })\n\n  await test('permissions API', async _ => {\n    const permissionStatus = await navigator.permissions.query({\n      name: 'notifications'\n    })\n    return (\n      Notification.permission === 'denied' &&\n      permissionStatus.state === 'prompt'\n    )\n  })\n\n  await test('permissions API overriden', _ => {\n    const permissions = window.navigator.permissions\n    if (permissions.query.toString() !== 'function query() { [native code] }')\n      return true\n    if (\n      permissions.query.toString.toString() !==\n      'function toString() { [native code] }'\n    )\n      return true\n    if (\n      permissions.query.toString.hasOwnProperty('[[Handler]]') && // eslint-disable-line\n      permissions.query.toString.hasOwnProperty('[[Target]]') && // eslint-disable-line\n      permissions.query.toString.hasOwnProperty('[[IsRevoked]]') // eslint-disable-line\n    )\n      return true\n    if (permissions.hasOwnProperty('query')) return true // eslint-disable-line\n  })\n\n  await test('navigator.plugins empty', _ => {\n    return navigator.plugins.length === 0\n  })\n\n  await test('navigator.languages blank', _ => {\n    return navigator.languages === ''\n  })\n\n  await test('iFrame for fresh window object', _ => {\n    // evaluateOnNewDocument scripts don't apply within [srcdoc] (or [sandbox]) iframes\n    // https://github.com/GoogleChrome/puppeteer/issues/1106#issuecomment-359313898\n    const iframe = document.createElement('iframe')\n    iframe.srcdoc = 'page intentionally left blank'\n    document.body.appendChild(iframe)\n\n    // Verify iframe prototype isn't touched\n    const descriptors = Object.getOwnPropertyDescriptors(\n      HTMLIFrameElement.prototype\n    )\n\n    if (\n      descriptors.contentWindow.get.toString() !==\n      'function get contentWindow() { [native code] }'\n    )\n      return true\n    // Verify iframe isn't remapped to main window\n    if (iframe.contentWindow === window) return true\n\n    // Here we would need to rerun all tests with `iframe.contentWindow` as `window`\n    // Example:\n    return iframe.contentWindow.navigator.plugins.length === 0\n  })\n\n  // This detects that a devtools protocol agent is attached.\n  // So it will also pass true in headful Chrome if the devtools window is attached\n  await test('toString', _ => {\n    let gotYou = 0\n    const spooky = /./\n    spooky.toString = function() {\n      gotYou++\n      return 'spooky'\n    }\n    console.debug(spooky)\n    return gotYou > 1\n  })\n\n  return results\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/fixtures/dummy-with-service-worker.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>title foo</title>\n    <!-- Testing evasions with a real html page makes things easier -->\n    <script>\n      if ('serviceWorker' in navigator) {\n        window.addEventListener('load', function() {\n          navigator.serviceWorker.register('/sw.js').then(function(registration) {\n            console.log('ServiceWorker registration successful with scope: ', registration.scope);\n          }, function(err) {\n            console.log('ServiceWorker registration failed: ', err);\n          });\n        });\n      }\n    </script>\n  </head>\n  <body>\n    <h1>Test page with service worker</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/fixtures/dummy.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>title foo</title>\n    <!-- Testing evasions with a real html page makes things easier -->\n  </head>\n  <body>\n    <h1>Test page</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/fixtures/sw.js",
    "content": "// Left empty\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/fpscanner.test.js",
    "content": "const test = require('ava')\n\nconst fpscanner = require('fpscanner')\n\nconst { getVanillaFingerPrint, getStealthFingerPrint, compareLooseVersionStrings } = require('./util')\nconst Plugin = require('../.')\n\n// Fix CI issues with old versions\nconst isOldPuppeteerVersion = () => {\n  const version = process.env.PUPPETEER_VERSION\n  if (!version) {\n    return false\n  }\n  if (version === '1.9.0' || version === '1.6.2') {\n    return true\n  }\n  return false\n}\n\ntest('vanilla: will fail multiple fpscanner tests', async t => {\n  const fingerPrint = await getVanillaFingerPrint()\n  const testedFingerPrints = fpscanner.analyseFingerprint(fingerPrint)\n  const failedChecks = Object.values(testedFingerPrints).filter(\n    val => val.consistent < 3\n  )\n\n  if (isOldPuppeteerVersion()) {\n    t.is(failedChecks.length, 8)\n  } else {\n    t.is(failedChecks.length, 7)\n  }\n})\n\ntest('stealth: will not fail a single fpscanner test', async t => {\n  const fingerPrint = await getStealthFingerPrint(Plugin)\n  const testedFingerPrints = fpscanner.analyseFingerprint(fingerPrint)\n  const failedChecks = Object.values(testedFingerPrints).filter(\n    val => val.consistent < 3\n  )\n\n  if (failedChecks.length) {\n    console.warn('The following fingerprints failed:', failedChecks)\n  }\n\n  if (compareLooseVersionStrings(fingerPrint.userAgent, '89.0.4339.0') >= 0) {\n    // Updated navigator.webdriver behavior breaks the fpscanner tests.\n    t.is(failedChecks.length, 1)\n    t.is(failedChecks[0].name, 'WEBDRIVER')\n  } else {\n    t.is(failedChecks.length, 0)\n  }\n})\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/service-worker.test.js",
    "content": "const test = require('ava')\n\nconst { vanillaPuppeteer, addExtra } = require('./util')\nconst Plugin = require('..')\nconst http = require('http')\nconst fs = require('fs')\nconst path = require('path')\n\n// Create a simple HTTP server. Service Workers cannot be served from file:// URIs\nconst httpServer = async () => {\n  const server = await http\n    .createServer((req, res) => {\n      let contents, type\n\n      if (req.url === '/sw.js') {\n        contents = fs.readFileSync(path.join(__dirname, './fixtures/sw.js'))\n        type = 'application/javascript'\n      } else {\n        contents = fs.readFileSync(\n          path.join(__dirname, './fixtures/dummy-with-service-worker.html')\n        )\n        type = 'text/html'\n      }\n\n      res.setHeader('Content-Type', type)\n      res.writeHead(200)\n      res.end(contents)\n    })\n    .listen(0) // random free port\n\n  return `http://127.0.0.1:${server.address().port}/`\n}\n\nlet browser, page, worker\n\ntest.before(async t => {\n  const address = await httpServer()\n  console.log(`Server is running on port ${address}`)\n\n  browser = await addExtra(vanillaPuppeteer)\n    .use(Plugin())\n    .launch({ headless: true })\n  page = await browser.newPage()\n\n  worker = new Promise(resolve => {\n    browser.on('targetcreated', async target => {\n      if (target.type() === 'service_worker') {\n        resolve(target.worker())\n      }\n    })\n  })\n\n  await page.goto(address)\n  worker = await worker\n})\n\ntest.after(async t => {\n  await browser.close()\n})\n\ntest.skip('stealth: inconsistencies between page and worker', async t => {\n  const pageFP = await page.evaluate(detectFingerprint)\n  const workerFP = await worker.evaluate(detectFingerprint)\n\n  t.deepEqual(pageFP, workerFP)\n})\n\ntest.serial.skip('stealth: creepjs has good trust score', async t => {\n  page.goto('https://abrahamjuliot.github.io/creepjs/')\n\n  const score = await (\n    await (\n      await page.waitForSelector('#fingerprint-data .unblurred')\n    ).getProperty('textContent')\n  ).jsonValue()\n\n  t.true(\n    parseInt(score) > 80,\n    `The creepjs score is: ${parseInt(score)}% but it should be at least 80%`\n  )\n})\n\n/* global OffscreenCanvas */\nfunction detectFingerprint() {\n  const results = {}\n\n  const props = [\n    'userAgent',\n    'language',\n    'hardwareConcurrency',\n    'deviceMemory',\n    'languages',\n    'platform'\n  ]\n  props.forEach(el => {\n    results[el] = navigator[el].toString()\n  })\n\n  const canvasOffscreenWebgl = new OffscreenCanvas(256, 256)\n  const contextWebgl = canvasOffscreenWebgl.getContext('webgl')\n  const rendererInfo = contextWebgl.getExtension('WEBGL_debug_renderer_info')\n  results.webglVendor = contextWebgl.getParameter(\n    rendererInfo.UNMASKED_VENDOR_WEBGL\n  )\n  results.webglRenderer = contextWebgl.getParameter(\n    rendererInfo.UNMASKED_RENDERER_WEBGL\n  )\n\n  results.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n\n  return results\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-stealth/test/util.js",
    "content": "const assert = require('assert')\nconst vanillaPuppeteer = require('puppeteer')\nconst { addExtra } = require('puppeteer-extra')\n\nconst fpCollectPath = require.resolve('fpcollect/dist/fpCollect.min.js')\n\nconst getFingerPrintFromPage = async page => {\n  return page.evaluate(() => fpCollect.generateFingerprint()) // eslint-disable-line\n}\n\nconst dummyHTMLPath = require('path').join(__dirname, './fixtures/dummy.html')\n\nconst getFingerPrint = async (puppeteer, pageFn) => {\n  const browser = await puppeteer.launch({ headless: true })\n  const page = await browser.newPage()\n  await page.goto('file://' + dummyHTMLPath)\n  await page.addScriptTag({ path: fpCollectPath })\n  const fingerPrint = await getFingerPrintFromPage(page)\n\n  let pageFnResult = null\n  if (pageFn) {\n    pageFnResult = await pageFn(page)\n  }\n\n  await browser.close()\n  return { ...fingerPrint, pageFnResult }\n}\n\nconst getVanillaFingerPrint = async pageFn =>\n  getFingerPrint(vanillaPuppeteer, pageFn)\nconst getStealthFingerPrint = async (Plugin, pageFn, pluginOptions = null) =>\n  getFingerPrint(addExtra(vanillaPuppeteer).use(Plugin(pluginOptions)), pageFn)\n\n// Expecting the input string to be in one of these formats:\n// - The UA string\n// - The shorter version string from Puppeteers browser.version()\n// - The shortest four-integer string\nconst parseLooseVersionString = looseVersionString => looseVersionString\n  .match(/(\\d+\\.){3}\\d+/)[0]\n  .split('.')\n  .map(x => parseInt(x))\n\nconst compareLooseVersionStrings = (version0, version1) => {\n  const parsed0 = parseLooseVersionString(version0)\n  const parsed1 = parseLooseVersionString(version1)\n  assert(parsed0.length == 4)\n  assert(parsed1.length == 4)\n  for (let i = 0; i < parsed0.length; i++) {\n    if (parsed0[i] < parsed1[i]) {\n      return -1\n    } else if (parsed0[i] > parsed1[i]) {\n      return 1\n    }\n  }\n  return 0\n}\n\nmodule.exports = {\n  getVanillaFingerPrint,\n  getStealthFingerPrint,\n  dummyHTMLPath,\n  vanillaPuppeteer,\n  addExtra,\n  compareLooseVersionStrings\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-data-dir/index.js",
    "content": "'use strict'\n\nconst util = require('util')\nconst fs = require('fs')\nconst fse = require('fs-extra')\nconst os = require('os')\nconst path = require('path')\nconst rimraf = require('rimraf')\nconst debug = require('debug')('puppeteer-extra-plugin:user-data-dir')\nconst mkdtempAsync = util.promisify(fs.mkdtemp)\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n *\n * Further reading:\n * https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n\n    this._userDataDir = null\n    this._isTemporary = false\n\n    const defaults = {\n      deleteTemporary: true,\n      deleteExisting: false,\n      files: []\n    }\n    // Follow Puppeteers temporary user data dir naming convention by default\n    defaults.folderPath = os.tmpdir()\n    defaults.folderPrefix = 'puppeteer_dev_profile-'\n\n    this._opts = Object.assign(defaults, opts)\n    debug('initialized', this._opts)\n  }\n\n  get name() {\n    return 'user-data-dir'\n  }\n\n  get requirements() {\n    return new Set(['runLast', 'dataFromPlugins'])\n  }\n\n  get shouldDeleteDirectory() {\n    if (this._isTemporary && this._opts.deleteTemporary) {\n      return true\n    }\n    return this._opts.deleteExisting\n  }\n\n  get temporaryDirectoryPath() {\n    return path.join(this._opts.folderPath, this._opts.folderPrefix)\n  }\n\n  get defaultProfilePath() {\n    return path.join(this._userDataDir, 'Default')\n  }\n\n  async makeTemporaryDirectory() {\n    this._userDataDir = await mkdtempAsync(this.temporaryDirectoryPath)\n    this._isTemporary = true\n  }\n\n  deleteUserDataDir() {\n    debug('removeUserDataDir', this._userDataDir)\n\n    if (!this._userDataDir) {\n      debug('No userDataDir, not running rimraf')\n      return\n    }\n\n    // We're using rimraf here because it throw errors and don't seem to freeze the process\n    // If ressources busy or locked by chrome try again 4 times, then give up. overall a timout of 400ms\n    rimraf(\n      this._userDataDir,\n      {\n        maxBusyTries: 4\n      },\n      err => {\n        debug(err)\n      }\n    )\n  }\n\n  async writeFilesToProfile() {\n    const filesFromPlugins = this.getDataFromPlugins('userDataDirFile').map(\n      d => d.value\n    )\n    const files = [].concat(filesFromPlugins, this._opts.files)\n    if (!files.length) {\n      return\n    }\n    for (const file of files) {\n      if (file.target !== 'Profile') {\n        console.warn(`Warning: Ignoring file with invalid target`, file)\n        continue\n      }\n      const filePath = path.join(this.defaultProfilePath, file.file)\n      try {\n        await fse.outputFile(filePath, file.contents)\n        debug(`Wrote file`, filePath)\n      } catch (err) {\n        console.warn('Warning: Failure writing file', filePath, file, err)\n      }\n    }\n  }\n\n  async beforeLaunch(options) {\n    this._userDataDir = options.userDataDir\n    if (!this._userDataDir) {\n      await this.makeTemporaryDirectory()\n      options.userDataDir = this._userDataDir\n      debug('created custom dir', options.userDataDir)\n    }\n    await this.writeFilesToProfile()\n  }\n\n  async onDisconnected() {\n    debug('onDisconnected')\n    if (this.shouldDeleteDirectory) {\n      this.deleteUserDataDir()\n    }\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-data-dir/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-user-data-dir\",\n  \"version\": \"2.4.1\",\n  \"description\": \"Custom user data directory for puppeteer.\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"user-data\",\n    \"userDataDir\",\n    \"profile\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"fs-extra\": \"^10.0.0\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"rimraf\": \"^3.0.2\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-data-dir/readme.md",
    "content": "# puppeteer-extra-plugin-user-data-dir\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-user-data-dir\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-user-data-dir/index.js#L19-L113)\n\n**Extends: PuppeteerExtraPlugin**\n\nFurther reading:\n<https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md>\n\nType: `function (opts)`\n\n-   `opts`   (optional, default `{}`)\n\n* * *\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-preferences/index.js",
    "content": "'use strict'\n\nconst merge = require('deepmerge')\n\nconst { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')\n\n/**\n * Launch puppeteer with arbitrary user preferences.\n *\n * The user defined preferences will be merged with preferences set by other plugins.\n * Plugins can add user preferences by exposing a data entry with the name `userPreferences`.\n *\n * Overview:\n * https://chromium.googlesource.com/chromium/src/+/master/chrome/common/pref_names.cc\n *\n * @param {Object} opts - Options\n * @param {Object} [opts.userPrefs={}] - An object containing the preferences.\n *\n * @example\n * const puppeteer = require('puppeteer-extra')\n * puppeteer.use(require('puppeteer-extra-plugin-user-preferences')({userPrefs: {\n *   webkit: {\n *     webprefs: {\n *       default_font_size: 22\n *     }\n *   }\n * }}))\n * const browser = await puppeteer.launch()\n */\nclass Plugin extends PuppeteerExtraPlugin {\n  constructor(opts = {}) {\n    super(opts)\n    this._userPrefsFromPlugins = {}\n\n    const defaults = {\n      userPrefs: {}\n    }\n\n    this._opts = Object.assign(defaults, opts)\n  }\n\n  get name() {\n    return 'user-preferences'\n  }\n\n  get requirements() {\n    return new Set(['runLast', 'dataFromPlugins'])\n  }\n\n  get dependencies() {\n    return new Set(['user-data-dir'])\n  }\n\n  get data() {\n    return [\n      {\n        name: 'userDataDirFile',\n        value: {\n          target: 'Profile',\n          file: 'Preferences',\n          contents: JSON.stringify(this.combinedPrefs, null, 2)\n        }\n      }\n    ]\n  }\n\n  get combinedPrefs() {\n    return merge(this._opts.userPrefs, this._userPrefsFromPlugins)\n  }\n\n  async beforeLaunch(options) {\n    this._userPrefsFromPlugins = merge.all(\n      this.getDataFromPlugins('userPreferences').map(d => d.value)\n    )\n    this.debug('_userPrefsFromPlugins', this._userPrefsFromPlugins)\n  }\n}\n\nmodule.exports = function(pluginConfig) {\n  return new Plugin(pluginConfig)\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-preferences/package.json",
    "content": "{\n  \"name\": \"puppeteer-extra-plugin-user-preferences\",\n  \"version\": \"2.4.1\",\n  \"description\": \"Launch puppeteer with arbitrary user preferences.\",\n  \"main\": \"index.js\",\n  \"repository\": \"berstend/puppeteer-extra\",\n  \"author\": \"berstend\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"docs\": \"node -e 0\",\n    \"lint\": \"eslint --ext .js .\",\n    \"test\": \"run-p lint\",\n    \"test-ci\": \"run-s test\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"keywords\": [\n    \"puppeteer\",\n    \"puppeteer-extra\",\n    \"puppeteer-extra-plugin\",\n    \"user-prefs\",\n    \"user-preferences\",\n    \"chrome\",\n    \"headless\",\n    \"pupeteer\"\n  ],\n  \"devDependencies\": {\n    \"ava\": \"2.4.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"puppeteer\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"deepmerge\": \"^4.2.2\",\n    \"puppeteer-extra-plugin\": \"^3.2.3\",\n    \"puppeteer-extra-plugin-user-data-dir\": \"^2.4.1\"\n  },\n  \"peerDependencies\": {\n    \"playwright-extra\": \"*\",\n    \"puppeteer-extra\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"puppeteer-extra\": {\n      \"optional\": true\n    },\n    \"playwright-extra\": {\n      \"optional\": true\n    }\n  },\n  \"gitHead\": \"babb041828cab50c525e0b9aab02d58f73416ef3\"\n}\n"
  },
  {
    "path": "packages/puppeteer-extra-plugin-user-preferences/readme.md",
    "content": "# puppeteer-extra-plugin-user-preferences\n\n> A plugin for [puppeteer-extra](https://github.com/berstend/puppeteer-extra).\n\n### Install\n\n```bash\nyarn add puppeteer-extra-plugin-user-preferences\n```\n\n## API\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n#### Table of Contents\n\n-   [Plugin](#plugin)\n\n### [Plugin](https://github.com/berstend/puppeteer-extra/blob/db57ea66cf10d407cf63af387892492e495a84f2/packages/puppeteer-extra-plugin-user-preferences/index.js#L30-L73)\n\n**Extends: PuppeteerExtraPlugin**\n\nLaunch puppeteer with arbitrary user preferences.\n\nThe user defined preferences will be merged with preferences set by other plugins.\nPlugins can add user preferences by exposing a data entry with the name `userPreferences`.\n\nOverview:\n<https://chromium.googlesource.com/chromium/src/+/master/chrome/common/pref_names.cc>\n\nType: `function (opts)`\n\n-   `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options (optional, default `{}`)\n    -   `opts.userPrefs` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object containing the preferences. (optional, default `{}`)\n\nExample:\n\n```javascript\nconst puppeteer = require('puppeteer-extra')\npuppeteer.use(require('puppeteer-extra-plugin-user-preferences')({userPrefs: {\n  webkit: {\n    webprefs: {\n      default_font_size: 22\n    }\n  }\n}}))\nconst browser = await puppeteer.launch()\n```\n\n* * *\n"
  }
]