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.

Frontend.ts 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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, tls: boolean = false): Promise<RPCSocket & T.RPCInterface<T>> {
  17. const socket = <RPCSocket & T> new RPCSocket(port, server, tls)
  18. return await socket.connect<T>()
  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 tls: boolean = 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>() : Promise<RPCSocket & T.RPCInterface<T>>{
  85. this.socket = await bsock.connect(this.port, this.server, this.tls)
  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)
  92. break
  93. case 'Hook':
  94. f = this.hookGenerator(i.uniqueName, i.argNames)
  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>> <any> this
  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[]): T.AnyFunction{
  116. const headerArgs = fnArgs.join(",")
  117. const argParams = fnArgs.map(stripAfterEquals).join(",")
  118. return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
  119. }
  120. /**
  121. * Utility {@link HookFunction} generator
  122. * @param fnName The function name
  123. * @param fnArgs A string-list of parameters
  124. */
  125. private hookGenerator(fnName: string, fnArgs:string[]): T.HookFunction{
  126. const headerArgs = fnArgs.join(",")
  127. const argParams = fnArgs.map(stripAfterEquals).join(",")
  128. return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
  129. const r = await this.socket.call("`+fnName+`", `+argParams+`)
  130. if(r.result === 'Success'){
  131. this.socket.hook(r.uuid, callback)
  132. }
  133. return r
  134. } )()` )
  135. }
  136. }