UNPKG

@extra/recaptcha

Version:

A plugin for playwright & puppeteer to solve reCAPTCHAs and hCaptchas automatically.

942 lines (927 loc) 35.9 kB
/*! * @extra/recaptcha v4.2.2 by berstend * https://github.com/berstend/puppeteer-extra/tree/master/packages/plugin-recaptcha * @license MIT */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var automationExtraPlugin = require('automation-extra-plugin'); var Debug = _interopDefault(require('debug')); const ContentScriptDefaultOpts = { visualFeedback: true, }; const ContentScriptDefaultData = { solutions: [], }; /** * Content script for Hcaptcha handling (runs in browser context) * @note External modules are not supported here (due to content script isolation) */ class HcaptchaContentScript { constructor(opts = ContentScriptDefaultOpts, data = ContentScriptDefaultData) { this.baseUrl = 'https://assets.hcaptcha.com/captcha/v1/'; this.opts = opts; this.data = data; } async _waitUntilDocumentReady() { return new Promise(function (resolve) { if (!document || !window) return resolve(null); const loadedAlready = /^loaded|^i|^c/.test(document.readyState); if (loadedAlready) return resolve(null); function onReady() { resolve(null); document.removeEventListener('DOMContentLoaded', onReady); window.removeEventListener('load', onReady); } document.addEventListener('DOMContentLoaded', onReady); window.addEventListener('load', onReady); }); } _paintCaptchaBusy($iframe) { try { if (this.opts.visualFeedback) { $iframe.style.filter = `opacity(60%) hue-rotate(400deg)`; // violet } } catch (error) { // noop } return $iframe; } /** Regular checkboxes */ _findRegularCheckboxes() { const nodeList = document.querySelectorAll(`iframe[src^='${this.baseUrl}'][data-hcaptcha-widget-id]:not([src*='invisible'])`); return Array.from(nodeList); } /** Find active challenges from invisible hcaptchas */ _findActiveChallenges() { const nodeList = document.querySelectorAll(`div[style*='visible'] iframe[src^='${this.baseUrl}'][src*='hcaptcha-challenge.html'][src*='invisible']`); return Array.from(nodeList); } _extractInfoFromIframes(iframes) { return iframes .map((el) => el.src.replace('.html#', '.html?')) .map((url) => { const { searchParams } = new URL(url); const result = { _vendor: 'hcaptcha', url: document.location.href, id: searchParams.get('id'), sitekey: searchParams.get('sitekey'), display: { size: searchParams.get('size') || 'normal', }, }; return result; }); } async findRecaptchas() { const result = { captchas: [], error: null, }; try { await this._waitUntilDocumentReady(); const iframes = [ ...this._findRegularCheckboxes(), ...this._findActiveChallenges(), ]; if (!iframes.length) { return result; } result.captchas = this._extractInfoFromIframes(iframes); iframes.forEach((el) => { this._paintCaptchaBusy(el); }); } catch (error) { result.error = error; return result; } return result; } async enterRecaptchaSolutions() { const result = { solved: [], error: null, }; try { await this._waitUntilDocumentReady(); const solutions = this.data.solutions; if (!solutions || !solutions.length) { result.error = 'No solutions provided'; return result; } result.solved = solutions .filter((solution) => solution._vendor === 'hcaptcha') .filter((solution) => solution.hasSolution === true) .map((solution) => { window.postMessage(JSON.stringify({ id: solution.id, label: 'challenge-closed', source: 'hcaptcha', contents: { event: 'challenge-passed', expiration: 120, response: solution.text, }, }), '*'); return { _vendor: solution._vendor, id: solution.id, isSolved: true, solvedAt: new Date(), }; }); } catch (error) { result.error = error; return result; } return result; } } const ContentScriptDefaultOpts$1 = { visualFeedback: true, }; const ContentScriptDefaultData$1 = { solutions: [], }; /** * Content script for Recaptcha handling (runs in browser context) * @note External modules are not supported here (due to content script isolation) */ class RecaptchaContentScript { constructor(opts = ContentScriptDefaultOpts$1, data = ContentScriptDefaultData$1) { // Poor mans _.pluck this._pick = (props) => (o) => props.reduce((a, e) => (Object.assign(Object.assign({}, a), { [e]: o[e] })), {}); // make sure the element is visible - this is equivalent to jquery's is(':visible') this._isVisible = (elem) => !!(elem.offsetWidth || elem.offsetHeight || (typeof elem.getClientRects === 'function' && elem.getClientRects().length)); this.opts = opts; this.data = data; } // Recaptcha client is a nested, circular object with object keys that seem generated // We flatten that object a couple of levels deep for easy access to certain keys we're interested in. _flattenObject(item, levels = 2, ignoreHTML = true) { const isObject = (x) => x && typeof x === 'object'; const isHTML = (x) => x && x instanceof HTMLElement; let newObj = {}; for (let i = 0; i < levels; i++) { item = Object.keys(newObj).length ? newObj : item; Object.keys(item).forEach((key) => { if (ignoreHTML && isHTML(item[key])) return; if (isObject(item[key])) { Object.keys(item[key]).forEach((innerKey) => { if (ignoreHTML && isHTML(item[key][innerKey])) return; const keyName = isObject(item[key][innerKey]) ? `obj_${key}_${innerKey}` : `${innerKey}`; newObj[keyName] = item[key][innerKey]; }); } else { newObj[key] = item[key]; } }); } return newObj; } // Helper function to return an object based on a well known value _getKeyByValue(object, value) { return Object.keys(object).find((key) => object[key] === value); } async _waitUntilDocumentReady() { return new Promise(function (resolve) { if (!document || !window) return resolve(null); const loadedAlready = /^loaded|^i|^c/.test(document.readyState); if (loadedAlready) return resolve(null); function onReady() { resolve(null); document.removeEventListener('DOMContentLoaded', onReady); window.removeEventListener('load', onReady); } document.addEventListener('DOMContentLoaded', onReady); window.addEventListener('load', onReady); }); } _paintCaptchaBusy($iframe) { try { if (this.opts.visualFeedback) { $iframe.style.filter = `opacity(60%) hue-rotate(400deg)`; // violet } } catch (error) { // noop } return $iframe; } _paintCaptchaSolved($iframe) { try { if (this.opts.visualFeedback) { $iframe.style.filter = `opacity(60%) hue-rotate(230deg)`; // green } } catch (error) { // noop } return $iframe; } _findVisibleIframeNodes() { return Array.from(document.querySelectorAll(`iframe[src^='https://www.google.com/recaptcha/api2/anchor'][name^="a-"]` + ', ' + `iframe[src^='https://www.google.com/recaptcha/enterprise/anchor'][name^="a-"]`)); } _findVisibleIframeNodeById(id) { return document.querySelector(`iframe[src^='https://www.google.com/recaptcha/api2/anchor'][name^="a-${id || ''}"]` + ', ' + `iframe[src^='https://www.google.com/recaptcha/enterprise/anchor'][name^="a-${id || ''}"]`); } _hideChallengeWindowIfPresent(id) { let frame = document.querySelector(`iframe[src^='https://www.google.com/recaptcha/api2/bframe'][name^="c-${id || ''}"]` + ', ' + `iframe[src^='https://www.google.com/recaptcha/enterprise/bframe'][name^="c-${id || ''}"]`); if (!frame) { return; } while (frame && frame.parentElement && frame.parentElement !== document.body) { frame = frame.parentElement; } if (frame) { frame.style.visibility = 'hidden'; } } getClients() { // Bail out early if there's no indication of recaptchas if (!window || !window.__google_recaptcha_client) return; if (!window.___grecaptcha_cfg || !window.___grecaptcha_cfg.clients) { return; } if (!Object.keys(window.___grecaptcha_cfg.clients).length) return; return window.___grecaptcha_cfg.clients; } getVisibleIframesIds() { // Find all regular visible recaptcha boxes through their iframes return this._findVisibleIframeNodes() .filter(($f) => this._isVisible($f)) .map(($f) => this._paintCaptchaBusy($f)) .filter(($f) => $f && $f.getAttribute('name')) .map(($f) => $f.getAttribute('name') || '') // a-841543e13666 .map((rawId) => rawId.split('-').slice(-1)[0] // a-841543e13666 => 841543e13666 ) .filter((id) => id); } getInvisibleIframesIds() { // Find all invisible recaptcha boxes through their iframes (only the ones with an active challenge window) return this._findVisibleIframeNodes() .filter(($f) => $f && $f.getAttribute('name')) .map(($f) => $f.getAttribute('name') || '') // a-841543e13666 .map((rawId) => rawId.split('-').slice(-1)[0] // a-841543e13666 => 841543e13666 ) .filter((id) => id) .filter((id) => document.querySelectorAll(`iframe[src^='https://www.google.com/recaptcha/api2/bframe'][name^="c-${id || ''}"]` + ', ' + `iframe[src^='https://www.google.com/recaptcha/enterprise/bframe'][name^="c-${id || ''}"]`).length); } getIframesIds() { // Find all recaptcha boxes through their iframes, check for invisible ones as fallback const results = [ ...this.getVisibleIframesIds(), ...this.getInvisibleIframesIds(), ]; // Deduplicate results by using the unique id as key return [...new Map(results.map((x) => [x.id, x])).values()]; } getResponseInputById(id) { if (!id) return; const $iframe = this._findVisibleIframeNodeById(id); if (!$iframe) return; const $parentForm = $iframe.closest(`form`); if ($parentForm) { return $parentForm.querySelector(`[name='g-recaptcha-response']`); } // Not all reCAPTCHAs are in forms // https://github.com/berstend/puppeteer-extra/issues/57 if (document && document.body) { return document.body.querySelector(`[name='g-recaptcha-response']`); } } getClientById(id) { if (!id) return; const clients = this.getClients(); // Lookup captcha "client" info using extracted id let client = Object.values(clients || {}) .filter((obj) => this._getKeyByValue(obj, id)) .shift(); // returns first entry in array or undefined if (!client) return; client = this._flattenObject(client); client.widgetId = client.id; client.id = id; return client; } extractInfoFromClient(client) { if (!client) return; const info = this._pick(['sitekey', 'callback'])(client); if (!info.sitekey) return; info._vendor = 'recaptcha'; info.id = client.id; info.s = client.s; // google site specific info.widgetId = client.widgetId; info.display = this._pick([ 'size', 'top', 'left', 'width', 'height', 'theme', ])(client); // callbacks can be strings or funtion refs if (info.callback && typeof info.callback === 'function') { info.callback = info.callback.name || 'anonymous'; } if (document && document.location) info.url = document.location.href; return info; } async findRecaptchas() { const result = { captchas: [], error: null, }; try { await this._waitUntilDocumentReady(); const clients = this.getClients(); if (!clients) return result; result.captchas = this.getIframesIds() .map((id) => this.getClientById(id)) .map((client) => this.extractInfoFromClient(client)) .map((info) => { if (!info) return; const $input = this.getResponseInputById(info.id); info.hasResponseElement = !!$input; return info; }) .filter((info) => info); } catch (error) { result.error = error; return result; } return result; } async enterRecaptchaSolutions() { const result = { solved: [], error: null, }; try { await this._waitUntilDocumentReady(); const clients = this.getClients(); if (!clients) { result.error = 'No recaptchas found'; return result; } const solutions = this.data.solutions; if (!solutions || !solutions.length) { result.error = 'No solutions provided'; return result; } result.solved = this.getIframesIds() .map((id) => this.getClientById(id)) .map((client) => { const solved = { _vendor: 'recaptcha', id: client.id, responseElement: false, responseCallback: false, }; const $iframe = this._findVisibleIframeNodeById(solved.id); if (!$iframe) { solved.error = `Iframe not found for id '${solved.id}'`; return solved; } const solution = solutions.find((s) => s.id === solved.id); if (!solution || !solution.text) { solved.error = `Solution not found for id '${solved.id}'`; return solved; } // Hide if present challenge window this._hideChallengeWindowIfPresent(solved.id); // Enter solution in response textarea const $input = this.getResponseInputById(solved.id); if ($input) { $input.innerHTML = solution.text; solved.responseElement = true; } // Enter solution in optional callback if (client.callback) { try { if (typeof client.callback === 'function') { client.callback.call(window, solution.text); } else { eval(client.callback).call(window, solution.text); // tslint:disable-line } solved.responseCallback = true; } catch (error) { solved.error = error; } } // Finishing up solved.isSolved = solved.responseCallback || solved.responseElement; solved.solvedAt = new Date(); this._paintCaptchaSolved($iframe); return solved; }); } catch (error) { result.error = error; return result; } return result; } } /* // Example data { "captchas": [{ "sitekey": "6LdAUwoUAAAAAH44X453L0tUWOvx11XXXXXXXX", "id": "lnfy52r0cccc", "widgetId": 0, "display": { "size": null, "top": 23, "left": 13, "width": 28, "height": 28, "theme": null }, "url": "https://example.com", "hasResponseElement": true }], "error": null } { "solutions": [{ "id": "lnfy52r0cccc", "provider": "2captcha", "providerCaptchaId": "61109548000", "text": "03AF6jDqVSOVODT-wLKZ47U0UXz...", "requestAt": "2019-02-09T18:30:43.587Z", "responseAt": "2019-02-09T18:30:57.937Z" }] "error": null } { "solved": [{ "id": "lnfy52r0cccc", "responseElement": true, "responseCallback": false, "isSolved": true, "solvedAt": {} }] "error": null } */ /* tslint:disable */ // @ts-ignore-start // https://github.com/bochkarev-artem/2captcha/blob/master/index.js // TODO: Create our own API wrapper var http = require('http'); var https = require('https'); var url = require('url'); var querystring = require('querystring'); var apiKey; var apiInUrl = 'http://2captcha.com/in.php'; var apiResUrl = 'http://2captcha.com/res.php'; var SOFT_ID = '2589'; var defaultOptions = { pollingInterval: 2000, retries: 3, }; function pollCaptcha(captchaId, options, invalid, callback) { invalid = invalid.bind({ options: options, captchaId: captchaId }); var intervalId = setInterval(function () { var httpRequestOptions = url.parse(apiResUrl + '?action=get&soft_id=' + SOFT_ID + '&key=' + apiKey + '&id=' + captchaId); var request = http.request(httpRequestOptions, function (response) { var body = ''; response.on('data', function (chunk) { body += chunk; }); response.on('end', function () { if (body === 'CAPCHA_NOT_READY') { return; } clearInterval(intervalId); var result = body.split('|'); if (result[0] !== 'OK') { callback(result[0]); //error } else { callback(null, { id: captchaId, text: result[1], }, invalid); } callback = function () { }; // prevent the callback from being called more than once, if multiple http requests are open at the same time. }); }); request.end(); }, options.pollingInterval || defaultOptions.pollingInterval); } const setApiKey = function (key) { apiKey = key; }; const decodeReCaptcha = function (captchaMethod, captcha, pageUrl, extraData, options, callback) { if (!callback) { callback = options; options = defaultOptions; } var httpRequestOptions = url.parse(apiInUrl); httpRequestOptions.method = 'POST'; var postData = Object.assign({ method: captchaMethod, key: apiKey, soft_id: SOFT_ID, // googlekey: captcha, pageurl: pageUrl }, extraData); if (captchaMethod === 'userrecaptcha') { postData.googlekey = captcha; } if (captchaMethod === 'hcaptcha') { postData.sitekey = captcha; } postData = querystring.stringify(postData); var request = http.request(httpRequestOptions, function (response) { var body = ''; response.on('data', function (chunk) { body += chunk; }); response.on('end', function () { var result = body.split('|'); if (result[0] !== 'OK') { return callback(result[0]); } pollCaptcha(result[1], options, function (error) { var callbackToInitialCallback = callback; report(this.captchaId); if (error) { return callbackToInitialCallback('CAPTCHA_FAILED'); } if (!this.options.retries) { this.options.retries = defaultOptions.retries; } if (this.options.retries > 1) { this.options.retries = this.options.retries - 1; decodeReCaptcha(captchaMethod, captcha, pageUrl, extraData, this.options, callback); } else { callbackToInitialCallback('CAPTCHA_FAILED_TOO_MANY_TIMES'); } }, callback); }); }); request.on('error', function (e) { request.destroy(); callback(e); }); request.write(postData); request.end(); }; const report = function (captchaId) { var reportUrl = apiResUrl + '?action=reportbad&soft_id=' + SOFT_ID + '&key=' + apiKey + '&id=' + captchaId; var options = url.parse(reportUrl); var request = http.request(options, function (response) { // var body = '' // response.on('data', function(chunk) { // body += chunk // }) // response.on('end', function() {}) }); request.end(); }; // @ts-ignore-end const PROVIDER_ID = '2captcha'; const debug = Debug(`puppeteer-extra-plugin:recaptcha:${PROVIDER_ID}`); const secondsBetweenDates = (before, after) => (after.getTime() - before.getTime()) / 1000; async function decodeRecaptchaAsync(token, vendor, sitekey, url, extraData, opts = { pollingInterval: 2000 }) { return new Promise((resolve) => { const cb = (err, result, invalid) => resolve({ err, result, invalid }); try { setApiKey(token); let method = 'userrecaptcha'; if (vendor === 'hcaptcha') { method = 'hcaptcha'; } decodeReCaptcha(method, sitekey, url, extraData, opts, cb); } catch (error) { return resolve({ err: error }); } }); } async function getSolutions(captchas = [], token) { const solutions = await Promise.all(captchas.map((c) => getSolution(c, token || ''))); return { solutions, error: solutions.find((s) => !!s.error) }; } async function getSolution(captcha, token) { const solution = { _vendor: captcha._vendor, provider: PROVIDER_ID, }; try { if (!captcha || !captcha.sitekey || !captcha.url || !captcha.id) { throw new Error('Missing data in captcha'); } solution.id = captcha.id; solution.requestAt = new Date(); debug('Requesting solution..', solution); const extraData = {}; if (captcha.s) { extraData['data-s'] = captcha.s; // google site specific property } const { err, result, invalid } = await decodeRecaptchaAsync(token, captcha._vendor, captcha.sitekey, captcha.url, extraData); debug('Got response', { err, result, invalid }); if (err) throw new Error(`${PROVIDER_ID} error: ${err}`); if (!result || !result.text || !result.id) { throw new Error(`${PROVIDER_ID} error: Missing response data: ${result}`); } solution.providerCaptchaId = result.id; solution.text = result.text; solution.responseAt = new Date(); solution.hasSolution = !!solution.text; solution.duration = secondsBetweenDates(solution.requestAt, solution.responseAt); } catch (error) { debug('Error', error); solution.error = error.toString(); } return solution; } const BuiltinSolutionProviders = [ { id: PROVIDER_ID, fn: getSolutions, }, ]; /** * A plugin to automatically detect and solve reCAPTCHAs. * @noInheritDoc */ class RecaptchaPlugin extends automationExtraPlugin.AutomationExtraPlugin { constructor(opts) { super(opts); this.debug('Initialized', this.opts); } get defaults() { return { visualFeedback: true, throwOnError: false, }; } get contentScriptOpts() { const { visualFeedback } = this.opts; return { visualFeedback, }; } _generateContentScript(vendor, fn, data) { this.debug('_generateContentScript', vendor, fn, data); let scriptSource = RecaptchaContentScript.toString(); let scriptName = 'RecaptchaContentScript'; if (vendor === 'hcaptcha') { scriptSource = HcaptchaContentScript.toString(); scriptName = 'HcaptchaContentScript'; } return `(async() => { const DATA = ${JSON.stringify(data || null)} const OPTS = ${JSON.stringify(this.contentScriptOpts)} ${scriptSource} const script = new ${scriptName}(OPTS, DATA) return script.${fn}() })()`; } async findRecaptchas(page) { this.debug('findRecaptchas'); // As this might be called very early while recaptcha is still loading // we add some extra waiting logic for developer convenience. const hasRecaptchaScriptTag = await page.$(`script[src*="/recaptcha/api.js"]`); this.debug('hasRecaptchaScriptTag', !!hasRecaptchaScriptTag); if (hasRecaptchaScriptTag) { this.debug('waitForRecaptchaClient - start', new Date()); // Testing for .clients is more robust then .count (some sites implement recaptcha in a broken way) await page.waitForFunction(` (function() { return Object.keys((window.___grecaptcha_cfg || {}).clients || {}).length })() `, { polling: 200, timeout: 10 * 1000 }); this.debug('waitForRecaptchaClient - end', new Date()); // used as timer } const hasHcaptchaScriptTag = await page.$(`script[src*="//hcaptcha.com/1/api.js"]`); this.debug('hasHcaptchaScriptTag', !!hasHcaptchaScriptTag); if (hasHcaptchaScriptTag) { this.debug('wait:hasHcaptchaScriptTag - start', new Date()); await page.waitForFunction(` (function() { return window.hcaptcha })() `, { polling: 200, timeout: 10 * 1000 }); this.debug('wait:hasHcaptchaScriptTag - end', new Date()); // used as timer } // Even without a recaptcha script tag we're trying, just in case. const resultRecaptcha = (await page.evaluate(this._generateContentScript('recaptcha', 'findRecaptchas'))); const resultHcaptcha = (await page.evaluate(this._generateContentScript('hcaptcha', 'findRecaptchas'))); const response = { captchas: [...resultRecaptcha.captchas, ...resultHcaptcha.captchas], error: resultRecaptcha.error || resultHcaptcha.error, }; this.debug('findRecaptchas', response); if (this.opts.throwOnError && response.error) { throw new Error(response.error); } return response; } async getRecaptchaSolutions(captchas, provider) { this.debug('getRecaptchaSolutions'); provider = provider || this.opts.provider; if (!provider || (!provider.token && !provider.fn) || (provider.token && provider.token === 'XXXXXXX' && !provider.fn)) { throw new Error('Please provide a solution provider to the plugin.'); } let fn = provider.fn; if (!fn) { const builtinProvider = BuiltinSolutionProviders.find((p) => p.id === (provider || {}).id); if (!builtinProvider || !builtinProvider.fn) { throw new Error(`Cannot find builtin provider with id '${provider.id}'.`); } fn = builtinProvider.fn; } const response = await fn.call(this, captchas, provider.token); response.error = response.error || response.solutions.find((s) => !!s.error); this.debug('getRecaptchaSolutions', response); if (response && response.error) { console.warn('PuppeteerExtraPluginRecaptcha: An error occured during "getRecaptchaSolutions":', response.error); } if (this.opts.throwOnError && response.error) { throw new Error(response.error); } return response; } async enterRecaptchaSolutions(page, solutions) { this.debug('enterRecaptchaSolutions'); const hasRecaptcha = !!solutions.find((s) => s._vendor === 'recaptcha'); const solvedRecaptcha = hasRecaptcha ? (await page.evaluate(this._generateContentScript('recaptcha', 'enterRecaptchaSolutions', { solutions, }))) : { solved: [] }; const hasHcaptcha = !!solutions.find((s) => s._vendor === 'hcaptcha'); const solvedHcaptcha = hasHcaptcha ? (await page.evaluate(this._generateContentScript('hcaptcha', 'enterRecaptchaSolutions', { solutions, }))) : { solved: [] }; const response = { solved: [...solvedRecaptcha.solved, ...solvedHcaptcha.solved], error: solvedRecaptcha.error || solvedHcaptcha.error, }; response.error = response.error || response.solved.find((s) => !!s.error); this.debug('enterRecaptchaSolutions', response); if (this.opts.throwOnError && response.error) { throw new Error(response.error); } return response; } async solveRecaptchas(page) { this.debug('solveRecaptchas'); const response = { captchas: [], solutions: [], solved: [], error: null, }; try { // If `this.opts.throwOnError` is set any of the // following will throw and abort execution. const { captchas, error: captchasError } = await this.findRecaptchas(page); response.captchas = captchas; if (captchas.length) { const { solutions, error: solutionsError, } = await this.getRecaptchaSolutions(response.captchas); response.solutions = solutions; const { solved, error: solvedError, } = await this.enterRecaptchaSolutions(page, response.solutions); response.solved = solved; response.error = captchasError || solutionsError || solvedError; } } catch (error) { response.error = error.toString(); } this.debug('solveRecaptchas', response); const fatalError = response.error && response.error.includes('Please provide a solution provider to the plugin'); if ((this.opts.throwOnError || fatalError) && response.error) { throw new Error(response.error); } return response; } _addCustomMethods(prop) { this.debug('_addCustomMethods', prop.url()); prop.findRecaptchas = async () => this.findRecaptchas(prop); prop.getRecaptchaSolutions = async (captchas, provider) => this.getRecaptchaSolutions(captchas, provider); prop.enterRecaptchaSolutions = async (solutions) => this.enterRecaptchaSolutions(prop, solutions); // Add convenience methods that wraps all others prop.solveRecaptchas = async () => this.solveRecaptchas(prop); } async beforeContext(options, browser) { if (!this.env.isPuppeteerBrowser(browser)) { return; } options.bypassCSP = true; } async onPageCreated(page) { this.debug('onPageCreated', page.url()); if (this.env.isPuppeteerPage(page)) { // Make sure we can run our content script await page.setBypassCSP(true); } // Add custom page methods this._addCustomMethods(page); // Add custom methods to potential frames as well page.on('frameattached', (frame) => { if (!frame) return; this._addCustomMethods(frame); }); } _addCustomMethodsToPages(pages) { this.debug('_addCustomMethodsToPages', pages.length); for (const page of pages) { this._addCustomMethods(page); for (const frame of page.mainFrame().childFrames()) { this._addCustomMethods(frame); } } } /** Add additions to already existing pages and frames */ async onBrowser(browser) { if (this.env.isPuppeteerBrowser(browser)) { const pages = await browser.pages(); this._addCustomMethodsToPages(pages); return; } if (this.env.isPlaywrightBrowser(browser)) { const pages = []; for (const context of browser.contexts()) { context.pages().forEach((p) => pages.push(p)); } this._addCustomMethodsToPages(pages); return; } } } RecaptchaPlugin.id = 'recaptcha'; /** Default export, RecaptchaPlugin */ const defaultExport = (options) => { return new RecaptchaPlugin(options || {}); }; exports.BuiltinSolutionProviders = BuiltinSolutionProviders; exports.RecaptchaPlugin = RecaptchaPlugin; exports.default = defaultExport; module.exports = exports.default || {} Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value }) //# sourceMappingURL=index.cjs.js.map