|
@@ -10,8 +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<any, any, SubResT>, owner: string, sesame?:T.SesameFunction):T.RpcInfo => {
|
14
|
|
-
|
|
13
|
+export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
|
15
|
14
|
switch (typeof rpc){
|
16
|
15
|
case "object":
|
17
|
16
|
if(rpc['call']){
|
|
@@ -20,10 +19,10 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner
|
20
|
19
|
argNames: extractArgs(rpc['call']),
|
21
|
20
|
type: "Call",
|
22
|
21
|
name: rpc.name,
|
23
|
|
- call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw new Error('Bad sesame')}:rpc['call'], // check & remove sesame
|
|
22
|
+ call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw makeError(rpc.name)}:rpc['call'], // check & remove sesame
|
24
|
23
|
}
|
25
|
24
|
}else{
|
26
|
|
- const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, sesame)
|
|
25
|
+ const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame)
|
27
|
26
|
return {
|
28
|
27
|
owner: owner,
|
29
|
28
|
argNames: extractArgs(generator(undefined)),
|
|
@@ -45,7 +44,7 @@ RPC did not provide a name.
|
45
|
44
|
argNames: extractArgs(rpc),
|
46
|
45
|
type: "Call",
|
47
|
46
|
name: rpc.name,
|
48
|
|
- call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args)}:rpc, // check & remove sesame
|
|
47
|
+ call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args); throw makeError(rpc.name)}:rpc, // check & remove sesame
|
49
|
48
|
}
|
50
|
49
|
}
|
51
|
50
|
throw new Error("Bad socketIORPC type "+ typeof rpc)
|
|
@@ -57,62 +56,97 @@ RPC did not provide a name.
|
57
|
56
|
* @param exporter The exporter
|
58
|
57
|
* @param makeUnique @default true Attach a suffix to RPC names
|
59
|
58
|
*/
|
60
|
|
-export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
|
|
59
|
+export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
|
61
|
60
|
const owner = exporter.name
|
62
|
61
|
const RPCs = [...exporter.exportRPCs()]
|
63
|
62
|
|
64
|
63
|
|
65
|
|
- return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, sesame))
|
|
64
|
+ return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, errorHandler, sesame))
|
66
|
65
|
.map(info => {
|
67
|
66
|
const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
|
68
|
67
|
const ret:any = info
|
69
|
68
|
ret.uniqueName = info.name+suffix
|
70
|
69
|
|
|
70
|
+ let rpcFunction
|
71
|
71
|
|
72
|
|
- switch(info.type){
|
73
|
|
- case "Hook":
|
74
|
|
- socket.hook(ret.uniqueName, info.generator(socket))
|
75
|
|
- break;
|
76
|
|
- case "Call":
|
77
|
|
- socket.hook(ret.uniqueName, info.call)
|
78
|
|
- break;
|
79
|
|
- }
|
80
|
|
- socket.on('close', () => socket.unhook(info.name))
|
|
72
|
+ if(info.type === 'Hook')
|
|
73
|
+ rpcFunction = info.generator(socket)
|
|
74
|
+ else
|
|
75
|
+ rpcFunction = info.call
|
|
76
|
+
|
|
77
|
+ socket.hook(ret.uniqueName, callGenerator(socket, rpcFunction, errorHandler))
|
81
|
78
|
return ret
|
82
|
79
|
})
|
83
|
80
|
}
|
|
81
|
+
|
|
82
|
+/**
|
|
83
|
+ * Decorate an RPC with the error handler
|
|
84
|
+ * @param rpcFunction the function to decorate
|
|
85
|
+ */
|
|
86
|
+const callGenerator = (socket: I.Socket, rpcFunction : T.AnyFunction, errorHandler: T.ErrorHandler) : T.AnyFunction => {
|
|
87
|
+ const argsArr = extractArgs(rpcFunction)
|
|
88
|
+ const args = argsArr.join(',')
|
|
89
|
+ const argsStr = argsArr.map(stripAfterEquals).join(',')
|
|
90
|
+
|
|
91
|
+ return eval(`async (`+args+`) => {
|
|
92
|
+ try{
|
|
93
|
+ return await rpcFunction(`+argsStr+`)
|
|
94
|
+ }catch(e){
|
|
95
|
+ errorHandler(socket)(e)
|
|
96
|
+ }
|
|
97
|
+ }`)
|
|
98
|
+}
|
|
99
|
+
|
|
100
|
+/**
|
|
101
|
+ * Utility function to strip parameters like "a = 3" of their defaults
|
|
102
|
+ * @param str The parameter to modify
|
|
103
|
+ */
|
|
104
|
+export function stripAfterEquals(str:string):string{
|
|
105
|
+ return str.split("=")[0]
|
|
106
|
+}
|
|
107
|
+
|
84
|
108
|
/**
|
85
|
109
|
* Utility function to generate {@link HookFunction} from a RPC for backend
|
86
|
110
|
* @param rpc The RPC to transform
|
87
|
111
|
* @returns A {@link HookFunction}
|
88
|
112
|
*/
|
89
|
|
-const hookGenerator = (rpc:T.HookRPC<any, any, any>, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
|
|
113
|
+const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
|
90
|
114
|
const argsArr = extractArgs(rpc.hook)
|
91
|
115
|
argsArr.pop() //remove 'callback' from the end
|
92
|
116
|
const argsStr = argsArr.join(',')
|
93
|
117
|
|
94
|
118
|
if(sesameFn){
|
95
|
119
|
const args = ['sesame', ...argsArr].join(',')
|
96
|
|
- const f = eval(`(socket) => async (`+args+`) => {
|
97
|
|
- if(!sesameFn(sesame)) return
|
98
|
|
- const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
|
99
|
|
- if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
|
100
|
|
- socket.call.apply(socket, [res.uuid, ...cbargs])
|
101
|
|
- })
|
102
|
|
- return res
|
|
120
|
+ return eval(`(socket) => async (`+args+`) => {
|
|
121
|
+ try{
|
|
122
|
+ if(!sesameFn(sesame)) return
|
|
123
|
+ const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
|
|
124
|
+ if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
|
|
125
|
+ socket.call.apply(socket, [res.uuid, ...cbargs])
|
|
126
|
+ })
|
|
127
|
+ return res
|
|
128
|
+ }catch(e){
|
|
129
|
+ errorHandler(socket, e)
|
|
130
|
+ }
|
103
|
131
|
}`)
|
104
|
|
- return f
|
105
|
132
|
}
|
106
|
133
|
const args = argsArr.join(',')
|
107
|
134
|
return eval(`(socket) => async (`+args+`) => {
|
108
|
|
- const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
|
109
|
|
- if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
|
110
|
|
- socket.call.apply(socket, [res.uuid, ...cbargs])
|
111
|
|
- })
|
112
|
|
- return res
|
|
135
|
+ try{
|
|
136
|
+ const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
|
|
137
|
+ if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
|
|
138
|
+ socket.call.apply(socket, [res.uuid, ...cbargs])
|
|
139
|
+ })
|
|
140
|
+ return res
|
|
141
|
+ }catch(e){
|
|
142
|
+ errorHandler(socket, e)
|
|
143
|
+ }
|
113
|
144
|
}`)
|
114
|
145
|
}
|
115
|
146
|
|
|
147
|
+const makeError = (callName: string) => {
|
|
148
|
+ return new Error("Unhandled Promise rejection: Call not found: "+callName+". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: "+callName)
|
|
149
|
+}
|
116
|
150
|
|
117
|
151
|
/**
|
118
|
152
|
* Extract a string list of parameters from a function
|