import * as uuidv4 from "uuid/v4" import * as T from "./Types"; import * as I from "./Interfaces"; import { SubscriptionResponse } from "./Types"; /** * Translate an RPC to RPCInfo for serialization. * @param rpc The RPC to transform * @param owner The owning RPC group's name * @throws Error on RPC without name property */ export const rpcToRpcinfo = (rpc : T.RPC, owner: T.Owner):T.RpcInfo => { switch (typeof rpc){ case "object": if(rpc['call']){ return { owner: owner, argNames: extractArgs(rpc['call']), type: "Call", name: rpc.name, call: rpc['call'], } }else{ const generator = hookGenerator(>rpc) return { owner: owner, argNames: extractArgs(generator(undefined)), type: "Hook", name: rpc.name, generator: generator, } } case "function": if(!rpc.name) throw new Error(` RPC did not provide a name. \nUse 'funtion name(..){ .. }' syntax instead. \n \n<------------OFFENDING RPC: \n`+rpc.toString()+` \n>------------OFFENDING RPC`) return { owner : owner, argNames: extractArgs(rpc), type: "Call", name: rpc.name, call: async(...args) => rpc.apply({}, args), } } throw new Error("Bad socketIORPC type "+ typeof rpc) } /** * Utility function to apply the RPCs of an {@link RPCExporter}. * @param socket The websocket (implementation: bsock) to hook on * @param exporter The exporter * @param makeUnique @default true Attach a suffix to RPC names */ export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter, makeUnique = true):T.ExtendedRpcInfo[]{ const owner = exporter.name const RPCs = [...exporter.exportRPCs()] const suffix = makeUnique?"-"+uuidv4().substr(0,4):"" return RPCs.map(rpc => rpcToRpcinfo(rpc, owner)) .map(info => { const ret:any = info ret.uniqueName = info.name+suffix switch(info.type){ case "Hook": socket.hook(ret.uniqueName, info.generator(socket)) break; case "Call": socket.hook(ret.uniqueName, info.call) break; } socket.on('close', () => socket.unhook(info.name)) return ret }) } /** * Utility function to generate {@link HookFunction} from a RPC * @param rpc The RPC to transform * @returns A {@link HookFunction} */ const hookGenerator = (rpc:T.HookRPC): T.HookInfo['generator'] => { const argsArr = extractArgs(rpc.hook) argsArr.pop() const args = argsArr.join(',') return eval(`(socket) => async (`+args+`) => { const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => { if(rpc.onCallback) rpc.onCallback.apply({}, cbargs) socket.call.apply(socket, [res.uuid, ...cbargs]) }) if(res.result === 'Success'){ if(rpc.onClose){ socket.on('close', async () => { rpc.onClose(res, rpc) }) } } return res }`) } /** * Extract a string list of parameters from a function * @param f The source function */ const extractArgs = (f:Function):T.Arg[] => { let fn return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",") } /** * Simple utility function to create basic {@link SubscriptionResponse} * @param uuid optional uuid to use, otherwise defaults to uuid/v4 */ export function makeSubResponse(uuid?:string):SubscriptionResponse{ return { result: "Success", uuid: uuid?uuid:uuidv4(), } }