/*
* 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.
*
*/
/**
* Interface for creating an IPC service endpoint for other appliations
* to access.
*
* Instances of this class are returned by
* [ipc.createService]{@link module:ipc~createService}, and are not
* instantiatable otherwise.
*
* @class IPCService
*
* @see module:ipc~createService
*
*/
/**
* Register receiver function with service interface.
*
* This method takes the function name and uses it to generate the
* {@link IPCInterfaceSpec} returned to remote clients.
*
* @function IPCService#addReceiver
*
* @param {function} receiver Interface function implementation
*/
/**
* Perform service API setup completion and signal remote clients
* that API is setup and ready to receive requests.
*
* @function IPCService#ready
*
*/
/**
* Get IPC service local interface, allows invoking service methods
* locally in original context.
*
* @function IPCService#getLocalInterface
*
* @returns {ServiceInterface} Local client API interface
*/
var __context
var __services = {}
/**
* Initialize vault-api IPC service API
*
* @access private
*
* @param {Vault} vault Vault API instance
*/
export function init (vault) {
__context = vault
}
/**
* Create a local service for remote IPC method calls.
*
* @access private
*
* @param {string} tag Local service descriptor
*/
export function register (tag) {
if (tag in __services) throw "IPC_TAG_IN_USE"
console.info('VAULT-API-IPC (Service): Attempting to register service:', tag)
__services[tag] = {
getInterface: getInterface(tag)
}
return {
addReceiver: addReceiver(tag),
ready: serviceReady(tag),
getLocalInterface: getLocalInterface(tag)
}
}
/**
* Register a receiver function for remote IPC method calls.
*
* @access private
*
* @param {string} tag Local service descriptor
*/
function addReceiver (tag) {
return function (f) {
console.info('VAULT-API-IPC (Service): Adding "' + tag + '" receiver:', f.name)
const service = __services[tag]
service[f.name] = f
}
}
/**
* Signal service is ready for use by remote end-points.
*
* @access private
*
* @param {string} tag Local service descriptor
*/
function serviceReady (tag) {
return function (f) {
console.info('VAULT-API-IPC (Service): Sending "' + tag + '" ready.')
__context.message({ IPCRouterRequest: { target: window.origin, callback: 'init-' + window.origin }})
}
}
/**
* Get service interface
*
* @access private
*
* @param {string} tag Local service descriptor
*/
function getInterface (tag) {
return function () {
console.info('VAULT-API-IPC (Service): getInterface('+tag+')')
const service = __services[tag]
return Object.keys(service)
.filter(v => v !== 'getInterface')
.map(v => ({ type: 'method', name: v, arity: service[v].length }))
}
}
/**
* Get service local API instance.
*
* @access private
*
* @param {string} tag Local service descriptor
*/
function getLocalInterface (tag) {
return function () {
const iface = {}
const service = __services[tag]
iface.origin = window.origin
Object.keys(service).forEach(k => {
iface[k] = service[k].bind(iface)
})
return iface
}
}
/**
* Takes incoming message events, and if it's a local service API request,
* invokes appropriate service receiver function.
*
* @access private
*
* @param {MessageEvent} ev
*/
export async function dispatch (ev) {
if (!ev.data.tag) return
if (!ev.data.call) return
console.info('VAULT-API-IPC (Service): Message received:', ev)
const service = __services[ev.data.tag]
if (!service) {
console.warn('VAULT-API-IPC (Service): Unrecognised service tag:', ev.data.tag)
return
}
const receiver = service[ev.data.call]
if (!receiver) {
console.warn('VAULT-API-IPC (Service): Unrecognised service receiver:', ev.data.call)
return
}
try {
var response = await receiver.apply({
origin: ev.data.origin
}, ev.data.args)
} catch (e) {
console.warn('VAULT-API-IPC (Service): Error calling receiver:', e)
return ev.source.postMessage({
IPCRouterRequest: {
target: window.origin,
callback: ev.data.callback,
error: '' + e
}
}, ev.origin)
}
ev.source.postMessage({
IPCRouterRequest: {
target: window.origin,
callback: ev.data.callback,
result: response
}
}, ev.origin)
}