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: string, sesame?:T.SesameFunction):T.RpcInfo => { switch (typeof rpc){ case "object": if(rpc['call']){ return { owner: owner, argNames: extractArgs(rpc['call']), type: "Call", name: rpc.name, call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args)}:rpc['call'], // check & remove sesame } }else{ const generator = hookGenerator(>rpc, sesame) 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: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args)}:rpc, // check & remove sesame } } 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, sesame?:T.SesameFunction, 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, sesame)) .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 for backend * @param rpc The RPC to transform * @returns A {@link HookFunction} */ const hookGenerator = (rpc:T.HookRPC, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => { const argsArr = extractArgs(rpc.hook) argsArr.pop() //remove 'callback' from the end const argsStr = argsArr.join(',') if(sesameFn){ const args = ['sesame', ...argsArr].join(',') const f = eval(`(socket) => async (`+args+`) => { if(!sesameFn(sesame)) return const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => { if(rpc.onCallback) rpc.onCallback.apply({}, cbargs) socket.call.apply(socket, [res.uuid, ...cbargs]) }) return res }`) return f } 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]) }) return res }`) } /** * Extract a string list of parameters from a function * @param f The source function */ const extractArgs = (f:Function):string[] => { let fn:string 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(extension:T):SubscriptionResponse & T{ return { result: "Success", uuid: uuidv4(), ...extension } } export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction { if(typeof sesame === 'function'){ return sesame } return (testSesame : string) => { return testSesame === sesame } }