您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Utils.ts 6.0KB

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