Home Reference Source

lib/connection/adaptor.js

const CRIConnection = require('./CRIConnection')
const CDPSession = require('./CDPSession')
const Events = require('../Events')
const { interopCRIApi, createProtocolError } = require('../__shared')

/**
 * A Symbol used to indicated we patched an original instance of chrome-remote-interface client (Chrome)
 * @type {symbol}
 */
const CRIClientPatched = Symbol('chrome-remote-interface-extra-client-patched')

/**
 * A Symbol used to indicated we patched an original instance of chrome-remote-interface client (Chrome)
 * @type {symbol}
 */
exports.CRIClientPatched = CRIClientPatched

/**
 * Adapts the client object returned by the default module.export of the chrome-remote-interface to appear and behave like the puppeteer classes
 * @param {Chrome|CRIConnection|CDPSession|Object} cdpClient
 * @return {Chrome|CRIConnection|CDPSession}
 * @since chrome-remote-interface-extra
 */
exports.adaptChromeRemoteInterfaceClient = function adaptChromeRemoteInterfaceClient (
  cdpClient
) {
  if (cdpClient instanceof CRIConnection || cdpClient instanceof CDPSession) {
    return cdpClient
  }

  if (cdpClient[CRIClientPatched]) return cdpClient

  /**
   * @type {!Map<string, !CDPSession>}
   */
  cdpClient._sessions = new Map()
  cdpClient._crieCallbacks = new Map()

  /**
   * @type {boolean}
   */
  cdpClient._closed = false

  /**
   * @param {Object} message
   * @return {*}
   */
  cdpClient.__originalHandleMessage$$ = cdpClient._handleMessage.bind(cdpClient)
  cdpClient.__originalSend$$ = cdpClient.send.bind(cdpClient)

  /**
   * @param {string} sessionId
   * @return {?CDPSession}
   */
  cdpClient.session = function session (sessionId) {
    return this._sessions.get(sessionId) || null
  }

  /**
   *
   */
  cdpClient.dispose = function dispose () {
    this._onClose()
    this.close()
  }

  /**
   * @param {Object} targetInfo
   * @return {Promise<CDPSession>}
   */
  cdpClient.createSession = async function createSession (targetInfo) {
    const { sessionId } = await this.send('Target.attachToTarget', {
      targetId: targetInfo.targetId,
      flatten: true
    })
    return this._sessions.get(sessionId)
  }

  cdpClient.send = function send (method, params = {}) {
    const id = this._rawSend({ method, params })
    return new Promise((resolve, reject) => {
      this._crieCallbacks.set(id, {
        resolve,
        reject,
        error: new Error(),
        method
      })
    })
  }

  /**
   */
  cdpClient._onClose = function _onClose () {
    if (this._closed) return
    this._closed = true
    for (const session of this._sessions.values()) {
      session._onClosed()
    }
    this._sessions.clear()
    this.emit(Events.CRIConnection.Disconnected)
  }

  /**
   *
   * @param {Object} message
   * @return {number}
   */
  cdpClient._rawSend = function _rawSend (message) {
    const id = this._nextCommandId++
    const msg = JSON.stringify(Object.assign({}, message, { id }))
    this._ws.send(msg)
    return id
  }

  /**
   * @param {Object} object
   * @return {*}
   */
  cdpClient._handleMessage = function _handleMessage (object) {
    if (object.id && object.id in this._callbacks) {
      return this.__originalHandleMessage$$(object)
    }
    if (object.method === 'Target.attachedToTarget') {
      const sessionId = object.params.sessionId
      const session = new CDPSession(
        this,
        object.params.targetInfo.type,
        sessionId
      )
      this._sessions.set(sessionId, session)
    } else if (object.method === 'Target.detachedFromTarget') {
      const session = this._sessions.get(object.params.sessionId)
      if (session) {
        session._onClosed()
        this._sessions.delete(object.params.sessionId)
      }
    }
    if (object.sessionId) {
      const session = this._sessions.get(object.sessionId)
      if (session) session._onMessage(object)
    } else if (object.id) {
      const cb = this._crieCallbacks.get(object.id)
      if (cb) {
        this._crieCallbacks.delete(object.id)
        if (object.error) {
          cb.reject(createProtocolError(cb.error, cb.method, object))
        } else {
          cb.resolve(object.result)
        }
      }
    } else {
      this.emit(object.method, object.params)
    }
  }

  cdpClient._interopSend = function _interopSend (method, params, callback) {
    return this.__originalSend$$(method, params, callback)
  }

  Object.defineProperty(cdpClient, CRIClientPatched, {
    value: true,
    writable: false,
    enumerable: false
  })

  Object.defineProperty(cdpClient, '$$disconnectEvent', {
    value: Events.CRIConnection.Disconnected,
    writable: false,
    enumerable: false
  })

  cdpClient.on(
    Events.CRIClient.Disconnected,
    cdpClient._onClose.bind(cdpClient)
  )
  if (cdpClient.setMaxListeners) {
    cdpClient.setMaxListeners(Infinity)
  }
  interopCRIApi(cdpClient)
  return cdpClient
}