瀏覽代碼

2.0 rework

master
peter 4 年之前
父節點
當前提交
cb974cbc61
共有 11 個文件被更改,包括 751 次插入523 次删除
  1. 134
    145
      README.md
  2. 0
    239
      demo.ts
  3. 435
    1
      package-lock.json
  4. 4
    2
      package.json
  5. 0
    53
      scratchpad.ts
  6. 15
    20
      src/Backend.ts
  7. 1
    2
      src/Interfaces.ts
  8. 6
    4
      src/PromiseIO/Server.ts
  9. 0
    2
      src/Types.ts
  10. 2
    2
      src/Utils.ts
  11. 154
    53
      test/Test.ts

+ 134
- 145
README.md 查看文件

@@ -12,33 +12,58 @@ rpclibrary is a simple to use websocket RPC library.
12 12
 npm i rpclibrary
13 13
 ```
14 14
 
15
-# Quickstart
15
+# Quickstart (TL;DR)
16 16
 ```typescript
17 17
 import {RPCServer, RPCSocket} from 'rpclibrary' 
18 18
 
19
-// TL;DR
20 19
 const echo = (text) => text
21 20
 const add = (a, b) => a + b
22
-
23
-new RPCServer(20000, [{
24
-    name: 'MyRPCGroup1',
25
-    exportRPCs: () => [
26
-        echo,
27
-        add,
28
-    ]
29
-}])
21
+const subtract = (a, b) => a - b
22
+
23
+new RPCServer([
24
+    {
25
+        name: 'MyRPCGroup1',
26
+        RPCs: [
27
+            echo,
28
+            add,
29
+        ]
30
+    },
31
+        name: 'MyOtherRPCGroup',
32
+        RPCs: [
33
+            subtract
34
+        ]
35
+    }
36
+]).listen(20000)
30 37
 
31 38
 new RPCSocket(20000, 'localhost').connect().then(async sock => {
32 39
     try{
33
-        const RPCs = sock['MyRPCGroup1']
34
-        await RPCs.echo("hello!").then(console.log)
35
-        await RPCs.add(1, Math.PI).then(console.log)
40
+        
41
+        await sock['MyRPCGroup1'].echo("hello!").then(console.log)
42
+        const sum = await sock['MyRPCGroup1'].add(1, Math.PI)
43
+        const result = await sock['MyOtherRPCGroup'].subtract(sum, 4)
44
+        
36 45
     }catch(e){
37 46
         console.log(String(e))
38 47
     }
39 48
 })
40 49
 ```
41 50
 
51
+# Working with http.Server and express
52
+```typescript
53
+import {RPCServer, RPCSocket} from 'rpclibrary' 
54
+import {Server as httpServer} from 'http'
55
+import * as express from 'express'
56
+
57
+const app = express()
58
+//...do express things...
59
+const httpServer = new http.Server(app)
60
+
61
+const rpcServer = new RPCServer([ ... ])
62
+rpcServer.attach(httpServer)
63
+rpcServer.listen(8080) //identical to httpServer.listen(8080)
64
+
65
+```
66
+
42 67
 # Async and Callbacks?
43 68
 
44 69
 rpclibrary offers full support for callbacks and Promises. 
@@ -69,16 +94,16 @@ const getCallback = (callback) => {
69 94
     return "Please wait for a callback :)"
70 95
 }
71 96
 
72
-new RPCServer(20000, [{
97
+new RPCServer([{
73 98
     name: 'MyRPCGroup1',
74
-    exportRPCs: () => [
99
+    RPCs: [
75 100
         getAsync,
76 101
         {
77 102
             name: 'getCallback',
78 103
             hook: getCallback,
79 104
         }
80 105
     ]
81
-}])
106
+}]).listen(20000)
82 107
 
83 108
 new RPCSocket(20000, 'localhost').connect().then(async sock => {
84 109
     try{
@@ -97,12 +122,10 @@ new RPCSocket(20000, 'localhost').connect().then(async sock => {
97 122
 There are a many things you can hook into to manage your connections
98 123
 
99 124
 ```typescript
