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
 [![Weekly Downloads](https://img.shields.io/npm/dw/rpclibrary?color=important)](https://www.npmjs.com/package/rpclibrary)
5
 [![Weekly Downloads](https://img.shields.io/npm/dw/rpclibrary?color=important)](https://www.npmjs.com/package/rpclibrary)
6
 [![License Type](https://img.shields.io/npm/l/rpclibrary?color=blueviolet)](https://gitea.nitowa.xyz/docs/rpclibrary/src/branch/master/LICENSE.md)
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
 # How to install
10
 # How to install
11
 ```
11
 ```
16
 ```typescript
16
 ```typescript
17
 import {RPCServer, RPCSocket} from 'rpclibrary' 
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
 ```typescript
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
 ```typescript
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
 # [Full documentation](https://gitea.nitowa.xyz/docs/rpclibrary)
318
 # [Full documentation](https://gitea.nitowa.xyz/docs/rpclibrary)

+ 239
- 0
demo.ts View File

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

+ 4
- 4
src/Frontend.ts View File

13
 export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I.Socket{
13
 export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I.Socket{
14
 
14
 
15
     static async makeSocket<T extends T.RPCInterface = T.RPCInterface>(port:number, server: string, sesame?:string, conf?:T.SocketConf): Promise<T.ConnectedSocket<T>> {
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
     private socket: I.Socket
20
     private socket: I.Socket
128
     /**
128
     /**
129
      * Connects to the server and attaches available RPCs to this object
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
         this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false)
132
         this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false)
133
         this.errorHandlers.forEach(h => this.socket.on('error', h))
133
         this.errorHandlers.forEach(h => this.socket.on('error', h))
134
         this.closeHandlers.forEach(h => this.socket.on('close', h))
134
         this.closeHandlers.forEach(h => this.socket.on('close', h))
153
             this[i.owner][i.name] = f
153
             this[i.owner][i.name] = f
154
             this[i.owner][i.name].bind(this)
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
  */
6
  */
7
 export type RPCExporter<
7
 export type RPCExporter<
8
     Ifc extends T.RPCInterface = T.RPCInterface,
8
     Ifc extends T.RPCInterface = T.RPCInterface,
9
-    Name extends keyof Ifc = string,
9
+    Name extends keyof Ifc = keyof Ifc,
10
 > = {
10
 > = {
11
     name: Name
11
     name: Name
12
     exportRPCs() : T.RPCDefinitions<Ifc>[Name]
12
     exportRPCs() : T.RPCDefinitions<Ifc>[Name]

+ 7
- 4
src/Types.ts View File

3
 
3
 
4
 export type AnyFunction = (...args:any) => any
4
 export type AnyFunction = (...args:any) => any
5
 export type HookFunction = AnyFunction
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
 export type Visibility = "127.0.0.1" | "0.0.0.0"
7
 export type Visibility = "127.0.0.1" | "0.0.0.0"
8
 export type ConnectionHandler = (socket:I.Socket) => void
8
 export type ConnectionHandler = (socket:I.Socket) => void
9
 export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
9
 export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
10
 export type CloseHandler = (socket:I.Socket) =>  void
10
 export type CloseHandler = (socket:I.Socket) =>  void
11
 export type SesameFunction = (sesame : string) => boolean
11
 export type SesameFunction = (sesame : string) => boolean
12
-export type ExceptionHandling = 'local' | 'remote'
13
 export type SesameConf = {
12
 export type SesameConf = {
14
     sesame?: string | SesameFunction
13
     sesame?: string | SesameFunction
15
 }
14
 }
20
 
19
 
21
 export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
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
 export type ServerConf<InterfaceT extends RPCInterface> = {
24
 export type ServerConf<InterfaceT extends RPCInterface> = {
26
     accessFilter?: AccessFilter<InterfaceT>
25
     accessFilter?: AccessFilter<InterfaceT>
28
     errorHandler?: ErrorHandler
27
     errorHandler?: ErrorHandler
29
     closeHandler?: CloseHandler
28
     closeHandler?: CloseHandler
30
     visibility?: Visibility
29
     visibility?: Visibility
31
-    exceptionHandling?: ExceptionHandling
32
 } & SesameConf
30
 } & SesameConf
33
 
31
 
34
 export type SocketConf = {
32
 export type SocketConf = {
95
 
93
 
96
 export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
94
 export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
97
 export type HookCloseFunction<T> = (res: T, rpc:HookRPC<any, any>) => any
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
 
330
 
331
     it('should not work without sesame', (done) => {
331
     it('should not work without sesame', (done) => {
332
         const sock = new RPCSocket(21004, "localhost")
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
             if (!cli.test)
334
             if (!cli.test)
335
                 done()
335
                 done()
336
             else {
336
             else {
343
 
343
 
344
     it('should fail with wrong sesame', (done) => {
344
     it('should fail with wrong sesame', (done) => {
345
         const sock = new RPCSocket(21004, "localhost")
345
         const sock = new RPCSocket(21004, "localhost")
346
-        sock.connect<SesameTestIfc>('abasd').then(async (cli) => {
346
+        sock.connect('abasd').then(async (cli) => {
347
             if (!cli.test)
347
             if (!cli.test)
348
                 done()
348
                 done()
349
             else {
349
             else {
586
     })
586
     })
587
 
587
 
588
     beforeEach((done) => {
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
             sock = conn
591
             sock = conn
592
             done()
592
             done()
593
         })
593
         })
645
 
645
 
646
 describe("attaching handlers before connecting", () => {
646
 describe("attaching handlers before connecting", () => {
647
     it("fires error if server is unreachable", (done) => {
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
         let errorHandleCount = 0
649
         let errorHandleCount = 0
650
 
650
 
651
         sock.on('error', (err) => {
651
         sock.on('error', (err) => {

+ 1
- 1
tsconfig.json View File

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

Loading…
Cancel
Save