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

Frontend.ts 4.9KB

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