100
-new RPCServer(20001, [{
125
+new RPCServer([{
101 126
     name: 'MyRPCGroup1',
102
-    exportRPCs: () => [
127
+    RPCs: [
103 128
         echo,
104
-        add,
105
-        getAsync,
106 129
         {
107 130
             name: 'getCallback',
108 131
             hook: getCallback,
@@ -111,11 +134,10 @@ new RPCServer(20001, [{
111 134
         }
112 135
     ],
113 136
 }], {
114
-    visibility: '127.0.0.1', //0.0.0.0
115 137
     closeHandler: (socket) => { /* global close handler */ },
116 138
     connectionHandler: (socket) => { /* new connection made */ },
117 139
     errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
118
-})
140
+}).listen(20001)
119 141
 
120 142
 const sock = new RPCSocket(20001, 'localhost')
121 143
 sock.on('error', (e) => { /* handle error */ })
@@ -132,184 +154,151 @@ sock.connect().then(_ => { /* ... */})
132 154
 
133 155
 # Restricting access
134 156
 
135
-rpclibrary offers some minimalistic permission management
157
+rpclibrary offers some minimalistic permission management in the form of a parameter prefix. 
158
+The ```sesame``` option will decorate all functions with a check for the sesame password. If the wrong string is provided, the RPC will not be executed or respond.
136 159
 
137 160
 ```typescript
138
-
139
-//Restricting access
140
-new RPCServer(20002, [{
141
-    name: 'MyRPCGroup1',
142
-    exportRPCs: () => [
143
-        echo,
144
-        add,
145
-        getAsync,
146
-        {
147
-            name: 'getCallback',
148
-            hook: getCallback,
149
-        }
150
-    ],
151
-}], {
161
+new RPCServer([ ... ], {
152 162
     sesame: "sesame open",
153
-    /* 
154
-    OR check sesame dynamically
155
-    and refine permissioning with accessfilter (optional)
156
-    */
163
+}).listen(20002)
157 164
 
158
-    //sesame: (sesame) => true
159
-    //accessFilter: (sesame, exporter) => { return exporter.name === "MyRPCGroup1" && sesame === "sesame open" },
165
+new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
166
+    /* use normally */
160 167
 })
161 168
 
162
-new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
163
-    try{
164
-        const RPCs = sock['MyRPCGroup1']
165
-        await RPCs.echo("hello!").then(console.log)
166
-        await RPCs.add(1, Math.PI).then(console.log)
167
-        await RPCs.getAsync().then(console.log)
168
-        await RPCs.getCallback(console.log).then(console.log)
169
-    }catch(e){
170
-        console.log(String(e))
171
-    }
169
+new RPCSocket(20002, 'localhost').connect("WRONG SESAME").then(async sock => {
170
+    const RPCs = sock['MyRPCGroup1'] //undefined
172 171
 })
172
+```
173 173
 
174
+The permissioning can also be performed dynamically with the ```accessfilter``` option and ```sesame``` function.
174 175
 
176
+```typescript
177
+new RPCServer([ ... ], {
178
+    sesame: (sesame) => checkIfSesameTokenShouldHaveAnyAccess(sesame),
179
+    accessFilter: (sesame, rpcGroupName) => checkIfSesameTokenShouldSeeRPCGroup(sesame, rpcGroupName),
180
+}).listen(20002)
175 181
 ```
176 182
 
177
-
178 183
 # Typescript support
179 184
 
180 185
 rpclibrary is a typescript-first project and offers full support for typing your RPCs.
181
-**NOTE** that your function implementations have to be currectly typed to make the compiler agree. 
182
-Explicit typing is recommended.
186
+**NOTE** that your function implementations and Group Names will usually have to be manually typed to make the compiler infer the correct types.
183 187
 
184
-Example:
188
+First apply types to your functions:
185 189
 ```typescript
186 190
 echo = (x) => x
187
-    /*becomes*/
191
+/*becomes*/
188 192
 echo = (x:string) : string => x
189
-```
190 193
 
194
+/* A faulty implementation */
195
+badEcho = (x: boolean) : number => 3
191 196
 
197
+```
198
+
199
+Next define a type matching the desired Group-to-RPC structure. This kind of type will be called ```pseudo-interface```:
192 200
 ```typescript
193
-type MyInterface = {
201
+type MyType = {
194 202
     MyRPCGroup1: {
195 203
         echo: (x: string) => string
196
-        add: (a: number, b: number) => number
197
-        getAsync: () => Promise<{ topic: string, message: string }>
198
-        getCallback: (callback:Function) => string
199
-    }
204
+        //...further RPC declarations
205
+    },
206
+    //... further Group declarations
200 207
 };
208
+```
201 209
 
210
+If ```MyType``` is provided as generic parameter to ```RPCServer```, ```RPCs``` becomes type-safe. 
211
+TSC's stack traces may become very long when errors happen in this section. Don't be intimidated by this.
212
+The relevant information is usually found at the end in a clear manner.
202 213
 
203
-/*
204
-exportRPCs is now type safe. Try swapping echo for badEcho.
205
-Sadly TSC's stack traces aren't the best, but try to scroll to the bottom of them to find useful info like
206
-
207
-Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
208
-*/
209
-
210
-const badEcho = (x: boolean) : number => 3
211
-
212
-new RPCServer<MyInterface>(20003, [{
213
-    name: 'MyRPCGroup1',
214
-    exportRPCs: () => [
215
-        //badEcho,
216
-        echo,
217
-        add,
218
-        getAsync,
219
-        {
220
-            name: 'getCallback',
221
-            hook: getCallback,
222
-        }
223
-    ],
224
-}])
214
+```typescript
215
+new RPCServer<MyType>([{
216
+    name: 'MyRPCGroup1' as 'MyRPCGroup1', //Otherwise the compiler may infer type 'string'
217
+    RPCs: [ badEcho ], //Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
218
+}]).listen(20003)
219
+```
225 220
 
226
-new RPCSocket<MyInterface>(20003, 'localhost').connect().then(async sock => {
227
-    try{
228
-        await sock.MyRPCGroup1.echo("hello!").then(console.log)
229
-        await sock.MyRPCGroup1.add(1, Math.PI).then(console.log)
230
-        await sock.MyRPCGroup1.getAsync().then(console.log)
231
-        await sock.MyRPCGroup1.getCallback(console.log).then(console.log)
232
-    }catch(e){
233
-        console.log(String(e))
234
-    }
221
+Finally also provide ```MyType``` to the client ```RPCSocket``` to gain type safety for the socket object.
222
+```typescript
223
+new RPCSocket<MyType>(20003, 'localhost').connect().then(async sock => {
224
+    //TSC knows about MyRPCGroup1 and its members 
225
+    await sock.MyRPCGroup1.echo("hello!").then(console.log) 
235 226
 })
236 227
 
237 228
 ```
238 229
 
239 230
 
240
-# A class-based scalable pattern for APIs
241
-
242
-because long lists of functions quickly become unwieldy, it is smart to break up the RPCs into chunks or features.
243
-A pattern I found to be useful is as follows:
231
+# A class-based pattern for modular APIs
244 232
 
233
+Define a regular Interface. This will function as the source of truth for type definitions.
245 234
 ```typescript
246
-interface IMyImplementation {
235
+interface IComponentA {
247 236
     echo: (x: string) => string
248 237
     add: (a: number, b: number) => number
238
+}
239
+
240
+interface IComponentB {
249 241
     getAsync: () => Promise<{ topic: string, message: string }>
250 242
     getCallback: (callback:Function) => string
251 243
 }
244
+```
252 245
 
253
-type MyInterface = {
254
-    MyRPCGroup1: {
255
-        echo: IMyImplementation['echo']
256
-        add: IMyImplementation['add']
257
-        getAsync: IMyImplementation['getAsync']
258
-        getCallback: IMyImplementation['getCallback']
246
+Create an RPC pseudo-interface type that references the proper interface. This will make sure the types declared by your RPCs are consistent with their implementations.
247
+```typescript
248
+type TComponentA = {
249
+    ComponentA: {
250
+        echo: IComponentA['echo']
251
+        add: IComponentA['add']
252
+    }
253
+}
254
+
255
+type TComponentB = {
256
+    ComponentB: {
257
+        getAsync: IComponentB['getAsync']
258
+        getCallback: IComponentB['getCallback']
259 259
     }
260 260
 }
261
+```
261 262
 
262
-class MyImplementation implements IMyImplementation, RPCExporter<MyInterface>{
263
-    //"X" as "X" syntax is required to satisfy the type system (as it assumes string to be the true type)
264
-    name = "MyRpcGroup11" as "MyRPCGroup1"
263
+Create classes that implement the interfaces you declared alongside ```RPCExporter<MyType>```, where MyType is the RPC pseudo-interface.
264
+```typescript
265
+class ComponentA implements IComponentA, RPCExporter<TComponentA> {
266
+    name = "ComponentA" as "ComponentA"
265 267
 
266
-    //List the functions you declared in MyInterface
267
-    exportRPCs = () => [
268
+    RPCs = [
268 269
         this.echo,
269 270
         this.add,
270
-        this.getAsync,
271
-        this.getCallback
272 271
     ]
273 272
 
274
-    //Write your implementations as you normally would
275 273
     echo = (text: string) => text
276
-
277 274
     add = (a: number, b: number) : number => a + b
275
+}
278 276
 
279
-    getAsync = async () : Promise<{topic: string, message:string}>=> await new Promise((res, _) => {
280
-        setTimeout(() => {
281
-            res({
282
-                topic: "Hey!!",
283
-                message: "Hello World Async!"
284
-            })
285
-        }, 250)
286
-    })
287
-
288
-    getCallback = (callback: Function) : string => {
289
-        setTimeout(() => {
290
-            try{
291
-                callback({
292
-                    topic: "Hey!!",
293
-                    message: "Hello World Callback!"
294
-                })
295
-            }catch(e){
296
-                console.log(String(e))
297
-            }
298
-        }, 250)
299
-        return "Please wait for a callback :)"
300
-    }
277
+class ComponentB implements IComponentB, RPCExporter<TComponentB> {
278
+    name = "ComponentB" as "ComponentB"
279
+
280
+    RPCs = [
281
+        this.getAsync,
282
+        {
283
+            name: 'getCallback',
284
+            hook: this.getCallback
285
+        }
286
+    ]
287
+
288
+    getAsync = async () : Promise<{topic: string, message:string}> => {...}
289
+    getCallback = (callback: Function) : string => {...}
301 290
 }
291
+```
302 292
 
303
-type ProjectInterface = MyInterface
304
-                    //& MyOtherInterface
305
-                    //& MyOtherOtherInterface
306
-                    // ...
307
-;
293
+Finally, merge all RPC pseudo-interfaces into a project-wide one: 
294
+```typescript
295
+type TProject = TComponentA
296
+              & TComponentB
308 297
 
309
-new RPCServer<ProjectInterface>(20004, [new MyImplementation() /*, new MyOtherImplementation(), new MyOtherOtherImplementation() */])
298
+new RPCServer<TProject>([new ComponentA(), new ComponentB()]).listen(port)
310 299
 
311
-new RPCSocket<ProjectInterface>(20004, 'localhost').connect().then(async sock => {
312
-    // ...
300
+new RPCSocket<TProject>(port, host).connect().then(async sock => {
301
+    
313 302
 })
314 303
 
315 304
 ```

+ 0
- 239
demo.ts 查看文件

@@ -1,239 +0,0 @@
1
-import { RPCServer, RPCSocket } from './Index'
2
-import { RPCExporter } from './src/Interfaces'
3
-import { RPCInterface } from './src/Types'
4
-
5
-// TL;DR
6
-const echo = (text: string) => text
7
-const add = (a: number, b: number) : number => a + b
8
-const getAsync = async () : Promise<{topic: string, message:string}>=> await new Promise((res, _) => {
9
-    setTimeout(() => {
10
-        res({
11
-            topic: "Hey!!",
12
-            message: "Hello World Async!"
13
-        })
14
-    }, 250)
15
-})
16
-const getCallback = (callback: Function) : string => {
17
-    setTimeout(() => {
18
-        try{
19
-            callback({
20
-                topic: "Hey!!",
21
-                message: "Hello World Callback!"
22
-            })
23
-        }catch(e){
24
-            console.log(String(e))
25
-        }
26
-    }, 250)
27
-    return "Please wait for a callback :)"
28
-}
29
-new RPCServer(20000, [{
30
-    name: 'MyRPCGroup1',
31
-    exportRPCs: () => [
32
-        echo,
33
-        add,
34
-        getAsync,
35
-        {
36
-            name: 'getCallback',
37
-            hook: getCallback,
38
-            onClose: (response, rpc) => { /* ... */ },
39
-            onCallback: (...callbackArgs) => { /* ... */ }
40
-        }
41
-    ]
42
-}])
43
-
44
-new RPCSocket(20000, 'localhost').connect().then(async sock => {
45
-    try{
46
-        const RPCs = sock['MyRPCGroup1']
47
-        await RPCs.echo("hello!").then(console.log)
48
-        await RPCs.add(1, Math.PI).then(console.log)
49
-        await RPCs.getAsync().then(console.log)
50
-        await RPCs.getCallback(console.log).then(console.log)
51
-    }catch(e){
52
-        console.log(String(e))
53
-    }
54
-})
55
-
56
-//Hooks and events
57
-new RPCServer(20001, [{
58
-    name: 'MyRPCGroup1',
59
-    exportRPCs: () => [
60
-        echo,
61
-        add,
62
-        getAsync,
63
-        {
64
-            name: 'getCallback',
65
-            hook: getCallback,
66
-            onClose: (response, rpc) => { /* client disconnected */ },
67
-            onCallback: (...callbackArgs) => { /* callback triggered */ }
68
-        }
69
-    ],
70
-}], {
71
-    visibility: '127.0.0.1', //0.0.0.0
72
-    closeHandler: (socket) => { /* global close handler */ },
73
-    connectionHandler: (socket) => { /* new connection made */ },
74
-    errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
75
-})
76
-
77
-const sock = new RPCSocket(20001, 'localhost')
78
-sock.on('error', (e) => { /* handle error */ })
79
-sock.on('close', () => { /* handle close event */ })
80
-
81
-sock.hook('RPCName', (/* arg0, arg1, ..., argN */) => { /* bind client-side RPCs */ }) 
82
-//Retrieve the socket from connectionHandler (Server-side) and trigger with 
83
-//socket.call('RPCName', arg0, arg1, ..., argN)
84
-
85
-sock.connect().then(_ => { /* ... */})
86
-
87
-//Restricting access
88
-new RPCServer(20002, [{
89
-    name: 'MyRPCGroup1',
90
-    exportRPCs: () => [
91
-        echo,
92
-        add,
93
-        getAsync,
94
-        {
95
-            name: 'getCallback',
96
-            hook: getCallback,
97
-        }
98
-    ],
99
-}], {
100
-    sesame: "sesame open",
101
-    /* 
102
-    OR check sesame dynamically
103
-    and refine permissioning with accessfilter (optional)
104
-    */
105
-
106
-    //sesame: (sesame) => true
107
-    //accessFilter: (sesame, exporter) => { return exporter.name === "MyRPCGroup1" && sesame === "sesame open" },
108
-})
109
-
110
-new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
111
-    try{
112
-        const RPCs = sock['MyRPCGroup1']
113
-        await RPCs.echo("hello!").then(console.log)
114
-        await RPCs.add(1, Math.PI).then(console.log)
115
-        await RPCs.getAsync().then(console.log)
116
-        await RPCs.getCallback(console.log).then(console.log)
117
-    }catch(e){
118
-        console.log(String(e))
119
-    }
120
-})
121
-
122
-
123
-//TypeScript and pseudo-interfaces
124
-
125
-type MyInterface = {
126
-    MyRPCGroup1: {
127
-        echo: (x: string) => string
128
-        add: (a: number, b: number) => number
129
-        getAsync: () => Promise<{ topic: string, message: string }>
130
-        getCallback: (callback:Function) => string
131
-    }
132
-};
133
-
134
-
135
-/*
136
-exportRPCs is now type safe. Try swapping echo for badEcho.
137
-Sadly TSC's stack traces aren't the best, but try to scroll to the bottom of them to find useful info like
138
-
139
-Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
140
-*/
141
-
142
-const badEcho = (x: boolean) : number => 3
143
-
144
-new RPCServer<MyInterface>(20003, [{
145
-    name: 'MyRPCGroup1',
146
-    exportRPCs: () => [
147
-        //badEcho,
148
-        echo,
149
-        add,
150
-        getAsync,
151
-        {
152
-            name: 'getCallback',
153
-            hook: getCallback,
154
-        }
155
-    ],
156
-}])
157
-
158
-new RPCSocket<MyInterface>(20003, 'localhost').connect().then(async sock => {
159
-    try{
160
-        await sock.MyRPCGroup1.echo("hello!").then(console.log)
161
-        await sock.MyRPCGroup1.add(1, Math.PI).then(console.log)
162
-        await sock.MyRPCGroup1.getAsync().then(console.log)
163
-        await sock.MyRPCGroup1.getCallback(console.log).then(console.log)
164
-    }catch(e){
165
-        console.log(String(e))
166
-    }
167
-})
168
-
169
-
170
-//Class-based pattern
171
-
172
-interface IMyImplementation {
173
-    echo: (x: string) => string
174
-    add: (a: number, b: number) => number
175
-    getAsync: () => Promise<{ topic: string, message: string }>
176
-    getCallback: (callback:Function) => string
177
-}
178
-
179
-type IfcGenerator<Name extends string, Interface, keys extends keyof Interface = keyof Interface> = {
180
-    [name in Name]: {
181
-        [key in keys] : Interface[key]
182
-    }
183
-}
184
-
185
-type MyIfc = IfcGenerator<"MyRPCGroup1", IMyImplementation>
186
-
187
-class MyImplementation implements IMyImplementation, RPCExporter<MyIfc>{
188
-    //"X" as "X" syntax is required to satisfy the type system (as it assumes string)
189
-    name = "MyRpcGroup11" as "MyRPCGroup1"
190
-
191
-    //List the functions you declared in MyIfc
192
-    exportRPCs = () => [
193
-        this.echo,
194
-        this.add,
195
-        this.getAsync,
196
-        this.getCallback
197
-    ]
198
-
199
-    //Write your implementations as you normally would
200
-    echo = (text: string) => text
201
-
202
-    add = (a: number, b: number) : number => a + b
203
-
204
-    getAsync = async () : Promise<{topic: string, message:string}>=> await new Promise((res, _) => {
205
-        setTimeout(() => {
206
-            res({
207
-                topic: "Hey!!",
208
-                message: "Hello World Async!"
209
-            })
210
-        }, 250)
211
-    })
212
-
213
-    getCallback = (callback: Function) : string => {
214
-        setTimeout(() => {
215
-            try{
216
-                callback({
217
-                    topic: "Hey!!",
218
-                    message: "Hello World Callback!"
219
-                })
220
-            }catch(e){
221
-                console.log(String(e))
222
-            }
223
-        }, 250)
224
-        return "Please wait for a callback :)"
225
-    }
226
-}
227
-
228
-new RPCServer<MyIfc>(20004, [new MyImplementation(), /* ... other RPCExporters */])
229
-
230
-new RPCSocket<MyIfc>(20004, 'localhost').connect().then(async sock => {
231
-    try{
232
-        await sock.MyRPCGroup1.echo("hello!").then(console.log)
233
-        await sock.MyRPCGroup1.add(1, Math.PI).then(console.log)
234
-        await sock.MyRPCGroup1.getAsync().then(console.log)
235
-        await sock.MyRPCGroup1.getCallback(console.log).then(console.log)
236
-    }catch(e){
237
-        console.log(String(e))
238
-    }
239
-})

+ 435
- 1
package-lock.json 查看文件

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "rpclibrary",
3
-  "version": "1.10.2",
3
+  "version": "1.12.3",
4 4
   "lockfileVersion": 1,
5 5
   "requires": true,
6 6
   "dependencies": {
@@ -631,6 +631,12 @@
631 631
       "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
632 632
       "dev": true
633 633
     },
634
+    "array-flatten": {
635
+      "version": "1.1.1",
636
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
637
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
638
+      "dev": true
639
+    },
634 640
     "array-unique": {
635 641
       "version": "0.3.2",
636 642
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -837,6 +843,41 @@
837 843
       "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
838 844
       "dev": true
839 845
     },
846
+    "body-parser": {
847
+      "version": "1.19.0",
848
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
849
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
850
+      "dev": true,
851
+      "requires": {
852
+        "bytes": "3.1.0",
853
+        "content-type": "~1.0.4",
854
+        "debug": "2.6.9",
855
+        "depd": "~1.1.2",
856
+        "http-errors": "1.7.2",
857
+        "iconv-lite": "0.4.24",
858
+        "on-finished": "~2.3.0",
859
+        "qs": "6.7.0",
860
+        "raw-body": "2.4.0",
861
+        "type-is": "~1.6.17"
862
+      },
863
+      "dependencies": {
864
+        "debug": {
865
+          "version": "2.6.9",
866
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
867
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
868
+          "dev": true,
869
+          "requires": {
870
+            "ms": "2.0.0"
871
+          }
872
+        },
873
+        "ms": {
874
+          "version": "2.0.0",
875
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
876
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
877
+          "dev": true
878
+        }
879
+      }
880
+    },
840 881
     "brace-expansion": {
841 882
       "version": "1.1.11",
842 883
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1001,6 +1042,12 @@
1001 1042
       "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
1002 1043
       "dev": true
1003 1044
     },
1045
+    "bytes": {
1046
+      "version": "3.1.0",
1047
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
1048
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
1049
+      "dev": true
1050
+    },
1004 1051
     "cacache": {
1005 1052
       "version": "12.0.3",
1006 1053
       "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
@@ -1314,6 +1361,21 @@
1314 1361
       "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
1315 1362
       "dev": true
1316 1363
     },
1364
+    "content-disposition": {
1365
+      "version": "0.5.3",
1366
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
1367
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
1368
+      "dev": true,
1369
+      "requires": {
1370
+        "safe-buffer": "5.1.2"
1371
+      }
1372
+    },
1373
+    "content-type": {
1374
+      "version": "1.0.4",
1375
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
1376
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
1377
+      "dev": true
1378
+    },
1317 1379
     "convert-source-map": {
1318 1380
       "version": "1.7.0",
1319 1381
       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@@ -1328,6 +1390,12 @@
1328 1390
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
1329 1391
       "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
1330 1392
     },
1393
+    "cookie-signature": {
1394
+      "version": "1.0.6",
1395
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
1396
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
1397
+      "dev": true
1398
+    },
1331 1399
     "copy-concurrently": {
1332 1400
       "version": "1.0.5",
1333 1401
       "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -1522,6 +1590,12 @@
1522 1590
         }
1523 1591
       }
1524 1592
     },
1593
+    "depd": {
1594
+      "version": "1.1.2",
1595
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
1596
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
1597
+      "dev": true
1598
+    },
1525 1599
     "des.js": {
1526 1600
       "version": "1.0.0",
1527 1601
       "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@@ -1532,6 +1606,12 @@
1532 1606
         "minimalistic-assert": "^1.0.0"
1533 1607
       }
1534 1608
     },
1609
+    "destroy": {
1610
+      "version": "1.0.4",
1611
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
1612
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
1613
+      "dev": true
1614
+    },
1535 1615
     "detect-file": {
1536 1616
       "version": "1.0.0",
1537 1617
       "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -1573,6 +1653,12 @@
1573 1653
         "stream-shift": "^1.0.0"
1574 1654
       }
1575 1655
     },
1656
+    "ee-first": {
1657
+      "version": "1.1.1",
1658
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
1659
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
1660
+      "dev": true
1661
+    },
1576 1662
     "elliptic": {
1577 1663
       "version": "6.5.1",
1578 1664
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
@@ -1600,6 +1686,12 @@
1600 1686
       "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
1601 1687
       "dev": true
1602 1688
     },
1689
+    "encodeurl": {
1690
+      "version": "1.0.2",
1691
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1692
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
1693
+      "dev": true
1694
+    },
1603 1695
     "end-of-stream": {
1604 1696
       "version": "1.4.4",
1605 1697
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -1735,6 +1827,12 @@
1735 1827
       "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
1736 1828
       "dev": true
1737 1829
     },
1830
+    "escape-html": {
1831
+      "version": "1.0.3",
1832
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
1833
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
1834
+      "dev": true
1835
+    },
1738 1836
     "escape-string-regexp": {
1739 1837
       "version": "1.0.5",
1740 1838
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -1778,6 +1876,12 @@
1778 1876
       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
1779 1877
       "dev": true
1780 1878
     },
1879
+    "etag": {
1880
+      "version": "1.8.1",
1881
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
1882
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
1883
+      "dev": true
1884
+    },
1781 1885
     "events": {
1782 1886
       "version": "3.0.0",
1783 1887
       "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
@@ -1868,6 +1972,67 @@
1868 1972
         "homedir-polyfill": "^1.0.1"
1869 1973
       }
1870 1974
     },
1975
+    "express": {
1976
+      "version": "4.17.1",
1977
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
1978
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
1979
+      "dev": true,
1980
+      "requires": {
1981
+        "accepts": "~1.3.7",
1982
+        "array-flatten": "1.1.1",
1983
+        "body-parser": "1.19.0",
1984
+        "content-disposition": "0.5.3",
1985
+        "content-type": "~1.0.4",
1986
+        "cookie": "0.4.0",
1987
+        "cookie-signature": "1.0.6",
1988
+        "debug": "2.6.9",
1989
+        "depd": "~1.1.2",
1990
+        "encodeurl": "~1.0.2",
1991
+        "escape-html": "~1.0.3",
1992
+        "etag": "~1.8.1",
1993
+        "finalhandler": "~1.1.2",
1994
+        "fresh": "0.5.2",
1995
+        "merge-descriptors": "1.0.1",
1996
+        "methods": "~1.1.2",
1997
+        "on-finished": "~2.3.0",
1998
+        "parseurl": "~1.3.3",
1999
+        "path-to-regexp": "0.1.7",
2000
+        "proxy-addr": "~2.0.5",
2001
+        "qs": "6.7.0",
2002
+        "range-parser": "~1.2.1",
2003
+        "safe-buffer": "5.1.2",
2004
+        "send": "0.17.1",
2005
+        "serve-static": "1.14.1",
2006
+        "setprototypeof": "1.1.1",
2007
+        "statuses": "~1.5.0",
2008
+        "type-is": "~1.6.18",
2009
+        "utils-merge": "1.0.1",
2010
+        "vary": "~1.1.2"
2011
+      },
2012
+      "dependencies": {
2013
+        "cookie": {
2014
+          "version": "0.4.0",
2015
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
2016
+          "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
2017
+          "dev": true
2018
+        },
2019
+        "debug": {
2020
+          "version": "2.6.9",
2021
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
2022
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
2023
+          "dev": true,
2024
+          "requires": {
2025
+            "ms": "2.0.0"
2026
+          }
2027
+        },
2028
+        "ms": {
2029
+          "version": "2.0.0",
2030
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
2031
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
2032
+          "dev": true
2033
+        }
2034
+      }
2035
+    },
1871 2036
     "extend-shallow": {
1872 2037
       "version": "3.0.2",
1873 2038
       "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
@@ -1995,6 +2160,38 @@
1995 2160
         }
1996 2161
       }
1997 2162
     },
2163
+    "finalhandler": {
2164
+      "version": "1.1.2",
2165
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
2166
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
2167
+      "dev": true,
2168
+      "requires": {
2169
+        "debug": "2.6.9",
2170
+        "encodeurl": "~1.0.2",
2171
+        "escape-html": "~1.0.3",
2172
+        "on-finished": "~2.3.0",
2173
+        "parseurl": "~1.3.3",
2174
+        "statuses": "~1.5.0",
2175
+        "unpipe": "~1.0.0"
2176
+      },
2177
+      "dependencies": {
2178
+        "debug": {
2179
+          "version": "2.6.9",
2180
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
2181
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
2182
+          "dev": true,
2183
+          "requires": {
2184
+            "ms": "2.0.0"
2185
+          }
2186
+        },
2187
+        "ms": {
2188
+          "version": "2.0.0",
2189
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
2190
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
2191
+          "dev": true
2192
+        }
2193
+      }
2194
+    },
1998 2195
     "find-cache-dir": {
1999 2196
       "version": "2.1.0",
2000 2197
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
@@ -2105,6 +2302,12 @@
2105 2302
         }
2106 2303
       }
2107 2304
     },
2305
+    "forwarded": {
2306
+      "version": "0.1.2",
2307
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
2308
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
2309
+      "dev": true
2310
+    },
2108 2311
     "fragment-cache": {
2109 2312
       "version": "0.2.1",
2110 2313
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -2114,6 +2317,12 @@
2114 2317
         "map-cache": "^0.2.2"
2115 2318
       }
2116 2319
     },
2320
+    "fresh": {
2321
+      "version": "0.5.2",
2322
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
2323
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
2324
+      "dev": true
2325
+    },
2117 2326
     "from2": {
2118 2327
       "version": "2.3.0",
2119 2328
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
@@ -3008,12 +3217,42 @@
3008 3217
       "resolved": "https://registry.npmjs.org/http/-/http-0.0.0.tgz",
3009 3218
       "integrity": "sha1-huYybSnF0Dnen6xYSkVon5KfT3I="
3010 3219
     },
3220
+    "http-errors": {
3221
+      "version": "1.7.2",
3222
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
3223
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
3224
+      "dev": true,
3225
+      "requires": {
3226
+        "depd": "~1.1.2",
3227
+        "inherits": "2.0.3",
3228
+        "setprototypeof": "1.1.1",
3229
+        "statuses": ">= 1.5.0 < 2",
3230
+        "toidentifier": "1.0.0"
3231
+      },
3232
+      "dependencies": {
3233
+        "inherits": {
3234
+          "version": "2.0.3",
3235
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
3236
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
3237
+          "dev": true
3238
+        }
3239
+      }
3240
+    },
3011 3241
     "https-browserify": {
3012 3242
       "version": "1.0.0",
3013 3243
       "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
3014 3244
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
3015 3245
       "dev": true
3016 3246
     },
3247
+    "iconv-lite": {
3248
+      "version": "0.4.24",
3249
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
3250
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
3251
+      "dev": true,
3252
+      "requires": {
3253
+        "safer-buffer": ">= 2.1.2 < 3"
3254
+      }
3255
+    },
3017 3256
     "ieee754": {
3018 3257
       "version": "1.1.13",
3019 3258
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
@@ -3093,6 +3332,12 @@
3093 3332
       "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
3094 3333
       "dev": true
3095 3334
     },
3335
+    "ipaddr.js": {
3336
+      "version": "1.9.1",
3337
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
3338
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
3339
+      "dev": true
3340
+    },
3096 3341
     "is-accessor-descriptor": {
3097 3342
       "version": "0.1.6",
3098 3343
       "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@@ -3721,6 +3966,12 @@
3721 3966
         "safe-buffer": "^5.1.2"
3722 3967
       }
3723 3968
     },
3969
+    "media-typer": {
3970
+      "version": "0.3.0",
3971
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
3972
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
3973
+      "dev": true
3974
+    },
3724 3975
     "mem": {
3725 3976
       "version": "4.3.0",
3726 3977
       "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
@@ -3742,6 +3993,18 @@
3742 3993
         "readable-stream": "^2.0.1"
3743 3994
       }
3744 3995
     },
3996
+    "merge-descriptors": {
3997
+      "version": "1.0.1",
3998
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
3999
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
4000
+      "dev": true
4001
+    },
4002
+    "methods": {
4003
+      "version": "1.1.2",
4004
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
4005
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
4006
+      "dev": true
4007
+    },
3745 4008
     "micromatch": {
3746 4009
       "version": "3.1.10",
3747 4010
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -3773,6 +4036,12 @@
3773 4036
         "brorand": "^1.0.1"
3774 4037
       }
3775 4038
     },
4039
+    "mime": {
4040
+      "version": "1.6.0",
4041
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
4042
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
4043
+      "dev": true
4044
+    },
3776 4045
     "mime-db": {
3777 4046
       "version": "1.44.0",
3778 4047
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
@@ -3970,6 +4239,12 @@
3970 4239
         "semver": "^5.7.0"
3971 4240
       }
3972 4241
     },
4242
+    "node-fetch": {
4243
+      "version": "2.6.0",
4244
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
4245
+      "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
4246
+      "dev": true
4247
+    },
3973 4248
     "node-libs-browser": {
3974 4249
       "version": "2.2.1",
3975 4250
       "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
@@ -4383,6 +4658,15 @@
4383 4658
         "isobject": "^3.0.1"
4384 4659
       }
4385 4660
     },
4661
+    "on-finished": {
4662
+      "version": "2.3.0",
4663
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
4664
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
4665
+      "dev": true,
4666
+      "requires": {
4667
+        "ee-first": "1.1.1"
4668
+      }
4669
+    },
4386 4670
     "once": {
4387 4671
       "version": "1.4.0",
4388 4672
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4535,6 +4819,12 @@
4535 4819
         "better-assert": "~1.0.0"
4536 4820
       }
4537 4821
     },
4822
+    "parseurl": {
4823
+      "version": "1.3.3",
4824
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
4825
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
4826
+      "dev": true
4827
+    },
4538 4828
     "pascalcase": {
4539 4829
       "version": "0.1.1",
4540 4830
       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
@@ -4577,6 +4867,12 @@
4577 4867
       "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
4578 4868
       "dev": true
4579 4869
     },
4870
+    "path-to-regexp": {
4871
+      "version": "0.1.7",
4872
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
4873
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
4874
+      "dev": true
4875
+    },
4580 4876
     "pbkdf2": {
4581 4877
       "version": "3.0.17",
4582 4878
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
@@ -4644,6 +4940,16 @@
4644 4940
       "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
4645 4941
       "dev": true
4646 4942
     },
4943
+    "proxy-addr": {
4944
+      "version": "2.0.6",
4945
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
4946
+      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
4947
+      "dev": true,
4948
+      "requires": {
4949
+        "forwarded": "~0.1.2",
4950
+        "ipaddr.js": "1.9.1"
4951
+      }
4952
+    },
4647 4953
     "prr": {
4648 4954
       "version": "1.0.1",
4649 4955
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -4703,6 +5009,12 @@
4703 5009
       "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
4704 5010
       "dev": true
4705 5011
     },
5012
+    "qs": {
5013
+      "version": "6.7.0",
5014
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
5015
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
5016
+      "dev": true
5017
+    },
4706 5018
     "querystring": {
4707 5019
       "version": "0.2.0",
4708 5020
       "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -4734,6 +5046,24 @@
4734 5046
         "safe-buffer": "^5.1.0"
4735 5047
       }
4736 5048
     },
5049
+    "range-parser": {
5050
+      "version": "1.2.1",
5051
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
5052
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
5053
+      "dev": true
5054
+    },
5055
+    "raw-body": {
5056
+      "version": "2.4.0",
5057
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
5058
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
5059
+      "dev": true,
5060
+      "requires": {
5061
+        "bytes": "3.1.0",
5062
+        "http-errors": "1.7.2",
5063
+        "iconv-lite": "0.4.24",
5064
+        "unpipe": "1.0.0"
5065
+      }
5066
+    },
4737 5067
     "readable-stream": {
4738 5068
       "version": "2.3.6",
4739 5069
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
@@ -4920,6 +5250,12 @@
4920 5250
         "ret": "~0.1.10"
4921 5251
       }
4922 5252
     },
5253
+    "safer-buffer": {
5254
+      "version": "2.1.2",
5255
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
5256
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
5257
+      "dev": true
5258
+    },
4923 5259
     "schema-utils": {
4924 5260
       "version": "1.0.0",
4925 5261
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@@ -4937,12 +5273,64 @@
4937 5273
       "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
4938 5274
       "dev": true
4939 5275
     },
5276
+    "send": {
5277
+      "version": "0.17.1",
5278
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
5279
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
5280
+      "dev": true,
5281
+      "requires": {
5282
+        "debug": "2.6.9",
5283
+        "depd": "~1.1.2",
5284
+        "destroy": "~1.0.4",
5285
+        "encodeurl": "~1.0.2",
5286
+        "escape-html": "~1.0.3",
5287
+        "etag": "~1.8.1",
5288
+        "fresh": "0.5.2",
5289
+        "http-errors": "~1.7.2",
5290
+        "mime": "1.6.0",
5291
+        "ms": "2.1.1",
5292
+        "on-finished": "~2.3.0",
5293
+        "range-parser": "~1.2.1",
5294
+        "statuses": "~1.5.0"
5295
+      },
5296
+      "dependencies": {
5297
+        "debug": {
5298
+          "version": "2.6.9",
5299
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
5300
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
5301
+          "dev": true,
5302
+          "requires": {
5303
+            "ms": "2.0.0"
5304
+          },
5305
+          "dependencies": {
5306
+            "ms": {
5307
+              "version": "2.0.0",
5308
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
5309
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
5310
+              "dev": true
5311
+            }
5312
+          }
5313
+        }
5314
+      }
5315
+    },
4940 5316
     "serialize-javascript": {
4941 5317
       "version": "2.1.2",
4942 5318
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
4943 5319
       "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
4944 5320
       "dev": true
4945 5321
     },
5322
+    "serve-static": {
5323
+      "version": "1.14.1",
5324
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
5325
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
5326
+      "dev": true,
5327
+      "requires": {
5328
+        "encodeurl": "~1.0.2",
5329
+        "escape-html": "~1.0.3",
5330
+        "parseurl": "~1.3.3",
5331
+        "send": "0.17.1"
5332
+      }
5333
+    },
4946 5334
     "set-blocking": {
4947 5335
       "version": "2.0.0",
4948 5336
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -4978,6 +5366,12 @@
4978 5366
       "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
4979 5367
       "dev": true
4980 5368
     },
5369
+    "setprototypeof": {
5370
+      "version": "1.1.1",
5371
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
5372
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
5373
+      "dev": true
5374
+    },
4981 5375
     "sha.js": {
4982 5376
       "version": "2.4.11",
4983 5377
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
@@ -5421,6 +5815,12 @@
5421 5815
         }
5422 5816
       }
5423 5817
     },
5818
+    "statuses": {
5819
+      "version": "1.5.0",
5820
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
5821
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
5822
+      "dev": true
5823
+    },
5424 5824
     "stream-browserify": {
5425 5825
       "version": "2.0.2",
5426 5826
       "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -5697,6 +6097,12 @@
5697 6097
         "repeat-string": "^1.6.1"
5698 6098
       }
5699 6099
     },
6100
+    "toidentifier": {
6101
+      "version": "1.0.0",
6102
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
6103
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
6104
+      "dev": true
6105
+    },
5700 6106
     "ts-loader": {
5701 6107
       "version": "5.4.5",
5702 6108
       "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
@@ -5784,6 +6190,16 @@
5784 6190
       "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
5785 6191
       "dev": true
5786 6192
     },
6193
+    "type-is": {
6194
+      "version": "1.6.18",
6195
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
6196
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
6197
+      "dev": true,
6198
+      "requires": {
6199
+        "media-typer": "0.3.0",
6200
+        "mime-types": "~2.1.24"
6201
+      }
6202
+    },
5787 6203
     "typedarray": {
5788 6204
       "version": "0.0.6",
5789 6205
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -5915,6 +6331,12 @@
5915 6331
       "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
5916 6332
       "dev": true
5917 6333
     },
6334
+    "unpipe": {
6335
+      "version": "1.0.0",
6336
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
6337
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
6338
+      "dev": true
6339
+    },
5918 6340
     "unset-value": {
5919 6341
       "version": "1.0.0",
5920 6342
       "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@@ -6023,6 +6445,12 @@
6023 6445
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
6024 6446
       "dev": true
6025 6447
     },
6448
+    "utils-merge": {
6449
+      "version": "1.0.1",
6450
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
6451
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
6452
+      "dev": true
6453
+    },
6026 6454
     "uuid": {
6027 6455
       "version": "3.3.3",
6028 6456
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
@@ -6034,6 +6462,12 @@
6034 6462
       "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
6035 6463
       "dev": true
6036 6464
     },
6465
+    "vary": {
6466
+      "version": "1.1.2",
6467
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
6468
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
6469
+      "dev": true
6470
+    },
6037 6471
     "vm-browserify": {
6038 6472
       "version": "1.1.0",
6039 6473
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",

+ 4
- 2
package.json 查看文件

@@ -1,7 +1,7 @@
1 1
 {
2 2
   "name": "rpclibrary",
3
-  "version": "1.12.1",
4
-  "description": "rpclibrary is a websocket on steroids!",
3
+  "version": "2.0.0",
4
+  "description": "rpclibrary is a websocket RPC library",
5 5
   "main": "./js/Index.js",
6 6
   "repository": {
7 7
     "type": "git",
@@ -38,7 +38,9 @@
38 38
     "@types/expect": "^1.20.4",
39 39
     "@types/mocha": "^5.2.7",
40 40
     "@types/node": "^11.13.19",
41
+    "express": "^4.17.1",
41 42
     "mocha": "^6.2.0",
43
+    "node-fetch": "^2.6.0",
42 44
     "nyc": "^15.0.0",
43 45
     "ts-loader": "^5.3.3",
44 46
     "ts-mocha": "^6.0.0",

+ 0
- 53
scratchpad.ts 查看文件

@@ -1,53 +0,0 @@
1
-import { RPCSocket, RPCServer } from "./Index"
2
-
3
-
4
-// TL;DR
5
-const echo = (text: string) => {throw new Error("XD")}
6
-const add = (a: number, b: number) : number => a + b
7
-const getAsync = async () : Promise<{topic: string, message:string}>=> await new Promise((res, _) => {
8
-    setTimeout(() => {
9
-        res({
10
-            topic: "Hey!!",
11
-            message: "Hello World Async!"
12
-        })
13
-    }, 250)
14
-})
15
-const getCallback = (cbb: Function) : string => {
16
-    setTimeout(() => {
17
-        try{
18
-            cbb({
19
-                topic: "Hey!!",
20
-                message: "Hello World Callback!"
21
-            })
22
-        }catch(e){
23
-            console.log(String(e))
24
-        }
25
-    }, 250)
26
-    return "Please wait for a callback :)"
27
-}
28
-new RPCServer(20000, [{
29
-    name: 'MyRPCGroup1',
30
-    exportRPCs: () => [
31
-        echo,
32
-        add,
33
-        getAsync,
34
-        {
35
-            name: 'getCallback',
36
-            hook: getCallback,
37
-            onClose: (response, rpc) => { },
38
-            onCallback: (...callbackArgs) => { }
39
-        }
40
-    ]
41
-}])
42
-
43
-
44
-new RPCSocket(20000, 'localhost').connect()
45
-.then(async sock => {
46
-    try{
47
-        const val = await sock.call('ABCD', 12345)
48
-        console.log("VAL",val);
49
-        
50
-    }catch(e){
51
-        console.log("RPC error "+e)
52
-    }
53
-}).catch(e => console.log("connect err: "+e))

+ 15
- 20
src/Backend.ts 查看文件

@@ -11,12 +11,12 @@ export class RPCServer<
11 11
 > {
12 12
 
13 13
     private pio = PromiseIO.createServer()
14
-    private visibility: T.Visibility
15 14
     private closeHandler: T.CloseHandler
16 15
     private errorHandler: T.ErrorHandler
17 16
     private connectionHandler: T.ConnectionHandler
18 17
     private sesame?: T.SesameFunction
19 18
     private accessFilter: T.AccessFilter<InterfaceT>
19
+    private attached = false
20 20
 
21 21
     /**
22 22
      * @throws On RPC with no name
@@ -25,13 +25,9 @@ export class RPCServer<
25 25
      * @param conf A {@link SocketConf} object with optional settings
26 26
      */
27 27
     constructor(
28
-        private port: number,
29 28
         private exporters: T.ExporterArray<InterfaceT> = [],
30
-        private conf: T.ServerConf<InterfaceT> = {},
31
-        private ws = http.createServer()
29
+        conf: T.ServerConf<InterfaceT> = {},
32 30
     ) {
33
-        if (!conf.visibility) this.visibility = "0.0.0.0"
34
-
35 31
         if (conf.sesame) {
36 32
             this.sesame = U.makeSesameFunction(conf.sesame)
37 33
         }
@@ -57,7 +53,7 @@ export class RPCServer<
57 53
 
58 54
         exporters.forEach(U.fixNames) //TSC for some reason doesn't preserve name properties of methods
59 55
 
60
-        let badRPC = exporters.flatMap(ex => ex.exportRPCs()).find(rpc => !rpc.name)
56
+        let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function"?ex.RPCs():(ex as any)).find(rpc => !rpc.name)
61 57
         if (badRPC) {
62 58
             throw new Error(`
63 59
             RPC did not provide a name. 
@@ -67,32 +63,32 @@ export class RPCServer<
67 63
             \n`+ badRPC.toString() + `
68 64
             \n>------------OFFENDING RPC`)
69 65
         }
70
-
71 66
         
72
-        this.startWebsocket()
73
-    }
74
-
75
-    private startWebsocket() {
76 67
         try {
77
-            this.pio.attach(this.ws)
78
-            
79 68
             this.pio.on('socket', (socket: I.Socket) => {
80 69
                 socket.on('error', (err) => this.errorHandler(socket, err, "system", []))
81 70
                 socket.on('close', () => this.closeHandler(socket))
82 71
                 this.connectionHandler(socket)
83 72
                 this.initRPCs(socket)
84 73
             })
85
-
86
-            if(this.conf.selfStart == null || this.conf.selfStart == true)
87
-                this.pio.listen(this.port)
88
-
89 74
         } catch (e) {
90 75
             this.errorHandler(this.pio, e, 'system', [])
91 76
         }
92 77
     }
93 78
 
79
+    public attach = (httpServer = new http.Server()) : RPCServer<InterfaceT> => {
80
+        this.pio.attach(httpServer)
81
+        this.attached = true
82
+        return this
83
+    }
84
+
85
+    public listen(port:number) : RPCServer<InterfaceT>{
86
+        if(!this.attached) this.attach()
87
+        this.pio.listen(port)
88
+        return this
89
+    }
90
+
94 91
     protected initRPCs(socket: I.Socket) {
95
-        
96 92
         socket.hook('info', async (sesame?: string) => {
97 93
             const rpcs = await Promise.all(this.exporters.map(async exp => {
98 94
                 const allowed = await this.accessFilter(sesame, exp)
@@ -105,6 +101,5 @@ export class RPCServer<
105 101
 
106 102
     close(): void {
107 103
         this.pio.close()
108
-        this.ws.close()
109 104
     }
110 105
 }

+ 1
- 2
src/Interfaces.ts 查看文件

@@ -9,13 +9,12 @@ export type RPCExporter<
9 9
     Name extends keyof Ifc = keyof Ifc,
10 10
 > = {
11 11
     name: Name
12
-    exportRPCs() : T.RPCDefinitions<Ifc>[Name]
12
+    RPCs : T.RPCDefinitions<Ifc>[Name] | (() => T.RPCDefinitions<Ifc>[Name])
13 13
 }
14 14
 
15 15
 export interface Socket {
16 16
     id?: string
17 17
     bind: (name: string, listener: T.PioBindListener) => void
18
-  
19 18
     hook: (rpcname: string, handler: T.PioHookListener) => void
20 19
     unhook: (rpcname: string, listener?:T.AnyFunction) => void
21 20
     call: (rpcname: string, ...args: any[]) => Promise<any>

+ 6
- 4
src/PromiseIO/Server.ts 查看文件

@@ -6,6 +6,7 @@ const socketio = require('socket.io')
6 6
 
7 7
 export class PromiseIO {
8 8
     io?: Server
9
+    httpServer: httpServer
9 10
     private listeners: { [eventName in string]: ((...args: any) => void)[] } = {
10 11
         socket: [],
11 12
         connect: []
@@ -16,10 +17,8 @@ export class PromiseIO {
16 17
     }
17 18
 
18 19
     attach(httpServer: httpServer) {
20
+        this.httpServer = httpServer
19 21
         this.io = socketio(httpServer)
20
-    }
21
-
22
-    listen(port: number) {
23 22
         this.io!.on('connection', (sock: Socket) => {
24 23
             const pioSock = U.makePioSocket(sock)
25 24
             this.listeners['socket'].forEach(listener => listener(pioSock))
@@ -36,7 +35,10 @@ export class PromiseIO {
36 35
             pioSock.on('reconnecting', ()=>console.log('reconnecting'));
37 36
             */
38 37
         })
39
-        this.io!.listen(port)
38
+    }
39
+
40
+    listen(port: number) {
41
+        this.httpServer!.listen(port)
40 42
     }
41 43
 
42 44
     on(eventName: string, listener: T.AnyFunction) {

+ 0
- 2
src/Types.ts 查看文件

@@ -28,12 +28,10 @@ export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RP
28 28
 export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & AsyncIfc<T>
29 29
 
30 30
 export type ServerConf<InterfaceT extends RPCInterface> = {
31
-    selfStart?: boolean
32 31
     accessFilter?: AccessFilter<InterfaceT>
33 32
     connectionHandler?: ConnectionHandler
34 33
     errorHandler?: ErrorHandler
35 34
     closeHandler?: CloseHandler
36
-    visibility?: Visibility
37 35
 } & SesameConf
38 36
 
39 37
 export type SocketConf = {

+ 2
- 2
src/Utils.ts 查看文件

@@ -61,7 +61,7 @@ RPC did not provide a name.
61 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 63
     const owner = exporter.name
64
-    const RPCs = exporter.exportRPCs()
64
+    const RPCs = typeof exporter.RPCs === "function" ? exporter.RPCs() : exporter.RPCs
65 65
 
66 66
     return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame))
67 67
         .map(info => {
@@ -262,7 +262,7 @@ export const makePioSocket = (socket: any): I.Socket => {
262 262
         close: () => {
263 263
             socket
264 264
             socket.disconnect(true)
265
-        },
265
+        }
266 266
     }
267 267
 }
268 268
 

+ 154
- 53
test/Test.ts 查看文件

@@ -3,13 +3,16 @@ import { RPCServer, RPCSocket } from '../Index'
3 3
 import { RPCExporter } from "../src/Interfaces";
4 4
 import { ConnectedSocket } from "../src/Types";
5 5
 import * as log from 'why-is-node-running';
6
+import * as http from 'http';
7
+import * as express from 'express';
8
+import * as fetch from 'node-fetch';
6 9
 
7 10
 const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
8 11
 function makeServer() {
9 12
     let subcallback
10
-    return new RPCServer(21010, [{
13
+    const sv = new RPCServer([{
11 14
         name: 'test',
12
-        exportRPCs: () => [
15
+        RPCs: [
13 16
             {
14 17
                 name: 'echo',
15 18
                 call: async (s: string) => s,
@@ -37,12 +40,15 @@ function makeServer() {
37 40
             add,
38 41
             function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
39 42
         ]
40
-    }], {
43
+    }], 
44
+    {
41 45
         connectionHandler: (socket) => {
42 46
         },
43 47
         closeHandler: (socket) => { },
44 48
         errorHandler: (socket, err) => { throw err }
45 49
     })
50
+    sv.listen(21010)
51
+    return sv
46 52
 }
47 53
 
48 54
 
@@ -51,9 +57,9 @@ describe('RPCServer', () => {
51 57
     const echo = (x) => x
52 58
 
53 59
     before(done => {
54
-        server = new RPCServer(21003, [{
60
+        server = new RPCServer([{
55 61
             name: 'HelloWorldRPCGroup',
56
-            exportRPCs: () => [
62
+            RPCs: () => [
57 63
                 echo, //named function variable
58 64
                 function echof(x) { return x }, //named function
59 65
                 {
@@ -62,11 +68,11 @@ describe('RPCServer', () => {
62 68
                 }
63 69
             ]
64 70
         }])
65
-
71
+        server.listen(21003)
66 72
         client = new RPCSocket(21003, 'localhost')
67 73
         done()
68 74
     })
69
-    
75
+
70 76
     after(done => {
71 77
         client.close()
72 78
         server.close()
@@ -83,7 +89,7 @@ describe('RPCServer', () => {
83 89
 
84 90
             if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
85 91
                 done()
86
-            }else{
92
+            } else {
87 93
                 done(new Error("Bad response"))
88 94
             }
89 95
         })
@@ -91,12 +97,13 @@ describe('RPCServer', () => {
91 97
 
92 98
     it('new RPCServer() should fail on bad RPC', (done) => {
93 99
         try {
94
-            new RPCServer(20001, [{
100
+            const sv = new RPCServer([{
95 101
                 name: 'bad',
96
-                exportRPCs: () => [
102
+                RPCs: () => [
97 103
                     (aaa, bbb, ccc) => { return aaa + bbb + ccc }
98 104
                 ]
99 105
             }])
106
+            sv.listen(20001)
100 107
             done(new Error("Didn't fail with bad RPC"))
101 108
         } catch (badRPCError) {
102 109
             done()
@@ -104,6 +111,91 @@ describe('RPCServer', () => {
104 111
     })
105 112
 })
106 113
 
114
+describe('RPCServer with premade http server', () => {
115
+    const echo = (x) => x
116
+    const RPCs = [
117
+        echo, //named function variable
118
+        function echof(x) { return x }, //named function
119
+        {
120
+            name: 'echoExplicit', //describing object
121
+            call: async (x, y, z) => [x, y, z]
122
+        }
123
+    ]
124
+
125
+    const RPCExporters = [
126
+        {
127
+            name: 'HelloWorldRPCGroup',
128
+            RPCs: RPCs,
129
+        }
130
+    ]
131
+
132
+    const RPCExporters2 = [
133
+        {
134
+            name: 'Grp2',
135
+            RPCs: [
136
+                function test(){ return "test" }
137
+            ],
138
+        }
139
+    ]
140
+
141
+    let client:RPCSocket, server:RPCServer
142
+
143
+    before(done => {
144
+        const expressServer = express()
145
+        const httpServer = new http.Server(expressServer)
146
+
147
+        expressServer.get('/REST_ping', (req, res)=>{
148
+            return res
149
+            .send('REST_pong')
150
+            .status(200)
151
+        })
152
+
153
+        server = new RPCServer(
154
+            RPCExporters,
155
+        )
156
+        server.attach(httpServer)
157
+        httpServer.listen(8080)
158
+
159
+        client = new RPCSocket(8080, 'localhost')
160
+        
161
+        done()
162
+    })
163
+
164
+    after(done => {
165
+        client.close()
166
+        server.close()
167
+
168
+        done()
169
+    })
170
+
171
+    it('should serve REST', (done) => {
172
+        fetch('http://localhost:8080/REST_ping').then(response => {
173
+            response.text().then(text => {
174
+                if(text === "REST_pong")
175
+                    done()
176
+                else
177
+                    done(new Error("REST repsonse was "+text))
178
+            })
179
+        })
180
+    })
181
+
182
+
183
+    it('should be able to use all kinds of RPC definitions', (done) => {
184
+        client.connect().then(async () => {
185
+            const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
186
+            const r1 = await client['HelloWorldRPCGroup'].echof('World')
187
+            const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
188
+
189
+
190
+            if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
191
+                done()
192
+            } else {
193
+                done(new Error("Bad response"))
194
+            }
195
+        })
196
+    })
197
+})
198
+
107 199
 
108 200
 describe('RPCSocket', () => {
109 201
     let client: RPCSocket
@@ -111,6 +203,7 @@ describe('RPCSocket', () => {
111 203
 
112 204
     before(async () => {
113 205
         server = makeServer()
206
+
114 207
         client = new RPCSocket(21010, "localhost")
115 208
         return await client.connect()
116 209
     })
@@ -183,9 +276,9 @@ describe('It should do unhook', () => {
183 276
     let server: RPCServer
184 277
 
185 278
     before(async () => {
186
-        server = new RPCServer(21010, [{
279
+        server = new RPCServer([{
187 280
             name: "test",
188
-            exportRPCs: () => [{
281
+            RPCs: () => [{
189 282
                 name: 'subscribe',
190 283
                 hook: async (callback): Promise<void> => {
191 284
                     cb = <Function>callback
@@ -211,11 +304,13 @@ describe('It should do unhook', () => {
211 304
             function publish(): string { cb(candy); return candy },
212 305
             function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
213 306
             ]
214
-        }], {
307
+        }], 
308
+        {
215 309
             connectionHandler: (socket) => { },
216 310
             closeHandler: (socket) => { },
217 311
             errorHandler: (socket, err) => { throw err }
218 312
         })
313
+        server.listen(21010)
219 314
         client = new RPCSocket(21010, "localhost")
220 315
         return await client.connect()
221 316
     })
@@ -238,15 +333,15 @@ describe('It should do unhook', () => {
238 333
     const expected = [yesCandy, noCandy, noCandy, noCandy]
239 334
 
240 335
     it('Unhook+unsubscribe should stop callbacks', (done) => {
241
-        
242
-        client['test'].subscribe(function myCallback(c){
243
-            if(run == 1)
244
-            (myCallback as any).destroy()
336
+
337
+        client['test'].subscribe(function myCallback(c) {
338
+            if (run == 1)
339
+                (myCallback as any).destroy()
245 340
 
246 341
             if (c !== expected[run++]) {
247 342
                 done(new Error(`Wrong candy '${c}' in iteration '${run - 1}'`))
248 343
             }
249
-        }).then(async function(res){
344
+        }).then(async function (res) {
250 345
             const r1 = await client['test'].publish()
251 346
             const r3 = await client['test'].unsubscribe()
252 347
             const r2 = await client['test'].publish()
@@ -266,11 +361,11 @@ type SesameTestIfc = {
266 361
     test: {
267 362
         checkCandy: () => Promise<string>
268 363
         subscribe: (callback: Function) => Promise<topicDTO>
269
-        manyParams: <A=string,B=number,C=boolean,D=Object>(a:A, b:B, c:C, d:D) => Promise<[A, B, C, D]>
364
+        manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
270 365
     }
271 366
 
272 367
     other: {
273
-        echo: (x:any) => Promise<any>
368
+        echo: (x: any) => Promise<any>
274 369
     }
275 370
 }
276 371
 
@@ -281,9 +376,9 @@ describe('Sesame should unlock the socket', () => {
281 376
     let cb: Function = (...args) => { }
282 377
 
283 378
     before((done) => {
284
-        server = new RPCServer<SesameTestIfc>(21004, [{
379
+        server = new RPCServer<SesameTestIfc>([{
285 380
             name: "test",
286
-            exportRPCs: () => [
381
+            RPCs: () => [
287 382
                 {
288 383
                     name: 'subscribe',
289 384
                     hook: async (callback) => {
@@ -297,15 +392,16 @@ describe('Sesame should unlock the socket', () => {
297 392
                 async function checkCandy() { cb(candy); cb = () => { }; return candy },
298 393
                 async function manyParams(a, b, c, d) { return [a, b, c, d] }
299 394
             ],
300
-        },{
395
+        }, {
301 396
             name: 'other',
302
-            exportRPCs: () => [
303
-                async function echo(x){return x}
397
+            RPCs: () => [
398
+                async function echo(x) { return x }
304 399
             ]
305
-        
400
+
306 401
         }], {
307 402
             sesame: (_sesame) => _sesame === 'sesame!'
308 403
         })
404
+        server.listen(21004)
309 405
         const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
310 406
         sock.connect('sesame!').then(cli => {
311 407
             client = cli
@@ -378,15 +474,14 @@ describe('Error handling', () => {
378 474
     }
379 475
 
380 476
     it("RPC throws on client without handler", (done) => {
381
-        let server = new RPCServer(21004, [{
477
+        let server = new RPCServer([{
382 478
             name: "createUser",
383
-            exportRPCs: () => [{
479
+            RPCs: () => [{
384 480
                 name: 'createUser' as 'createUser',
385 481
                 call: createUser
386 482
             }]
387
-        }], {
388
-
389
-        })
483
+        }])
484
+        server.listen(21004)
390 485
 
391 486
         let sock = new RPCSocket(21004, 'localhost')
392 487
         sock.connect().then((cli) => {
@@ -413,9 +508,9 @@ describe('Error handling', () => {
413 508
     })
414 509
 
415 510
     it("RPC throws on server with handler", (done) => {
416
-        let server = new RPCServer(21004, [{
511
+        let server = new RPCServer([{
417 512
             name: "createUser",
418
-            exportRPCs: () => [{
513
+            RPCs: () => [{
419 514
                 name: 'createUser' as 'createUser',
420 515
                 call: createUser
421 516
             }]
@@ -424,6 +519,7 @@ describe('Error handling', () => {
424 519
                 done()
425 520
             }
426 521
         })
522
+        server.listen(21004)
427 523
 
428 524
         let sock = new RPCSocket(21004, 'localhost')
429 525
         sock.connect().then((cli) => {
@@ -457,9 +553,9 @@ describe("Errorhandler functionality", () => {
457 553
     }
458 554
 
459 555
     it("correct values are passed to the handler", (done) => {
460
-        let server = new RPCServer(21004, [{
556
+        let server = new RPCServer([{
461 557
             name: "createUser",
462
-            exportRPCs: () => [{
558
+            RPCs: () => [{
463 559
                 name: 'createUser' as 'createUser',
464 560
                 call: createUser
465 561
             }]
@@ -469,6 +565,7 @@ describe("Errorhandler functionality", () => {
469 565
                     done()
470 566
             }
471 567
         })
568
+        server.listen(21004)
472 569
 
473 570
         let sock = new RPCSocket(21004, 'localhost')
474 571
         sock.connect().then((cli) => {
@@ -493,9 +590,9 @@ describe("Errorhandler functionality", () => {
493 590
 
494 591
     it("handler sees sesame", (done) => {
495 592
         let sesame = "AAAAAAAAAAAAAAA"
496
-        let server = new RPCServer(21004, [{
593
+        let server = new RPCServer([{
497 594
             name: "createUser" as "createUser",
498
-            exportRPCs: () => [{
595
+            RPCs: () => [{
499 596
                 name: 'createUser' as 'createUser',
500 597
                 call: createUser
501 598
             }]
@@ -507,6 +604,7 @@ describe("Errorhandler functionality", () => {
507 604
             }
508 605
 
509 606
         })
607
+        server.listen(21004)
510 608
 
511 609
         let sock = new RPCSocket(21004, 'localhost')
512 610
         sock.connect(sesame).then((cli) => {
@@ -547,7 +645,7 @@ describe("Class binding", () => {
547 645
 
548 646
     class MyExporter implements RPCExporter<myExporterIfc>{
549 647
         name = "MyExporter" as "MyExporter"
550
-        exportRPCs = () => [
648
+        RPCs = () => [
551 649
             this.myRPC
552 650
         ]
553 651
 
@@ -559,7 +657,7 @@ describe("Class binding", () => {
559 657
 
560 658
     class MyOtherExporter implements RPCExporter<myExporterIfc>{
561 659
         name = "MyExporter" as "MyExporter"
562
-        exportRPCs = () => [
660
+        RPCs = () => [
563 661
             this.myRPC
564 662
         ]
565 663
 
@@ -571,18 +669,21 @@ describe("Class binding", () => {
571 669
 
572 670
     before(done => {
573 671
         exporter1 = new MyExporter()
574
-        serv = new RPCServer<myExporterIfc>(21004, [exporter1], {
575
-            accessFilter: async (sesame, exporter) => {
576
-                if(exporter.name === 'MyExporter'){
577
-                    if (!allowed) return false
578
-                    allowed = false
579
-                    return sesame === 'xxx';
580
-                }else{
581
-                    return false
582
-                }
583
-            },
584
-            sesame: "xxx"
585
-        })
672
+        serv = new RPCServer<myExporterIfc>(
673
+            [exporter1], 
674
+            {
675
+                accessFilter: async (sesame, exporter) => {
676
+                    if (exporter.name === 'MyExporter') {
677
+                        if (!allowed) return false
678
+                        allowed = false
679
+                        return sesame === 'xxx';
680
+                    } else {
681
+                        return false
682
+                    }
683
+                },
684
+                sesame: "xxx"
685
+            })
686
+        serv.listen(21004)
586 687
         done()
587 688
     })
588 689
 
@@ -630,7 +731,7 @@ describe("Class binding", () => {
630 731
 
631 732
 
632 733
     it("use sesameFilter for available", (done) => {
633
-        if (sock['MyExporter']){
734
+        if (sock['MyExporter']) {
634 735
             allowed = false
635 736
             done()
636 737
         }
@@ -722,6 +823,6 @@ describe("attaching handlers before connecting", () => {
722 823
 
723 824
 describe('finally', () => {
724 825
     it('print open handles (Ignore `DNSCHANNEL` and `Immediate`)', () => {
725
-        log()
826
+        //log(console)
726 827
     })
727 828
 })

Loading…
取消
儲存