jsonrpc-client-websocket 1.5.4 → 1.6.0
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 +46 -13
- package/lib/get-parameter-names.d.ts +1 -0
- package/lib/get-parameter-names.js +5 -0
- package/lib/jsonrpc-websocket.d.ts +7 -2
- package/lib/jsonrpc-websocket.js +68 -36
- package/package.json +20 -23
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/fabriciobastian/jsonrpc-client-websocket/actions/workflows/ci.yml) [](https://choosealicense.com/licenses/mit/) [](https://www.npmjs.com/package/jsonrpc-client-websocket)
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
@@ -74,33 +74,66 @@ The defined RPC methods can also be called with both positional and named parame
|
|
|
74
74
|
|
|
75
75
|
### Minification/Obfuscation
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
combination of the following:
|
|
80
|
-
- do not minify/obfuscate the code where `on` is used, so the function registration does not get modified
|
|
81
|
-
- use positional parameters
|
|
82
|
-
- register functions as following:
|
|
77
|
+
For code that will be minified or obfuscated, pass the public parameter names as
|
|
78
|
+
the optional third argument to `on`:
|
|
83
79
|
|
|
84
80
|
```typescript
|
|
85
|
-
websocket.on('
|
|
86
|
-
return params.a + params.b;
|
|
87
|
-
});
|
|
81
|
+
websocket.on('subtract', (x: number, y: number) => x - y, ['a', 'b']);
|
|
88
82
|
```
|
|
89
83
|
|
|
90
|
-
|
|
84
|
+
The list follows the callback's argument order: `a` maps to `x`, and `b` maps to `y`.
|
|
85
|
+
The order of keys in the request object does not matter:
|
|
91
86
|
|
|
92
87
|
```typescript
|
|
93
|
-
websocket.call('
|
|
88
|
+
websocket.call('subtract', { b: 2, a: 5 })
|
|
94
89
|
.then((response) => {
|
|
95
|
-
//
|
|
90
|
+
// response.result is 3
|
|
96
91
|
})
|
|
97
92
|
.catch((error) => {
|
|
98
93
|
// handle error
|
|
99
94
|
});
|
|
100
95
|
```
|
|
101
96
|
|
|
97
|
+
Positional calls such as `websocket.call('subtract', [5, 2])` also work. The explicit
|
|
98
|
+
list defines both the parameter order and the required argument count, even if
|
|
99
|
+
minification renames or removes unused callback parameters. Names must be unique;
|
|
100
|
+
use an empty list for a method that takes no arguments. The list is copied when
|
|
101
|
+
the method is registered.
|
|
102
|
+
|
|
103
|
+
Without the third argument, existing registrations continue to infer named
|
|
104
|
+
parameters from the callback's source. Those names may change during minification
|
|
105
|
+
or obfuscation, so use explicit names for named calls in production bundles.
|
|
106
|
+
If property mangling is enabled, preserve the public keys in request objects too.
|
|
107
|
+
|
|
102
108
|
## Remove RPC method
|
|
103
109
|
|
|
104
110
|
```typescript
|
|
105
111
|
websocket.off('sum');
|
|
106
112
|
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
Use the Node.js version in `.nvmrc` and its bundled npm, then run:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
npm ci
|
|
120
|
+
npm test
|
|
121
|
+
npm run lint
|
|
122
|
+
npm run format:check
|
|
123
|
+
npm run build
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
TypeScript checks both the library and tests. ESLint uses native flat configuration
|
|
127
|
+
with TypeScript and Jest rules. TypeScript stays on the latest 6.0 patch supported
|
|
128
|
+
by `typescript-eslint`; upgrading to TypeScript 7 requires its official support.
|
|
129
|
+
Tests live alongside the source and are excluded from build output.
|
|
130
|
+
`npm run update-all` respects peer dependency requirements when selecting updates.
|
|
131
|
+
|
|
132
|
+
These tooling requirements apply to development only. The published library still
|
|
133
|
+
targets ES2020 and has no runtime dependencies.
|
|
134
|
+
|
|
135
|
+
## Publishing
|
|
136
|
+
|
|
137
|
+
Releases are staged through GitHub Actions using
|
|
138
|
+
[npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC).
|
|
139
|
+
A maintainer approves each staged package on npm before it becomes publicly available.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getParameterNames(callback: (...args: never[]) => unknown): string[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function getParameterNames(callback) {
|
|
2
|
+
const source = Function.prototype.toString.call(callback).replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, ' ');
|
|
3
|
+
const parameters = source.match(/^(?:async\s+)?([^\s()=]+)\s*=>/)?.[1] ?? source.slice(source.indexOf('(') + 1, source.indexOf(')'));
|
|
4
|
+
return parameters.replace(/=[^,]+/g, '').match(/[^\s,]+/g) ?? [];
|
|
5
|
+
}
|
|
@@ -17,10 +17,14 @@ export declare class JsonRpcWebsocket {
|
|
|
17
17
|
private pendingRequests;
|
|
18
18
|
private rpcMethods;
|
|
19
19
|
get state(): WebsocketReadyStates;
|
|
20
|
-
constructor(url: string, requestTimeoutMs: number, onError?: ErrorCallback);
|
|
20
|
+
constructor(url: string, requestTimeoutMs: number, onError?: ErrorCallback | undefined);
|
|
21
21
|
open(): Promise<Event>;
|
|
22
22
|
close(): Promise<CloseEvent>;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Registers a callback, optionally declaring its public parameter names in argument order.
|
|
25
|
+
* Explicit names also determine the expected argument count, independently of the callback's signature.
|
|
26
|
+
*/
|
|
27
|
+
on(methodName: string, callback: (...args: any[]) => any, parameterNames?: readonly string[]): void;
|
|
24
28
|
off(methodName: string): void;
|
|
25
29
|
call(method: string, params?: unknown): Promise<JsonRpcResponse>;
|
|
26
30
|
notify(method: string, params?: unknown): void;
|
|
@@ -38,4 +42,5 @@ export declare class JsonRpcWebsocket {
|
|
|
38
42
|
private callOnError;
|
|
39
43
|
private getRequestId;
|
|
40
44
|
private hasProperty;
|
|
45
|
+
private getErrorMessage;
|
|
41
46
|
}
|
package/lib/jsonrpc-websocket.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getParameterNames } from './get-parameter-names';
|
|
2
2
|
import { DeferredPromise } from './deferred-promise';
|
|
3
3
|
import { JsonRpcErrorCodes } from './jsonrpc.model';
|
|
4
4
|
export var WebsocketReadyStates;
|
|
@@ -35,9 +35,20 @@ export class JsonRpcWebsocket {
|
|
|
35
35
|
this.websocket = undefined;
|
|
36
36
|
return this.closeDeferredPromise.asPromise();
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Registers a callback, optionally declaring its public parameter names in argument order.
|
|
40
|
+
* Explicit names also determine the expected argument count, independently of the callback's signature.
|
|
41
|
+
*/
|
|
42
|
+
on(methodName,
|
|
38
43
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
callback, parameterNames) {
|
|
45
|
+
if (parameterNames && new Set(parameterNames).size !== parameterNames.length) {
|
|
46
|
+
throw new Error('Parameter names must be unique');
|
|
47
|
+
}
|
|
48
|
+
this.rpcMethods[methodName.toLowerCase()] = {
|
|
49
|
+
callback,
|
|
50
|
+
parameterNames: parameterNames?.slice(),
|
|
51
|
+
}; // case-insensitive!
|
|
41
52
|
}
|
|
42
53
|
off(methodName) {
|
|
43
54
|
delete this.rpcMethods[methodName.toLowerCase()]; // case-insensitive!
|
|
@@ -57,7 +68,10 @@ export class JsonRpcWebsocket {
|
|
|
57
68
|
}
|
|
58
69
|
catch (e) {
|
|
59
70
|
// istanbul ignore next
|
|
60
|
-
return Promise.reject({
|
|
71
|
+
return Promise.reject({
|
|
72
|
+
code: JsonRpcErrorCodes.INTERNAL_ERROR,
|
|
73
|
+
message: `Internal error. ${this.getErrorMessage(e)}`,
|
|
74
|
+
});
|
|
61
75
|
}
|
|
62
76
|
return this.createPendingRequest(request);
|
|
63
77
|
}
|
|
@@ -74,8 +88,7 @@ export class JsonRpcWebsocket {
|
|
|
74
88
|
this.websocket.send(JSON.stringify(request));
|
|
75
89
|
}
|
|
76
90
|
catch (e) {
|
|
77
|
-
|
|
78
|
-
throw Error(e);
|
|
91
|
+
throw Error(this.getErrorMessage(e), { cause: e });
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
94
|
createWebsocket() {
|
|
@@ -131,12 +144,11 @@ export class JsonRpcWebsocket {
|
|
|
131
144
|
this.websocket.send(JSON.stringify(response));
|
|
132
145
|
}
|
|
133
146
|
catch (e) {
|
|
134
|
-
|
|
135
|
-
throw Error(e);
|
|
147
|
+
throw Error(this.getErrorMessage(e), { cause: e });
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
handleMessage(msg) {
|
|
139
|
-
let data
|
|
151
|
+
let data;
|
|
140
152
|
try {
|
|
141
153
|
data = JSON.parse(msg);
|
|
142
154
|
}
|
|
@@ -167,52 +179,53 @@ export class JsonRpcWebsocket {
|
|
|
167
179
|
this.handleError(JsonRpcErrorCodes.METHOD_NOT_FOUND, `Method '${request.method}' was not found`, request.id);
|
|
168
180
|
return;
|
|
169
181
|
}
|
|
170
|
-
let requestParams
|
|
182
|
+
let requestParams;
|
|
171
183
|
try {
|
|
172
184
|
requestParams = this.getRequestParams(method, request);
|
|
173
185
|
}
|
|
174
186
|
catch (error) {
|
|
175
|
-
this.handleError(JsonRpcErrorCodes.INVALID_PARAMS, error
|
|
187
|
+
this.handleError(JsonRpcErrorCodes.INVALID_PARAMS, this.getErrorMessage(error), request.id);
|
|
176
188
|
return;
|
|
177
189
|
}
|
|
178
190
|
try {
|
|
179
|
-
const
|
|
191
|
+
const { callback } = method;
|
|
192
|
+
const result = callback(...requestParams);
|
|
180
193
|
if (request.id) {
|
|
181
194
|
this.respondOk(request.id, result);
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
catch (error) {
|
|
185
|
-
this.handleError(JsonRpcErrorCodes.REQUEST_FAILED, `Method '${request.method}' has thrown: '${error
|
|
198
|
+
this.handleError(JsonRpcErrorCodes.REQUEST_FAILED, `Method '${request.method}' has thrown: '${this.getErrorMessage(error)}'`, request.id);
|
|
186
199
|
}
|
|
187
200
|
}
|
|
188
201
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
202
|
getRequestParams(method, request) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
else if (request.params instanceof Object) {
|
|
199
|
-
const parameterNames = getParameterNames(method);
|
|
200
|
-
if (method.length !== Object.keys(request.params).length) {
|
|
201
|
-
throw new Error(`Invalid parameters. Method '${request.method}' expects parameters [${parameterNames}], but got [${Object.keys(request.params)}]`);
|
|
202
|
-
}
|
|
203
|
-
parameterNames.forEach((paramName) => {
|
|
204
|
-
const paramValue = request.params[paramName];
|
|
205
|
-
if (paramValue === undefined) {
|
|
206
|
-
throw new Error(`Invalid parameters. Method '${request.method}' expects parameters [${parameterNames}], but got [${Object.keys(request.params)}]`);
|
|
207
|
-
}
|
|
208
|
-
requestParams.push(paramValue);
|
|
209
|
-
});
|
|
203
|
+
if (!request.params && method.parameterNames === undefined) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const params = request.params === undefined ? [] : request.params;
|
|
207
|
+
const parameterCount = method.parameterNames?.length ?? method.callback.length;
|
|
208
|
+
if (params instanceof Array) {
|
|
209
|
+
if (parameterCount !== params.length) {
|
|
210
|
+
throw new Error(`Invalid parameters. Method '${request.method}' expects ${parameterCount} parameters, but got ${params.length}`);
|
|
210
211
|
}
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
return params;
|
|
213
|
+
}
|
|
214
|
+
if (params instanceof Object) {
|
|
215
|
+
const parameterNames = method.parameterNames ?? getParameterNames(method.callback);
|
|
216
|
+
const requestParameterNames = Object.keys(params);
|
|
217
|
+
const invalidParametersMessage = `Invalid parameters. Method '${request.method}' expects parameters [${parameterNames}], but got [${requestParameterNames}]`;
|
|
218
|
+
if (parameterCount !== requestParameterNames.length) {
|
|
219
|
+
throw new Error(invalidParametersMessage);
|
|
213
220
|
}
|
|
221
|
+
return parameterNames.map((paramName) => {
|
|
222
|
+
if (!this.hasProperty(params, paramName)) {
|
|
223
|
+
throw new Error(invalidParametersMessage);
|
|
224
|
+
}
|
|
225
|
+
return params[paramName];
|
|
226
|
+
});
|
|
214
227
|
}
|
|
215
|
-
|
|
228
|
+
throw new Error(`Invalid parameters. Expected array or object, but got ${typeof params}`);
|
|
216
229
|
}
|
|
217
230
|
handleResponse(response) {
|
|
218
231
|
const activeRequest = this.pendingRequests[response.id];
|
|
@@ -289,4 +302,23 @@ export class JsonRpcWebsocket {
|
|
|
289
302
|
hasProperty(object, propertyName) {
|
|
290
303
|
return !!Object.prototype.hasOwnProperty.call(object, propertyName);
|
|
291
304
|
}
|
|
305
|
+
getErrorMessage(error) {
|
|
306
|
+
try {
|
|
307
|
+
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
308
|
+
const message = error.message;
|
|
309
|
+
if (typeof message === 'string') {
|
|
310
|
+
return message;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return JSON.stringify(error) ?? String(error);
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
try {
|
|
317
|
+
return String(error);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return 'Unknown error';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
292
324
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsonrpc-client-websocket",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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,17 +8,18 @@
|
|
|
8
8
|
"lib/**/*"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "jest",
|
|
12
|
-
"coverage": "
|
|
11
|
+
"test": "tsc --project tsconfig.spec.json && jest",
|
|
12
|
+
"coverage": "npm test -- --coverage",
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
15
|
-
"
|
|
15
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
16
|
+
"lint": "tsc --noEmit && eslint .",
|
|
16
17
|
"prepublishOnly": "npm test && npm run lint",
|
|
17
18
|
"prepare": "npm run build",
|
|
18
19
|
"preversion": "npm run lint",
|
|
19
20
|
"version": "npm run format && git add -A src",
|
|
20
21
|
"postversion": "git push && git push --tags",
|
|
21
|
-
"
|
|
22
|
+
"update-all": "npm-check-updates --upgrade --peer"
|
|
22
23
|
},
|
|
23
24
|
"repository": {
|
|
24
25
|
"type": "git",
|
|
@@ -38,23 +39,19 @@
|
|
|
38
39
|
"author": "Fabricio Bastian",
|
|
39
40
|
"license": "MIT",
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@
|
|
43
|
-
"
|
|
44
|
-
"eslint": "^
|
|
45
|
-
"eslint-
|
|
46
|
-
"
|
|
47
|
-
"jest": "^
|
|
48
|
-
"jest-
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"typescript": "^5.0.4"
|
|
56
|
-
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"get-parameter-names": "^0.3.0"
|
|
42
|
+
"@eslint/js": "^10.0.1",
|
|
43
|
+
"@types/jest": "^30.0.0",
|
|
44
|
+
"eslint": "^10.14.0",
|
|
45
|
+
"eslint-config-prettier": "^10.1.8",
|
|
46
|
+
"eslint-plugin-jest": "^29.17.0",
|
|
47
|
+
"jest": "^30.5.2",
|
|
48
|
+
"jest-environment-jsdom": "^30.5.2",
|
|
49
|
+
"jest-websocket-mock": "^2.5.0",
|
|
50
|
+
"mock-socket": "^9.3.1",
|
|
51
|
+
"npm-check-updates": "^23.1.1",
|
|
52
|
+
"prettier": "^3.9.10",
|
|
53
|
+
"ts-jest": "^29.4.15",
|
|
54
|
+
"typescript": "~6.0.3",
|
|
55
|
+
"typescript-eslint": "^8.72.1"
|
|
59
56
|
}
|
|
60
57
|
}
|