You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Utils.ts 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import * as uuidv4 from "uuid/v4"
  2. import * as T from "./Types";
  3. import * as I from "./Interfaces";
  4. import { SubscriptionResponse } from "./Types";
  5. /**
  6. * Translate an RPC to RPCInfo for serialization.
  7. * @param rpc The RPC to transform
  8. * @param owner The owning RPC group's name
  9. * @throws Error on RPC without name property
  10. */
  11. export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
  12. switch (typeof rpc){
  13. case "object":
  14. if(rpc['call']){
  15. return {
  16. owner: owner,
  17. argNames: extractArgs(rpc['call']),
  18. type: "Call",
  19. name: rpc.name,
  20. call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw makeError(rpc.name)}:rpc['call'], // check & remove sesame
  21. }
  22. }else{
  23. const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame)
  24. return {
  25. owner: owner,
  26. argNames: extractArgs(generator(undefined)),
  27. type: "Hook",
  28. name: rpc.name,
  29. generator: generator,
  30. }
  31. }
  32. case "function":
  33. if(!rpc.name) throw new Error(`
  34. RPC did not provide a name.
  35. \nUse 'funtion name(..){ .. }' syntax instead.
  36. \n
  37. \n<------------OFFENDING RPC:
  38. \n`+rpc.toString()+`
  39. \n>------------OFFENDING RPC`)
  40. return {
  41. owner : owner,
  42. argNames: extractArgs(rpc),
  43. type: "Call",
  44. name: rpc.name,
  45. call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args); throw makeError(rpc.name)}:rpc, // check & remove sesame
  46. }
  47. }
  48. throw new Error("Bad socketIORPC type "+ typeof rpc)
  49. }
  50. /**
  51. * Utility function to apply the RPCs of an {@link RPCExporter}.
  52. * @param socket The websocket (implementation: bsock) to hook on
  53. * @param exporter The exporter
  54. * @param makeUnique @default true Attach a suffix to RPC names
  55. */
  56. export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
  57. const owner = exporter.name
  58. const RPCs = [...exporter.exportRPCs()]
  59. return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, errorHandler, sesame))
  60. .map(info => {
  61. const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
  62. const ret:any = info
  63. ret.uniqueName = info.name+suffix
  64. let rpcFunction
  65. if(info.type === 'Hook')
  66. rpcFunction = info.generator(socket)
  67. else
  68. rpcFunction = info.call
  69. socket.hook(ret.uniqueName, callGenerator(info.name, socket, rpcFunction, errorHandler))
  70. return ret
  71. })
  72. }
  73. /**
  74. * Decorate an RPC with the error handler
  75. * @param rpcFunction the function to decorate
  76. */
  77. const callGenerator = (rpcName : string, socket: I.Socket, rpcFunction : T.AnyFunction, errorHandler: T.ErrorHandler) : T.AnyFunction => {
  78. const argsArr = extractArgs(rpcFunction)
  79. const args = argsArr.join(',')
  80. const argsStr = argsArr.map(stripAfterEquals).join(',')
  81. return eval(`async (`+args+`) => {
  82. try{
  83. return await rpcFunction(`+argsStr+`)
  84. }catch(e){
  85. errorHandler(socket)(e, rpcName, [`+args+`])
  86. }
  87. }`)
  88. }
  89. /**
  90. * Utility function to strip parameters like "a = 3" of their defaults
  91. * @param str The parameter to modify
  92. */
  93. export function stripAfterEquals(str:string):string{
  94. return str.split("=")[0]
  95. }
  96. /**
  97. * Utility function to generate {@link HookFunction} from a RPC for backend
  98. * @param rpc The RPC to transform
  99. * @returns A {@link HookFunction}
  100. */
  101. const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
  102. const argsArr = extractArgs(rpc.hook)
  103. argsArr.pop() //remove 'callback' from the end
  104. const argsStr = argsArr.join(',')
  105. if(sesameFn){
  106. const args = ['sesame', ...argsArr].join(',')
  107. return eval(`(socket) => async (`+args+`) => {
  108. try{
  109. if(!sesameFn(sesame)) return
  110. const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
  111. if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
  112. socket.call.apply(socket, [res.uuid, ...cbargs])
  113. })
  114. return res
  115. }catch(e){
  116. errorHandler(socket)(e, rpc.name, [`+args+`])
  117. }
  118. }`)
  119. }
  120. const args = argsArr.join(',')
  121. return eval(`(socket) => async (`+args+`) => {
  122. try{
  123. const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
  124. if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
  125. socket.call.apply(socket, [res.uuid, ...cbargs])
  126. })
  127. return res
  128. }catch(e){
  129. errorHandler(socket)(e, rpc.name, [`+args+`])
  130. }
  131. }`)
  132. }
  133. const makeError = (callName: string) => {
  134. return new Error("Unhandled Promise rejection: Call not found: "+callName+". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: "+callName)
  135. }
  136. /**
  137. * Extract a string list of parameters from a function
  138. * @param f The source function
  139. */
  140. const extractArgs = (f:Function):string[] => {
  141. let fn:string
  142. return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
  143. }
  144. /**
  145. * Simple utility function to create basic {@link SubscriptionResponse}
  146. * @param uuid optional uuid to use, otherwise defaults to uuid/v4
  147. */
  148. export function makeSubResponse<T extends {} = {}>(extension:T):SubscriptionResponse & T{
  149. return {
  150. result: "Success",
  151. uuid: uuidv4(),
  152. ...extension
  153. }
  154. }
  155. export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction {
  156. if(typeof sesame === 'function'){
  157. return sesame
  158. }
  159. return (testSesame : string) => {
  160. return testSesame === sesame
  161. }
  162. }