Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

Frontend.ts 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use strict'
  2. import bsock = require('bsock');
  3. import * as T from './Types';
  4. import * as I from './Interfaces';
  5. /**
  6. * Utility function to strip parameters like "a = 3" of their defaults
  7. * @param str The parameter to modify
  8. */
  9. function stripAfterEquals(str:string):string{
  10. return str.split("=")[0]
  11. }
  12. /**
  13. * A websocket-on-steroids with built-in RPC capabilities
  14. */
  15. export class RPCSocket implements I.Socket{
  16. static async makeSocket<T extends T.RPCInterface = T.RPCInterface>(port:number, server: string, sesame?:string, conf?:T.SocketConf): Promise<RPCSocket & T> {
  17. const socket = new RPCSocket(port, server, conf)
  18. return await socket.connect<T>(sesame)
  19. }
  20. private socket: I.Socket
  21. /**
  22. *
  23. * @param port Port to connect to
  24. * @param server Server address
  25. * @param tls @default false use TLS
  26. */
  27. constructor(public port:number, private server: string, private conf:T.SocketConf = { tls: false }){
  28. Object.defineProperty(this, 'socket', {value: undefined, writable: true})
  29. }
  30. /**
  31. * Hooks a handler to a function name. Use {@link call} to trigger it.
  32. * @param name The function name to listen on
  33. * @param handler The handler to attach
  34. */
  35. public hook(name: string, handler: (...args:any[]) => any | Promise<any>){
  36. return this.socket.hook(name, handler)
  37. }
  38. /**
  39. * Removes a {@link hook} listener by name.
  40. * @param name The function name
  41. */
  42. public unhook(name: string){
  43. return this.socket.unhook(name)
  44. }
  45. /**
  46. * Attach a listener to error or close events
  47. * @param type 'error' or 'close'
  48. * @param f The listener to attach
  49. */
  50. public on(type: "error" | "close", f: (e?: any) => void){
  51. return this.socket.on(type, f)
  52. }
  53. /**
  54. * Destroys the socket
  55. */
  56. public destroy(){
  57. return this.socket.destroy()
  58. }
  59. /**
  60. * Closes the socket. It may attempt to reconnect.
  61. */
  62. public close(){
  63. return this.socket.close()
  64. }
  65. /**
  66. * Trigger a hooked handler on the server
  67. * @param rpcname The function to call
  68. * @param args other arguments
  69. */
  70. public async call (rpcname: string, ...args: any[]) : Promise<any>{
  71. return await this.socket.call.apply(this.socket, [rpcname, ...args])
  72. }
  73. /**
  74. * An alternative to call that does not wait for confirmation and doesn't return a value.
  75. * @param rpcname The function to call
  76. * @param args other arguments
  77. */
  78. public async fire(rpcname: string, ...args: any[]) : Promise<void>{
  79. await this.socket.fire.apply(this.socket, [rpcname, ...args])
  80. }
  81. /**
  82. * Connects to the server and attaches available RPCs to this object
  83. */
  84. public async connect<T extends T.RPCInterface= T.RPCInterface>( sesame?: string ) : Promise<RPCSocket & T>{
  85. this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false)
  86. const info:T.ExtendedRpcInfo[] = await this.info()
  87. info.forEach(i => {
  88. let f: any
  89. switch (i.type) {
  90. case 'Call':
  91. f = this.callGenerator(i.uniqueName, i.argNames, sesame)
  92. break
  93. case 'Hook':
  94. f = this.frontEndHookGenerator(i.uniqueName, i.argNames, sesame)
  95. break
  96. }
  97. if(this[i.owner] == null)
  98. this[i.owner] = {}
  99. this[i.owner][i.name] = f
  100. this[i.owner][i.name].bind(this)
  101. })
  102. return <RPCSocket & T.RPCInterface<T>> (this as any)
  103. }
  104. /**
  105. * Get a list of available RPCs from the server
  106. */
  107. public async info(){
  108. return await this.socket.call('info')
  109. }
  110. /**
  111. * Utility {@link AsyncFunction} generator
  112. * @param fnName The function name
  113. * @param fnArgs A string-list of parameters
  114. */
  115. private callGenerator(fnName: string, fnArgs:string[], sesame?:string): T.AnyFunction{
  116. const headerArgs = fnArgs.join(",")
  117. const argParams = fnArgs.map(stripAfterEquals).join(",")
  118. if(!sesame)
  119. return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
  120. else
  121. return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", "'+sesame+'", '+argParams+')} )()' )
  122. }
  123. /**
  124. * Utility {@link HookFunction} generator
  125. * @param fnName The function name
  126. * @param fnArgs A string-list of parameters
  127. */
  128. private frontEndHookGenerator(fnName: string, fnArgs:string[], sesame?:string): T.HookFunction{
  129. fnArgs.pop()
  130. const headerArgs = fnArgs.join(",")
  131. const argParams = fnArgs.map(stripAfterEquals).join(",")
  132. if(!sesame){
  133. return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
  134. const r = await this.socket.call("`+fnName+`", `+argParams+`)
  135. if(r && r.result === 'Success'){
  136. this.socket.hook(r.uuid, callback)
  137. }
  138. return r
  139. } )()` )
  140. }else{
  141. return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
  142. const r = await this.socket.call("`+fnName+`", "`+sesame+`", `+argParams+`)
  143. if(r && r.result === 'Success'){
  144. this.socket.hook(r.uuid, callback)
  145. }
  146. return r
  147. } )()` )
  148. }
  149. }
  150. }