jsonrpc-client-websocket 1.3.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/lib/deferred-promise.js +1 -4
- package/lib/index.js +2 -7
- package/lib/jsonrpc-websocket.d.ts +3 -2
- package/lib/jsonrpc-websocket.js +41 -41
- package/lib/jsonrpc.model.d.ts +2 -1
- package/lib/jsonrpc.model.js +6 -10
- package/package.json +19 -16
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[](https://travis-ci.com/fabriciobastian/jsonrpc-client-websocket) [](https://choosealicense.com/licenses/mit/) [](https://www.npmjs.com/package/jsonrpc-client-websocket)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
A simple JSON RPC 2.0 client over websockets
|
|
6
6
|
|
|
@@ -70,3 +70,8 @@ websocket.on('sum', (a: number, b: number) => {
|
|
|
70
70
|
});
|
|
71
71
|
```
|
|
72
72
|
The defined RPC methods can also be called with both positional and named parameters.
|
|
73
|
+
|
|
74
|
+
## Remove RPC method
|
|
75
|
+
```typescript
|
|
76
|
+
websocket.off('sum');
|
|
77
|
+
```
|
package/lib/deferred-promise.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
class DeferredPromise {
|
|
1
|
+
export class DeferredPromise {
|
|
4
2
|
constructor() {
|
|
5
3
|
this.promise = new Promise((resolve, reject) => {
|
|
6
4
|
this.deferResolve = resolve;
|
|
@@ -17,4 +15,3 @@ class DeferredPromise {
|
|
|
17
15
|
this.deferReject(error);
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
|
-
exports.DeferredPromise = DeferredPromise;
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
4
|
-
}
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
__export(require("./jsonrpc-websocket"));
|
|
7
|
-
__export(require("./jsonrpc.model"));
|
|
1
|
+
export * from './jsonrpc-websocket';
|
|
2
|
+
export * from './jsonrpc.model';
|
|
@@ -21,8 +21,9 @@ export declare class JsonRpcWebsocket {
|
|
|
21
21
|
open(): Promise<Event>;
|
|
22
22
|
close(): Promise<CloseEvent>;
|
|
23
23
|
on(methodName: string, callback: (...args: any[]) => any): void;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
off(methodName: string): void;
|
|
25
|
+
call(method: string, params?: unknown): Promise<JsonRpcResponse>;
|
|
26
|
+
notify(method: string, params?: unknown): void;
|
|
26
27
|
private createWebsocket;
|
|
27
28
|
private respondOk;
|
|
28
29
|
private respondError;
|
package/lib/jsonrpc-websocket.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const jsonrpc_model_1 = require("./jsonrpc.model");
|
|
6
|
-
var WebsocketReadyStates;
|
|
1
|
+
import * as getParameterNames from 'get-parameter-names';
|
|
2
|
+
import { DeferredPromise } from './deferred-promise';
|
|
3
|
+
import { JsonRpcErrorCodes } from './jsonrpc.model';
|
|
4
|
+
export var WebsocketReadyStates;
|
|
7
5
|
(function (WebsocketReadyStates) {
|
|
8
6
|
WebsocketReadyStates[WebsocketReadyStates["CONNECTING"] = 0] = "CONNECTING";
|
|
9
7
|
WebsocketReadyStates[WebsocketReadyStates["OPEN"] = 1] = "OPEN";
|
|
10
8
|
WebsocketReadyStates[WebsocketReadyStates["CLOSING"] = 2] = "CLOSING";
|
|
11
9
|
WebsocketReadyStates[WebsocketReadyStates["CLOSED"] = 3] = "CLOSED";
|
|
12
|
-
})(WebsocketReadyStates
|
|
13
|
-
class JsonRpcWebsocket {
|
|
10
|
+
})(WebsocketReadyStates || (WebsocketReadyStates = {}));
|
|
11
|
+
export class JsonRpcWebsocket {
|
|
14
12
|
constructor(url, requestTimeoutMs, onError) {
|
|
15
13
|
this.url = url;
|
|
16
14
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
@@ -30,21 +28,23 @@ class JsonRpcWebsocket {
|
|
|
30
28
|
return this.createWebsocket();
|
|
31
29
|
}
|
|
32
30
|
close() {
|
|
33
|
-
if (this.websocket ===
|
|
31
|
+
if (this.websocket === undefined) {
|
|
34
32
|
return Promise.resolve(new CloseEvent('No websocket was opened', { wasClean: false, code: 1005 }));
|
|
35
33
|
}
|
|
36
34
|
this.websocket.close(1000); // 1000 = normal closure
|
|
37
|
-
this.websocket =
|
|
35
|
+
this.websocket = undefined;
|
|
38
36
|
return this.closeDeferredPromise.asPromise();
|
|
39
37
|
}
|
|
40
38
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
39
|
on(methodName, callback) {
|
|
42
40
|
this.rpcMethods[methodName.toLowerCase()] = callback; // case-insensitive!
|
|
43
41
|
}
|
|
44
|
-
|
|
42
|
+
off(methodName) {
|
|
43
|
+
delete this.rpcMethods[methodName.toLowerCase()]; // case-insensitive!
|
|
44
|
+
}
|
|
45
45
|
call(method, params) {
|
|
46
46
|
if (!this.websocket || this.state !== WebsocketReadyStates.OPEN) {
|
|
47
|
-
return Promise.reject({ code:
|
|
47
|
+
return Promise.reject({ code: JsonRpcErrorCodes.INTERNAL_ERROR, message: 'The websocket is not opened' });
|
|
48
48
|
}
|
|
49
49
|
const request = {
|
|
50
50
|
id: this.getRequestId(),
|
|
@@ -57,11 +57,10 @@ class JsonRpcWebsocket {
|
|
|
57
57
|
}
|
|
58
58
|
catch (e) {
|
|
59
59
|
// istanbul ignore next
|
|
60
|
-
return Promise.reject({ code:
|
|
60
|
+
return Promise.reject({ code: JsonRpcErrorCodes.INTERNAL_ERROR, message: `Internal error. ${e}` });
|
|
61
61
|
}
|
|
62
62
|
return this.createPendingRequest(request);
|
|
63
63
|
}
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
64
|
notify(method, params) {
|
|
66
65
|
if (!this.websocket || this.state !== WebsocketReadyStates.OPEN) {
|
|
67
66
|
throw new Error('The websocket is not opened');
|
|
@@ -83,8 +82,8 @@ class JsonRpcWebsocket {
|
|
|
83
82
|
// See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent for close event codes
|
|
84
83
|
// this.websocket = new WebSocket(this.url, ['jsonrpc-2.0']);
|
|
85
84
|
this.websocket = new WebSocket(this.url);
|
|
86
|
-
const openDeferredPromise = new
|
|
87
|
-
this.closeDeferredPromise = new
|
|
85
|
+
const openDeferredPromise = new DeferredPromise();
|
|
86
|
+
this.closeDeferredPromise = new DeferredPromise();
|
|
88
87
|
this.websocket.onopen = (event) => {
|
|
89
88
|
openDeferredPromise.resolve(event);
|
|
90
89
|
};
|
|
@@ -92,7 +91,7 @@ class JsonRpcWebsocket {
|
|
|
92
91
|
openDeferredPromise.reject(err);
|
|
93
92
|
};
|
|
94
93
|
this.websocket.onclose = (event) => {
|
|
95
|
-
this.websocket =
|
|
94
|
+
this.websocket = undefined;
|
|
96
95
|
if (event.code !== 1000) {
|
|
97
96
|
// 1000 = normal closure
|
|
98
97
|
const error = {
|
|
@@ -107,14 +106,12 @@ class JsonRpcWebsocket {
|
|
|
107
106
|
this.websocket.onmessage = (message) => this.handleMessage(message.data);
|
|
108
107
|
return openDeferredPromise.asPromise();
|
|
109
108
|
}
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
109
|
respondOk(id, result) {
|
|
112
110
|
this.respond(id, result);
|
|
113
111
|
}
|
|
114
112
|
respondError(id, error) {
|
|
115
|
-
this.respond(id,
|
|
113
|
+
this.respond(id, undefined, error);
|
|
116
114
|
}
|
|
117
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
115
|
respond(id, result, error) {
|
|
119
116
|
// istanbul ignore if
|
|
120
117
|
if (!this.websocket || this.state !== WebsocketReadyStates.OPEN) {
|
|
@@ -128,7 +125,7 @@ class JsonRpcWebsocket {
|
|
|
128
125
|
jsonrpc: this.jsonRpcVersion,
|
|
129
126
|
error: error,
|
|
130
127
|
id: id,
|
|
131
|
-
result: result
|
|
128
|
+
result: result,
|
|
132
129
|
};
|
|
133
130
|
try {
|
|
134
131
|
this.websocket.send(JSON.stringify(response));
|
|
@@ -144,14 +141,14 @@ class JsonRpcWebsocket {
|
|
|
144
141
|
data = JSON.parse(msg);
|
|
145
142
|
}
|
|
146
143
|
catch (e) /* istanbul ignore next */ {
|
|
147
|
-
this.handleError(
|
|
144
|
+
this.handleError(JsonRpcErrorCodes.PARSE_ERROR, `Invalid JSON was received. ${e}`);
|
|
148
145
|
return;
|
|
149
146
|
}
|
|
150
147
|
const isResponse = !!data && this.hasProperty(data, 'id') && (this.hasProperty(data, 'result') || this.hasProperty(data, 'error'));
|
|
151
148
|
const isRequest = !!data && this.hasProperty(data, 'method');
|
|
152
|
-
const requestId = isRequest && data.id ? data.id :
|
|
149
|
+
const requestId = isRequest && data.id ? data.id : undefined;
|
|
153
150
|
if (!data.jsonrpc || data.jsonrpc !== this.jsonRpcVersion) {
|
|
154
|
-
this.handleError(
|
|
151
|
+
this.handleError(JsonRpcErrorCodes.INVALID_REQUEST, `Invalid JSON RPC protocol version. Expecting ${this.jsonRpcVersion}, but got ${data.jsonrpc}`, requestId);
|
|
155
152
|
return;
|
|
156
153
|
}
|
|
157
154
|
if (isResponse) {
|
|
@@ -161,13 +158,13 @@ class JsonRpcWebsocket {
|
|
|
161
158
|
this.handleRequest(data);
|
|
162
159
|
}
|
|
163
160
|
else {
|
|
164
|
-
this.handleError(
|
|
161
|
+
this.handleError(JsonRpcErrorCodes.INVALID_REQUEST, `Received unknown data: ${JSON.stringify(data)}`, requestId);
|
|
165
162
|
}
|
|
166
163
|
}
|
|
167
164
|
handleRequest(request) {
|
|
168
165
|
const method = this.rpcMethods[request.method.toLowerCase()]; // case-insensitive!
|
|
169
166
|
if (!method) {
|
|
170
|
-
this.handleError(
|
|
167
|
+
this.handleError(JsonRpcErrorCodes.METHOD_NOT_FOUND, `Method '${request.method}' was not found`, request.id);
|
|
171
168
|
return;
|
|
172
169
|
}
|
|
173
170
|
let requestParams = [];
|
|
@@ -175,12 +172,17 @@ class JsonRpcWebsocket {
|
|
|
175
172
|
requestParams = this.getRequestParams(method, request);
|
|
176
173
|
}
|
|
177
174
|
catch (error) {
|
|
178
|
-
this.handleError(
|
|
175
|
+
this.handleError(JsonRpcErrorCodes.INVALID_PARAMS, error.message, request.id);
|
|
179
176
|
return;
|
|
180
177
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
try {
|
|
179
|
+
const result = method(...requestParams);
|
|
180
|
+
if (request.id) {
|
|
181
|
+
this.respondOk(request.id, result);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.handleError(JsonRpcErrorCodes.REQUEST_FAILED, `Method '${request.method}' has thrown: '${error.message}'`, request.id);
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -198,9 +200,9 @@ class JsonRpcWebsocket {
|
|
|
198
200
|
if (method.length !== Object.keys(request.params).length) {
|
|
199
201
|
throw new Error(`Invalid parameters. Method '${request.method}' expects parameters [${parameterNames}], but got [${Object.keys(request.params)}]`);
|
|
200
202
|
}
|
|
201
|
-
parameterNames.forEach(paramName => {
|
|
203
|
+
parameterNames.forEach((paramName) => {
|
|
202
204
|
const paramValue = request.params[paramName];
|
|
203
|
-
if (
|
|
205
|
+
if (paramValue === undefined) {
|
|
204
206
|
throw new Error(`Invalid parameters. Method '${request.method}' expects parameters [${parameterNames}], but got [${Object.keys(request.params)}]`);
|
|
205
207
|
}
|
|
206
208
|
requestParams.push(paramValue);
|
|
@@ -214,9 +216,9 @@ class JsonRpcWebsocket {
|
|
|
214
216
|
}
|
|
215
217
|
handleResponse(response) {
|
|
216
218
|
const activeRequest = this.pendingRequests[response.id];
|
|
217
|
-
if (activeRequest ===
|
|
219
|
+
if (activeRequest === undefined) {
|
|
218
220
|
this.callOnError({
|
|
219
|
-
code:
|
|
221
|
+
code: JsonRpcErrorCodes.INTERNAL_ERROR,
|
|
220
222
|
message: `Received a response with id ${response.id}, which does not match any requests made by this client`,
|
|
221
223
|
});
|
|
222
224
|
return;
|
|
@@ -225,7 +227,7 @@ class JsonRpcWebsocket {
|
|
|
225
227
|
if (this.hasProperty(response, 'result') && this.hasProperty(response, 'error')) {
|
|
226
228
|
const errorResponse = {
|
|
227
229
|
error: {
|
|
228
|
-
code:
|
|
230
|
+
code: JsonRpcErrorCodes.INVALID_RESPONSE,
|
|
229
231
|
message: `Invalid response. Either result or error must be set, but not both. ${JSON.stringify(response)}`,
|
|
230
232
|
},
|
|
231
233
|
id: activeRequest.request.id,
|
|
@@ -249,7 +251,7 @@ class JsonRpcWebsocket {
|
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
253
|
createPendingRequest(request) {
|
|
252
|
-
const response = new
|
|
254
|
+
const response = new DeferredPromise();
|
|
253
255
|
this.pendingRequests[request.id] = {
|
|
254
256
|
request,
|
|
255
257
|
response,
|
|
@@ -261,12 +263,12 @@ class JsonRpcWebsocket {
|
|
|
261
263
|
return self.setTimeout(() => {
|
|
262
264
|
const activeRequest = this.pendingRequests[requestId];
|
|
263
265
|
// istanbul ignore if
|
|
264
|
-
if (activeRequest ===
|
|
266
|
+
if (activeRequest === undefined) {
|
|
265
267
|
return;
|
|
266
268
|
}
|
|
267
269
|
const response = {
|
|
268
270
|
error: {
|
|
269
|
-
code:
|
|
271
|
+
code: JsonRpcErrorCodes.REQUEST_TIMEOUT,
|
|
270
272
|
message: `Request ${activeRequest.request.id} exceeded the maximum time of ${this.requestTimeoutMs}ms and was aborted`,
|
|
271
273
|
},
|
|
272
274
|
id: activeRequest.request.id,
|
|
@@ -277,16 +279,14 @@ class JsonRpcWebsocket {
|
|
|
277
279
|
}, this.requestTimeoutMs);
|
|
278
280
|
}
|
|
279
281
|
callOnError(error) {
|
|
280
|
-
if (this.onError !==
|
|
282
|
+
if (this.onError !== undefined) {
|
|
281
283
|
this.onError(error);
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
getRequestId() {
|
|
285
287
|
return ++this.requestId;
|
|
286
288
|
}
|
|
287
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
288
289
|
hasProperty(object, propertyName) {
|
|
289
290
|
return !!Object.prototype.hasOwnProperty.call(object, propertyName);
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
exports.JsonRpcWebsocket = JsonRpcWebsocket;
|
package/lib/jsonrpc.model.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare enum JsonRpcErrorCodes {
|
|
|
5
5
|
INVALID_PARAMS = -32602,
|
|
6
6
|
INTERNAL_ERROR = -32603,
|
|
7
7
|
INVALID_RESPONSE = -32001,
|
|
8
|
-
REQUEST_TIMEOUT = -32002
|
|
8
|
+
REQUEST_TIMEOUT = -32002,
|
|
9
|
+
REQUEST_FAILED = -32003
|
|
9
10
|
}
|
|
10
11
|
export declare class JsonRpcError {
|
|
11
12
|
code: number;
|
package/lib/jsonrpc.model.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* tslint:disable:max-classes-per-file */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
2
|
// See https://www.jsonrpc.org/specification for the protocol details
|
|
5
|
-
var JsonRpcErrorCodes;
|
|
3
|
+
export var JsonRpcErrorCodes;
|
|
6
4
|
(function (JsonRpcErrorCodes) {
|
|
7
5
|
JsonRpcErrorCodes[JsonRpcErrorCodes["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
|
|
8
6
|
JsonRpcErrorCodes[JsonRpcErrorCodes["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
|
|
@@ -12,13 +10,11 @@ var JsonRpcErrorCodes;
|
|
|
12
10
|
// App specific
|
|
13
11
|
JsonRpcErrorCodes[JsonRpcErrorCodes["INVALID_RESPONSE"] = -32001] = "INVALID_RESPONSE";
|
|
14
12
|
JsonRpcErrorCodes[JsonRpcErrorCodes["REQUEST_TIMEOUT"] = -32002] = "REQUEST_TIMEOUT";
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
JsonRpcErrorCodes[JsonRpcErrorCodes["REQUEST_FAILED"] = -32003] = "REQUEST_FAILED";
|
|
14
|
+
})(JsonRpcErrorCodes || (JsonRpcErrorCodes = {}));
|
|
15
|
+
export class JsonRpcError {
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
class JsonRpcResponse {
|
|
17
|
+
export class JsonRpcResponse {
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
class JsonRpcRequest {
|
|
19
|
+
export class JsonRpcRequest {
|
|
23
20
|
}
|
|
24
|
-
exports.JsonRpcRequest = JsonRpcRequest;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsonrpc-client-websocket",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "A simple JSON RPC 2.0 websocket client",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
"lib/**/*"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "jest
|
|
11
|
+
"test": "jest",
|
|
12
12
|
"coverage": "jest --config jestconfig.json --coverage",
|
|
13
13
|
"build": "tsc",
|
|
14
|
-
"format": "prettier --write \"src/**/*.ts\"
|
|
14
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
15
15
|
"lint": "tsc --noEmit && eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
|
16
16
|
"prepublishOnly": "npm test && npm run lint",
|
|
17
17
|
"prepare": "npm run build",
|
|
18
18
|
"preversion": "npm run lint",
|
|
19
19
|
"version": "npm run format && git add -A src",
|
|
20
|
-
"postversion": "git push && git push --tags"
|
|
20
|
+
"postversion": "git push && git push --tags",
|
|
21
|
+
"updateAll": "npm-check-updates --upgrade"
|
|
21
22
|
},
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
@@ -37,18 +38,20 @@
|
|
|
37
38
|
"author": "Fabricio Bastian",
|
|
38
39
|
"license": "MIT",
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@types/jest": "^
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
42
|
-
"@typescript-eslint/parser": "^
|
|
43
|
-
"eslint": "^
|
|
44
|
-
"eslint-config-prettier": "^
|
|
45
|
-
"eslint-plugin-jest": "^
|
|
46
|
-
"jest": "^
|
|
47
|
-
"jest-websocket-mock": "^
|
|
48
|
-
"mock-socket": "^
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
41
|
+
"@types/jest": "^27.0.3",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
|
43
|
+
"@typescript-eslint/parser": "^5.4.0",
|
|
44
|
+
"eslint": "^8.3.0",
|
|
45
|
+
"eslint-config-prettier": "^8.3.0",
|
|
46
|
+
"eslint-plugin-jest": "^25.3.0",
|
|
47
|
+
"jest": "^27.3.1",
|
|
48
|
+
"jest-websocket-mock": "^2.2.1",
|
|
49
|
+
"mock-socket": "^9.0.8",
|
|
50
|
+
"npm-check-updates": "^12.0.2",
|
|
51
|
+
"prettier": "^2.5.0",
|
|
52
|
+
"ts-jest": "^27.0.7",
|
|
53
|
+
"ts-node": "^10.4.0",
|
|
54
|
+
"typescript": "^4.5.2"
|
|
52
55
|
},
|
|
53
56
|
"dependencies": {
|
|
54
57
|
"get-parameter-names": "^0.3.0"
|