peter vor 5 Jahren
Ursprung
Commit
257c1eeced
7 geänderte Dateien mit 135 neuen und 48 gelöschten Zeilen
  1. 3
    3
      src/Backend.ts
  2. 13
    7
      src/Frontend.ts
  3. 11
    7
      src/Interfaces.ts
  4. 24
    21
      src/Types.ts
  5. 7
    7
      src/Utils.ts
  6. 3
    3
      test/Test.ts
  7. 74
    0
      test/devtest.ts

+ 3
- 3
src/Backend.ts Datei anzeigen

@@ -11,9 +11,9 @@ import * as I from './Interfaces';
11 11
  * A Websocket-server-on-steroids with built-in RPC capabilities
12 12
  */
13 13
 export class RPCServer<
14
-    SubResType = {}
14
+    SubResType = {},
15
+    InterfaceT extends T.RPCInterface = T.RPCInterface,
15 16
 > implements I.Destroyable{
16
-        
17 17
     private ws = http.createServer()
18 18
     private io = bsock.createServer()
19 19
     private visibility:T.Visibility
@@ -29,7 +29,7 @@ export class RPCServer<
29 29
      */
30 30
     constructor(
31 31
         private port:number,
32
-        private exporters: I.RPCExporter<SubResType>[] = [],
32
+        private exporters: I.RPCExporter<T.RPCInterface<InterfaceT>, keyof T.RPCInterface<InterfaceT>, SubResType>[] = [],
33 33
         conf: T.SocketConf =  {}
34 34
     ){
35 35
         

+ 13
- 7
src/Frontend.ts Datei anzeigen

@@ -17,6 +17,11 @@ function stripAfterEquals(str:string):string{
17 17
  * A websocket-on-steroids with built-in RPC capabilities
18 18
  */
19 19
 export class RPCSocket implements I.Socket{
20
+    static async makeSocket<T extends T.RPCInterface= T.RPCInterface>(port:number, server: string, tls: boolean = false): Promise<RPCSocket & T.RPCInterface<T>> {
21
+        const socket = <RPCSocket & T> new RPCSocket(port, server, tls)
22
+        return await socket.connect<T>()
23
+    }
24
+
20 25
     private socket: I.Socket
21 26
 
22 27
     /**
@@ -34,7 +39,7 @@ export class RPCSocket implements I.Socket{
34 39
      * @param name The function name to listen on
35 40
      * @param handler The handler to attach
36 41
      */
37
-    public hook(name: T.Name, handler: (...args:any[]) => any | Promise<any>){
42
+    public hook(name: string, handler: (...args:any[]) => any | Promise<any>){
38 43
         return this.socket.hook(name, handler)
39 44
     }
40 45
 
@@ -42,7 +47,7 @@ export class RPCSocket implements I.Socket{
42 47
      * Removes a {@link hook} listener by name.
43 48
      * @param name The function name 
44 49
      */
45
-    public unhook(name: T.Name){
50
+    public unhook(name: string){
46 51
         return this.socket.unhook(name)
47 52
     }
48 53
 
@@ -74,7 +79,7 @@ export class RPCSocket implements I.Socket{
74 79
      * @param rpcname The function to call
75 80
      * @param args other arguments
76 81
      */
77
-    public async call (rpcname: T.Name, ...args: T.Any[]) : Promise<T.Any>{
82
+    public async call (rpcname: string, ...args: any[]) : Promise<any>{
78 83
         return await this.socket.call.apply(this.socket, [rpcname, ...args])
79 84
     }
80 85
 
@@ -83,14 +88,14 @@ export class RPCSocket implements I.Socket{
83 88
      * @param rpcname The function to call
84 89
      * @param args other arguments
85 90
      */
86
-    public async fire(rpcname: T.Name, ...args: T.Any[]) : Promise<void>{
91
+    public async fire(rpcname: string, ...args: any[]) : Promise<void>{
87 92
         await this.socket.fire.apply(this.socket, [rpcname, ...args])
88 93
     }
89 94
     
90 95
     /**
91 96
      * Connects to the server and attaches available RPCs to this object
92 97
      */
93
-    public async connect(){
98
+    public async connect<T extends T.RPCInterface= T.RPCInterface>() : Promise<RPCSocket & T.RPCInterface<T>>{
94 99
         this.socket = await bsock.connect(this.port, this.server, this.tls)
95 100
 
96 101
         const info:T.ExtendedRpcInfo[] = await this.info()
@@ -109,6 +114,7 @@ export class RPCSocket implements I.Socket{
109 114
             this[i.owner][i.name] = f
110 115
             this[i.owner][i.name].bind(this)
111 116
         })
117
+        return <RPCSocket & T.RPCInterface<T>> <any> this
112 118
     }
113 119
 
114 120
     /**
@@ -123,7 +129,7 @@ export class RPCSocket implements I.Socket{
123 129
      * @param fnName The function name
124 130
      * @param fnArgs A string-list of parameters
125 131
      */
126
-    private callGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.AsyncFunction{
132
+    private callGenerator(fnName: string, fnArgs:string[]): T.AsyncFunction{
127 133
         const headerArgs = fnArgs.join(",")
128 134
         const argParams = fnArgs.map(stripAfterEquals).join(",")
129 135
         return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
@@ -134,7 +140,7 @@ export class RPCSocket implements I.Socket{
134 140
      * @param fnName The function name
135 141
      * @param fnArgs A string-list of parameters
136 142
      */
137
-    private hookGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.HookFunction{
143
+    private hookGenerator(fnName: string, fnArgs:string[]): T.HookFunction{
138 144
         const headerArgs = fnArgs.join(",")
139 145
         const argParams = fnArgs.map(stripAfterEquals).join(",")
140 146
         return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {

+ 11
- 7
src/Interfaces.ts Datei anzeigen

@@ -4,9 +4,13 @@ import * as I from "./Interfaces"
4 4
 /**
5 5
  * Interface for all classes that may export RPCs
6 6
  */
7
-export interface RPCExporter<T = {}>{
8
-    name: T.Name
9
-    exportRPCs() : T.RPC<T>[]
7
+export interface RPCExporter<
8
+    Ifc extends T.RPCInterface, 
9
+    K extends keyof Ifc, 
10
+    SubresT = {}
11
+>{
12
+    name: K
13
+    exportRPCs() : T.RPC<Ifc[K], keyof Ifc[K], SubresT>[]
10 14
 }
11 15
 
12 16
 /**
@@ -14,10 +18,10 @@ export interface RPCExporter<T = {}>{
14 18
  */
15 19
 export interface Socket extends Destroyable {
16 20
     port: number
17
-    hook: (rpcname: T.Name, handler: (...args:any[]) => any | Promise<any>) => I.Socket
18
-    unhook: (rpcname:T.Name) => I.Socket
19
-    call: (rpcname:T.Name, ...args: T.Any[]) => Promise<T.Any>
20
-    fire: (rpcname:T.Name, ...args: T.Any[]) => Promise<T.Any>
21
+    hook: (rpcname: string, handler: T.AnyFunction) => I.Socket
22
+    unhook: (rpcname:string) => I.Socket
23
+    call: (rpcname:string, ...args: any[]) => Promise<any>
24
+    fire: (rpcname:string, ...args: any[]) => Promise<any>
21 25
     on: T.OnFunction    
22 26
     close() : void
23 27
 }

+ 24
- 21
src/Types.ts Datei anzeigen

@@ -1,10 +1,11 @@
1 1
 import * as I from "./Interfaces";
2 2
 
3
+export type AnyFunction = (...any:any)=>any
4
+export type AsyncFunction<Fn extends AnyFunction = AnyFunction> = ReturnType<Fn> extends Promise<any> ? Fn : (...any:Parameters<Fn>) => Promise<ReturnType<Fn>>
5
+export type RPCGroup = { [name in string] : AsyncFunction }
6
+export type RPCInterface<Impl extends RPCInterface = {}> = { [groupName in string]: RPCGroup } & Impl
7
+
3 8
 export type Visibility = "127.0.0.1" | "0.0.0.0"
4
-export type Any = any
5
-export type Arg = string
6
-export type Name = Arg
7
-export type Owner = Name
8 9
 export type ConnectionHandler = (socket:I.Socket) => void
9 10
 export type ErrorHandler = (socket:I.Socket, error:any) => void
10 11
 export type CloseHandler = (socket:I.Socket) =>  void
@@ -26,29 +27,32 @@ export type SubscriptionResponse<T = {}> = Respose<T> & { result: "Success"; uui
26 27
 
27 28
 export type RPCType = 'Hook' | 'Unhook' | 'Call'
28 29
 
29
-export type HookRPC<T = {}> = {
30
-    name: Name
31
-    hook: HookFunction<T>
30
+export type HookT<G extends RPCGroup, K extends keyof G, SubresT> = AsyncFunction<HookFunction<G[K], SubresT>>
31
+export type CallT<G extends RPCGroup, K extends keyof G> = AsyncFunction<G[K]>
32
+
33
+export type HookRPC<G extends RPCGroup, K extends keyof G, SubresT = {}> = {
34
+    name: K
35
+    hook: HookT<G, K, SubresT>
32 36
     onCallback?: CallbackFunction,
33
-    onClose?: HookCloseFunction<T>
37
+    onClose?: HookCloseFunction<SubresT>
34 38
 }
35 39
 
36
-export type CallRPC = {
37
-    name: Name
38
-    call: AsyncFunction
39
-} | Function
40
+export type CallRPC<G extends RPCGroup, K extends keyof G> = {
41
+    name: K
42
+    call: CallT<G,K>
43
+} 
40 44
 
41
-export type RPC<T = {}> = CallRPC | HookRPC<T>
45
+export type RPC<G extends RPCGroup, Kg extends keyof G, SubresT> = CallRPC<G, Kg> | HookRPC<G, Kg, SubresT>
42 46
 
43 47
 export type BaseInfo = {
44
-    name: Name,
45
-    owner: Name,
46
-    argNames: Name[],
48
+    name: string,
49
+    owner: string,
50
+    argNames: string[],
47 51
 }
48 52
 
49 53
 export type HookInfo<T = {}> = BaseInfo & { 
50 54
     type: 'Hook', 
51
-    generator: (socket?:I.Socket) => HookFunction<T>
55
+    generator: (socket?:I.Socket) => HookFunction<AnyFunction, T>
52 56
 }
53 57
 
54 58
 export type CallInfo = BaseInfo & {
@@ -60,7 +64,6 @@ export type RpcInfo = HookInfo |  CallInfo
60 64
 export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 
61 65
 
62 66
 export type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => I.Socket
63
-export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<T>) => any
64
-export type HookFunction<T = {}> = (...args:any) => Promise<SubscriptionResponse<T> | ErrorResponse>
65
-export type AsyncFunction = (...args:any) => Promise<any>
66
-export type CallbackFunction = (...args:any) => void
67
+export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<any, any, T>) => any
68
+export type HookFunction<F extends AnyFunction = AnyFunction, SubResT = {}> = AsyncFunction<(...args:Parameters<F>) => SubscriptionResponse<SubResT> | ErrorResponse>
69
+export type CallbackFunction = (callback:AnyFunction, ...args:any) => any

+ 7
- 7
src/Utils.ts Datei anzeigen

@@ -10,7 +10,7 @@ import { SubscriptionResponse } from "./Types";
10 10
  * @param owner The owning RPC group's name
11 11
  * @throws Error on RPC without name property
12 12
  */
13
-export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<SubResT>, owner: T.Owner):T.RpcInfo => {
13
+export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string):T.RpcInfo => {
14 14
     switch (typeof rpc){
15 15
         case  "object":
16 16
             if(rpc['call']){
@@ -22,7 +22,7 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<SubResT>, owner: T.Owner)
22 22
                     call: rpc['call'],
23 23
                 }
24 24
             }else{
25
-                const generator = hookGenerator(<T.HookRPC<any>>rpc)
25
+                const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc)
26 26
                 return {
27 27
                     owner: owner,
28 28
                     argNames: extractArgs(generator(undefined)),
@@ -44,7 +44,7 @@ RPC did not provide a name.
44 44
                 argNames: extractArgs(rpc),
45 45
                 type: "Call",
46 46
                 name: rpc.name,
47
-                call: async(...args) => rpc.apply({}, args),
47
+                call: async(...args) => (<Function>rpc).apply({}, args),
48 48
             }
49 49
     }
50 50
     throw new Error("Bad socketIORPC type "+ typeof rpc)
@@ -56,7 +56,7 @@ RPC did not provide a name.
56 56
  * @param exporter The exporter
57 57
  * @param makeUnique @default true Attach a suffix to RPC names
58 58
  */
59
-export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
59
+export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
60 60
     const owner = exporter.name
61 61
     const RPCs = [...exporter.exportRPCs()]
62 62
     const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
@@ -82,7 +82,7 @@ export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter
82 82
  * @param rpc The RPC to transform
83 83
  * @returns A {@link HookFunction}
84 84
  */
85
-const hookGenerator = (rpc:T.HookRPC<any>): T.HookInfo['generator'] => { 
85
+const hookGenerator = (rpc:T.HookRPC<any, any, any>): T.HookInfo['generator'] => { 
86 86
     const argsArr = extractArgs(rpc.hook)
87 87
     argsArr.pop()
88 88
     const args = argsArr.join(',')
@@ -107,8 +107,8 @@ const hookGenerator = (rpc:T.HookRPC<any>): T.HookInfo['generator'] => {
107 107
  * Extract a string list of parameters from a function
108 108
  * @param f The source function
109 109
  */
110
-const extractArgs = (f:Function):T.Arg[] => {
111
-    let fn
110
+const extractArgs = (f:Function):string[] => {
111
+    let fn:string
112 112
     return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
113 113
 }
114 114
 

+ 3
- 3
test/Test.ts Datei anzeigen

@@ -54,7 +54,7 @@ function makeServer(){
54 54
 
55 55
 
56 56
 describe('RPCServer', () => {
57
-    let server: RPCServer<{ topic: string}>
57
+    let server: RPCServer<{ topic: string }, any>
58 58
 
59 59
     before((done) => {
60 60
         server = makeServer()
@@ -160,8 +160,8 @@ describe('It should do unhook', () => {
160 160
             name: "test",
161 161
             exportRPCs: () => [{
162 162
                 name: 'subscribe',
163
-                hook: async(callback) => {
164
-                    cb = callback
163
+                hook: async(callback):Promise<SubscriptionResponse<{topic:string}>> => {
164
+                    cb = <Function> callback
165 165
                     return {
166 166
                         result: "Success",
167 167
                         uuid: uuidv4(),

+ 74
- 0
test/devtest.ts Datei anzeigen

@@ -0,0 +1,74 @@
1
+import { RPCServer } from "../src/Backend";
2
+import { SubscriptionResponse, CallRPC, HookRPC, RPCInterface } from "../src/Types";
3
+import { RPCSocket } from "../src/Frontend";
4
+
5
+type MyInterface = RPCInterface<{ 
6
+    Group1: { 
7
+        triggerCallbacks: (...args:any[]) => Promise<void>,
8
+        subscribe: (param:string, callback:Function) => Promise<SubscriptionResponse<{a: string}>>,
9
+        unsubscribe: (uuid:string) => Promise<void>
10
+    },
11
+    Group2: {
12
+        echo: (x:string) => Promise<string>
13
+    }
14
+}>
15
+
16
+const callbacks:Map<string, Function> = new Map()
17
+
18
+new RPCServer<{a:string}, MyInterface>(20000, 
19
+    [{
20
+        name: "Group1",
21
+        exportRPCs: () => [
22
+        <HookRPC<MyInterface['Group1'], 'subscribe', {a:string}>>{
23
+            name: 'subscribe',
24
+            hook: async (param, callback) => { const _uuid = ""+Math.random(); console.log(param); callbacks.set(_uuid, callback); return { result: 'Success', a: '3', uuid: _uuid} }
25
+        },
26
+        <CallRPC<MyInterface['Group1'], 'unsubscribe'>>{
27
+            name: 'unsubscribe',
28
+            call: async (uuid) => { callbacks.delete(uuid) }
29
+        },
30
+        <CallRPC<MyInterface['Group1'], 'triggerCallbacks'>>{
31
+            name: 'triggerCallbacks',
32
+            call: async (...args) => { callbacks.forEach(cb => cb.apply({}, args)) }
33
+        }]
34
+    },{
35
+        name: 'Group2',
36
+        exportRPCs: () => [
37
+        <CallRPC<MyInterface['Group2'], 'echo'>>{
38
+            name: 'echo',
39
+            call: async (x) => x
40
+        }]
41
+    }]
42
+)
43
+
44
+new RPCServer<{a:string}, MyInterface>(20001, 
45
+    [{
46
+        name: "Group1",
47
+        exportRPCs: () => []
48
+    },{
49
+        name: 'Group2',
50
+        exportRPCs: () => [{
51
+            name: 'echo',
52
+            call: async (x) => x+" lol"
53
+        }]
54
+    }]
55
+)
56
+
57
+RPCSocket.makeSocket<MyInterface>(20000, 'localhost').then((async (client) => {
58
+    console.log(client)
59
+    const res = await client.Group1.subscribe('test', async (...args:any) => {
60
+        console.log.apply(console, args)
61
+
62
+        /* close the callbacks once you're done */
63
+        await client.Group1.unsubscribe(res.uuid)
64
+        client.unhook(res.uuid)
65
+    })
66
+
67
+    await client.Group1.triggerCallbacks("Hello", "World", "Callbacks")
68
+}))
69
+
70
+RPCSocket.makeSocket<MyInterface>(20001, 'localhost').then((async (client) => {
71
+    console.log(client)
72
+    const r = await client.Group2.echo("hee")
73
+    console.log(r)
74
+}))

Laden…
Abbrechen
Speichern