Browse Source

new readme and some small fixes

master
peter 4 years ago
parent
commit
81e2115ad9
8 changed files with 516 additions and 122 deletions
  1. 259
    103
      README.md
  2. 239
    0
      demo.ts
  3. 0
    4
      src/Backend.ts
  4. 4
    4
      src/Frontend.ts
  5. 1
    1
      src/Interfaces.ts
  6. 7
    4
      src/Types.ts
  7. 5
    5
      test/Test.ts
  8. 1
    1
      tsconfig.json

+ 259
- 103
README.md View File

@@ -5,7 +5,7 @@
5 5
 [![Weekly Downloads](https://img.shields.io/npm/dw/rpclibrary?color=important)](https://www.npmjs.com/package/rpclibrary)
6 6
 [![License Type](https://img.shields.io/npm/l/rpclibrary?color=blueviolet)](https://gitea.nitowa.xyz/docs/rpclibrary/src/branch/master/LICENSE.md)
7 7
 
8
-rpclibrary is a websocket on steroids!
8
+rpclibrary is a simple to use websocket RPC library.
9 9
 
10 10
 # How to install
11 11
 ```
@@ -16,147 +16,303 @@ npm i rpclibrary
16 16
 ```typescript
17 17
 import {RPCServer, RPCSocket} from 'rpclibrary' 
18 18
 
19
-const port = 1234
20
-const host = 'locahost'
19
+// TL;DR
20
+const echo = (text) => text
21
+const add = (a, b) => a + b
21 22
 
22
-const echo = (x) => x
23
+new RPCServer(20000, [{
24
+    name: 'MyRPCGroup1',
25
+    exportRPCs: () => [
26
+        echo,
27
+        add,
28
+    ]
29
+}])
30
+
31
+new RPCSocket(20000, 'localhost').connect().then(async sock => {
32
+    try{
33
+        const RPCs = sock['MyRPCGroup1']
34
+        await RPCs.echo("hello!").then(console.log)
35
+        await RPCs.add(1, Math.PI).then(console.log)
36
+    }catch(e){
37
+        console.log(String(e))
38
+    }
39
+})
40
+```
41
+
42
+# Async and Callbacks?
43
+
44
+rpclibrary offers full support for callbacks and Promises. 
45
+Please note that **there may only be one callback per RPC and it has to be the last parameter**
46
+
47
+```typescript
23 48
 
24
-const server = new RPCServer(port, [{
25
-    name: 'HelloWorldRPCGroup',
26
-    exportRPCs: ()  => [
27
-        echo, //named function variable
28
-        function echof(x){ return x }, //named function
49
+const getAsync = async () => await new Promise((res, _) => {
50
+    setTimeout(() => {
51
+        res({
52
+            topic: "Hey!!",
53
+            message: "Hello World Async!"
54
+        })
55
+    }, 250)
56
+})
57
+
58
+const getCallback = (callback) => {
59
+    setTimeout(() => {
60
+        try{
61
+            callback({
62
+                topic: "Hey!!",
63
+                message: "Hello World Callback!"
64
+            })
65
+        }catch(e){
66
+            console.log(String(e))
67
+        }
68
+    }, 250)
69
+    return "Please wait for a callback :)"
70
+}
71
+
72
+new RPCServer(20000, [{
73
+    name: 'MyRPCGroup1',
74
+    exportRPCs: () => [
75
+        getAsync,
29 76
         {
30
-            name: 'echoExplicit', //describing object
31
-            call: async (x) => x
77
+            name: 'getCallback',
78
+            hook: getCallback,
32 79
         }
33 80
     ]
34 81
 }])
35 82
 
36
-const client = new RPCSocket(port, host)
37
-
38
-client.connect().then(async () => {
39
-    const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
40
-    const r1 = await client['HelloWorldRPCGroup'].echof('World')
41
-    const r2 = await client['HelloWorldRPCGroup'].echoExplicit('RPC!')
42
-
43
-    console.log(r0,r1,r2) //Hello World RPC!
83
+new RPCSocket(20000, 'localhost').connect().then(async sock => {
84
+    try{
85
+        const RPCs = sock['MyRPCGroup1']
86
+        await RPCs.getAsync().then(console.log)
87
+        await RPCs.getCallback(console.log).then(console.log)
88
+    }catch(e){
89
+        console.log(String(e))
90
+    }
44 91
 })
45 92
 
46 93
 ```
47 94
 
48
-# Using callbacks
95
+# Hooks and Events
96
+
97
+There are a many things you can hook into to manage your connections
49 98
 
50
-rpclibrary offers a special type of call that can be used with callbacks. The callback **has to be the last argument** and **may be the only passed function**. 
99
+```typescript
100
+new RPCServer(20001, [{
101
+    name: 'MyRPCGroup1',
102
+    exportRPCs: () => [
103
+        echo,
104
+        add,
105
+        getAsync,
106
+        {
107
+            name: 'getCallback',
108
+            hook: getCallback,
109
+            onClose: (response, rpc) => { /* client disconnected */ },
110
+            onCallback: (...callbackArgs) => { /* callback triggered */ }
111
+        }
112
+    ],
113
+}], {
114
+    visibility: '127.0.0.1', //0.0.0.0
115
+    closeHandler: (socket) => { /* global close handler */ },
116
+    connectionHandler: (socket) => { /* new connection made */ },
117
+    errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
118
+})
51 119
 
52
-In order to function, some metadata has to be included in the return value of hooks. On success, the function is expected to return a `{ result: 'Success', uuid: string }` (Types.SubscriptionResponse) or in case of errors a `{ result: 'Error' }`(Types.ErrorResponse).
120
+const sock = new RPCSocket(20001, 'localhost')
121
+sock.on('error', (e) => { /* handle error */ })
122
+sock.on('close', () => { /* handle close event */ })
53 123
 
54
-The uuid, as the name implies, is used to uniquely identify the callback for a given invocation and also dictates the name given to the client-side RPC. Unless you got a preferred way of generating these (e.g. using some kind of unique information important to your task) we recommend [uuid](https://www.npmjs.com/package/uuid) for this purpose. 
124
+sock.hook('RPCName', (/* arg0, arg1, ..., argN */) => { /* bind client-side RPCs */ }) 
125
+//Retrieve the socket from connectionHandler (Server-side) and trigger with 
126
+//socket.call('RPCName', arg0, arg1, ..., argN)
55 127
 
56
-You should unhook the client socket once you're done with it as not to cause security or control flow issues.
128
+sock.connect().then(_ => { /* ... */})
129
+
130
+```
57 131
 
58
-```typescript
59
-import {RPCServer, RPCSocket} from 'rpclibrary'
60 132
 
61
-const port = 1234
62
-const host = 'locahost'
133
+# Restricting access
63 134
 
64
-const callbacks:Map<string, Function> = new Map()
135
+rpclibrary offers some minimalistic permission management
65 136
 
66
-new RPCServer(port, [{
67
-    name: 'HelloWorldRPCGroup',
68
-    exportRPCs: ()  => [
69
-        function  triggerCallbacks(...messages){ callbacks.forEach(cb => cb.apply({}, messages)) },
137
+```typescript
138
+
139
+//Restricting access
140
+new RPCServer(20002, [{
141
+    name: 'MyRPCGroup1',
142
+    exportRPCs: () => [
143
+        echo,
144
+        add,
145
+        getAsync,
70 146
         {
71
-            name: 'subscribe',
72
-            hook: async (callback) => {
73
-                const randStr = 'generate_a_random_string_here'
74
-                callbacks.set(randStr, callback); 
75
-                return { result: 'Success', uuid: randStr} 
76
-            }
77
-        },{
78
-            name: 'unsubscribe',
79
-            call: async (uuid) => { callbacks.delete(uuid) }
147
+            name: 'getCallback',
148
+            hook: getCallback,
80 149
         }
81
-    ]
82
-}])
150
+    ],
151
+}], {
152
+    sesame: "sesame open",
153
+    /* 
154
+    OR check sesame dynamically
155
+    and refine permissioning with accessfilter (optional)
156
+    */
157
+
158
+    //sesame: (sesame) => true
159
+    //accessFilter: (sesame, exporter) => { return exporter.name === "MyRPCGroup1" && sesame === "sesame open" },
160
+})
83 161
 
84
-const client = new RPCSocket(port, host)
85
-client.connect().then(async () => {
86
-    const res = await client['HelloWorldRPCGroup'].subscribe(async (...args:any) => {
87
-        console.log.apply(console, args)
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
+    }
172
+})
88 173
 
89
-        /* close the callbacks once you're done */
90
-        await client['HelloWorldRPCGroup'].unsubscribe(res.uuid)
91
-        client.unhook(res.uuid)
92
-    })
93 174
 
94
-    await client['HelloWorldRPCGroup'].triggerCallbacks("Hello", "World", "Callbacks")
95
-})
96 175
 ```
97 176
 
98
-If you need to include further response data into your `SubscriptionResponse` you can extend it using the server's first generic parameter `SubResType`
177
+
178
+# Typescript support
179
+
180
+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.
183
+
184
+Example:
185
+```typescript
186
+echo = (x) => x
187
+    /*becomes*/
188
+echo = (x:string) : string => x
189
+```
190
+
99 191
 
100 192
 ```typescript
101
-new RPCServer<{extension: string}>(port, [{
102
-    name: 'MyRPCGroup',
103
-    exportRPCs: ()  => [{
104
-        name: 'subscribe',
105
-        hook: async (callback) => {
106
-            return {
107
-                result: 'Success',
108
-                uuid: 'very_random_string',
109
-                extension: 'your_data_here' //tsc will demand this field
110
-            }
193
+type MyInterface = {
194
+    MyRPCGroup1: {
195
+        echo: (x: string) => string
196
+        add: (a: number, b: number) => number
197
+        getAsync: () => Promise<{ topic: string, message: string }>
198
+        getCallback: (callback:Function) => string
199
+    }
200
+};
201
+
202
+
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,
111 222
         }
112
-    }]}
113
-])
223
+    ],
224
+}])
225
+
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
+    }
235
+})
114 236
 
115 237
 ```
116 238
 
117
-#Experimental typing support
118
-It is possible to declare pseudo-interfaces for servers and clients by using server's second generic parameter.
119
-This feature is currently still in development and considered **unstable and untested**. Use with caution.
239
+
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:
120 244
 
121 245
 ```typescript
122
-type MyInterface = { 
123
-    Group1: { 
124
-        triggerCallbacks: (...args:any[]) => Promise<void>,
125
-        subscribe: (param:string, callback:Function) => Promise<SubscriptionResponse<{a: string}>>,
126
-        unsubscribe: (uuid:string) => Promise<void>
127
-    },
128
-    Group2: {
129
-        echo: (x:string) => Promise<string>
246
+interface IMyImplementation {
247
+    echo: (x: string) => string
248
+    add: (a: number, b: number) => number
249
+    getAsync: () => Promise<{ topic: string, message: string }>
250
+    getCallback: (callback:Function) => string
251
+}
252
+
253
+type MyInterface = {
254
+    MyRPCGroup1: {
255
+        echo: IMyImplementation['echo']
256
+        add: IMyImplementation['add']
257
+        getAsync: IMyImplementation['getAsync']
258
+        getCallback: IMyImplementation['getCallback']
130 259
     }
131 260
 }
132
-```
133
-Create a client using
134
-```typescript
135
-RPCSocket.makeSocket<MyInterface>(port, host).then((async (client) => {
136
-    const r = await client.Group2.echo("hee") //tsc knows about available RPCs 
137
-}))
138 261
 
139
-/* OR */
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"
140 265
 
141
-const client = new RPCSocket(port, host)
142
-client.connect<MyInterface>().then((async (client) => {
143
-    const r = await client.Group2.echo("hee") //tsc knows about available RPCs 
144
-})) 
145
-```
266
+    //List the functions you declared in MyInterface
267
+    exportRPCs = () => [
268
+        this.echo,
269
+        this.add,
270
+        this.getAsync,
271
+        this.getCallback
272
+    ]
273
+
274
+    //Write your implementations as you normally would
275
+    echo = (text: string) => text
276
+
277
+    add = (a: number, b: number) : number => a + b
278
+
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
+    }
301
+}
302
+
303
+type ProjectInterface = MyInterface
304
+                    //& MyOtherInterface
305
+                    //& MyOtherOtherInterface
306
+                    // ...
307
+;
308
+
309
+new RPCServer<ProjectInterface>(20004, [new MyImplementation() /*, new MyOtherImplementation(), new MyOtherOtherImplementation() */])
310
+
311
+new RPCSocket<ProjectInterface>(20004, 'localhost').connect().then(async sock => {
312
+    // ...
313
+})
146 314
 
147
-Create a server using
148
-```typescript
149
-new RPCServer<{a:string}, MyInterface>(port, 
150
-    [{
151
-        //...
152
-    },{
153
-        name: 'Group2', //Auto completion for names
154
-        exportRPCs: () => [{
155
-            name: 'echo', //this name too!
156
-            call: async (x) => x+"llo World!" //the paramter and return types are known by tsc 
157
-        }]
158
-    }]
159
-)
160 315
 ```
161 316
 
317
+
162 318
 # [Full documentation](https://gitea.nitowa.xyz/docs/rpclibrary)

+ 239
- 0
demo.ts View File

@@ -0,0 +1,239 @@
1
+import { RPCServer, RPCSocket } from './Index'
2
+import { RPCExporter } from './src/Interfaces'
3
+
4
+// TL;DR
5
+const echo = (text: string) => text
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 = (callback: Function) : string => {
16
+    setTimeout(() => {
17
+        try{
18
+            callback({
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
+new RPCSocket(20000, 'localhost').connect().then(async sock => {
44
+    try{
45
+        const RPCs = sock['MyRPCGroup1']
46
+        await RPCs.echo("hello!").then(console.log)
47
+        await RPCs.add(1, Math.PI).then(console.log)
48
+        await RPCs.getAsync().then(console.log)
49
+        await RPCs.getCallback(console.log).then(console.log)
50
+    }catch(e){
51
+        console.log(String(e))
52
+    }
53
+})
54
+
55
+//Hooks and events
56
+new RPCServer(20001, [{
57
+    name: 'MyRPCGroup1',
58
+    exportRPCs: () => [
59
+        echo,
60
+        add,
61
+        getAsync,
62
+        {
63
+            name: 'getCallback',
64
+            hook: getCallback,
65
+            onClose: (response, rpc) => { /* client disconnected */ },
66
+            onCallback: (...callbackArgs) => { /* callback triggered */ }
67
+        }
68
+    ],
69
+}], {
70
+    visibility: '127.0.0.1', //0.0.0.0
71
+    closeHandler: (socket) => { /* global close handler */ },
72
+    connectionHandler: (socket) => { /* new connection made */ },
73
+    errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
74
+})
75
+
76
+const sock = new RPCSocket(20001, 'localhost')
77
+sock.on('error', (e) => { /* handle error */ })
78
+sock.on('close', () => { /* handle close event */ })
79
+
80
+sock.hook('RPCName', (/* arg0, arg1, ..., argN */) => { /* bind client-side RPCs */ }) 
81
+//Retrieve the socket from connectionHandler (Server-side) and trigger with 
82
+//socket.call('RPCName', arg0, arg1, ..., argN)
83
+
84
+sock.connect().then(_ => { /* ... */})
85
+
86
+//Restricting access
87
+new RPCServer(20002, [{
88
+    name: 'MyRPCGroup1',
89
+    exportRPCs: () => [
90
+        echo,
91
+        add,
92
+        getAsync,
93
+        {
94
+            name: 'getCallback',
95
+            hook: getCallback,
96
+        }
97
+    ],
98
+}], {
99
+    sesame: "sesame open",
100
+    /* 
101
+    OR check sesame dynamically
102
+    and refine permissioning with accessfilter (optional)
103
+    */
104
+
105
+    //sesame: (sesame) => true
106
+    //accessFilter: (sesame, exporter) => { return exporter.name === "MyRPCGroup1" && sesame === "sesame open" },
107
+})
108
+
109
+new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
110
+    try{
111
+        const RPCs = sock['MyRPCGroup1']
112
+        await RPCs.echo("hello!").then(console.log)
113
+        await RPCs.add(1, Math.PI).then(console.log)
114
+        await RPCs.getAsync().then(console.log)
115
+        await RPCs.getCallback(console.log).then(console.log)
116
+    }catch(e){
117
+        console.log(String(e))
118
+    }
119
+})
120
+
121
+
122
+//TypeScript and pseudo-interfaces
123
+
124
+type MyInterface = {
125
+    MyRPCGroup1: {
126
+        echo: (x: string) => string
127
+        add: (a: number, b: number) => number
128
+        getAsync: () => Promise<{ topic: string, message: string }>
129
+        getCallback: (callback:Function) => string
130
+    }
131
+};
132
+
133
+
134
+/*
135
+exportRPCs is now type safe. Try swapping echo for badEcho.
136
+Sadly TSC's stack traces aren't the best, but try to scroll to the bottom of them to find useful info like
137
+
138
+Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
139
+*/
140
+
141
+const badEcho = (x: boolean) : number => 3
142
+
143
+new RPCServer<MyInterface>(20003, [{
144
+    name: 'MyRPCGroup1',
145
+    exportRPCs: () => [
146
+        //badEcho,
147
+        echo,
148
+        add,
149
+        getAsync,
150
+        {
151
+            name: 'getCallback',
152
+            hook: getCallback,
153
+        }
154
+    ],
155
+}])
156
+
157
+new RPCSocket<MyInterface>(20003, 'localhost').connect().then(async sock => {
158
+    try{
159
+        await sock.MyRPCGroup1.echo("hello!").then(console.log)
160
+        await sock.MyRPCGroup1.add(1, Math.PI).then(console.log)
161
+        await sock.MyRPCGroup1.getAsync().then(console.log)
162
+        await sock.MyRPCGroup1.getCallback(console.log).then(console.log)
163
+    }catch(e){
164
+        console.log(String(e))
165
+    }
166
+})
167
+
168
+
169
+//Class-based pattern
170
+
171
+interface IMyImplementation {
172
+    echo: (x: string) => string
173
+    add: (a: number, b: number) => number
174
+    getAsync: () => Promise<{ topic: string, message: string }>
175
+    getCallback: (callback:Function) => string
176
+}
177
+
178
+type MyIfc = {
179
+    MyRPCGroup1: {
180
+        echo: IMyImplementation['echo']
181
+        add: IMyImplementation['add']
182
+        getAsync: IMyImplementation['getAsync']
183
+        getCallback: IMyImplementation['getCallback']
184
+    }
185
+}
186
+
187
+class MyImplementation implements IMyImplementation, RPCExporter<MyIfc>{
188
+    //"X" as "X" syntax is required to satisfy the type system (as it assumed 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
+})

+ 0
- 4
src/Backend.ts View File

@@ -6,10 +6,6 @@ import * as T from './Types';
6 6
 import * as U from './Utils';
7 7
 import * as I from './Interfaces';
8 8
 
9
-
10
-/**
11
- * A Websocket-server-on-steroids with built-in RPC capabilities
12
- */
13 9
 export class RPCServer<
14 10
     InterfaceT extends T.RPCInterface = T.RPCInterface,
15 11
 > implements I.Destroyable {

+ 4
- 4
src/Frontend.ts View File

@@ -13,8 +13,8 @@ import { stripAfterEquals, appendComma } from './Utils';
13 13
 export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I.Socket{
14 14
 
15 15
     static async makeSocket<T extends T.RPCInterface = T.RPCInterface>(port:number, server: string, sesame?:string, conf?:T.SocketConf): Promise<T.ConnectedSocket<T>> {
16
-        const socket = new RPCSocket(port, server, conf)
17
-        return await socket.connect<T>(sesame)
16
+        const socket = new RPCSocket<T>(port, server, conf)
17
+        return await socket.connect(sesame)
18 18
     }
19 19
 
20 20
     private socket: I.Socket
@@ -128,7 +128,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
128 128
     /**
129 129
      * Connects to the server and attaches available RPCs to this object
130 130
      */
131
-    public async connect<T extends T.RPCInterface= Ifc>( sesame?: string ) : Promise<T.ConnectedSocket<T>> {
131
+    public async connect( sesame?: string ) : Promise<T.ConnectedSocket<Ifc>> {
132 132
         this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false)
133 133
         this.errorHandlers.forEach(h => this.socket.on('error', h))
134 134
         this.closeHandlers.forEach(h => this.socket.on('close', h))
@@ -153,7 +153,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
153 153
             this[i.owner][i.name] = f
154 154
             this[i.owner][i.name].bind(this)
155 155
         })
156
-        return <RPCSocket & T.RPCInterface<T>> (this as any) 
156
+        return <T.ConnectedSocket<Ifc>> (this as any) 
157 157
     }
158 158
 
159 159
     /**

+ 1
- 1
src/Interfaces.ts View File

@@ -6,7 +6,7 @@ import * as I from "./Interfaces"
6 6
  */
7 7
 export type RPCExporter<
8 8
     Ifc extends T.RPCInterface = T.RPCInterface,
9
-    Name extends keyof Ifc = string,
9
+    Name extends keyof Ifc = keyof Ifc,
10 10
 > = {
11 11
     name: Name
12 12
     exportRPCs() : T.RPCDefinitions<Ifc>[Name]

+ 7
- 4
src/Types.ts View File

@@ -3,13 +3,12 @@ import { RPCSocket } from "./Frontend";
3 3
 
4 4
 export type AnyFunction = (...args:any) => any
5 5
 export type HookFunction = AnyFunction
6
-export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> 
6
+export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean
7 7
 export type Visibility = "127.0.0.1" | "0.0.0.0"
8 8
 export type ConnectionHandler = (socket:I.Socket) => void
9 9
 export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
10 10
 export type CloseHandler = (socket:I.Socket) =>  void
11 11
 export type SesameFunction = (sesame : string) => boolean
12
-export type ExceptionHandling = 'local' | 'remote'
13 12
 export type SesameConf = {
14 13
     sesame?: string | SesameFunction
15 14
 }
@@ -20,7 +19,7 @@ export type FrontEndHandlerType = {
20 19
 
21 20
 export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
22 21
 
23
-export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & T
22
+export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & AsyncIfc<T>
24 23
 
25 24
 export type ServerConf<InterfaceT extends RPCInterface> = {
26 25
     accessFilter?: AccessFilter<InterfaceT>
@@ -28,7 +27,6 @@ export type ServerConf<InterfaceT extends RPCInterface> = {
28 27
     errorHandler?: ErrorHandler
29 28
     closeHandler?: CloseHandler
30 29
     visibility?: Visibility
31
-    exceptionHandling?: ExceptionHandling
32 30
 } & SesameConf
33 31
 
34 32
 export type SocketConf = {
@@ -95,3 +93,8 @@ export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
95 93
 
96 94
 export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
97 95
 export type HookCloseFunction<T> = (res: T, rpc:HookRPC<any, any>) => any
96
+
97
+
98
+export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: {[rpcname in keyof Ifc[grp]] : AsyncAnyFunction<Ifc[grp][rpcname]> } }
99
+
100
+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>

+ 5
- 5
test/Test.ts View File

@@ -330,7 +330,7 @@ describe('Sesame should unlock the socket', () => {
330 330
 
331 331
     it('should not work without sesame', (done) => {
332 332
         const sock = new RPCSocket(21004, "localhost")
333
-        sock.connect<SesameTestIfc>( /* no sesame */).then(async (cli) => {
333
+        sock.connect( /* no sesame */).then(async (cli) => {
334 334
             if (!cli.test)
335 335
                 done()
336 336
             else {
@@ -343,7 +343,7 @@ describe('Sesame should unlock the socket', () => {
343 343
 
344 344
     it('should fail with wrong sesame', (done) => {
345 345
         const sock = new RPCSocket(21004, "localhost")
346
-        sock.connect<SesameTestIfc>('abasd').then(async (cli) => {
346
+        sock.connect('abasd').then(async (cli) => {
347 347
             if (!cli.test)
348 348
                 done()
349 349
             else {
@@ -586,8 +586,8 @@ describe("Class binding", () => {
586 586
     })
587 587
 
588 588
     beforeEach((done) => {
589
-        const s = new RPCSocket(21004, 'localhost')
590
-        s.connect<myExporterIfc>("xxx").then(conn => {
589
+        const s = new RPCSocket<myExporterIfc>(21004, 'localhost')
590
+        s.connect("xxx").then(conn => {
591 591
             sock = conn
592 592
             done()
593 593
         })
@@ -645,7 +645,7 @@ describe("Class binding", () => {
645 645
 
646 646
 describe("attaching handlers before connecting", () => {
647 647
     it("fires error if server is unreachable", (done) => {
648
-        const sock = new RPCSocket(21004, 'localhost')
648
+        const sock = new RPCSocket<myExporterIfc>(21004, 'localhost')
649 649
         let errorHandleCount = 0
650 650
 
651 651
         sock.on('error', (err) => {

+ 1
- 1
tsconfig.json View File

@@ -9,6 +9,6 @@
9 9
       "strict": true,
10 10
       "experimentalDecorators": true
11 11
     },
12
-    "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts"],
12
+    "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts", "demo.ts"],
13 13
     "exclude": ["node_modules"]
14 14
   }

Loading…
Cancel
Save