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.

RPCSocketServer.ts 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import http = require('http');
  2. import bsock = require('bsock');
  3. import * as uuid from "uuid/v4"
  4. import { Socket } from "./RPCSocketServer"
  5. type rpcType = 'hook' | 'unhook' | 'call'
  6. type visibility = 'public' | 'private'
  7. export type Outcome = "Success" | "Error"
  8. /* Responses */
  9. export class Response{
  10. constructor(
  11. public message?:string
  12. ){}
  13. }
  14. export class SuccessResponse extends Response{
  15. result:Outcome = "Success"
  16. constructor(
  17. message?:string
  18. ){
  19. super(message)
  20. }
  21. }
  22. export class ErrorResponse extends Response{
  23. result:Outcome = "Error"
  24. constructor(
  25. message: string = "Unknown error"
  26. ){
  27. super(message)
  28. }
  29. }
  30. export class SubscriptionResponse extends SuccessResponse{
  31. constructor(
  32. public uid: string,
  33. message?:string
  34. ){
  35. super(message)
  36. }
  37. }
  38. export type UnhookFunction = (uid:string) => Promise<SuccessResponse | ErrorResponse>
  39. export type callbackFunction = (...args) => Promise<SubscriptionResponse | ErrorResponse>
  40. export type AsyncFunction = (...args) => Promise<any>
  41. export interface RPCExporter{
  42. name: string
  43. exportRPCs() : socketioRPC[]
  44. }
  45. type baseRPC = {
  46. type: rpcType
  47. name: string
  48. visibility: visibility
  49. }
  50. type hookRPC = baseRPC & {
  51. type: 'hook'
  52. func: callbackFunction
  53. unhook: UnhookFunction
  54. }
  55. type unhookRPC = baseRPC & {
  56. type: 'unhook'
  57. func: UnhookFunction
  58. }
  59. type callRPC = baseRPC & {
  60. type: 'call'
  61. func: (...args) => Promise<any>
  62. }
  63. export type socketioRPC = callRPC | unhookRPC | hookRPC
  64. export type baseInfo = {
  65. owner: string,
  66. argNames: string[],
  67. }
  68. type HookInfo = baseRPC & baseInfo & {
  69. type: 'hook',
  70. generator: (socket) => callbackFunction
  71. unhook: UnhookFunction
  72. }
  73. type UnhookInfo = baseRPC & baseInfo & {
  74. type: 'unhook',
  75. func: UnhookFunction
  76. }
  77. type CallInfo = baseRPC & baseInfo & {
  78. type: 'call',
  79. func: AsyncFunction
  80. }
  81. type RpcInfo = HookInfo | UnhookInfo | CallInfo
  82. export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
  83. export const rpcToRpcinfo = (rpc : socketioRPC, owner: string):RpcInfo => {
  84. switch(rpc.type){
  85. case "call" :
  86. return {
  87. owner: owner,
  88. argNames: extractArgs(rpc.func),
  89. type: rpc.type,
  90. visibility: rpc.visibility,
  91. name: rpc.name,
  92. func: rpc.func,
  93. }
  94. case "unhook" :
  95. return {
  96. owner: owner,
  97. argNames: extractArgs(rpc.func),
  98. type: rpc.type,
  99. visibility: rpc.visibility,
  100. name: rpc.name,
  101. func: rpc.func,
  102. }
  103. case "hook" :
  104. const generator = hookGenerator(rpc)
  105. return {
  106. owner: owner,
  107. argNames: extractArgs(generator(undefined)),
  108. type: rpc.type,
  109. visibility: rpc.visibility,
  110. name: rpc.name,
  111. unhook: rpc.unhook,
  112. generator: generator,
  113. }
  114. }
  115. }
  116. export const rpcHooker = (socket: Socket, owner:string, RPCs: socketioRPC[], makeUnique = true):ExtendedRpcInfo[] => {
  117. const suffix = makeUnique?"-"+uuid().substr(0,4):""
  118. return RPCs.map(rpc => rpcToRpcinfo(rpc, owner))
  119. .map(info => {
  120. const ret:any = info
  121. ret.uniqueName = info.name+suffix
  122. switch(info.type){
  123. case "hook":
  124. socket.hook(ret.uniqueName, info.generator(socket))
  125. break;
  126. default:
  127. socket.hook(ret.uniqueName, info.func)
  128. }
  129. socket.on('close', () => socket.unhook(info.name))
  130. return ret
  131. })
  132. }
  133. const hookGenerator = (rpc:hookRPC): HookInfo['generator'] => {
  134. const argsArr = extractArgs(rpc.func)
  135. argsArr.pop()
  136. const args = argsArr.join(',')
  137. return eval(`(socket) => async (`+args+`) => {
  138. const res = await rpc.func(`+args+(args.length!==0?',':'')+` (x) => {
  139. socket.call(res.uid, x)
  140. })
  141. if(res.result == 'Success'){
  142. socket.on('close', async () => {
  143. const unhookRes = await rpc.unhook(res.uid)
  144. console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
  145. })
  146. }
  147. return res
  148. }`)
  149. }
  150. const extractArgs = (f:Function):string[] => {
  151. let fn = String(f)
  152. let args = fn.substr(0, fn.indexOf(")"))
  153. args = args.substr(fn.indexOf("(")+1)
  154. let ret = args.split(",")
  155. return ret
  156. }
  157. type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => Socket
  158. export type Socket = {
  159. port: number
  160. hook: (rpcname: string, ...args: any[]) => Socket
  161. unhook: (rpcname:string) => Socket
  162. call: (rpcname:string, ...args: any[]) => Promise<any>
  163. fire: (rpcname:string, ...args: any[]) => Promise<any>
  164. on: OnFunction
  165. destroy: ()=>void
  166. close: ()=>void
  167. }
  168. export type RPCSocketConf = {
  169. connectionHandler: (socket:Socket) => void
  170. errorHandler: (socket:Socket) => (error:any) => void
  171. closeHandler: (socket:Socket) => () => void
  172. }
  173. export class RPCSocketServer{
  174. private io = bsock.createServer()
  175. private wsServer = http.createServer()
  176. constructor(
  177. private port:number,
  178. private rpcExporters: RPCExporter[] = [],
  179. private conf: RPCSocketConf = {
  180. errorHandler: (socket:Socket) => (error:any) => { socket.destroy(); console.error(error) },
  181. closeHandler: (socket:Socket) => () => { console.log("Socket closing") },
  182. connectionHandler: (socket:Socket) => { console.log("New websocket connection in port "+socket.port) }
  183. }
  184. ){
  185. this.startWebsocket()
  186. }
  187. private startWebsocket(){
  188. try{
  189. this.io.attach(this.wsServer)
  190. this.io.on('socket', (socket:Socket) => {
  191. socket.on('error', this.conf.errorHandler(socket))
  192. socket.on('close', this.conf.closeHandler(socket))
  193. this.initApis(socket)
  194. })
  195. this.wsServer.listen(this.port)
  196. }catch(e){
  197. //@ts-ignore
  198. this.errorHandler(undefined)("Unable to connect to socket")
  199. }
  200. }
  201. protected initApis(socket){
  202. const adminRPCs:socketioRPC[] = [
  203. {
  204. name: 'info',
  205. type: 'call',
  206. visibility: 'private',
  207. func: async () => rpcInfos
  208. }
  209. ]
  210. const rpcInfos:ExtendedRpcInfo[] = [
  211. ...rpcHooker(socket, "Admin", adminRPCs, false),
  212. ...this.rpcExporters.flatMap(exporter => rpcHooker(socket, exporter.name, exporter.exportRPCs()))
  213. ]
  214. }
  215. }