All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.dev-tools.services.ServiceManager.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Host from '../host/host.js';

/**
 * @unrestricted
 */
export class ServiceManager {
  /**
   * @param {string} serviceName
   * @return {!Promise}
   */
  createRemoteService(serviceName) {
    if (!this._remoteConnection) {
      const url = Root.Runtime.queryParam('service-backend');
      if (!url) {
        console.error('No endpoint address specified');
        return /** @type {!Promise} */ (Promise.resolve(null));
      }
      this._remoteConnection = new Connection(new RemoteServicePort(url));
    }
    return this._remoteConnection._createService(serviceName);
  }

  /**
   * @param {string} appName
   * @param {string} serviceName
   * @return {!Promise}
   */
  createAppService(appName, serviceName) {
    let url = appName + '.js';
    const remoteBase = Root.Runtime.queryParam('remoteBase');
    const debugFrontend = Root.Runtime.queryParam('debugFrontend');
    const isUnderTest = Host.InspectorFrontendHost.isUnderTest();

    const queryParams = [];
    if (remoteBase) {
      queryParams.push('remoteBase=' + remoteBase);
    }
    if (debugFrontend) {
      queryParams.push('debugFrontend=' + debugFrontend);
    }
    if (isUnderTest) {
      queryParams.push('isUnderTest=true');
    }

    if (queryParams.length) {
      url += `?${queryParams.join('&')}`;
    }

    const worker = new Worker(url, {type: 'module'});
    const connection = new Connection(new WorkerServicePort(worker));
    return connection._createService(serviceName);
  }
}

/**
 * @unrestricted
 */
export class Connection {
  /**
   * @param {!ServicePort} port
   */
  constructor(port) {
    this._port = port;
    this._port.setHandlers(this._onMessage.bind(this), this._connectionClosed.bind(this));

    this._lastId = 1;
    /** @type {!Map}*/
    this._callbacks = new Map();
    /** @type {!Map}*/
    this._services = new Map();
  }

  /**
   * @param {string} serviceName
   * @return {!Promise}
   */
  _createService(serviceName) {
    return this._sendCommand(serviceName + '.create').then(result => {
      if (!result) {
        console.error('Could not initialize service: ' + serviceName);
        return null;
      }
      const service = new Service(this, serviceName, result.id);
      this._services.set(serviceName + ':' + result.id, service);
      return service;
    });
  }

  /**
   * @param {!Service} service
   */
  _serviceDisposed(service) {
    this._services.delete(service._serviceName + ':' + service._objectId);
    if (!this._services.size) {
      // Terminate the connection since it is no longer used.
      this._port.close();
    }
  }

  /**
   * @param {string} method
   * @param {!Object=} params
   * @return {!Promise}
   */
  _sendCommand(method, params) {
    const id = this._lastId++;
    const message = JSON.stringify({id: id, method: method, params: params || {}});
    return this._port.send(message).then(success => {
      if (!success) {
        return Promise.resolve(null);
      }
      return new Promise(fulfill => this._callbacks.set(id, fulfill));
    });
  }

  /**
   * @param {string} data
   */
  _onMessage(data) {
    let object;
    try {
      object = JSON.parse(data);
    } catch (e) {
      console.error(e);
      return;
    }
    if (object.id) {
      if (object.error) {
        console.error('Service error: ' + object.error);
      }
      this._callbacks.get(object.id)(object.error ? null : object.result);
      this._callbacks.delete(object.id);
      return;
    }

    const tokens = object.method.split('.');
    const serviceName = tokens[0];
    const methodName = tokens[1];
    const service = this._services.get(serviceName + ':' + object.params.id);
    if (!service) {
      console.error('Unable to lookup stub for ' + serviceName + ':' + object.params.id);
      return;
    }
    service._dispatchNotification(methodName, object.params);
  }

  _connectionClosed() {
    for (const callback of this._callbacks.values()) {
      callback(null);
    }
    this._callbacks.clear();
    for (const service of this._services.values()) {
      service._dispatchNotification('disposed');
    }
    this._services.clear();
  }
}

/**
 * @unrestricted
 */
export class Service {
  /**
   * @param {!Connection} connection
   * @param {string} serviceName
   * @param {string} objectId
   */
  constructor(connection, serviceName, objectId) {
    this._connection = connection;
    this._serviceName = serviceName;
    this._objectId = objectId;
    /** @type {!Map}*/
    this._notificationHandlers = new Map();
  }

