Browse Source

fix listener bugs

master
peter 4 years ago
parent
commit
8352ac1bf7
9 changed files with 278 additions and 144 deletions
  1. 1
    1
      package-lock.json
  2. 12
    10
      src/Backend.ts
  3. 13
    9
      src/Frontend.ts
  4. 1
    1
      src/Interfaces.ts
  5. 8
    3
      src/PromiseIO/Client.ts
  6. 15
    6
      src/PromiseIO/Server.ts
  7. 4
    4
      src/Types.ts
  8. 54
    57
      src/Utils.ts
  9. 170
    53
      test/Test.ts

+ 1
- 1
package-lock.json View File

1
 {
1
 {
2
   "name": "rpclibrary",
2
   "name": "rpclibrary",
3
-  "version": "2.0.0",
3
+  "version": "2.2.1",
4
   "lockfileVersion": 1,
4
   "lockfileVersion": 1,
5
   "requires": true,
5
   "requires": true,
6
   "dependencies": {
6
   "dependencies": {

+ 12
- 10
src/Backend.ts View File

10
     InterfaceT extends T.RPCInterface = T.RPCInterface,
10
     InterfaceT extends T.RPCInterface = T.RPCInterface,
11
 > {
11
 > {
12
 
12
 
13
-    private pio = PromiseIO.createServer()
13
+    private pio = new PromiseIO()
14
     private closeHandler: T.CloseHandler
14
     private closeHandler: T.CloseHandler
15
     private errorHandler: T.ErrorHandler
15
     private errorHandler: T.ErrorHandler
16
     private connectionHandler: T.ConnectionHandler
16
     private connectionHandler: T.ConnectionHandler
37
             return this.sesame!(sesame!)
37
             return this.sesame!(sesame!)
38
         })
38
         })
39
 
39
 
40
-
41
         this.errorHandler = (socket: I.Socket | PromiseIO) => (error: any, rpcName: string, args: any[]) => {
40
         this.errorHandler = (socket: I.Socket | PromiseIO) => (error: any, rpcName: string, args: any[]) => {
42
             if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
41
             if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
43
             else throw error
42
             else throw error
65
         }
64
         }
66
         
65
         
67
         try {
66
         try {
68
-            this.pio.on('socket', (socket: I.Socket) => {
69
-                socket.on('error', (err) => this.errorHandler(socket, err, "system", []))
70
-                socket.on('close', () => this.closeHandler(socket))
71
-                this.connectionHandler(socket)
72
-                this.initRPCs(socket)
67
+
68
+            this.pio.on('socket', (clientSocket: I.Socket) => {
69
+                const sock:any = clientSocket;
70
+                clientSocket.on('disconnect', () => this.closeHandler(clientSocket))
71
+                this.connectionHandler(clientSocket)
72
+                this.initRPCs(clientSocket)
73
+                
73
             })
74
             })
74
         } catch (e) {
75
         } catch (e) {
75
             this.errorHandler(this.pio, e, 'system', [])
76
             this.errorHandler(this.pio, e, 'system', [])
88
         return this
89
         return this
89
     }
90
     }
90
 
91
 
91
-    protected initRPCs(socket: I.Socket) {
92
-        socket.hook('info', async (sesame?: string) => {
92
+    protected initRPCs(clientSocket: I.Socket) {
93
+        clientSocket.hook('info', async (sesame?: string) => {
93
             const rpcs = await Promise.all(this.exporters.map(async exp => {
94
             const rpcs = await Promise.all(this.exporters.map(async exp => {
94
                 const allowed = await this.accessFilter(sesame, exp)
95
                 const allowed = await this.accessFilter(sesame, exp)
95
                 if (!allowed) return []
96
                 if (!allowed) return []
96
-                return U.rpcHooker(socket, exp, this.errorHandler, this.sesame)
97
+                const infos = U.rpcHooker(clientSocket, exp, this.errorHandler, this.sesame)
98
+                return infos
97
             }))
99
             }))
98
             return rpcs.flat()
100
             return rpcs.flat()
99
         })
101
         })

+ 13
- 9
src/Frontend.ts View File

16
         return await socket.connect(sesame)
16
         return await socket.connect(sesame)
17
     }
17
     }
18
 
18
 
19
-    private protocol: 'http:' | 'https:'
19
+    private protocol: 'http' | 'https'
20
     private socket: I.Socket
20
     private socket: I.Socket
21
     private handlers : {
21
     private handlers : {
22
         [name in string]: T.AnyFunction[]
22
         [name in string]: T.AnyFunction[]
32
      * @param server Server address
32
      * @param server Server address
33
      * @param tls @default false use TLS
33
      * @param tls @default false use TLS
34
      */
34
      */
35
-    constructor(public port:number, private server: string, conf:T.SocketConf = { tls: false }){
35
+    constructor(public port:number, public address: string, conf:T.SocketConf = { tls: false }){
36
         Object.defineProperty(this, 'socket', {value: undefined, writable: true})
36
         Object.defineProperty(this, 'socket', {value: undefined, writable: true})
37
-        this.protocol = conf.tls ? "https:" : "http:"
37
+        this.protocol = conf.tls ? "https" : "http"
38
     }
38
     }
39
 
39
 
40
     /**
40
     /**
141
     public async connect( sesame?: string ) : Promise<T.ConnectedSocket<Ifc>> {
141
     public async connect( sesame?: string ) : Promise<T.ConnectedSocket<Ifc>> {
142
 
142
 
143
         try{
143
         try{
144
-            this.socket = await PromiseIOClient.connect(this.port, this.server, this.protocol)
144
+            this.socket = await PromiseIOClient.connect(this.port, this.address, this.protocol)
145
         }catch(e){
145
         }catch(e){
146
             this.handlers['error'].forEach(h => h(e))
146
             this.handlers['error'].forEach(h => h(e))
147
             throw e
147
             throw e
215
         sesame = appendComma(sesame, true)
215
         sesame = appendComma(sesame, true)
216
         headerArgs = fnArgs.length>0?headerArgs+",":headerArgs 
216
         headerArgs = fnArgs.length>0?headerArgs+",":headerArgs 
217
 
217
 
218
-        return eval( `
219
-        async (${headerArgs} callback) => {
218
+        const frontendHookStr = `
219
+        async (${headerArgs} $__callback__$) => {
220
             const r = await this.call("${fnName}", ${sesame} ${argParams})
220
             const r = await this.call("${fnName}", ${sesame} ${argParams})
221
             try{
221
             try{
222
                 if(r){
222
                 if(r){
223
                     if(r.uuid){
223
                     if(r.uuid){
224
-                        callback['destroy'] = () => { this.socket.unhook(r.uuid) }
225
-                        this.socket.hook(r.uuid, callback)
224
+                        $__callback__$['destroy'] = () => {
225
+                            this.socket.fire(r.uuid)
226
+                            this.socket.unhook(r.uuid) 
227
+                        }
228
+                        this.socket.hook(r.uuid, $__callback__$)
226
                     }
229
                     }
227
                     return r.return
230
                     return r.return
228
                 }else{
231
                 }else{
231
             }catch(e){
234
             }catch(e){
232
                 throw e
235
                 throw e
233
             }
236
             }
234
-        }`)
237
+        }`
238
+        return eval(frontendHookStr)
235
     }
239
     }
236
 }
240
 }

+ 1
- 1
src/Interfaces.ts View File

20
     call: (rpcname: string, ...args: any[]) => Promise<any>
20
     call: (rpcname: string, ...args: any[]) => Promise<any>
21
     fire: (rpcname: string, ...args: any[]) => Promise<any>
21
     fire: (rpcname: string, ...args: any[]) => Promise<any>
22
     on: (type: string, f: T.AnyFunction)=>any
22
     on: (type: string, f: T.AnyFunction)=>any
23
-    emit: (eventName: string, data: any) => void
23
+    emit: (eventName: string, ...args: any[]) => void
24
     close(): void
24
     close(): void
25
   }
25
   }

+ 8
- 3
src/PromiseIO/Client.ts View File

5
 
5
 
6
 export class PromiseIOClient {
6
 export class PromiseIOClient {
7
     
7
     
8
-    static connect = (port: number, host = "localhost", protocol : 'http:' | 'https:' = "http:"): Promise<I.Socket> => new Promise((res, rej) => {
8
+    static connect = (port: number, host = "localhost", protocol : 'http' | 'https' = "http"): Promise<I.Socket> => new Promise((res, rej) => {
9
         try {
9
         try {
10
-            const socket = socketio(`${protocol}//${host}:${port}`, {
10
+            const address = `${host}:${port}`
11
+            const socket = socketio(`${protocol}://${address}`, {
11
                 reconnectionAttempts: 2,
12
                 reconnectionAttempts: 2,
12
                 reconnectionDelay: 200,
13
                 reconnectionDelay: 200,
13
                 timeout: 450,
14
                 timeout: 450,
14
                 reconnection: false,
15
                 reconnection: false,
15
             })
16
             })
17
+
16
             socket.on('connect_error', e => {
18
             socket.on('connect_error', e => {
17
                 sock.emit('error', e)
19
                 sock.emit('error', e)
18
                 rej(e) 
20
                 rej(e) 
19
             })
21
             })
20
 
22
 
23
+            socket['address'] = address
21
             const sock = U.makePioSocket(socket)
24
             const sock = U.makePioSocket(socket)
22
-            socket.on('connect', ()=>{ res(sock) })
25
+            socket.on('connect', ()=>{
26
+                res(sock) 
27
+            })
23
 
28
 
24
 
29
 
25
             /*
30
             /*

+ 15
- 6
src/PromiseIO/Server.ts View File

4
 import * as T from '../Types'
4
 import * as T from '../Types'
5
 import socketio = require('socket.io')
5
 import socketio = require('socket.io')
6
 
6
 
7
+
7
 export class PromiseIO {
8
 export class PromiseIO {
8
     io?: Server
9
     io?: Server
9
     httpServer: httpServer
10
     httpServer: httpServer
12
         connect: []
13
         connect: []
13
     }
14
     }
14
 
15
 
15
-    static createServer(): PromiseIO {
16
-        return new PromiseIO();
17
-    }
18
-
19
     attach(httpServer: httpServer) {
16
     attach(httpServer: httpServer) {
20
         this.httpServer = httpServer
17
         this.httpServer = httpServer
21
         this.io = socketio(httpServer, { cookie:false })
18
         this.io = socketio(httpServer, { cookie:false })
22
-        this.io!.on('connection', (sock: Socket) => {
23
-            const pioSock = U.makePioSocket(sock)
19
+
20
+        this.io!.on('connection', (clientSocket: Socket) => {
21
+            console.log(this.listeners);
22
+
23
+            clientSocket.use((packet, next) => {
24
+                console.log(this.listeners[packet[0]]);
25
+                
26
+                console.log(packet[0]);
27
+                next()
28
+            })
29
+    
30
+
31
+            clientSocket['address'] = clientSocket.handshake.headers["x-real-ip"] || clientSocket.handshake.address
32
+            const pioSock = U.makePioSocket(clientSocket)
24
             this.listeners['socket'].forEach(listener => listener(pioSock))
33
             this.listeners['socket'].forEach(listener => listener(pioSock))
25
             this.listeners['connect'].forEach(listener => listener(pioSock))
34
             this.listeners['connect'].forEach(listener => listener(pioSock))
26
             /*
35
             /*

+ 4
- 4
src/Types.ts View File

5
 export type PioBindListener = (...args: any) => void
5
 export type PioBindListener = (...args: any) => void
6
 export type PioHookListener = AnyFunction
6
 export type PioHookListener = AnyFunction
7
 
7
 
8
-
9
-
10
 export type AnyFunction = (...args:any) => any
8
 export type AnyFunction = (...args:any) => any
11
 export type HookFunction = AnyFunction
9
 export type HookFunction = AnyFunction
12
 export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean
10
 export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean
57
     name: Name
55
     name: Name
58
     hook: Func
56
     hook: Func
59
     onCallback?: AnyFunction
57
     onCallback?: AnyFunction
60
-    onClose?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
58
+    onDestroy?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
61
 }
59
 }
62
 
60
 
63
 export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name,Func> | Func
61
 export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name,Func> | Func
101
 
99
 
102
 export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: {[rpcname in keyof Ifc[grp]] : AsyncAnyFunction<Ifc[grp][rpcname]> } }
100
 export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: {[rpcname in keyof Ifc[grp]] : AsyncAnyFunction<Ifc[grp][rpcname]> } }
103
 
101
 
104
-export type AsyncAnyFunction<F extends AnyFunction = AnyFunction> = F extends (...args: Parameters<F>) => infer R ? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R> ) : Promise<any>
102
+export type AsyncAnyFunction<F extends AnyFunction = AnyFunction> = F extends (...args: Parameters<F>) => infer R 
103
+                                                                    ? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R> ) 
104
+                                                                    : Promise<any>

+ 54
- 57
src/Utils.ts View File

2
 
2
 
3
 import * as T from "./Types";
3
 import * as T from "./Types";
4
 import * as I from "./Interfaces";
4
 import * as I from "./Interfaces";
5
-import { Server as ioServer, Socket as ioSocket, Socket } from "socket.io"
6
-import { Socket as ioClientSocket } from "socket.io-client"
5
+import { Socket } from "socket.io"
7
 
6
 
8
 /**
7
 /**
9
  * Translate an RPC to RPCInfo for serialization.
8
  * Translate an RPC to RPCInfo for serialization.
17
     switch (typeof rpc) {
16
     switch (typeof rpc) {
18
         case "object":
17
         case "object":
19
             if (rpc['call']) {
18
             if (rpc['call']) {
19
+                const _rpc: T.CallRPC<any, any> = rpc
20
                 return {
20
                 return {
21
                     owner: owner,
21
                     owner: owner,
22
                     argNames: extractArgs(rpc['call']),
22
                     argNames: extractArgs(rpc['call']),
23
                     type: "Call",
23
                     type: "Call",
24
                     name: rpc.name,
24
                     name: rpc.name,
25
-                    call: sesame ? async (_sesame, ...args) => { if (sesame(_sesame)) return await rpc['call'].apply({}, args); socket.close() } : rpc['call'], // check & remove sesame 
25
+                    call: sesame ? async ($__sesame__$, ...args) => { if (sesame($__sesame__$)) return await rpc['call'].apply({}, args); socket.close() } : rpc['call'], // check & remove sesame 
26
                 }
26
                 }
27
             } else {
27
             } else {
28
-                const generator = hookGenerator(<T.HookRPC<any, any>>rpc, errorHandler, sesame)
28
+                const _rpc: T.HookRPC<any, any> = rpc
29
+                const generator = hookGenerator(_rpc, errorHandler, sesame)
29
                 return {
30
                 return {
30
                     owner: owner,
31
                     owner: owner,
31
                     argNames: extractArgs(generator(undefined)),
32
                     argNames: extractArgs(generator(undefined)),
32
                     type: "Hook",
33
                     type: "Hook",
33
-                    name: rpc.name,
34
+                    name: _rpc.name,
34
                     generator: generator,
35
                     generator: generator,
35
                 }
36
                 }
36
             }
37
             }
47
                 argNames: extractArgs(rpc),
48
                 argNames: extractArgs(rpc),
48
                 type: "Call",
49
                 type: "Call",
49
                 name: rpc.name,
50
                 name: rpc.name,
50
-                call: sesame ? async (_sesame, ...args) => { if (sesame(_sesame)) return await rpc.apply({}, args); throw makeError(rpc.name) } : rpc, // check & remove sesame 
51
+                call: sesame ? async ($__sesame__$, ...args) => { if (sesame($__sesame__$)) return await rpc.apply({}, args); throw makeError(rpc.name) } : rpc, // check & remove sesame 
51
             }
52
             }
52
     }
53
     }
53
     throw new Error("Bad socketIORPC type " + typeof rpc)
54
     throw new Error("Bad socketIORPC type " + typeof rpc)
55
 
56
 
56
 /**
57
 /**
57
  * Utility function to apply the RPCs of an {@link RPCExporter}.
58
  * Utility function to apply the RPCs of an {@link RPCExporter}.
58
- * @param socket The websocket (implementation: bsock) to hook on
59
+ * @param serverSocket The websocket (implementation: socket.io) to hook on
59
  * @param exporter The exporter
60
  * @param exporter The exporter
60
  * @param makeUnique @default true Attach a suffix to RPC names
61
  * @param makeUnique @default true Attach a suffix to RPC names
61
  */
62
  */
62
-export function rpcHooker(socket: I.Socket, exporter: I.RPCExporter<any, any>, errorHandler: T.ErrorHandler, sesame?: T.SesameFunction, makeUnique = true): T.ExtendedRpcInfo[] {
63
+export function rpcHooker(serverSocket: I.Socket, exporter: I.RPCExporter<any, any>, errorHandler: T.ErrorHandler, sesame?: T.SesameFunction, makeUnique = true): T.ExtendedRpcInfo[] {
63
     const owner = exporter.name
64
     const owner = exporter.name
64
     const RPCs = typeof exporter.RPCs === "function" ? exporter.RPCs() : exporter.RPCs
65
     const RPCs = typeof exporter.RPCs === "function" ? exporter.RPCs() : exporter.RPCs
65
 
66
 
66
-    return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame))
67
+    return RPCs
68
+        .map(rpc => rpcToRpcinfo(serverSocket, rpc, owner, errorHandler, sesame))
67
         .map(info => {
69
         .map(info => {
68
             const suffix = makeUnique ? "-" + uuidv4().substr(0, 4) : ""
70
             const suffix = makeUnique ? "-" + uuidv4().substr(0, 4) : ""
69
             const ret: any = info
71
             const ret: any = info
70
             ret.uniqueName = info.name + suffix
72
             ret.uniqueName = info.name + suffix
71
-            let rpcFunction = info.type === 'Hook' ? info.generator(socket)
72
-                : info.call
73
-
74
-            socket.hook(ret.uniqueName, callGenerator(info.name, socket, rpcFunction, errorHandler))
73
+            let rpcFunction = info.type === 'Hook' ? info.generator(serverSocket) : info.call
74
+            serverSocket.hook(ret.uniqueName, callGenerator(info.name, serverSocket, rpcFunction, errorHandler))
75
             return ret
75
             return ret
76
         })
76
         })
77
 }
77
 }
80
  * Decorate an RPC with the error handler
80
  * Decorate an RPC with the error handler
81
  * @param rpcFunction the function to decorate
81
  * @param rpcFunction the function to decorate
82
  */
82
  */
83
-const callGenerator = (rpcName: string, socket: I.Socket, rpcFunction: T.AnyFunction, errorHandler: T.ErrorHandler): T.AnyFunction => {
83
+const callGenerator = (rpcName: string, $__socket__$: I.Socket, rpcFunction: T.AnyFunction, errorHandler: T.ErrorHandler): T.AnyFunction => {
84
     const argsArr = extractArgs(rpcFunction)
84
     const argsArr = extractArgs(rpcFunction)
85
     const args = argsArr.join(',')
85
     const args = argsArr.join(',')
86
     const argsStr = argsArr.map(stripAfterEquals).join(',')
86
     const argsStr = argsArr.map(stripAfterEquals).join(',')
87
 
87
 
88
-    return eval(`async (` + args + `) => {
88
+    const callStr = `async (${args}) => {
89
         try{
89
         try{
90
-            return await rpcFunction(`+ argsStr + `)
90
+            return await rpcFunction(${argsStr})
91
         }catch(e){
91
         }catch(e){
92
-            errorHandler(socket)(e, rpcName, [`+ args + `])
92
+            errorHandler($__socket__$)(e, rpcName, [${args}])
93
         }
93
         }
94
-    }`)
94
+    }`
95
+
96
+    return eval(callStr);
95
 }
97
 }
96
 
98
 
97
 /**
99
 /**
107
  * @param rpc The RPC to transform
109
  * @param rpc The RPC to transform
108
  * @returns A {@link HookFunction}
110
  * @returns A {@link HookFunction}
109
  */
111
  */
110
-const hookGenerator = (rpc: T.HookRPC<any, any>, /*not unused!*/ errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
111
-
112
+const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
112
     let argsArr = extractArgs(rpc.hook)
113
     let argsArr = extractArgs(rpc.hook)
113
-    argsArr.pop() //remove 'callback' from the end
114
-    let callArgs = argsArr.join(',')
114
+    argsArr.pop() //remove callback param
115
 
115
 
116
+    let callArgs = argsArr.join(',')
116
     const args = sesameFn ? (['sesame', ...argsArr].join(','))
117
     const args = sesameFn ? (['sesame', ...argsArr].join(','))
117
-        : callArgs
118
+                          : callArgs
118
 
119
 
119
     callArgs = appendComma(callArgs, false)
120
     callArgs = appendComma(callArgs, false)
120
 
121
 
121
-    //note rpc.hook is the associated RPC, not a socket.hook
122
-    return eval(`
123
-    (clientSocket) => async (${args}) => {
122
+    const hookStr = `
123
+    ($__socket__$) => async (${args}) => {
124
         try{
124
         try{
125
             if(sesameFn && !sesameFn(sesame)) return
125
             if(sesameFn && !sesameFn(sesame)) return
126
             const uuid = uuidv4()
126
             const uuid = uuidv4()
127
             const res = await rpc.hook(${callArgs} (...cbargs) => {
127
             const res = await rpc.hook(${callArgs} (...cbargs) => {
128
-                if(rpc.onCallback){
129
-                    rpc.onCallback.apply({}, cbargs)
130
-                }
131
-                clientSocket.call.apply(clientSocket, [uuid, ...cbargs])
128
+                ${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
129
+                $__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
132
             })
130
             })
133
-            if(rpc.onClose){
134
-                clientSocket.on('close', () => rpc.onClose(res, rpc))
135
-            }
131
+            ${rpc.onDestroy ? `$__socket__$.bind(uuid, () => {
132
+                rpc.onDestroy(res, rpc)
133
+            })` : ``}
136
             return {'uuid': uuid, 'return': res}
134
             return {'uuid': uuid, 'return': res}
137
         }catch(e){
135
         }catch(e){
138
             //can throw to pass exception to client or swallow to keep it local
136
             //can throw to pass exception to client or swallow to keep it local
139
-            errorHandler(clientSocket)(e, ${rpc.name}, [${args}])
137
+            errorHandler($__socket__$)(e, ${rpc.name}, [${args}])
140
         }
138
         }
141
-    }`)
142
-}
139
+    }`
143
 
140
 
144
-const makeError = (callName: string) => {
145
-    return new Error("Call not found: " + callName + ". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: " + callName)
141
+    return eval(hookStr)
146
 }
142
 }
147
 
143
 
144
+const makeError = (callName: string) =>  new Error(`Call not found: ${callName}. ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: ${callName}`)
145
+
148
 /**
146
 /**
149
  * Extract a string list of parameters from a function
147
  * Extract a string list of parameters from a function
150
  * @param f The source function
148
  * @param f The source function
189
     })
187
     })
190
 }
188
 }
191
 
189
 
190
+/**
191
+ * Transforms a socket.io instance into one conforming to I.Socket
192
+ * @param socket A socket.io socket
193
+ */
192
 export const makePioSocket = (socket: any): I.Socket => {
194
 export const makePioSocket = (socket: any): I.Socket => {
193
-    return {
194
-        bind: (name: string, listener: T.PioBindListener) => socket.on(name, (...args: any) => {
195
-            const ack = args.pop()
196
-            listener.apply(null, args)
197
-            ack()
198
-        }),
195
+    return <I.Socket>{
196
+        id: socket.id,
197
+        bind: (name: string, listener: T.PioBindListener) => socket.on(name, (...args: any) => listener.apply(null, args)),
199
 
198
 
200
         hook: (name: string, listener: T.PioHookListener) => {
199
         hook: (name: string, listener: T.PioHookListener) => {
201
             const args = extractArgs(listener)
200
             const args = extractArgs(listener)
202
             let argNames
201
             let argNames
203
             let restParam = args.find(e => e.includes('...'))
202
             let restParam = args.find(e => e.includes('...'))
204
-            if(!restParam){
205
-                argNames = [...args, '...__args__'].join(',')
206
-                restParam = '__args__'
207
-            }else{
203
+            if (!restParam) {
204
+                argNames = [...args, '...$__args__$'].join(',')
205
+                restParam = '$__args__$'
206
+            } else {
208
                 argNames = [...args].join(',')
207
                 argNames = [...args].join(',')
209
-                restParam = restParam.replace('...','')
208
+                restParam = restParam.replace('...', '')
210
             }
209
             }
211
 
210
 
212
             const decoratedListener = eval(`(() => async (${argNames}) => {
211
             const decoratedListener = eval(`(() => async (${argNames}) => {
229
         call: (name: string, ...args: any) => {
228
         call: (name: string, ...args: any) => {
230
             return new Promise((res, rej) => {
229
             return new Promise((res, rej) => {
231
                 const params: any = [name, ...args, (resp) => {
230
                 const params: any = [name, ...args, (resp) => {
232
-                    if(isError(resp)){
231
+                    if (isError(resp)) {
233
                         const err = new Error()
232
                         const err = new Error()
234
                         err.stack = resp.stack
233
                         err.stack = resp.stack
235
                         err.name = resp.name
234
                         err.name = resp.name
238
                     }
237
                     }
239
                     res(resp)
238
                     res(resp)
240
                 }]
239
                 }]
241
-                socket.emit.apply(socket, params)                    
240
+                socket.emit.apply(socket, params)
242
             })
241
             })
243
         },
242
         },
244
 
243
 
256
             }
255
             }
257
         },
256
         },
258
 
257
 
259
-        id: socket.id,
260
         on: (...args) => socket.on.apply(socket, args),
258
         on: (...args) => socket.on.apply(socket, args),
261
         emit: (...args) => socket.emit.apply(socket, args),
259
         emit: (...args) => socket.emit.apply(socket, args),
262
         close: () => {
260
         close: () => {
263
-            socket
264
             socket.disconnect(true)
261
             socket.disconnect(true)
265
         }
262
         }
266
     }
263
     }
267
 }
264
 }
268
 
265
 
269
-export const isError = function(e){
270
-    return e && e.stack && e.message && typeof e.stack === 'string' 
271
-           && typeof e.message === 'string';
272
-   }
266
+export const isError = function (e) {
267
+    return e && e.stack && e.message && typeof e.stack === 'string'
268
+        && typeof e.message === 'string';
269
+}

+ 170
- 53
test/Test.ts View File

1
 import { describe, it } from "mocha";
1
 import { describe, it } from "mocha";
2
 import { RPCServer, RPCSocket } from '../Index'
2
 import { RPCServer, RPCSocket } from '../Index'
3
-import { RPCExporter } from "../src/Interfaces";
3
+import { RPCExporter, Socket } from "../src/Interfaces";
4
 import { ConnectedSocket } from "../src/Types";
4
 import { ConnectedSocket } from "../src/Types";
5
 import * as log from 'why-is-node-running';
5
 import * as log from 'why-is-node-running';
6
 import * as http from 'http';
6
 import * as http from 'http';
7
 import * as express from 'express';
7
 import * as express from 'express';
8
 import * as fetch from 'node-fetch';
8
 import * as fetch from 'node-fetch';
9
+import { PromiseIO } from "../src/PromiseIO/Server";
10
+import { PromiseIOClient } from "../src/PromiseIO/Client";
11
+
12
+const noop = (...args) => { }
9
 
13
 
10
 const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
14
 const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
11
-function makeServer() {
15
+function makeServer(onCallback = noop, connectionHandler = noop, hookCloseHandler = noop, closeHandler = noop, errorHandler = (socket, err) => { throw err }) {
12
     let subcallback
16
     let subcallback
13
-    const sv = new RPCServer([{
17
+    const serv = new RPCServer([{
14
         name: 'test',
18
         name: 'test',
15
         RPCs: [
19
         RPCs: [
16
             {
20
             {
22
                     subcallback = callback
26
                     subcallback = callback
23
                     return { topic: "test" }
27
                     return { topic: "test" }
24
                 },
28
                 },
25
-                onClose: (res) => { }
29
+                onDestroy: hookCloseHandler
26
             }, {
30
             }, {
27
                 name: 'subscribe',
31
                 name: 'subscribe',
28
                 hook: async (callback) => {
32
                 hook: async (callback) => {
29
                     subcallback = callback
33
                     subcallback = callback
30
                     return { topic: "test" }
34
                     return { topic: "test" }
31
                 },
35
                 },
32
-                onClose: (res, rpc) => {
33
-                    console.log("onClose", rpc.name === 'subscribe' && res ? "OK" : "")
34
-                    subcallback = null
35
-                },
36
-                onCallback: (...args: any) => {
37
-                    console.log("onCallback", args[0] === "test" && args[1] === "callback" ? "OK" : "")
38
-                }
36
+                onDestroy: hookCloseHandler,
37
+                onCallback: onCallback
39
             },
38
             },
40
             add,
39
             add,
41
             function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
40
             function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
41
+            function brokenRPC(){ throw new Error("Intended error") }
42
         ]
42
         ]
43
-    }], 
44
-    {
45
-        connectionHandler: (socket) => {
46
-        },
47
-        closeHandler: (socket) => { },
48
-        errorHandler: (socket, err) => { throw err }
49
-    })
50
-    sv.listen(21010)
51
-    return sv
43
+    }],
44
+        {
45
+            connectionHandler: connectionHandler,
46
+            closeHandler: closeHandler,
47
+            errorHandler: errorHandler
48
+        })
49
+    serv.listen(21010)
50
+    return serv
52
 }
51
 }
53
 
52
 
54
 
53
 
54
+describe('PromiseIO', () => {
55
+
56
+    it("bind + fire", (done) => {
57
+        const server = new PromiseIO()
58
+        server.attach(new http.Server())
59
+        server.on("socket", clientSocket => {
60
+            clientSocket.bind("test123", (p1,p2) => {
61
+                server.close()
62
+                if(p1 === "p1" && p2 === "p2")
63
+                    done()
64
+            })
65
+        });
66
+
67
+        server.listen(21003)
68
+        PromiseIOClient.connect(21003, "localhost", "http").then(cli => {
69
+            cli.fire("test123", "p1", "p2")
70
+            cli.close()
71
+        })
72
+    })
73
+
74
+    it("hook + call", (done) => {
75
+        const server = new PromiseIO()
76
+        server.attach(new http.Server())
77
+        server.on("socket", clientSocket => {
78
+            clientSocket.hook("test123", (p1,p2) => {
79
+                if(p1 === "p1" && p2 === "p2")
80
+                    return "OK"
81
+            })
82
+        });
83
+
84
+        server.listen(21003)
85
+        PromiseIOClient.connect(21003, "localhost", "http").then(cli => {
86
+            cli.call("test123", "p1", "p2").then(resp => {
87
+                cli.close()
88
+                server.close()
89
+
90
+                if(resp === "OK")
91
+                    done()
92
+            })
93
+        })
94
+    })
95
+
96
+    it("on + emit", (done) => {
97
+        const server = new PromiseIO()
98
+        server.attach(new http.Server())
99
+        server.on("socket", clientSocket => {
100
+            clientSocket.on("test123", (p1,p2) => {
101
+                server.close()
102
+                if(p1 === "p1" && p2 === "p2")
103
+                    done()
104
+            })
105
+        });
106
+
107
+        server.listen(21003)
108
+        PromiseIOClient.connect(21003, "localhost", "http").then(cli => {
109
+            cli.emit("test123", "p1", "p2")
110
+            cli.close()
111
+        })
112
+    })
113
+})
114
+
55
 describe('RPCServer', () => {
115
 describe('RPCServer', () => {
56
     let client, server
116
     let client, server
57
     const echo = (x) => x
117
     const echo = (x) => x
133
         {
193
         {
134
             name: 'Grp2',
194
             name: 'Grp2',
135
             RPCs: [
195
             RPCs: [
136
-                function test(){ return "test" }
196
+                function test() { return "test" }
137
             ],
197
             ],
138
         }
198
         }
139
     ]
199
     ]
140
 
200
 
141
-    let client:RPCSocket, server:RPCServer
201
+    let client: RPCSocket, server: RPCServer
142
 
202
 
143
     before(done => {
203
     before(done => {
144
         const expressServer = express()
204
         const expressServer = express()
145
         const httpServer = new http.Server(expressServer)
205
         const httpServer = new http.Server(expressServer)
146
 
206
 
147
-        expressServer.get('/REST_ping', (req, res)=>{
207
+        expressServer.get('/REST_ping', (req, res) => {
148
             return res
208
             return res
149
-            .send('REST_pong')
150
-            .status(200)
209
+                .send('REST_pong')
210
+                .status(200)
151
         })
211
         })
152
 
212
 
153
         server = new RPCServer(
213
         server = new RPCServer(
157
         httpServer.listen(8080)
217
         httpServer.listen(8080)
158
 
218
 
159
         client = new RPCSocket(8080, 'localhost')
219
         client = new RPCSocket(8080, 'localhost')
160
-        
220
+
161
         done()
221
         done()
162
     })
222
     })
163
 
223
 
171
     it('should serve REST', (done) => {
231
     it('should serve REST', (done) => {
172
         fetch('http://localhost:8080/REST_ping').then(response => {
232
         fetch('http://localhost:8080/REST_ping').then(response => {
173
             response.text().then(text => {
233
             response.text().then(text => {
174
-                if(text === "REST_pong")
234
+                if (text === "REST_pong")
175
                     done()
235
                     done()
176
                 else
236
                 else
177
-                    done(new Error("REST repsonse was "+text))
237
+                    done(new Error("REST repsonse was " + text))
178
             })
238
             })
179
         })
239
         })
180
     })
240
     })
197
 })
257
 })
198
 
258
 
199
 
259
 
260
+describe('Serverside Triggers', () => {
261
+    let server, client
262
+    const closerFunction = (done) => () => {
263
+        client.close()
264
+        server.close()
265
+        done()
266
+    }
267
+
268
+    it('trigger onCallback', (done) => {
269
+        server = makeServer(closerFunction(done))
270
+        client = new RPCSocket(21010, "localhost")
271
+        client.connect().then(_ => {
272
+            client['test'].subscribe(noop).then(_ => client['test'].triggerCallback())
273
+        })
274
+    })
275
+
276
+    it('trigger connectionHandler', (done) => {
277
+        server = makeServer(undefined, closerFunction(done))
278
+        client = new RPCSocket(21010, "localhost")
279
+        client.connect()
280
+    })
281
+
282
+    
283
+    it('trigger hook closeHandler', (done) => {
284
+        server = makeServer(undefined, undefined, closerFunction(done))
285
+        client = new RPCSocket(21010, "localhost")
286
+        client.connect().then(_ => {
287
+            client['test'].subscribe(function cb(){
288
+                cb['destroy']()
289
+            }).then(_ => client['test'].triggerCallback())
290
+        })
291
+    })
292
+    
293
+
294
+    it('trigger global closeHandler', (done) => {
295
+        server = makeServer(undefined, undefined, undefined, () => {
296
+            server.close()
297
+            done()
298
+        })
299
+        client = new RPCSocket(21010, "localhost")
300
+        client.connect().then(_ => {
301
+            client['test'].subscribe(noop).then(_ => client.close())
302
+        })
303
+    })
304
+    
305
+
306
+})
307
+
200
 describe('RPCSocket', () => {
308
 describe('RPCSocket', () => {
201
     let client: RPCSocket
309
     let client: RPCSocket
202
     let server: RPCServer
310
     let server: RPCServer
266
     })
374
     })
267
 })
375
 })
