UNPKG

@ch1/rpc

Version:

JavaScript Remote Procedure Call (RPC)

258 lines 9.29 kB
/** * Low level functions that respond to remote procedure calls: */ import { safeCall } from './remote'; import { createErrorFromRPCError } from './rpc-error'; import { createEvent, createErrorEvent } from './events'; import { defer, isDefer, isRPCErrorPayload, isRPCInvocationPayload, isRPCReturnPayload, rangeError, throwIfNotRPCEvent, typeError, isFunction, } from './utils'; import { RPCEventType, RPCAsyncType, } from './interfaces'; const fnReturn = (c, payload, id, callbacks) => returnPayload(c, payload, callbacks, id); export function createFunctionalState() { return { responders: { // [RPCEventType.ack]: nodeOn, // [RPCEventType.ack]: nodeRemoveListener, // [RPCEventType.ack]: nodeCallback, }, successHandlers: {}, errorHandlers: {}, }; } function register(dictionary, key, value) { if (dictionary[key]) { return; } dictionary[key] = value; } export function create(config, callbacks, remoteDesc) { bootstrap(config.functionalState); const id = config.uid(); const initState = createInitializationState(config, remoteDesc, id); return initialize(config, initState).then(localRemoteDesc => { const off = on(sendAck, config, callbacks, id); /** @todo implement RPC destroy here */ return { off: () => new Promise(resolve => resolve(off())), remoteDesc: localRemoteDesc, }; }); } export function createInitializationState(config, remoteDesc, id) { const d = defer(); const readTimeout = setTimeout(() => d.reject(new Error('RPC initialization failed, maximum delay of ' + `${config.defaultCreateWait}ms exceeded`)), config.defaultCreateWait); let delay = config.defaultCreateRetry; let createTimeout = setTimeout(fireCreate, delay); function fireCreate() { config.emit(createEvent(RPCEventType.create, { result: [remoteDesc] }, config.uid())); delay *= config.defaultCreateRetryCurve; createTimeout = setTimeout(fireCreate, delay); } function clean() { clearTimeout(readTimeout); stopCreateSpam(); } function stopCreateSpam() { clearTimeout(createTimeout); } return { clean, defer: d, id, isCreated: false, hasCreated: false, localRemoteDesc: null, stopCreateSpam, }; } export function initialize(config, initState) { const off = config.on((event) => { const { payload } = event; if (isRPCErrorPayload(payload)) { throw createErrorFromRPCError(config, payload.error); } if (!isRPCReturnPayload(payload)) { rangeError('unexpected payload received during initialization ' + JSON.stringify(event)); } if (isRPCReturnPayload(payload)) { if (event.type === RPCEventType.create) { if (initState.hasCreated) { return; } // create local remote initState.localRemoteDesc = payload.result[0]; initState.hasCreated = true; config.emit(createEvent(RPCEventType.createReturn, { result: [initState.id] }, config.uid())); } else if (event.type === RPCEventType.createReturn) { if (initState.isCreated) { return; } initState.stopCreateSpam(); initState.isCreated = true; } else { rangeError('unexpected event received during initialization: ' + event.type); } } if (initState.isCreated && initState.hasCreated) { initState.clean(); off(); initState.defer.resolve(initState.localRemoteDesc); } }); return initState.defer.promise; } export function ack(c, event) { if (!c.useAcks) { typeError('ack even received but useAcks is disabled'); } const payload = event.payload; if (isRPCReturnPayload(payload)) { const id = payload.result[0]; if (!c.useAcks[id]) { typeError(`ack expecting to find ack timeout for: ${id}`); } clearTimeout(c.useAcks[id]); delete c.useAcks[id]; } else { typeError('ack received invalid payload'); } } export function sendAck(c, id) { c.emit(createEvent(RPCEventType.ack, { result: [id] }, c.uid())); } export function invoke(c, payload, id) { if (isRPCInvocationPayload(payload)) { const result = safeCall(c, payload.fn, payload.args); if (result instanceof Error) { c.emit(createErrorEvent(c, RPCEventType.fnReturn, result, id)); return; } c.emit(createEvent(RPCEventType.fnReturn, { result: [result] }, id)); } else { c.emit(createErrorEvent(c, RPCEventType.fnReturn, new TypeError('invoke: invalidPayload'), id)); } } export function fireError(c, payload, asyncReturn) { const error = createErrorFromRPCError(c, payload.error); const asyncFn = asyncReturn.async; if (c.functionalState.errorHandlers[asyncReturn.type]) { c.functionalState.errorHandlers[asyncReturn.type](asyncFn, error); } else { throw error; } } export function fireSuccess(c, payload, asyncReturn) { const asyncFn = asyncReturn.async; if (c.functionalState.successHandlers[asyncReturn.type]) { c.functionalState.successHandlers[asyncReturn.type](asyncFn, payload.result); } else { rangeError('fireSuccess: no async handler'); } } export function returnPayload(c, payload, callbacks, id) { if (!callbacks[id]) { rangeError(`invokeReturn: no matching callback for ${id}`); } if (isRPCErrorPayload(payload)) { fireError(c, payload, callbacks[id]); delete callbacks[id]; return; } else if (isRPCReturnPayload(payload)) { fireSuccess(c, payload, callbacks[id]); delete callbacks[id]; return; } typeError('returnPayload: unexpected payload for event: ' + event.type); } export function promise(c, payload, id) { if (isRPCInvocationPayload(payload)) { const result = safeCall(c, payload.fn, payload.args); if (result instanceof Error) { c.emit(createErrorEvent(c, RPCEventType.fnReturn, result, id)); return; } if (isFunction(result.then)) { result .then((...args) => c.emit(createEvent(RPCEventType.fnReturn, { result: args }, id))) .catch(err => c.emit(createErrorEvent(c, RPCEventType.fnReturn, err, id))); } else { // support direct invocation fallback c.emit(createEvent(RPCEventType.fnReturn, { result: [result] }, id)); } } else { c.emit(createErrorEvent(c, RPCEventType.fnReturn, new TypeError('promise: invalidPayload'), id)); } } function tryHandler(fn, args, event) { try { return fn.apply(null, args); } catch (err) { throw new Error(`RPC: No registered handler for event: ${event.type}: ` + err.message); } } export function on(sendAckFn, c, callbacks, id) { return c.on(event => { throwIfNotRPCEvent(event, `expecting an RPCEvent: Received: ${typeof event} `); tryHandler(c.functionalState.responders[event.type], [c, event.payload, event.uid, callbacks, id], event); if (event.useAcks && event.type !== RPCEventType.ack) { sendAckFn(c, event.uid); } }); } export function bootstrap(functionalState) { const registerResponder = register.bind(null, functionalState.responders); const registerSuccessHandler = register.bind(null, functionalState.successHandlers); const registerErrorHandler = register.bind(null, functionalState.errorHandlers); /** Bootstrap the inbuilt respoonders */ registerResponder(RPCEventType.ack, ack); registerResponder(RPCEventType.invoke, invoke); registerResponder(RPCEventType.fnReturn, fnReturn); registerResponder(RPCEventType.promise, promise); registerErrorHandler(RPCAsyncType.promise, (asyncFn, error) => { if (isDefer(asyncFn)) { asyncFn.reject(error); } else { throw new RangeError('registerErrorHandler: incorrect function type'); } }); // case 'nodeCallback': // if (isRPCNodeCallback<any>(asyncFn)) { // asyncFn(error); // return; // } // break; registerSuccessHandler(RPCAsyncType.promise, (asyncFn, payload) => { if (isDefer(asyncFn)) { asyncFn.resolve.apply(asyncFn.resolve, payload); } else { throw new RangeError('registerSuccessHandler: incorrect function type'); } }); // case 'nodeCallback': // if (isRPCNodeCallback(asyncFn)) { // asyncFn.apply(null, [null].concat(payload.result)); // return; // } // break; // case 'nodeEvent': // if (isRPCNotify(asyncFn)) { // asyncFn.apply(null, [null].concat(payload.result)); // return; // } // break; return functionalState; } //# sourceMappingURL=response.js.map