  /**
   * @return {!Promise}
   */
  dispose() {
    const params = {id: this._objectId};
    return this._connection._sendCommand(this._serviceName + '.dispose', params).then(() => {
      this._connection._serviceDisposed(this);
    });
  }

  /**
   * @param {string} methodName
   * @param {function(!Object=)} handler
   */
  on(methodName, handler) {
    this._notificationHandlers.set(methodName, handler);
  }

  /**
   * @param {string} methodName
   * @param {!Object=} params
   * @return {!Promise}
   */
  send(methodName, params) {
    params = params || {};
    params.id = this._objectId;
    return this._connection._sendCommand(this._serviceName + '.' + methodName, params);
  }

  /**
   * @param {string} methodName
   * @param {!Object=} params
   */
  _dispatchNotification(methodName, params) {
    const handler = this._notificationHandlers.get(methodName);
    if (!handler) {
      console.error('Could not report notification \'' + methodName + '\' on \'' + this._objectId + '\'');
      return;
    }
    handler(params);
  }
}

/**
 * @implements {ServicePort}
 * @unrestricted
 */
export class RemoteServicePort {
  /**
   * @param {string} url
   */
  constructor(url) {
    this._url = url;
  }

  /**
   * @override
   * @param {function(string)} messageHandler
   * @param {function(string)} closeHandler
   */
  setHandlers(messageHandler, closeHandler) {
    this._messageHandler = messageHandler;
    this._closeHandler = closeHandler;
  }

  /**
   * @return {!Promise}
   */
  _open() {
    if (!this._connectionPromise) {
      this._connectionPromise = new Promise(promiseBody.bind(this));
    }
    return this._connectionPromise;

    /**
     * @param {function(boolean)} fulfill
     * @this {RemoteServicePort}
     */
    function promiseBody(fulfill) {
      let socket;
      try {
        socket = new WebSocket(/** @type {string} */ (this._url));
        socket.onmessage = onMessage.bind(this);
        socket.onclose = onClose.bind(this);
        socket.onopen = onConnect.bind(this);
      } catch (e) {
        fulfill(false);
      }

      /**
       * @this {RemoteServicePort}
       */
      function onConnect() {
        this._socket = socket;
        fulfill(true);
      }

      /**
       * @param {!Event} event
       * @this {RemoteServicePort}
       */
      function onMessage(event) {
        this._messageHandler(event.data);
      }

      /**
       * @this {RemoteServicePort}
       */
      function onClose() {
        if (!this._socket) {
          fulfill(false);
        }
        this._socketClosed(!!this._socket);
      }
    }
  }

  /**
   * @override
   * @param {string} message
   * @return {!Promise}
   */
  send(message) {
    return this._open().then(() => {
      if (this._socket) {
        this._socket.send(message);
        return true;
      }
      return false;
    });
  }

  /**
   * @override
   * @return {!Promise}
   */
  close() {
    return this._open().then(() => {
      if (this._socket) {
        this._socket.close();
        this._socketClosed(true);
      }
      return true;
    });
  }

  /**
   * @param {boolean=} notifyClient
   */
  _socketClosed(notifyClient) {
    this._socket = null;
    delete this._connectionPromise;
    if (notifyClient) {
      this._closeHandler();
    }
  }
}

/**
 * @implements {ServicePort}
 * @unrestricted
 */
export class WorkerServicePort {
  /**
   * @param {!Worker} worker
   */
  constructor(worker) {
    this._worker = worker;

    let fulfill;
    this._workerPromise = new Promise(resolve => fulfill = resolve);

    this._worker.onmessage = onMessage.bind(this);
    this._worker.onclose = this._closeHandler;

    /**
     * @param {!Event} event
     * @this {WorkerServicePort}
     */
    function onMessage(event) {
      if (event.data === 'workerReady') {
        fulfill(true);
        return;
      }
      this._messageHandler(event.data);
    }
  }

  /**
   * @override
   * @param {function(string)} messageHandler
   * @param {function(string)} closeHandler
   */
  setHandlers(messageHandler, closeHandler) {
    this._messageHandler = messageHandler;
    this._closeHandler = closeHandler;
  }

  /**
   * @override
   * @param {string} message
   * @return {!Promise}
   */
  send(message) {
    return this._workerPromise.then(() => {
      try {
        this._worker.postMessage(message);
        return true;
      } catch (e) {
        return false;
      }
    });
  }

  /**
   * @override
   * @return {!Promise}
   */
  close() {
    return this._workerPromise.then(() => {
      if (this._worker) {
        this._worker.terminate();
      }
      return false;
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy