Daniel Hübleitner пре 4 година
родитељ
комит
a6663147c2

+ 97
- 0
lib/src/backend/RPCSocketServer.d.ts Прегледај датотеку

@@ -0,0 +1,97 @@
1
+import { Socket } from "./RPCSocketServer";
2
+declare type rpcType = 'hook' | 'unhook' | 'call';
3
+export declare type Outcome = "Success" | "Error";
4
+export declare type Visibility = "127.0.0.1" | "0.0.0.0";
5
+export declare class Response {
6
+    message?: string | undefined;
7
+    constructor(message?: string | undefined);
8
+}
9
+export declare class SuccessResponse extends Response {
10
+    result: Outcome;
11
+    constructor(message?: string);
12
+}
13
+export declare class ErrorResponse extends Response {
14
+    result: Outcome;
15
+    constructor(message?: string);
16
+}
17
+export declare class SubscriptionResponse extends SuccessResponse {
18
+    uid: string;
19
+    constructor(uid: string, message?: string);
20
+}
21
+export declare type UnhookFunction = (uid: string) => Promise<SuccessResponse | ErrorResponse>;
22
+export declare type callbackFunction = (...args: any[]) => Promise<SubscriptionResponse | ErrorResponse>;
23
+export declare type AsyncFunction = (...args: any[]) => Promise<any>;
24
+export interface RPCExporter {
25
+    name: string;
26
+    exportRPCs(): socketioRPC[];
27
+    exportPublicRPCs(): socketioRPC[];
28
+}
29
+declare type baseRPC = {
30
+    type: rpcType;
31
+    name: string;
32
+};
33
+declare type hookRPC = baseRPC & {
34
+    type: 'hook';
35
+    func: callbackFunction;
36
+    unhook: UnhookFunction;
37
+};
38
+declare type unhookRPC = baseRPC & {
39
+    type: 'unhook';
40
+    func: UnhookFunction;
41
+};
42
+declare type callRPC = baseRPC & {
43
+    type: 'call';
44
+    func: (...args: any[]) => Promise<any>;
45
+};
46
+export declare type socketioRPC = callRPC | unhookRPC | hookRPC;
47
+export declare type baseInfo = {
48
+    owner: string;
49
+    argNames: string[];
50
+};
51
+declare type HookInfo = baseRPC & baseInfo & {
52
+    type: 'hook';
53
+    generator: (socket: any) => callbackFunction;
54
+    unhook: UnhookFunction;
55
+};
56
+declare type UnhookInfo = baseRPC & baseInfo & {
57
+    type: 'unhook';
58
+    func: UnhookFunction;
59
+};
60
+declare type CallInfo = baseRPC & baseInfo & {
61
+    type: 'call';
62
+    func: AsyncFunction;
63
+};
64
+declare type RpcInfo = HookInfo | UnhookInfo | CallInfo;
65
+export declare type ExtendedRpcInfo = RpcInfo & {
66
+    uniqueName: string;
67
+};
68
+export declare const rpcToRpcinfo: (rpc: socketioRPC, owner: string) => RpcInfo;
69
+declare type OnFunction = (type: 'error' | 'close', f: (e?: any) => void) => Socket;
70
+export interface Socket {
71
+    port: number;
72
+    hook: (rpcname: string, ...args: any[]) => Socket;
73
+    unhook: (rpcname: string) => Socket;
74
+    call: (rpcname: string, ...args: any[]) => Promise<any>;
75
+    fire: (rpcname: string, ...args: any[]) => Promise<any>;
76
+    on: OnFunction;
77
+    destroy: () => void;
78
+    close: () => void;
79
+}
80
+export declare type RPCSocketConf = {
81
+    connectionHandler: (socket: Socket) => void;
82
+    errorHandler: (socket: Socket) => (error: any) => void;
83
+    closeHandler: (socket: Socket) => () => void;
84
+};
85
+export declare class RPCSocketServer {
86
+    private port;
87
+    private rpcExporters;
88
+    private visibility;
89
+    private conf;
90
+    private io;
91
+    private wsServer;
92
+    constructor(port: number, rpcExporters?: RPCExporter[], visibility?: Visibility, conf?: RPCSocketConf);
93
+    private startWebsocket;
94
+    protected initRPCs(socket: Socket): void;
95
+    protected initPublicRPCs(socket: Socket): void;
96
+}
97
+export {};

