peter před 4 roky
rodič
revize
257c1eeced
7 změnil soubory, kde provedl 135 přidání a 48 odebrání
  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 Zobrazit soubor

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

+ 13
- 7
src/Frontend.ts Zobrazit soubor

17
  * A websocket-on-steroids with built-in RPC capabilities
17
  * A websocket-on-steroids with built-in RPC capabilities
18
  */
18
  */
19
 export class RPCSocket implements I.Socket{
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
     private socket: I.Socket
25
     private socket: I.Socket
21
 
26
 
22
     /**
27
     /**
34
      * @param name The function name to listen on
39
      * @param name The function name to listen on
35
      * @param handler The handler to attach
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
         return this.socket.hook(name, handler)
43
         return this.socket.hook(name, handler)
39
     }
44
     }
40
 
45
 
42
      * Removes a {@link hook} listener by name.
47
      * Removes a {@link hook} listener by name.
43
      * @param name The function name 
48
      * @param name The function name 
44
      */
49
      */
45
-    public unhook(name: T.Name){
50
+    public unhook(name: string){
46
         return this.socket.unhook(name)
51
         return this.socket.unhook(name)
47
     }
52
     }
48
 
53
 
74
      * @param rpcname The function to call
79
      * @param rpcname The function to call
75
      * @param args other arguments
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
         return await this.socket.call.apply(this.socket, [rpcname, ...args])
83
         return await this.socket.call.apply(this.socket, [rpcname, ...args])
79
     }
84
     }
80
 
85
 
83
      * @param rpcname The function to call
88
      * @param rpcname The function to call
84
      * @param args other arguments
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
         await this.socket.fire.apply(this.socket, [rpcname, ...args])
92
         await this.socket.fire.apply(this.socket, [rpcname, ...args])
88
     }
93
     }
89
     
94
     
90
     /**
95
     /**
91
      * Connects to the server and attaches available RPCs to this object
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
         this.socket = await bsock.connect(this.port, this.server, this.tls)
99
         this.socket = await bsock.connect(this.port, this.server, this.tls)
95
 
100
 
96
         const info:T.ExtendedRpcInfo[] = await this.info()
101
         const info:T.ExtendedRpcInfo[] = await this.info()
109
             this[i.owner][i.name] = f
114
             this[i.owner][i.name] = f
110
             this[i.owner][i.name].bind(this)
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
      * @param fnName The function name
129
      * @param fnName The function name
124
      * @param fnArgs A string-list of parameters
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
         const headerArgs = fnArgs.join(",")
133
         const headerArgs = fnArgs.join(",")
128
         const argParams = fnArgs.map(stripAfterEquals).join(",")
134
         const argParams = fnArgs.map(stripAfterEquals).join(",")
129
         return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
135
         return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
134
      * @param fnName The function name
140
      * @param fnName The function name
135
      * @param fnArgs A string-list of parameters
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
         const headerArgs = fnArgs.join(",")
144
         const headerArgs = fnArgs.join(",")
139
         const argParams = fnArgs.map(stripAfterEquals).join(",")
145
         const argParams = fnArgs.map(stripAfterEquals).join(",")
140
         return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
146
         return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {

+ 11
- 7
src/Interfaces.ts Zobrazit soubor

4
 /**
4
 /**
5
  * Interface for all classes that may export RPCs
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
  */
18
  */
15
 export interface Socket extends Destroyable {
19
 export interface Socket extends Destroyable {
16
     port: number
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
     on: T.OnFunction    
25
     on: T.OnFunction    
22
     close() : void
26
     close() : void
23
 }
27
 }

+ 24
- 21
src/Types.ts Zobrazit soubor

1
 import * as I from "./Interfaces";
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
 export type Visibility = "127.0.0.1" | "0.0.0.0"
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
 export type ConnectionHandler = (socket:I.Socket) => void
9
 export type ConnectionHandler = (socket:I.Socket) => void
9
 export type ErrorHandler = (socket:I.Socket, error:any) => void
10
 export type ErrorHandler = (socket:I.Socket, error:any) => void
10
 export type CloseHandler = (socket:I.Socket) =>  void
11
 export type CloseHandler = (socket:I.Socket) =>  void
26
 
27
 
27
 export type RPCType = 'Hook' | 'Unhook' | 'Call'
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
     onCallback?: CallbackFunction,
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
 export type BaseInfo = {
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
 export type HookInfo<T = {}> = BaseInfo & { 
53
 export type HookInfo<T = {}> = BaseInfo & { 
50
     type: 'Hook', 
54
     type: 'Hook', 
51
-    generator: (socket?:I.Socket) => HookFunction<T>
55
+    generator: (socket?:I.Socket) => HookFunction<AnyFunction, T>
52
 }
56
 }
53
 
57
 
54
 export type CallInfo = BaseInfo & {
58
 export type CallInfo = BaseInfo & {
60
 export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 
64
 export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 
61
 
65
 
62
 export type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => I.Socket
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 Zobrazit soubor

10
  * @param owner The owning RPC group's name
10
  * @param owner The owning RPC group's name
11
  * @throws Error on RPC without name property
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
     switch (typeof rpc){
14
     switch (typeof rpc){
15
         case  "object":
15
         case  "object":
16
             if(rpc['call']){
16
             if(rpc['call']){
22
                     call: rpc['call'],
22
                     call: rpc['call'],
23
                 }
23
                 }
24
             }else{
24
             }else{
25
-                const generator = hookGenerator(<T.HookRPC<any>>rpc)
25
+                const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc)
26
                 return {
26
                 return {
27
                     owner: owner,
27
                     owner: owner,
28
                     argNames: extractArgs(generator(undefined)),
28
                     argNames: extractArgs(generator(undefined)),
44
                 argNames: extractArgs(rpc),
44
                 argNames: extractArgs(rpc),
45
                 type: "Call",
45
                 type: "Call",
46
                 name: rpc.name,
46
                 name: rpc.name,
47
-                call: async(...args) => rpc.apply({}, args),
47
+                call: async(...args) => (<Function>rpc).apply({}, args),
48
             }
48
             }
49
     }
49
     }
50
     throw new Error("Bad socketIORPC type "+ typeof rpc)
50
     throw new Error("Bad socketIORPC type "+ typeof rpc)
56
  * @param exporter The exporter
56
  * @param exporter The exporter
57
  * @param makeUnique @default true Attach a suffix to RPC names
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
     const owner = exporter.name
60
     const owner = exporter.name
61
     const RPCs = [...exporter.exportRPCs()]
61
     const RPCs = [...exporter.exportRPCs()]
62
     const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
62
     const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
82
  * @param rpc The RPC to transform
82
  * @param rpc The RPC to transform
83
  * @returns A {@link HookFunction}
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
     const argsArr = extractArgs(rpc.hook)
86
     const argsArr = extractArgs(rpc.hook)
87
     argsArr.pop()
87
     argsArr.pop()
88
     const args = argsArr.join(',')
88
     const args = argsArr.join(',')
107
  * Extract a string list of parameters from a function
107
  * Extract a string list of parameters from a function
108
  * @param f The source function
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
     return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
112
     return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
113
 }
113
 }
114
 
114
 

+ 3
- 3
test/Test.ts Zobrazit soubor

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

+ 74
- 0
test/devtest.ts Zobrazit soubor

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
+}))

Načítá se…
Zrušit
Uložit