Rename to hkt.sh
This commit is contained in:
254
node_modules/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js
generated
vendored
Normal file
254
node_modules/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
'use strict'
|
||||
|
||||
const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')
|
||||
|
||||
const withUtils = require('../_utils/withUtils')
|
||||
|
||||
const STATIC_DATA = require('./staticData.json')
|
||||
|
||||
/**
|
||||
* Mock the `chrome.runtime` object if not available (e.g. when running headless) and on a secure site.
|
||||
*/
|
||||
class Plugin extends PuppeteerExtraPlugin {
|
||||
constructor(opts = {}) {
|
||||
super(opts)
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'stealth/evasions/chrome.runtime'
|
||||
}
|
||||
|
||||
get defaults() {
|
||||
return { runOnInsecureOrigins: false } // Override for testing
|
||||
}
|
||||
|
||||
async onPageCreated(page) {
|
||||
await withUtils(page).evaluateOnNewDocument(
|
||||
(utils, { opts, STATIC_DATA }) => {
|
||||
if (!window.chrome) {
|
||||
// Use the exact property descriptor found in headful Chrome
|
||||
// fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`
|
||||
Object.defineProperty(window, 'chrome', {
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: false, // note!
|
||||
value: {} // We'll extend that later
|
||||
})
|
||||
}
|
||||
|
||||
// That means we're running headful and don't need to mock anything
|
||||
const existsAlready = 'runtime' in window.chrome
|
||||
// `chrome.runtime` is only exposed on secure origins
|
||||
const isNotSecure = !window.location.protocol.startsWith('https')
|
||||
if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {
|
||||
return // Nothing to do here
|
||||
}
|
||||
|
||||
window.chrome.runtime = {
|
||||
// There's a bunch of static data in that property which doesn't seem to change,
|
||||
// we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)`
|
||||
...STATIC_DATA,
|
||||
// `chrome.runtime.id` is extension related and returns undefined in Chrome
|
||||
get id() {
|
||||
return undefined
|
||||
},
|
||||
// These two require more sophisticated mocks
|
||||
connect: null,
|
||||
sendMessage: null
|
||||
}
|
||||
|
||||
const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({
|
||||
NoMatchingSignature: new TypeError(
|
||||
preamble + `No matching signature.`
|
||||
),
|
||||
MustSpecifyExtensionID: new TypeError(
|
||||
preamble +
|
||||
`${method} called from a webpage must specify an Extension ID (string) for its first argument.`
|
||||
),
|
||||
InvalidExtensionID: new TypeError(
|
||||
preamble + `Invalid extension id: '${extensionId}'`
|
||||
)
|
||||
})
|
||||
|
||||
// Valid Extension IDs are 32 characters in length and use the letter `a` to `p`:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90
|
||||
const isValidExtensionID = str =>
|
||||
str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)
|
||||
|
||||
/** Mock `chrome.runtime.sendMessage` */
|
||||
const sendMessageHandler = {
|
||||
apply: function(target, ctx, args) {
|
||||
const [extensionId, options, responseCallback] = args || []
|
||||
|
||||
// Define custom errors
|
||||
const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): `
|
||||
const Errors = makeCustomRuntimeErrors(
|
||||
errorPreamble,
|
||||
`chrome.runtime.sendMessage()`,
|
||||
extensionId
|
||||
)
|
||||
|
||||
// Check if the call signature looks ok
|
||||
const noArguments = args.length === 0
|
||||
const tooManyArguments = args.length > 4
|
||||
const incorrectOptions = options && typeof options !== 'object'
|
||||
const incorrectResponseCallback =
|
||||
responseCallback && typeof responseCallback !== 'function'
|
||||
if (
|
||||
noArguments ||
|
||||
tooManyArguments ||
|
||||
incorrectOptions ||
|
||||
incorrectResponseCallback
|
||||
) {
|
||||
throw Errors.NoMatchingSignature
|
||||
}
|
||||
|
||||
// At least 2 arguments are required before we even validate the extension ID
|
||||
if (args.length < 2) {
|
||||
throw Errors.MustSpecifyExtensionID
|
||||
}
|
||||
|
||||
// Now let's make sure we got a string as extension ID
|
||||
if (typeof extensionId !== 'string') {
|
||||
throw Errors.NoMatchingSignature
|
||||
}
|
||||
|
||||
if (!isValidExtensionID(extensionId)) {
|
||||
throw Errors.InvalidExtensionID
|
||||
}
|
||||
|
||||
return undefined // Normal behavior
|
||||
}
|
||||
}
|
||||
utils.mockWithProxy(
|
||||
window.chrome.runtime,
|
||||
'sendMessage',
|
||||
function sendMessage() {},
|
||||
sendMessageHandler
|
||||
)
|
||||
|
||||
/**
|
||||
* Mock `chrome.runtime.connect`
|
||||
*
|
||||
* @see https://developer.chrome.com/apps/runtime#method-connect
|
||||
*/
|
||||
const connectHandler = {
|
||||
apply: function(target, ctx, args) {
|
||||
const [extensionId, connectInfo] = args || []
|
||||
|
||||
// Define custom errors
|
||||
const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): `
|
||||
const Errors = makeCustomRuntimeErrors(
|
||||
errorPreamble,
|
||||
`chrome.runtime.connect()`,
|
||||
extensionId
|
||||
)
|
||||
|
||||
// Behavior differs a bit from sendMessage:
|
||||
const noArguments = args.length === 0
|
||||
const emptyStringArgument = args.length === 1 && extensionId === ''
|
||||
if (noArguments || emptyStringArgument) {
|
||||
throw Errors.MustSpecifyExtensionID
|
||||
}
|
||||
|
||||
const tooManyArguments = args.length > 2
|
||||
const incorrectConnectInfoType =
|
||||
connectInfo && typeof connectInfo !== 'object'
|
||||
|
||||
if (tooManyArguments || incorrectConnectInfoType) {
|
||||
throw Errors.NoMatchingSignature
|
||||
}
|
||||
|
||||
const extensionIdIsString = typeof extensionId === 'string'
|
||||
if (extensionIdIsString && extensionId === '') {
|
||||
throw Errors.MustSpecifyExtensionID
|
||||
}
|
||||
if (extensionIdIsString && !isValidExtensionID(extensionId)) {
|
||||
throw Errors.InvalidExtensionID
|
||||
}
|
||||
|
||||
// There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate
|
||||
const validateConnectInfo = ci => {
|
||||
// More than a first param connectInfo as been provided
|
||||
if (args.length > 1) {
|
||||
throw Errors.NoMatchingSignature
|
||||
}
|
||||
// An empty connectInfo has been provided
|
||||
if (Object.keys(ci).length === 0) {
|
||||
throw Errors.MustSpecifyExtensionID
|
||||
}
|
||||
// Loop over all connectInfo props an check them
|
||||
Object.entries(ci).forEach(([k, v]) => {
|
||||
const isExpected = ['name', 'includeTlsChannelId'].includes(k)
|
||||
if (!isExpected) {
|
||||
throw new TypeError(
|
||||
errorPreamble + `Unexpected property: '${k}'.`
|
||||
)
|
||||
}
|
||||
const MismatchError = (propName, expected, found) =>
|
||||
TypeError(
|
||||
errorPreamble +
|
||||
`Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.`
|
||||
)
|
||||
if (k === 'name' && typeof v !== 'string') {
|
||||
throw MismatchError(k, 'string', typeof v)
|
||||
}
|
||||
if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {
|
||||
throw MismatchError(k, 'boolean', typeof v)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (typeof extensionId === 'object') {
|
||||
validateConnectInfo(extensionId)
|
||||
throw Errors.MustSpecifyExtensionID
|
||||
}
|
||||
|
||||
// Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well
|
||||
return utils.patchToStringNested(makeConnectResponse())
|
||||
}
|
||||
}
|
||||
utils.mockWithProxy(
|
||||
window.chrome.runtime,
|
||||
'connect',
|
||||
function connect() {},
|
||||
connectHandler
|
||||
)
|
||||
|
||||
function makeConnectResponse() {
|
||||
const onSomething = () => ({
|
||||
addListener: function addListener() {},
|
||||
dispatch: function dispatch() {},
|
||||
hasListener: function hasListener() {},
|
||||
hasListeners: function hasListeners() {
|
||||
return false
|
||||
},
|
||||
removeListener: function removeListener() {}
|
||||
})
|
||||
|
||||
const response = {
|
||||
name: '',
|
||||
sender: undefined,
|
||||
disconnect: function disconnect() {},
|
||||
onDisconnect: onSomething(),
|
||||
onMessage: onSomething(),
|
||||
postMessage: function postMessage() {
|
||||
if (!arguments.length) {
|
||||
throw new TypeError(`Insufficient number of arguments.`)
|
||||
}
|
||||
throw new Error(`Attempting to use a disconnected port object`)
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
},
|
||||
{
|
||||
opts: this.opts,
|
||||
STATIC_DATA
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(pluginConfig) {
|
||||
return new Plugin(pluginConfig)
|
||||
}
|
||||
Reference in New Issue
Block a user