268
 
376
 
377
+
269
 describe('It should do unhook', () => {
378
 describe('It should do unhook', () => {
270
     const yesCandy = "OK"
379
     const yesCandy = "OK"
271
     const noCandy = "stolen"
380
     const noCandy = "stolen"
304
             function publish(): string { cb(candy); return candy },
413
             function publish(): string { cb(candy); return candy },
305
             function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
414
             function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
306
             ]
415
             ]
307
-        }], 
308
-        {
309
-            connectionHandler: (socket) => { },
310
-            closeHandler: (socket) => { },
311
-            errorHandler: (socket, err) => { throw err }
312
-        })
416
+        }],
417
+            {
418
+                connectionHandler: noop,
419
+                closeHandler: noop,
420
+                errorHandler: (socket, err) => { throw err }
421
+            })
313
         server.listen(21010)
422
         server.listen(21010)
314
         client = new RPCSocket(21010, "localhost")
423
         client = new RPCSocket(21010, "localhost")
315
         return await client.connect()
424
         return await client.connect()
321
     })
430
     })
322
 
431
 
323
     it('Subscribe with param', (done) => {
432
     it('Subscribe with param', (done) => {
324
-        client['test'].subscribeWithParam("OK", c => { }).then(async (res) => {
433
+        client['test'].subscribeWithParam("OK", noop).then(async (res) => {
325
             if (res.uuid === candy) {
434
             if (res.uuid === candy) {
326
                 done()
435
                 done()
327
             } else
436
             } else
363
         subscribe: (callback: Function) => Promise<topicDTO>
472
         subscribe: (callback: Function) => Promise<topicDTO>
364
         manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
473
         manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
365
     }
474
     }
366
-
367
-    other: {
368
-        echo: (x: any) => Promise<any>
369
-    }
370
 }
475
 }
371
 
476
 
372
 describe('Sesame should unlock the socket', () => {
477
 describe('Sesame should unlock the socket', () => {
387
                             topic: 'test'
492
                             topic: 'test'
388
                         }
493
                         }
389
                     },
494
                     },
390
-                    onClose: (a) => { }
495
+                    onDestroy: noop
391
                 },
496
                 },
392
-                async function checkCandy() { cb(candy); cb = () => { }; return candy },
497
+                async function checkCandy() { cb(candy); cb = noop; return candy },
393
                 async function manyParams(a, b, c, d) { return [a, b, c, d] }
498
                 async function manyParams(a, b, c, d) { return [a, b, c, d] }
394
             ],
499
             ],
