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.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. private socket: I.Socket
  17. /**
  18. *
  19. * @param port Port to connect to
  20. * @param server Server address
  21. * @param tls @default false use TLS
  22. */
  23. constructor(public port:number, private server: string, private tls: boolean = false){
  24. Object.defineProperty(this, 'socket', {value: undefined, writable: true})
  25. }
  26. /**
  27. * Hooks a handler to a function name. Use {@link call} to trigger it.
  28. * @param name The function name to listen on
  29. * @param handler The handler to attach
  30. */
  31. public hook(name: T.Name, handler: (...args:any[]) => any | Promise<any>){
  32. return this.socket.hook(name, handler)
  33. }
  34. /**
  35. * Removes a {@link hook} listener by name.
  36. * @param name The function name
  37. */
  38. public unhook(name: T.Name){
  39. return this.socket.unhook(name)
  40. }
  41. /**
  42. * Attach a listener to error or close events
  43. * @param type 'error' or 'close'
  44. * @param f The listener to attach
  45. */
  46. public on(type: "error" | "close", f: (e?: any) => void){
  47. return this.socket.on(type, f)
  48. }
  49. /**
  50. * Destroys the socket
  51. */
  52. public destroy(){
  53. return this.socket.destroy()
  54. }
  55. /**
  56. * Closes the socket. It may attempt to reconnect.
  57. */
  58. public close(){
  59. return this.socket.close()
  60. }
  61. /**
  62. * Trigger a hooked handler on the server
  63. * @param rpcname The function to call
  64. * @param args other arguments
  65. */
  66. public async call (rpcname: T.Name, ...args: T.Any[]) : Promise<T.Any>{
  67. return await this.socket.call.apply(this.socket, [rpcname, ...args])
  68. }
  69. /**
  70. * An alternative to call that does not wait for confirmation and doesn't return a value.
  71. * @param rpcname The function to call
  72. * @param args other arguments
  73. */
  74. public async fire(rpcname: T.Name, ...args: T.Any[]) : Promise<void>{
  75. await this.socket.fire.apply(this.socket, [rpcname, ...args])
  76. }
  77. /**
  78. * Connects to the server and attaches available RPCs to this object
  79. */
  80. public async connect(){
  81. this.socket = await bsock.connect(this.port, this.server, this.tls)
  82. const info:T.ExtendedRpcInfo[] = await this.info()
  83. info.forEach(i => {
  84. let f: any
  85. switch (i.type) {
  86. case 'Call':
  87. f = this.callGenerator(i.uniqueName, i.argNames)
  88. break
  89. case 'Hook':
  90. f = this.hookGenerator(i.uniqueName, i.argNames)
  91. break
  92. }
  93. if(this[i.owner] == null)
  94. this[i.owner] = {}
  95. this[i.owner][i.name] = f
  96. this[i.owner][i.name].bind(this)
  97. })
  98. }
  99. /**
  100. * Get a list of available RPCs from the server
  101. */
  102. public async info(){
  103. return await this.socket.call('info')
  104. }
  105. /**
  106. * Utility {@link AsyncFunction} generator
  107. * @param fnName The function name
  108. * @param fnArgs A string-list of parameters
  109. */
  110. private callGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.AsyncFunction{
  111. const headerArgs = fnArgs.join(",")
  112. const argParams = fnArgs.map(stripAfterEquals).join(",")
  113. return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
  114. }
  115. /**
  116. * Utility {@link HookFunction} generator
  117. * @param fnName The function name
  118. * @param fnArgs A string-list of parameters
  119. */
  120. private hookGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.HookFunction{
  121. const headerArgs = fnArgs.join(",")
  122. const argParams = fnArgs.map(stripAfterEquals).join(",")
  123. return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
  124. const r = await this.socket.call("`+fnName+`", `+argParams+`)
  125. if(r.result === 'Success'){
  126. this.socket.hook(r.uuid, callback)
  127. }
  128. return r
  129. } )()` )
  130. }
  131. }