+ 153
- 0
lib/src/backend/RPCSocketServer.js Прегледај датотеку

@@ -0,0 +1,153 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+const http = require("http");
4
+const bsock = require("bsock");
5
+const uuid = require("uuid/v4");
6
+/* Responses */
7
+class Response {
8
+    constructor(message) {
9
+        this.message = message;
10
+    }
11
+}
12
+exports.Response = Response;
13
+class SuccessResponse extends Response {
14
+    constructor(message) {
15
+        super(message);
16
+        this.result = "Success";
17
+    }
18
+}
19
+exports.SuccessResponse = SuccessResponse;
20
+class ErrorResponse extends Response {
21
+    constructor(message = "Unknown error") {
22
+        super(message);
23
+        this.result = "Error";
24
+    }
25
+}
26
+exports.ErrorResponse = ErrorResponse;
27
+class SubscriptionResponse extends SuccessResponse {
28
+    constructor(uid, message) {
29
+        super(message);
30
+        this.uid = uid;
31
+    }
32
+}
33
+exports.SubscriptionResponse = SubscriptionResponse;
34
+exports.rpcToRpcinfo = (rpc, owner) => {
35
+    switch (rpc.type) {
36
+        case "call":
37
+            return {
38
+                owner: owner,
39
+                argNames: extractArgs(rpc.func),
40
+                type: rpc.type,
41
+                name: rpc.name,
42
+                func: rpc.func,
43
+            };
44
+        case "unhook":
45
+            return {
46
+                owner: owner,
47
+                argNames: extractArgs(rpc.func),
48
+                type: rpc.type,
49
+                name: rpc.name,
50
+                func: rpc.func,
51
+            };
52
+        case "hook":
53
+            const generator = hookGenerator(rpc);
54
+            return {
55
+                owner: owner,
56
+                argNames: extractArgs(generator(undefined)),
57
+                type: rpc.type,
58
+                name: rpc.name,
59
+                unhook: rpc.unhook,
60
+                generator: generator,
61
+            };
62
+    }
63
+};
64
+function rpcHooker(socket, exporter, makeUnique = true) {
65
+    const owner = exporter.name;
66
+    const RPCs = [...exporter.exportPublicRPCs(), ...exporter.exportRPCs()];
67
+    const suffix = makeUnique ? "-" + uuid().substr(0, 4) : "";
68
+    return RPCs.map(rpc => exports.rpcToRpcinfo(rpc, owner))
69
+        .map(info => {
70
+        const ret = info;
71
+        ret.uniqueName = info.name + suffix;
72
+        switch (info.type) {
73
+            case "hook":
74
+                socket.hook(ret.uniqueName, info.generator(socket));
75
+                break;
76
+            default:
77
+                socket.hook(ret.uniqueName, info.func);
78
+        }
79
+        socket.on('close', () => socket.unhook(info.name));
80
+        return ret;
81
+    });
82
+}
83
+const hookGenerator = (rpc) => {
84
+    const argsArr = extractArgs(rpc.func);
85
+    argsArr.pop();
86
+    const args = argsArr.join(',');
87
+    return eval(`(socket) => async (` + args + `) => { 
88
+        const res = await rpc.func(` + args + (args.length !== 0 ? ',' : '') + ` (x) => {
89
+            socket.call(res.uid, x)
90
+        })
91
+        if(res.result == 'Success'){
92
+            socket.on('close', async () => {
93
+                const unhookRes = await rpc.unhook(res.uid)
94
+                console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
95
+            })
96
+
97
+        }
98
+        return res
99
+    }`);
100
+};
101
+const extractArgs = (f) => {
102
+    let fn = String(f);
103
+    let args = fn.substr(0, fn.indexOf(")"));
104
+    args = args.substr(fn.indexOf("(") + 1);
105
+    let ret = args.split(",");
106
+    return ret;
107
+};
108
+class RPCSocketServer {
109
+    constructor(port, rpcExporters = [], visibility = "127.0.0.1", conf = {
110
+        errorHandler: (socket) => (error) => { socket.destroy(); console.error(error); },
111
+        closeHandler: (socket) => () => { console.log("Socket closing"); },
112
+        connectionHandler: (socket) => { console.log("New websocket connection in port " + socket.port); }
113
+    }) {
114
+        this.port = port;
115
+        this.rpcExporters = rpcExporters;
116
+        this.visibility = visibility;
117
+        this.conf = conf;
118
+        this.io = bsock.createServer();
119
+        this.wsServer = http.createServer();
120
+        this.startWebsocket();
121
+    }
122
+    startWebsocket() {
123
+        try {
124
+            this.io.attach(this.wsServer);
125
+            this.io.on('socket', (socket) => {
126
+                socket.on('error', this.conf.errorHandler(socket));
127
+                socket.on('close', this.conf.closeHandler(socket));
128
+                if (this.visibility === "127.0.0.1")
129
+                    this.initRPCs(socket);
130
+                else
131
+                    this.initPublicRPCs(socket);
132
+            });
133
+            this.wsServer.listen(this.port, this.visibility);
134
+        }
135
+        catch (e) {
136
+            //@ts-ignore
137
+            this.errorHandler(undefined)("Unable to connect to socket");
138
+        }
139
+    }
140
+    initRPCs(socket) {
141
+        socket.hook('info', () => rpcInfos);
142
+        const rpcInfos = [
143
+            ...this.rpcExporters.flatMap(exporter => rpcHooker(socket, exporter))
144
+        ];
145
+    }
146
+    initPublicRPCs(socket) {
147
+        socket.hook('info', () => rpcInfos);
148
+        const rpcInfos = [
149
+            ...this.rpcExporters.flatMap(exporter => rpcHooker(socket, exporter))
150
+        ];
151
+    }
152
+}
153
+exports.RPCSocketServer = RPCSocketServer;

