ipc/client.js

/*
 * Copyright (c) 2019 Zippie Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/**
 * @enum {string} IPCMemberType
 */
const IPCMemberType = {
  /** Service event */
  EVENT: 'event',
  /** Service method */
  METHOD: 'method',
  /** Service property */
  PROPERTY: 'property'
}

/**
 * Describes a single member of an IPC services' interface
 * @typedef {object} IPCMember
 * 
 * @property {IPCMemberType} type The member type, event, method, property, etc.
 * @property {string} name The symbolic name of the member.
 * @property {Number} [arity] Number of parameters expected for member calls.
 * 
 */
/**
 * Describes a dynamic service interface
 * @typedef {IPCMember[]} IPCInterfaceSpec
 * 
 */
/**
 * Interface for accessing a remote IPC end-point.
 * 
 * Instances of this class are returned by
 * [ipc.createClient]{@link module:ipc~createClient}, and are not
 * instantiatable otherwise.
 * 
 * @class IPCClient
 * 
 * @see module:ipc~createClient
 * 
 */
/** @member {string} IPCClient#uri Remote service end-point URI */
/** @member {string} IPCClient#tag Remote service end-point descriptor */
/**
 * Query remote end-point and return service interface specification
 * 
 * @function IPCClient#getInterface
 * @returns {IPCInterfaceSpec} Remote IPC interface specification
 */

var __context
var __clients = {}

/**
 * Initialize vault-api IPC client API
 * 
 * @access private
 * 
 * @param {Vault} vault Vault API instance
 */
export function init (vault) {
  __context = vault
}

/**
 * Connect to a remote IPC end-point through vault.
 * 
 * @access private
 * 
 * @param {string} uri Remote application host uri
 * @param {string} tag Remote application service descriptor
 */
export function connect (uri, tag) {
  const serviceId = tag+'+'+uri

  if (serviceId in __clients) return __clients[serviceId]

  console.info('VAULT-API-IPC (CLIENT): Connecting to remote service:', serviceId)

  // Send IPC init request to vault, to connect to remote end-point.
  // Once connected, call 'getInterface' to get remote service interface spec.
  // Then dynamically construct API based on interface spec.
  return message(uri, tag, 'init')
    .then(queue(uri, tag, 'getInterface', []))
    .then(iface => {
      const inst = {
        uri,
        tag,
        getInterface: method(uri, tag, 'getInterface', 0),
      }

      // Auto-generate remote IPC client interface.
      // XXX - Currently only 'method' type supported, need 'event' type, etc.
      iface.forEach(v => {
        if (v.type === 'method') {
          console.info('VAULT-API-IPC (CLIENT): Generating method:', serviceId, v.name, v.arity)
          inst[v.name] = method(uri, tag, v.name, v.arity)
        }

        if (v.type === 'event') {
          console.info('VAULT-API-IPC (CLIENT): Ignoring event type (not implemented yet!):', serviceId, v.name)
        }
      })

      __clients[serviceId] = inst
      return inst
    })
}

/**
 * Call a remote IPC end-point method with parameters.
 * 
 * @access private
 * 
 * @param {string} endpoint Remote application host uri
 * @param {string} tag Remote application service descriptor
 * @param {string} call Method name
 * @param {Array} args Method parameters
 */
function message (endpoint, tag, call, args) {
  return __context.message({
      IPCRouterRequest: {
        target: endpoint,
        payload: {call: call, args: args, tag: tag}
    }})
}

/**
 * Create a continuation which when invoked calls a remote IPC end-point method.
 * Useful when chaining promises.
 * 
 * @access private
 * 
 * @param {string} endpoint Remote application host uri
 * @param {string} tag Remote application service descriptor
 * @param {string} call Method name
 * @param {Array} args Method parameters
 */
function queue (endpoint, tag, method, args) {
  return function () { return message(endpoint, tag, method, args) }
}

/**
 * Create a remote IPC method call interface method.
 * Used for constructing dynamic IPC client interfaces.
 * 
 * @access private
 * 
 * @param {string} endpoint Remote application host uri
 * @param {string} tag Remote application service descriptor
 * @param {string} call Method name
 * @param {Integer} arity 
 */
function method (endpoint, tag, method, arity) {
  return function () {
    if (arguments.length < arity) throw 'INVALID_PARAMS'

    return message.apply(
      null,
      [endpoint, tag, method, [Array.prototype.slice.call(arguments)]]
    )
  }
}