395
-        }, {
396
-            name: 'other',
397
-            RPCs: () => [
398
-                async function echo(x) { return x }
399
-            ]
400
-
401
         }], {
500
         }], {
402
             sesame: (_sesame) => _sesame === 'sesame!'
501
             sesame: (_sesame) => _sesame === 'sesame!'
403
         })
502
         })
413
         client.close()
512
         client.close()
414
         server.close()
513
         server.close()
415
     })
514
     })
416
-
515
+    
417
     it('should work with sesame', (done) => {
516
     it('should work with sesame', (done) => {
418
         client.test.checkCandy().then(c => done())
517
         client.test.checkCandy().then(c => done())
419
     })
518
     })
575
             })
674
             })
576
                 .then(r => {
675
                 .then(r => {
577
                     if (r != null)
676
                     if (r != null)
578
-                        done("UNEXPECTED RESULT " + r)
677
+                        done(new Error("UNEXPECTED RESULT " + r))
579
                 })
678
                 })
580
                 .catch((e) => {
679
                 .catch((e) => {
581
                     done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
680
                     done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
614
             })
713
             })
615
                 .then(r => {
714
                 .then(r => {
616
                     if (r != null)
715
                     if (r != null)
617
-                        done("UNEXPECTED RESULT " + r)
716
+                        done(new Error("UNEXPECTED RESULT " + r))
618
                 })
717
                 })
