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.

Backend.ts 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. 'use strict'
  2. import http = require('http');
  3. import { PromiseIO } from "./PromiseIO/Server";
  4. import * as T from './Types';
  5. import * as U from './Utils';
  6. import * as I from './Interfaces';
  7. export class RPCServer<
  8. InterfaceT extends T.RPCInterface = T.RPCInterface,
  9. > {
  10. private pio = new PromiseIO()
  11. private closeHandler: T.CloseHandler
  12. private errorHandler: T.ErrorHandler
  13. private connectionHandler: T.ConnectionHandler
  14. private sesame?: T.SesameFunction
  15. private accessFilter: T.AccessFilter<InterfaceT>
  16. private attached = false
  17. /**
  18. * @throws On RPC with no name
  19. * @param port The port to listen on
  20. * @param exporters A list of {@link RPCExporter} to publish
  21. * @param conf A {@link SocketConf} object with optional settings
  22. */
  23. constructor(
  24. private exporters: T.ExporterArray<InterfaceT> = [],
  25. conf: T.ServerConf<InterfaceT> = {},
  26. ) {
  27. if (conf.sesame) {
  28. this.sesame = U.makeSesameFunction(conf.sesame)
  29. }
  30. this.accessFilter = conf.accessFilter || (async (sesame) => {
  31. if (!this.sesame) return true
  32. return this.sesame!(sesame!)
  33. })
  34. this.errorHandler = (socket: I.Socket | PromiseIO) => (error: any, rpcName: string, args: any[]) => {
  35. if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
  36. else throw error
  37. }
  38. this.closeHandler = (socket: I.Socket) => {
  39. if (conf.closeHandler) conf.closeHandler(socket)
  40. }
  41. this.connectionHandler = (socket: I.Socket) => {
  42. if (conf.connectionHandler) conf.connectionHandler(socket)
  43. }
  44. exporters.forEach(U.fixNames) //TSC for some reason doesn't preserve name properties of methods
  45. let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function"?ex.RPCs():(ex as any)).find(rpc => !rpc.name)
  46. if (badRPC) {
  47. throw new Error(`
  48. RPC did not provide a name.
  49. \nUse 'funtion name(..){ .. }' syntax instead.
  50. \n
  51. \n<------------OFFENDING RPC:
  52. \n`+ badRPC.toString() + `
  53. \n>------------OFFENDING RPC`)
  54. }
  55. try {
  56. this.pio.on('socket', (clientSocket: I.Socket) => {
  57. const sock:any = clientSocket;
  58. clientSocket.on('disconnect', () => this.closeHandler(clientSocket))
  59. this.connectionHandler(clientSocket)
  60. this.initRPCs(clientSocket)
  61. })
  62. } catch (e) {
  63. this.errorHandler(this.pio, e, 'system', [])
  64. }
  65. }
  66. public attach = (httpServer = new http.Server()) : RPCServer<InterfaceT> => {
  67. this.pio.attach(httpServer)
  68. this.attached = true
  69. return this
  70. }
  71. public listen(port:number) : RPCServer<InterfaceT>{
  72. if(!this.attached) this.attach()
  73. this.pio.listen(port)
  74. return this
  75. }
  76. protected initRPCs(clientSocket: I.Socket) {
  77. clientSocket.hook('info', async (sesame?: string) => {
  78. const rpcs = await Promise.all(this.exporters.map(async exp => {
  79. const allowed = await this.accessFilter(sesame, exp)
  80. if (!allowed) return []
  81. const infos = U.rpcHooker(clientSocket, exp, this.errorHandler, this.sesame)
  82. return infos
  83. }))
  84. return rpcs.flat()
  85. })
  86. }
  87. close(): void {
  88. this.pio.close()
  89. }
  90. }