+ 4
- 0
lib/test/test.js Прегледај датотеку

@@ -12,7 +12,11 @@ new Server_1.Server(20000, [{
12 12
                 func: async (s) => s,
13 13
             }],
14 14
     }]);
15
+<<<<<<< HEAD
15 16
 const caller = new Client_1.Client(20000, 'localhost');
17
+=======
18
+const caller = new RPCSocket_1.RPCSocket(20000, 'localhost');
19
+>>>>>>> 17dc58c5b3fd3c76113d592d895400498578affa
16 20
 caller.connect().then(_ => {
17 21
     caller.info().then(console.log);
18 22
     caller["HelloWorldRPCGroup"].echo("x").then(console.log);

+ 256
- 0
src/backend/RPCSocketServer.ts Прегледај датотеку

@@ -0,0 +1,256 @@
1
+import http = require('http');
2
+import bsock = require('bsock');
3
+
4
+import * as uuid from "uuid/v4"
5
+import { Socket } from "./RPCSocketServer"
6
+
7
+type rpcType = 'hook' | 'unhook' | 'call'
8
+export type Outcome = "Success" | "Error"
9
+export type Visibility = "127.0.0.1" | "0.0.0.0"
10
+
11
+/* Responses */
12
+export class Response{
13
+    constructor(
14
+        public message?:string
15
+    ){}
16
+}
17
+
18
+export class SuccessResponse extends Response{
19
+    result:Outcome = "Success"
20
+
21
+    constructor(
22
+        message?:string
23
+    ){
24
+        super(message)
25
+    }
26
+}
27
+
28
+export class ErrorResponse extends Response{
29
+    result:Outcome = "Error"
30
+
31
+    constructor(
32
+        message: string = "Unknown error"
33
+    ){
34
+        super(message)
35
+    } 
36
+}
37
+
38
+export class SubscriptionResponse extends SuccessResponse{
39
+    constructor(
40
+        public uid: string,
41
+        message?:string
42
+    ){
43
+        super(message)
44
+    }
45
+}
46
+export type UnhookFunction = (uid:string) => Promise<SuccessResponse | ErrorResponse>
47
+export type callbackFunction = (...args) => Promise<SubscriptionResponse | ErrorResponse>
48
+export type AsyncFunction = (...args) => Promise<any>
49
+
50
+export interface RPCExporter{
51
+    name: string
52
+    exportRPCs() : socketioRPC[]
53
+    exportPublicRPCs() : socketioRPC[]
54
+}
55
+
56
+type baseRPC = { 
57
+    type: rpcType
58
+    name: string
59
+}
60
+
61
+type hookRPC = baseRPC & {
62
+    type: 'hook'
63
+    func: callbackFunction
64
+    unhook: UnhookFunction
65
+}
66
+
67
+type unhookRPC = baseRPC & {
68
+    type: 'unhook'
69
+    func: UnhookFunction
70
+}
71
+
72
+type callRPC = baseRPC & {
73
+    type: 'call'
74
+    func: (...args) => Promise<any>
75
+}
76
+
77
+export type socketioRPC = callRPC | unhookRPC | hookRPC
78
+
79
+export type baseInfo = {
80
+    owner: string,
81
+    argNames: string[],
82
+}
83
+
84
+type HookInfo = baseRPC & baseInfo & { 
85
+    type: 'hook', 
86
+    generator: (socket) => callbackFunction
87
+    unhook: UnhookFunction
88
+}
89
+
90
+type UnhookInfo = baseRPC & baseInfo & {
91
+    type: 'unhook',
92
+    func: UnhookFunction
93
+}
94
+
95
+type CallInfo = baseRPC & baseInfo & {
96
+    type: 'call',
97
+    func: AsyncFunction
98
+}
99
+
100
+type RpcInfo = HookInfo | UnhookInfo | CallInfo
101
+
102
+export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 
103
+
104
+export const rpcToRpcinfo = (rpc : socketioRPC, owner: string):RpcInfo => {
105
+    switch(rpc.type){
106
+        case "call" :
107
+            return {
108
+                owner: owner,
109
+                argNames: extractArgs(rpc.func),
110
+                type: rpc.type,
111
+                name: rpc.name,
112
+                func: rpc.func,
113
+            }
114
+        case "unhook" :
115
+            return {
116
+                owner: owner,
117
+                argNames: extractArgs(rpc.func),
118
+                type: rpc.type,
119
+                name: rpc.name,
120
+                func: rpc.func,
121
+            }
122
+        case "hook" :
123
+            const generator = hookGenerator(rpc)
124
+            return {
125
+                owner: owner,
126
+                argNames: extractArgs(generator(undefined)),
127
+                type: rpc.type,
128
+                name: rpc.name,
129
+                unhook: rpc.unhook,
130
+                generator: generator,
131
+            }
132
+    }
133
+}
134
+
135
+
136
+function rpcHooker(socket: Socket, exporter:RPCExporter, makeUnique = true):ExtendedRpcInfo[]{
137
+    const owner = exporter.name
138
+    const RPCs = [...exporter.exportPublicRPCs(), ...exporter.exportRPCs()]
139
+    const suffix = makeUnique?"-"+uuid().substr(0,4):""
140
+    return RPCs.map(rpc => rpcToRpcinfo(rpc, owner))
141
+    .map(info => {
142
+        const ret:any = info
143
+        ret.uniqueName = info.name+suffix
144
+
145
+        switch(info.type){
146
+            case "hook":
147
+                socket.hook(ret.uniqueName, info.generator(socket))
148
+                break;
149
+            default:
150
+                socket.hook(ret.uniqueName, info.func)
151
+        }
152
+        socket.on('close', () => socket.unhook(info.name))
153
+        return ret
154
+    })
155
+}
156
+
157
+const hookGenerator = (rpc:hookRPC): HookInfo['generator'] => { 
158
+    const argsArr = extractArgs(rpc.func)
159
+    argsArr.pop()
160
+    const args = argsArr.join(',')
161
+
162
+    return eval(`(socket) => async (`+args+`) => { 
163
+        const res = await rpc.func(`+args+(args.length!==0?',':'')+` (x) => {
164
+            socket.call(res.uid, x)
165
+        })
166
+        if(res.result == 'Success'){
167
+            socket.on('close', async () => {
168
+                const unhookRes = await rpc.unhook(res.uid)
169
+                console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
170
+            })
171
+
172
+        }
173
+        return res
174
+    }`)
175
+}
176
+
177
+const extractArgs = (f:Function):string[] => {
178
+    let fn = String(f)
179
+    let args = fn.substr(0, fn.indexOf(")"))
180
+    args = args.substr(fn.indexOf("(")+1)
181
+    let ret = args.split(",")
182
+    return ret
183
+}
184
+
185
+
186
+type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => Socket
187
+
188
+export interface Socket {
189
+    port: number
190
+    hook: (rpcname: string, ...args: any[]) => Socket
191
+    unhook: (rpcname:string) => Socket
192
+    call: (rpcname:string, ...args: any[]) => Promise<any>
193
+    fire: (rpcname:string, ...args: any[]) => Promise<any>
194
+    on: OnFunction
195
+    destroy: ()=>void
196
+    close: ()=>void
197
+}
198
+
199
+export type RPCSocketConf = {
200
+    connectionHandler: (socket:Socket) => void
201
+    errorHandler: (socket:Socket) => (error:any) => void
202
+    closeHandler: (socket:Socket) => () =>  void
203
+}
204
+
205
+export class RPCSocketServer{
206
+    
207
+    private io = bsock.createServer()
208
+    private wsServer = http.createServer()
209
+
210
+    constructor(
211
+        private port:number,
212
+        private rpcExporters: RPCExporter[] = [],
213
+        private visibility: Visibility = "127.0.0.1",
214
+        private conf: RPCSocketConf = {
215
+            errorHandler: (socket:Socket) => (error:any) => { socket.destroy(); console.error(error) },
216
+            closeHandler: (socket:Socket) => () => { console.log("Socket closing") },
217
+            connectionHandler: (socket:Socket) => { console.log("New websocket connection in port "+socket.port) }
218
+        }
219
+    ){
220
+        this.startWebsocket()
221
+    }
222
+
223
+    private startWebsocket(){
224
+        try{
225
+            this.io.attach(this.wsServer)
226
+            this.io.on('socket', (socket:Socket) => {
227
+                socket.on('error', this.conf.errorHandler(socket))
228
+                socket.on('close', this.conf.closeHandler(socket))
229
+                if(this.visibility === "127.0.0.1")
230
+                    this.initRPCs(socket)
231
+                else
232
+                    this.initPublicRPCs(socket)
233
+            })
234
+            this.wsServer.listen(this.port, this.visibility)
235
+        }catch(e){
236
+            //@ts-ignore
237
+            this.errorHandler(undefined)("Unable to connect to socket")
238
+        }
239
+    }
240
+
241
+    protected initRPCs(socket:Socket){
242
+        socket.hook('info', () => rpcInfos)
243
+
244
+        const rpcInfos:ExtendedRpcInfo[] = [
245
+            ...this.rpcExporters.flatMap(exporter => rpcHooker(socket, exporter))
246
+        ]
247
+    }
248
+
249
+    protected initPublicRPCs(socket:Socket){
250
+        socket.hook('info', () => rpcInfos)
251
+
252
+        const rpcInfos:ExtendedRpcInfo[] = [
253
+            ...this.rpcExporters.flatMap(exporter => rpcHooker(socket, exporter))
254
+        ]
255
+    }
256
+}

Loading…
Откажи
Сачувај