123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- 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 = <SubResT = {}>(socket: I.Socket, rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, 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); socket.destroy()}:rpc['call'], // check & remove sesame
- }
- }else{
- const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, 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); throw makeError(rpc.name)}: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<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
- const owner = exporter.name
- const RPCs = [...exporter.exportRPCs()]
-
-
- return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame))
- .map(info => {
- const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
- const ret:any = info
- ret.uniqueName = info.name+suffix
-
- let rpcFunction
-
- if(info.type === 'Hook')
- rpcFunction = info.generator(socket)
- else
- rpcFunction = info.call
-
- socket.hook(ret.uniqueName, callGenerator(info.name, socket, rpcFunction, errorHandler))
- return ret
- })
- }
-
- /**
- * Decorate an RPC with the error handler
- * @param rpcFunction the function to decorate
- */
- const callGenerator = (rpcName : string, socket: I.Socket, rpcFunction : T.AnyFunction, errorHandler: T.ErrorHandler) : T.AnyFunction => {
- const argsArr = extractArgs(rpcFunction)
- const args = argsArr.join(',')
- const argsStr = argsArr.map(stripAfterEquals).join(',')
-
- return eval(`async (`+args+`) => {
- try{
- return await rpcFunction(`+argsStr+`)
- }catch(e){
- errorHandler(socket)(e, rpcName, [`+args+`])
- }
- }`)
- }
-
- /**
- * Utility function to strip parameters like "a = 3" of their defaults
- * @param str The parameter to modify
- */
- export function stripAfterEquals(str:string):string{
- return str.split("=")[0]
- }
-
- /**
- * 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<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
- let argsArr = extractArgs(rpc.hook)
- argsArr.pop() //remove 'callback' from the end
- let callArgs = argsArr.join(',')
-
- const args = sesameFn?(['sesame', ...argsArr].join(','))
- :callArgs
-
- callArgs = appendComma(callArgs)
-
- //note rpc.hook is the associated RPC, not a socket.hook
- return eval(`
- (socket) => async (${args}) => {
- try{
- if(sesameFn && !sesameFn(sesame)) return
- const res = await rpc.hook(${callArgs} (...cbargs) => {
- if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
- socket.call.apply(socket, [res.uuid, ...cbargs])
- })
- return res
- }catch(e){
- errorHandler(socket)(e, ${rpc.name}, [${args}])
- }
- }`)
- }
-
- const makeError = (callName: string) => {
- return new Error("Call not found: "+callName+". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: "+callName)
- }
-
- /**
- * 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<T extends {} = {}>(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
- }
- }
-
-
- export function appendComma(s?:string):string{
- return s?`'${s}',`:""
- }
|