1
1
type handlerFn = ( params : any ) => Promise < any >
2
2
type promisePair = { resolve : Function ; reject : Function }
3
3
4
+ type Message = {
5
+ method ?: string
6
+ id ?: number
7
+ params ?: any
8
+ result ?: any
9
+ error ?: any
10
+ }
11
+
12
+ export interface SerDe {
13
+ encode ( data : Message ) : any
14
+ decode ( obj : any ) : Message
15
+ }
16
+
17
+ const JSONSerDe : SerDe = {
18
+ encode : ( data ) => JSON . stringify ( data ) ,
19
+ decode : ( obj ) => JSON . parse ( obj ) ,
20
+ }
21
+
22
+ const NOOPSerDe : SerDe = {
23
+ encode : ( data ) => data ,
24
+ decode : ( obj ) => obj ,
25
+ }
26
+
4
27
export interface JsonRPC {
5
28
/** register a method to be called from other side */
6
29
register : ( method : string , handler : handlerFn ) => void
@@ -32,26 +55,27 @@ const NO_METHOD_ERRPR = {
32
55
33
56
class WSRPC implements JsonRPC {
34
57
private id = 0
58
+ private serde : SerDe
35
59
private transport : WSTransport
36
60
private methods : Map < string , handlerFn > = new Map ( )
37
61
private pending : Map < number , promisePair > = new Map ( )
38
62
39
- constructor ( transport : WSTransport ) {
63
+ constructor ( transport : WSTransport , serde = JSONSerDe ) {
40
64
this . transport = transport
41
- transport . setOnMessage ( this . on . bind ( this ) )
65
+ this . serde = serde
66
+ transport . setOnMessage ( ( data : any ) => this . on ( serde . decode ( data ) ) )
42
67
}
43
68
44
- private on ( data : string ) {
45
- const msg = JSON . parse ( data )
69
+ private on ( msg : Message ) {
46
70
47
71
// call
48
72
if ( 'method' in msg ) {
49
- const fn = this . methods . get ( msg . method )
73
+ const fn = this . methods . get ( msg . method ! )
50
74
if ( ! fn ) {
51
75
const err = { ...NO_METHOD_ERRPR }
52
76
err . data += `method=${ msg . method } `
53
77
if ( 'id' in msg ) {
54
- this . send ( JSON . stringify ( { id : msg . id , error : err } ) )
78
+ this . send ( { id : msg . id , error : err } )
55
79
} else {
56
80
console . error ( `WSRPC: Cant 'notify' unknown method "${ msg . method } "` )
57
81
}
@@ -63,11 +87,11 @@ class WSRPC implements JsonRPC {
63
87
const resp = { /*jsonrpc: '2.0',*/ id : msg . id }
64
88
65
89
fn ( msg . params )
66
- . then ( result => this . send ( JSON . stringify ( { ...resp , result } ) ) )
90
+ . then ( result => this . send ( { ...resp , result } ) )
67
91
. catch ( error => {
68
92
const msg = { ...resp , error : { ...SERVER_ERROR } }
69
93
msg . error . data += `name=${ error . name } , message=${ error . message } `
70
- this . send ( JSON . stringify ( msg ) )
94
+ this . send ( msg )
71
95
} )
72
96
return
73
97
}
@@ -79,27 +103,27 @@ class WSRPC implements JsonRPC {
79
103
80
104
// resolve
81
105
if ( 'id' in msg ) {
82
- if ( ! this . pending . has ( msg . id ) ) {
106
+ if ( ! this . pending . has ( msg . id ! ) ) {
83
107
console . error ( 'WSRPC: Cant resolve requestID: ' , msg . id )
84
108
return
85
109
}
86
110
87
- const { resolve, reject } = this . pending . get ( msg . id ) !
111
+ const { resolve, reject } = this . pending . get ( msg . id ! ) !
88
112
if ( 'result' in msg ) resolve ( msg . result )
89
113
else if ( 'error' in msg ) reject ( msg . error )
90
114
else {
91
115
console . warn (
92
116
`Received msgID=${ msg . id } with neither 'result' or 'error'. ` +
93
- `Likely service method is for 'notify' but is called as 'request'` ,
117
+ `Likely service method is for 'notify' but is called as 'request'` ,
94
118
)
95
119
resolve ( )
96
120
}
97
121
return
98
122
}
99
123
}
100
124
101
- private send ( msg : string ) {
102
- this . transport . send ( msg )
125
+ private send ( msg : Message ) {
126
+ this . transport . send ( this . serde . encode ( msg ) )
103
127
}
104
128
105
129
public register ( method : string , handler : handlerFn ) {
@@ -108,7 +132,7 @@ class WSRPC implements JsonRPC {
108
132
109
133
public call ( method : string , params ?: any ) : Promise < any > {
110
134
const id = this . id ++
111
- const msg = JSON . stringify ( { /*jsonrpc: '2.0',*/ id, method, params } )
135
+ const msg = { /*jsonrpc: '2.0',*/ id, method, params }
112
136
113
137
try {
114
138
this . send ( msg )
@@ -122,14 +146,19 @@ class WSRPC implements JsonRPC {
122
146
}
123
147
124
148
public notify ( method : string , params ?: any ) {
125
- const msg = JSON . stringify ( { /*jsonrpc: '2.0',*/ method, params } )
149
+ const msg = { /*jsonrpc: '2.0',*/ method, params }
126
150
this . send ( msg )
127
151
}
128
152
}
129
153
130
154
export interface WSTransport {
131
155
setOnMessage ( fn : ( data : any ) => void ) : void
132
- send ( msg : string ) : void
156
+ send ( msg : any ) : void
157
+ }
158
+
159
+ export interface WorkerLike {
160
+ onmessage : ( ( this : Worker , ev : MessageEvent ) => any ) | null ;
161
+ postMessage ( message : any , options ?: any ) : void ;
133
162
}
134
163
135
164
/**
@@ -142,9 +171,9 @@ class WSClientTransport implements WSTransport {
142
171
private openCB : ( ) => void
143
172
private errCB : ( e : Error ) => void
144
173
private sendBuffer : string [ ] = [ ]
145
- private onmessage = ( data : any ) => { }
174
+ private onmessage = ( data : any ) => { }
146
175
147
- constructor ( url : string , openCB = ( ) => { } , errCB = console . error ) {
176
+ constructor ( url : string , openCB = ( ) => { } , errCB = console . error ) {
148
177
this . url = url
149
178
this . openCB = openCB
150
179
this . errCB = errCB
@@ -205,20 +234,45 @@ class WSClientTransport implements WSTransport {
205
234
206
235
class WSServerTransport implements WSTransport {
207
236
private ws : WebSocket
208
- private onmessage = ( data : any ) => { }
209
237
210
238
constructor ( ws : WebSocket ) {
211
239
this . ws = ws
212
- this . ws . onmessage = evt => this . onmessage ( evt . data )
213
240
}
214
241
215
242
public setOnMessage ( fn : ( data : any ) => void ) {
216
- this . onmessage = fn
243
+ this . ws . onmessage = evt => fn ( evt . data )
217
244
}
218
245
219
246
public send ( msg : string ) {
220
247
this . ws . send ( msg )
221
248
}
222
249
}
250
+ /**
251
+ * WebWorkerTransport supports both Worker & SharedWorker on the main thread side
252
+ * On the Worker side either pass MessagePort for SharedWorker or
253
+ * pass a WorkerLike like object with `onmessage` and `postMessage` methods
254
+ */
255
+ class WebWorkerTransport implements WSTransport {
256
+ private worker : Worker | WorkerLike
257
+
258
+ constructor ( worker : Worker | WorkerLike ) {
259
+ this . worker = worker
260
+ }
261
+
262
+ public setOnMessage ( fn : ( data : any ) => void ) : void {
263
+ this . worker . onmessage = evt => fn ( evt . data )
264
+ }
223
265
224
- module . exports = { WSRPC , WSClientTransport, WSServerTransport }
266
+ public send ( msg : string ) : void {
267
+ this . worker . postMessage ( msg )
268
+ }
269
+ }
270
+
271
+ module . exports = {
272
+ JSONSerDe,
273
+ NOOPSerDe,
274
+ WSRPC ,
275
+ WSClientTransport,
276
+ WSServerTransport,
277
+ WebWorkerTransport,
278
+ }
0 commit comments