Home Reference Source

lib/workers/ServiceWorker.js

const util = require('util')
const { URL } = require('url')
const EventEmitter = require('eventemitter3')
const Events = require('../Events')
const { ExecutionContext } = require('../executionContext')
const { JSHandle } = require('../JSHandle')
const { debugError } = require('../helper')

const RunningStates = {
  stopped: 'stopped',
  starting: 'starting',
  running: 'running',
  stopping: 'stopping'
}

const StatusStates = {
  new: 'new',
  installing: 'installing',
  installed: 'installed',
  activating: 'activating',
  activated: 'activated',
  redundant: 'redundant'
}

/**
 * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker
 * @since chrome-remote-interface-extra
 */
class ServiceWorker extends EventEmitter {
  /**
   * @param {WorkerManager} manager
   */
  constructor (manager) {
    super()
    /**
     * @type {WorkerManager}
     * @private
     */
    this._manager = manager

    /**
     * @type {ServiceWorkerInfo}
     * @private
     */
    this._info = {}

    /**
     * @type {?string}
     * @private
     */
    this._origin = null
  }

  /**
   *
   * @return {string}
   */
  versionId () {
    return this._info.versionId
  }

  /**
   *
   * @return {string}
   */
  registrationId () {
    return this._info.registrationId
  }

  /**
   *
   * @return {string}
   */
  scopeURL () {
    return this._info.scopeURL
  }

  /**
   *
   * @return {boolean}
   */
  isDeleted () {
    return this._info.isDeleted
  }

  /**
   *
   * @return {string}
   */
  runningStatus () {
    return this._info.runningStatus
  }

  /**
   * Returns the ServiceWorkers status
   * Possible status:
   *  - new
   *  - installing
   *  - installed
   *  - activating
   *  - activated
   *  - redundant
   * @return {string}
   */
  status () {
    return this._info.status
  }

  /**
   * @return {?number}
   */
  scriptLastModified () {
    return this._info.scriptLastModified
  }

  /**
   * @return {?number}
   */
  scriptResponseTime () {
    return this._info.scriptResponseTime
  }

  /**
   * @return {string}
   */
  targetId () {
    return this._info.targetId
  }

  /**
   * @return {?Array<string>}
   */
  controlledClients () {
    return this._info.controlledClients
  }

  /**
   * @return {boolean}
   */
  isStopped () {
    return this._info.runningStatus === RunningStates.stopped
  }

  /**
   * @return {boolean}
   */
  isStarting () {
    return this._info.runningStatus === RunningStates.starting
  }

  /**
   * @return {boolean}
   */
  isRunning () {
    return this._info.runningStatus === RunningStates.running
  }

  /**
   * @return {boolean}
   */
  isStopping () {
    return this._info.runningStatus === RunningStates.stopping
  }

  /**
   * @return {boolean}
   */
  isNew () {
    return this._info.status === StatusStates.new
  }

  /**
   * @return {boolean}
   */
  isInstalling () {
    return this._info.status === StatusStates.installing
  }

  /**
   * @return {boolean}
   */
  isInstalled () {
    return this._info.status === StatusStates.installed
  }

  /**
   * @return {boolean}
   */
  isActivating () {
    return this._info.status === StatusStates.activating
  }

  /**
   * @return {boolean}
   */
  isActivated () {
    return this._info.status === StatusStates.activated
  }

  /**
   * @return {boolean}
   */
  isRedundant () {
    return this._info.status === StatusStates.redundant
  }

  /**
   * Delivers a push message to the supplied origin from the ServiceWorker
   * @param {string} data - The data of the message
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-deliverPushMessage
   */
  async deliverPushMessage (data) {
    await this._manager.swDeliverPushMessage({
      registrationId: this._info.registrationId,
      origin: this._origin,
      data
    })
  }

  /**
   *
   * @param {string} tag
   * @param {boolean} [lastChance]
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-dispatchSyncEvent
   */
  async dispatchSyncEvent (tag, lastChance) {
    await this._manager.swDispatchSyncEvent({
      origin: this._origin,
      registrationId: this._info.registrationId,
      tag,
      lastChance
    })
  }

  /**
   * Makes the ServiceWorker skip waiting
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-skipWaiting
   */
  async skipWaiting () {
    await this._manager.makeServiceWorkerSkipWaiting(this._info.scopeURL)
  }

  /**
   * Starts the ServiceWorker
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-startWorker
   */
  async start () {
    await this._manager.startServiceWorker(this._info.scopeURL)
  }

  /**
   * Stops the ServiceWorker
   * @param {string} versionId
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-stopWorker
   */
  async stop (versionId) {
    await this._manager.stopServiceWorker(this._info.versionId)
  }

  /**
   * Un-registers the ServiceWorker
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-unregister
   */
  async unregister () {
    await this._manager.unregisterServiceWorker(this._info.scopeURL)
  }

  /**
   * Updates the ServiceWorker's registrations
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-updateRegistration
   */
  async updateRegistration () {
    await this._manager.updateServiceWorkerRegistration(this._info.scopeURL)
  }

  /**
   * @param {CDPServiceWorkerErrorMessage} error
   */
  _errorReported (error) {
    this.emit(Events.ServiceWorker.Error, error)
  }

  /**
   * @param {CDPServiceWorkerRegistration|CDPServiceWorkerVersion} updateHow
   * @param {boolean} isVersion
   */
  _bookKeeping (updateHow, isVersion) {
    // console.log(`ServiceWorker._bookKeeping: isVersion: ${isVersion}`, updateHow)
    let prevRegId = this._info.registrationId
    if (isVersion) {
      this._info = Object.assign(this._info, updateHow)
      this.emit(Events.ServiceWorker.VersionUpdated)
      if (this._info.isDeleted) {
        this._manager._removeServiceWorker(this.registrationId())
        this.emit(Events.ServiceWorker.Deleted)
      }
    } else {
      if (this._info.scopeURL !== updateHow.scopeURL) {
        const purl = new URL(updateHow.scopeURL)
        this._origin = purl.origin
      }
      this._info = Object.assign(this._info, updateHow)
      this.emit(Events.ServiceWorker.RegistrationUpdated)
    }
    if (prevRegId != null && prevRegId !== this._info.registrationId) {
      this._manager._ensureServiceWorkerSwapped(this, prevRegId)
    }
  }

  _destroyed () {
    this.emit(Events.ServiceWorker.Closed)
  }

  /**
   * @return {ServiceWorkerInfo}
   */
  toJSON () {
    return this._info
  }

  /** @ignore */
  // eslint-disable-next-line space-before-function-paren
  [util.inspect.custom](depth, options) {
    if (depth < 0) {
      return options.stylize(`[ServiceWorker]`, 'special')
    }
    const newOptions = Object.assign({}, options, {
      depth: options.depth == null ? null : options.depth - 1
    })
    const inner = util.inspect(this._info, newOptions)
    return `${options.stylize('ServiceWorker', 'special')} ${inner}`
  }
}

/**
 * @type {ServiceWorker}
 */
module.exports = ServiceWorker