text: // https://github.com/berstend/puppeteer-extra/blob/c44c8bb0224c6bba2554017bfb9d7a1d0119f92f/packages/puppeteer-extra-plugin-stealth/evasions/chrome.runtime/index.js
  
  (runOnInsecureOrigins) => {
    const STATIC_DATA = {
      "OnInstalledReason": {
        "CHROME_UPDATE": "chrome_update",
        "INSTALL": "install",
        "SHARED_MODULE_UPDATE": "shared_module_update",
        "UPDATE": "update"
      },
      "OnRestartRequiredReason": {
        "APP_UPDATE": "app_update",
        "OS_UPDATE": "os_update",
        "PERIODIC": "periodic"
      },
      "PlatformArch": {
        "ARM": "arm",
        "ARM64": "arm64",
        "MIPS": "mips",
        "MIPS64": "mips64",
        "X86_32": "x86-32",
        "X86_64": "x86-64"
      },
      "PlatformNaclArch": {
        "ARM": "arm",
        "MIPS": "mips",
        "MIPS64": "mips64",
        "X86_32": "x86-32",
        "X86_64": "x86-64"
      },
      "PlatformOs": {
        "ANDROID": "android",
        "CROS": "cros",
        "LINUX": "linux",
        "MAC": "mac",
        "OPENBSD": "openbsd",
        "WIN": "win"
      },
      "RequestUpdateCheckStatus": {
        "NO_UPDATE": "no_update",
        "THROTTLED": "throttled",
        "UPDATE_AVAILABLE": "update_available"
      }
    }
    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 && !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
    }
  }
  