619
                 .catch((e) => {
718
                 .catch((e) => {
620
-                    done("UNEXPECTED CLIENT ERROR " + e)
621
-                    done(e)
719
+                    done(new Error("UNEXPECTED CLIENT ERROR " + e))
622
                 })
720
                 })
623
                 .finally(() => {
721
                 .finally(() => {
624
                     cli.close()
722
                     cli.close()
670
     before(done => {
768
     before(done => {
671
         exporter1 = new MyExporter()
769
         exporter1 = new MyExporter()
672
         serv = new RPCServer<myExporterIfc>(
770
         serv = new RPCServer<myExporterIfc>(
673
-            [exporter1], 
771
+            [exporter1],
674
             {
772
             {
675
                 accessFilter: async (sesame, exporter) => {
773
                 accessFilter: async (sesame, exporter) => {
676
                     if (exporter.name === 'MyExporter') {
774
                     if (exporter.name === 'MyExporter') {
772
         })
870
         })
773
     })
871
     })
774
 
872
 
873
+    it("fires error if call is unknown", (done) => {
874
+        const serv = new RPCServer().listen(21004)
875
+        const sock = new RPCSocket(21004, 'localhost')
876
+
877
+        sock.on('error', (err) => {
878
+            sock.close()
879
+            serv.close()
880
+            done()
881
+        })
882
+
883
+        sock.connect().then(_ => {
884
+            sock.call("unknownRPC123", "AAAAA").catch(e => {  }).then(x => {
885
+                done(new Error("unexpected return value"))
886
+            })
887
+        }).catch(e => {
888
+            done(e)
889
+        })
890
+    })
891
+
775
     /*
892
     /*
776
      * ## 1.11.0 breaking ##
893
      * ## 1.11.0 breaking ##
777
      * 
894
      * 

Loading…
Cancel
Save