1
- import { type JSONRPCMethodSet } from './types'
2
- import { JSONRPCNotification , JSONRPCRequest } from './dto/request'
1
+ import { type JSONRPCMethodSet } from './types.ts'
2
+ import { JSONRPCNotification , JSONRPCRequest } from './dto/request.ts'
3
+ import { JSONRPCErrorResponse , JSONRPCSuccessResponse } from './dto/response.ts'
4
+ import { isJSONRPCResponse , JSONRPCResponse } from './dto/response.ts'
3
5
import {
4
- JSONRPCErrorResponse ,
5
- JSONRPCResponse ,
6
- isJSONRPCResponse ,
7
- } from './dto/response'
8
- import { selfAddIdGenerator , getIDFromGenerator , type IDGenerator } from './id'
9
- import { isJSONRPCError } from './dto/errors'
6
+ getIDFromGenerator ,
7
+ type IDGenerator ,
8
+ selfAddIdGenerator ,
9
+ } from './id.ts'
10
+
11
+ type JSONRPCAnyRequest =
12
+ | JSONRPCNotification
13
+ | JSONRPCRequest
14
+ | Array < JSONRPCNotification | JSONRPCRequest >
10
15
11
16
/**
12
17
* the client cannot parse the server response
13
18
*/
14
19
export class JSONRPCClientParseError extends Error {
15
20
name = 'JSONRPCClientParseError'
21
+ request : JSONRPCAnyRequest
22
+ constructor ( message : string , request : JSONRPCAnyRequest ) {
23
+ super ( message )
24
+ this . request = request
25
+ }
16
26
}
17
27
18
28
/**
19
29
* just wrap the JSON.parse function
20
30
*/
21
- function parseJSON ( text : string ) : unknown {
31
+ function parseJSON (
32
+ text : string ,
33
+ associatedRequest : JSONRPCAnyRequest ,
34
+ ) : unknown {
22
35
try {
23
36
return JSON . parse ( text )
24
37
} catch {
25
- throw new JSONRPCClientParseError ( `The server send an malformed json` )
38
+ throw new JSONRPCClientParseError (
39
+ `The server send an malformed json` ,
40
+ associatedRequest ,
41
+ )
26
42
}
27
43
}
28
44
@@ -38,15 +54,15 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
38
54
39
55
constructor (
40
56
processor : ( input : string ) => Promise < string > ,
41
- idGenerator ?: IDGenerator
57
+ idGenerator ?: IDGenerator ,
42
58
) {
43
59
this . processor = processor
44
60
this . idGenerator = idGenerator || selfAddIdGenerator ( )
45
61
}
46
62
47
63
public createRequest < T extends keyof MethodSet > (
48
64
method : T extends string ? T : never ,
49
- params ?: Parameters < MethodSet [ T ] > [ 0 ]
65
+ params ?: Parameters < MethodSet [ T ] > [ 0 ] ,
50
66
) : JSONRPCRequest {
51
67
const id = getIDFromGenerator ( this . idGenerator )
52
68
const request = new JSONRPCRequest ( {
@@ -59,7 +75,7 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
59
75
60
76
public createNotifaction < T extends keyof MethodSet > (
61
77
method : T extends string ? T : never ,
62
- params ?: Parameters < MethodSet [ T ] > [ 0 ]
78
+ params ?: Parameters < MethodSet [ T ] > [ 0 ] ,
63
79
) : JSONRPCNotification {
64
80
const notification = new JSONRPCNotification ( {
65
81
method,
@@ -68,25 +84,29 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
68
84
return notification
69
85
}
70
86
71
- private processOneJsonValue ( jsonValue : unknown ) : JSONRPCResponse {
87
+ private processOneJsonValue (
88
+ jsonValue : unknown ,
89
+ associatedRequest : JSONRPCAnyRequest ,
90
+ ) : JSONRPCResponse {
72
91
if ( ! isJSONRPCResponse ( jsonValue ) ) {
73
92
throw new JSONRPCClientParseError (
74
- `The server sent an incorrect response object`
93
+ `The server sent an incorrect response object` ,
94
+ associatedRequest ,
75
95
)
76
96
}
77
97
return jsonValue
78
98
}
79
99
80
100
async request < T extends keyof MethodSet > (
81
101
method : T extends string ? T : never ,
82
- params ?: Parameters < MethodSet [ T ] > [ 0 ]
102
+ params ?: Parameters < MethodSet [ T ] > [ 0 ] ,
83
103
) : Promise < ReturnType < MethodSet [ T ] > > {
84
104
const request = this . createRequest ( method , params )
85
105
// responsed json string
86
106
const jsonString = await this . processor ( JSON . stringify ( request ) )
87
- const jsonValue = parseJSON ( jsonString )
107
+ const jsonValue = parseJSON ( jsonString , request )
88
108
// parsed response
89
- const response = this . processOneJsonValue ( jsonValue )
109
+ const response = this . processOneJsonValue ( jsonValue , request )
90
110
if ( 'error' in response ) {
91
111
return Promise . reject ( response . error )
92
112
} else {
@@ -96,50 +116,102 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
96
116
97
117
async notify < T extends keyof MethodSet > (
98
118
method : T extends string ? T : never ,
99
- params ?: Parameters < MethodSet [ T ] > [ 0 ]
119
+ params ?: Parameters < MethodSet [ T ] > [ 0 ] ,
100
120
) : Promise < void > {
101
121
const notification = this . createNotifaction ( method , params )
102
122
await this . processor ( JSON . stringify ( notification ) )
103
123
}
104
124
105
125
/**
106
- * You should use the createRequest or createNotifaction method to
126
+ * You should use the createRequest() or createNotifaction() method to
107
127
* create the requests array
108
128
*/
109
129
async batch (
110
130
...requests : Array < JSONRPCRequest | JSONRPCNotification >
111
- ) : Promise < JSONRPCErrorResponse | Array < JSONRPCResponse > | undefined > {
131
+ // deno-lint-ignore no-explicit-any
132
+ ) : Promise < PromiseSettledResult < any > [ ] | PromiseSettledResult < any > > {
112
133
// responsed json string
113
134
const jsonString = await this . processor ( JSON . stringify ( requests ) )
114
- if ( requests . every ( ( r ) => ! ( 'id' in r ) ) ) {
135
+ const requestCount = requests . filter ( ( r ) => 'id' in r ) . length
136
+ if ( requestCount === 0 ) {
115
137
// all the requests are notification
116
- return
138
+ return [ ]
117
139
}
118
140
// parsed response
119
- const jsonValue = parseJSON ( jsonString )
141
+ const jsonValue = parseJSON ( jsonString , requests )
120
142
121
143
if ( ! Array . isArray ( jsonValue ) ) {
122
144
if ( isJSONRPCResponse ( jsonValue ) && 'error' in jsonValue ) {
123
145
// If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value,
124
146
// the response from the Server MUST be a single Response object.
125
- return jsonValue
147
+ return {
148
+ status : 'rejected' ,
149
+ reason : jsonValue . error ,
150
+ }
126
151
}
127
152
128
153
// requests contains request, so response must be an array
129
154
throw new JSONRPCClientParseError (
130
- `The server incorrectly handle the batch request`
155
+ `The server incorrectly handle the batch request` ,
156
+ requests ,
157
+ )
158
+ }
159
+
160
+ if ( jsonValue . length !== requestCount ) {
161
+ throw new JSONRPCClientParseError (
162
+ `The server returned batch response does not match the request count` ,
163
+ requests ,
131
164
)
132
165
}
133
166
134
- const responses : JSONRPCResponse [ ] = [ ]
167
+ if ( ! jsonValue . every ( isJSONRPCResponse ) ) {
168
+ throw new JSONRPCClientParseError (
169
+ `The server returned batch response contains invalid value` ,
170
+ requests ,
171
+ )
172
+ }
173
+
174
+ const unorderedResponses : JSONRPCResponse [ ] = jsonValue
175
+ let errorStartIndex = 0
176
+ // deno-lint-ignore no-explicit-any
177
+ const responses : PromiseSettledResult < any > [ ] = [ ] // ordered
178
+
135
179
for ( const request of requests ) {
136
180
if ( ! ( 'id' in request ) ) {
137
181
continue
138
182
}
139
- const { id } = request
140
- // TODO
183
+
141
184
// The Response objects being returned from a batch call MAY be returned in any order within the Array.
142
185
// The Client SHOULD match contexts between the set of Request objects and the resulting set of Response objects based on the id member within each Object.
186
+
187
+ const index = unorderedResponses . findIndex (
188
+ ( response ) => response . id === request . id ,
189
+ )
190
+ if ( index === - 1 ) {
191
+ // no corresponding id, so the response will be JSONRPCErrorResponse
192
+ // find the first (not been scanned) error
193
+ const errRespIndex = unorderedResponses
194
+ . slice ( errorStartIndex )
195
+ . findIndex (
196
+ ( response ) =>
197
+ 'error' in response && response . id === null ,
198
+ )
199
+ // this implemention expect that all the JSONRPCErrorResponse are ordered
200
+ responses . push ( {
201
+ status : 'rejected' ,
202
+ reason : ( unorderedResponses [
203
+ errRespIndex
204
+ ] as JSONRPCErrorResponse ) . error ,
205
+ } )
206
+ // update the error start index
207
+ errorStartIndex = errRespIndex
208
+ } else {
209
+ responses . push ( {
210
+ status : 'fulfilled' ,
211
+ value : ( unorderedResponses [ index ] as JSONRPCSuccessResponse )
212
+ . result ,
213
+ } )
214
+ }
143
215
}
144
216
return responses
145
217
}
0 commit comments