libshv-js 7.2.0 → 8.0.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/dist/access.js +1 -5
- package/dist/chainpack.d.ts +3 -3
- package/dist/chainpack.js +28 -35
- package/dist/cpcontext.js +1 -5
- package/dist/cpon.d.ts +2 -2
- package/dist/cpon.js +31 -40
- package/dist/index.d.ts +9 -9
- package/dist/index.js +9 -25
- package/dist/json.d.ts +1 -1
- package/dist/json.js +19 -24
- package/dist/rpcmessage.d.ts +1 -1
- package/dist/rpcmessage.js +19 -25
- package/dist/rpcvalue.js +2 -17
- package/dist/shv-ri.js +1 -5
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +2 -7
- package/dist/ws-client.d.ts +5 -4
- package/dist/ws-client.js +167 -163
- package/package.json +5 -1
package/dist/ws-client.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const rpcvalue_1 = require("./rpcvalue");
|
|
9
|
-
const utils_1 = require("./utils");
|
|
1
|
+
import { minimatch } from 'minimatch';
|
|
2
|
+
import { accessLevelFromAccessString } from './access.js';
|
|
3
|
+
import { ChainPackReader, CHAINPACK_PROTOCOL_TYPE, ChainPackWriter, toChainPack } from './chainpack.js';
|
|
4
|
+
import { CPON_PROTOCOL_TYPE, toCpon } from './cpon.js';
|
|
5
|
+
import { ERROR_MESSAGE, ErrorCode, ERROR_CODE, isSignal, isRequest, isResponse, ERROR_DATA, RPC_MESSAGE_METHOD, RPC_MESSAGE_SHV_PATH, RPC_MESSAGE_REQUEST_ID, RPC_MESSAGE_PARAMS, RPC_MESSAGE_ERROR, RPC_MESSAGE_RESULT, RPC_MESSAGE_CALLER_IDS, RPC_MESSAGE_DELAY, RPC_MESSAGE_ABORT, RPC_MESSAGE_ACCESS_LEVEL, RPC_MESSAGE_USER_ID } from './rpcmessage.js';
|
|
6
|
+
import { makeMap, makeIMap, RpcValueWithMetaData, makeMetaMap, Double } from './rpcvalue.js';
|
|
7
|
+
import { resolveRI } from './utils.js';
|
|
10
8
|
const DEFAULT_TIMEOUT = 5000;
|
|
11
9
|
const DEFAULT_PING_INTERVAL = 30 * 1000;
|
|
12
10
|
const defaultParseMessage = (rpcVal) => {
|
|
@@ -15,13 +13,13 @@ const defaultParseMessage = (rpcVal) => {
|
|
|
15
13
|
return rpcMsg;
|
|
16
14
|
};
|
|
17
15
|
const dataToRpcValue = (buff) => {
|
|
18
|
-
const rd = new
|
|
16
|
+
const rd = new ChainPackReader(buff);
|
|
19
17
|
rd.readUIntData();
|
|
20
18
|
const proto = rd.ctx.getByte();
|
|
21
19
|
switch (proto) {
|
|
22
|
-
case
|
|
20
|
+
case CHAINPACK_PROTOCOL_TYPE:
|
|
23
21
|
break;
|
|
24
|
-
case
|
|
22
|
+
case CPON_PROTOCOL_TYPE:
|
|
25
23
|
throw new Error('CPON protocol type is not supported anymore');
|
|
26
24
|
default:
|
|
27
25
|
throw new Error(`Unsupported protocol type ${proto}`);
|
|
@@ -29,19 +27,16 @@ const dataToRpcValue = (buff) => {
|
|
|
29
27
|
const rpcVal = rd.read();
|
|
30
28
|
return rpcVal;
|
|
31
29
|
};
|
|
32
|
-
class RequestHandler {
|
|
30
|
+
export class RequestHandler {
|
|
33
31
|
options;
|
|
34
32
|
constructor(options) {
|
|
35
33
|
this.options = options;
|
|
36
34
|
// nothing
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const validateDirParam = (param) => param === undefined || typeof param === 'string' || typeof param === 'boolean';
|
|
43
|
-
exports.validateDirParam = validateDirParam;
|
|
44
|
-
var DirFlags;
|
|
37
|
+
export const validateLsParam = (param) => param === undefined || typeof param === 'string';
|
|
38
|
+
export const validateDirParam = (param) => param === undefined || typeof param === 'string' || typeof param === 'boolean';
|
|
39
|
+
export var DirFlags;
|
|
45
40
|
(function (DirFlags) {
|
|
46
41
|
DirFlags[DirFlags["Reserved"] = 1] = "Reserved";
|
|
47
42
|
DirFlags[DirFlags["Getter"] = 2] = "Getter";
|
|
@@ -49,49 +44,49 @@ var DirFlags;
|
|
|
49
44
|
DirFlags[DirFlags["LargeResultHint"] = 8] = "LargeResultHint";
|
|
50
45
|
DirFlags[DirFlags["NotIdempotent"] = 16] = "NotIdempotent";
|
|
51
46
|
DirFlags[DirFlags["RequiresClientId"] = 32] = "RequiresClientId";
|
|
52
|
-
})(DirFlags || (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
})(DirFlags || (DirFlags = {}));
|
|
48
|
+
export const DIR_NAME = 1;
|
|
49
|
+
export const DIR_FLAGS = 2;
|
|
50
|
+
export const DIR_PARAM = 3;
|
|
51
|
+
export const DIR_RESULT = 4;
|
|
52
|
+
export const DIR_ACCESS = 5;
|
|
53
|
+
export const DIR_SIGNALS = 6;
|
|
54
|
+
export const DIR_EXTRA = 63;
|
|
60
55
|
class RpcError extends Error {
|
|
61
56
|
err_info;
|
|
62
57
|
constructor(err_info) {
|
|
63
58
|
const errorCodeToMessage = (code) => {
|
|
64
59
|
switch (code) {
|
|
65
|
-
case
|
|
66
|
-
case
|
|
67
|
-
case
|
|
68
|
-
case
|
|
69
|
-
case
|
|
70
|
-
case
|
|
71
|
-
case
|
|
72
|
-
case
|
|
73
|
-
case
|
|
74
|
-
case
|
|
75
|
-
case
|
|
76
|
-
case
|
|
60
|
+
case ErrorCode.InvalidRequest: return 'InvalidRequest';
|
|
61
|
+
case ErrorCode.MethodNotFound: return 'MethodNotFound';
|
|
62
|
+
case ErrorCode.InvalidParams: return 'InvalidParams';
|
|
63
|
+
case ErrorCode.InternalError: return 'InternalError';
|
|
64
|
+
case ErrorCode.ParseError: return 'ParseError';
|
|
65
|
+
case ErrorCode.MethodCallTimeout: return 'MethodCallTimeout';
|
|
66
|
+
case ErrorCode.MethodCallCancelled: return 'MethodCallCancelled';
|
|
67
|
+
case ErrorCode.MethodCallException: return 'MethodCallException';
|
|
68
|
+
case ErrorCode.Unknown: return 'Unknown';
|
|
69
|
+
case ErrorCode.LoginRequired: return 'LoginRequired';
|
|
70
|
+
case ErrorCode.UserIDRequired: return 'UserIDRequired';
|
|
71
|
+
case ErrorCode.NotImplemented: return 'NotImplemented';
|
|
77
72
|
default: return 'Unknown RpcError';
|
|
78
73
|
}
|
|
79
74
|
};
|
|
80
|
-
super(err_info[
|
|
75
|
+
super(err_info[ERROR_MESSAGE] ?? `${errorCodeToMessage(err_info[ERROR_CODE])} (${err_info[ERROR_CODE]})`);
|
|
81
76
|
this.err_info = err_info;
|
|
82
77
|
}
|
|
83
78
|
data() {
|
|
84
|
-
return this.err_info[
|
|
79
|
+
return this.err_info[ERROR_DATA];
|
|
85
80
|
}
|
|
86
81
|
}
|
|
87
82
|
const makeErrInfo = (errInfoOrMsg, code) => {
|
|
88
83
|
if (typeof errInfoOrMsg === 'object') {
|
|
89
84
|
return errInfoOrMsg;
|
|
90
85
|
}
|
|
91
|
-
return
|
|
92
|
-
[
|
|
93
|
-
[
|
|
94
|
-
[
|
|
86
|
+
return makeIMap({
|
|
87
|
+
[ERROR_CODE]: code,
|
|
88
|
+
[ERROR_MESSAGE]: errInfoOrMsg,
|
|
89
|
+
[ERROR_DATA]: undefined,
|
|
95
90
|
});
|
|
96
91
|
};
|
|
97
92
|
const createErrorClass = (code) => class extends RpcError {
|
|
@@ -100,24 +95,24 @@ const createErrorClass = (code) => class extends RpcError {
|
|
|
100
95
|
}
|
|
101
96
|
};
|
|
102
97
|
/* eslint-disable @typescript-eslint/naming-convention -- We want class names like this */
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
export const InvalidRequest = createErrorClass(ErrorCode.InvalidRequest);
|
|
99
|
+
export const MethodNotFound = createErrorClass(ErrorCode.MethodNotFound);
|
|
100
|
+
export const InvalidParams = createErrorClass(ErrorCode.InvalidParams);
|
|
101
|
+
export const InternalError = createErrorClass(ErrorCode.InternalError);
|
|
102
|
+
export const ParseError = createErrorClass(ErrorCode.ParseError);
|
|
103
|
+
export const MethodCallTimeout = createErrorClass(ErrorCode.MethodCallTimeout);
|
|
104
|
+
export const MethodCallCancelled = createErrorClass(ErrorCode.MethodCallCancelled);
|
|
105
|
+
export const MethodCallException = createErrorClass(ErrorCode.MethodCallException);
|
|
106
|
+
export const Unknown = createErrorClass(ErrorCode.Unknown);
|
|
107
|
+
export const LoginRequired = createErrorClass(ErrorCode.LoginRequired);
|
|
108
|
+
export const UserIDRequired = createErrorClass(ErrorCode.UserIDRequired);
|
|
109
|
+
export const NotImplemented = createErrorClass(ErrorCode.NotImplemented);
|
|
115
110
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
116
111
|
const makeTimeout = (ms, resolve) => setTimeout(() => {
|
|
117
|
-
resolve(new
|
|
118
|
-
[
|
|
119
|
-
[
|
|
120
|
-
[
|
|
112
|
+
resolve(new MethodCallTimeout(makeIMap({
|
|
113
|
+
[ERROR_CODE]: ErrorCode.MethodCallTimeout,
|
|
114
|
+
[ERROR_MESSAGE]: `Shv call timeout after: ${ms} msec.`,
|
|
115
|
+
[ERROR_DATA]: undefined,
|
|
121
116
|
})));
|
|
122
117
|
}, ms);
|
|
123
118
|
class RpcRequestPromise extends Promise {
|
|
@@ -129,11 +124,19 @@ class RpcRequestPromise extends Promise {
|
|
|
129
124
|
this.progressQuery = progressQuery;
|
|
130
125
|
}
|
|
131
126
|
}
|
|
132
|
-
var ShvApiVersion;
|
|
127
|
+
export var ShvApiVersion;
|
|
133
128
|
(function (ShvApiVersion) {
|
|
134
129
|
ShvApiVersion[ShvApiVersion["V2"] = 0] = "V2";
|
|
135
130
|
ShvApiVersion[ShvApiVersion["V3"] = 1] = "V3";
|
|
136
131
|
})(ShvApiVersion || (ShvApiVersion = {}));
|
|
132
|
+
export const pathMatchesSubscription = (subscriptionPath, signalPath, apiVersion) => {
|
|
133
|
+
if (apiVersion === ShvApiVersion.V2) {
|
|
134
|
+
const remainder = signalPath.slice(subscriptionPath.length);
|
|
135
|
+
return signalPath.startsWith(subscriptionPath) && (remainder.length === 0 || remainder.startsWith('/'));
|
|
136
|
+
}
|
|
137
|
+
const recursivePathPrefix = subscriptionPath.endsWith('/**') ? subscriptionPath.slice(0, -3) : undefined;
|
|
138
|
+
return (recursivePathPrefix !== undefined && minimatch(signalPath, recursivePathPrefix)) || minimatch(signalPath, subscriptionPath);
|
|
139
|
+
};
|
|
137
140
|
class WsClient {
|
|
138
141
|
requestId = 1;
|
|
139
142
|
pingTimerId = undefined;
|
|
@@ -153,12 +156,13 @@ class WsClient {
|
|
|
153
156
|
this.timeout = 'timeout' in options && options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT;
|
|
154
157
|
this.websocket = new WebSocket(options.wsUri);
|
|
155
158
|
this.websocket.binaryType = 'arraybuffer';
|
|
156
|
-
const handleSignal = (signal) => {
|
|
159
|
+
const handleSignal = async (signal) => {
|
|
160
|
+
const apiVersion = await this.getShvApiVersion();
|
|
157
161
|
for (const sub of this.subscriptions) {
|
|
158
|
-
const shvPath = signal.meta[
|
|
159
|
-
const signalName = signal.meta[
|
|
160
|
-
if (
|
|
161
|
-
sub.callback(shvPath, signalName, signal.value[
|
|
162
|
+
const shvPath = signal.meta[RPC_MESSAGE_SHV_PATH];
|
|
163
|
+
const signalName = signal.meta[RPC_MESSAGE_METHOD];
|
|
164
|
+
if (pathMatchesSubscription(sub.path, shvPath, apiVersion) && signalName === sub.signal) {
|
|
165
|
+
sub.callback(shvPath, signalName, signal.value[RPC_MESSAGE_PARAMS]);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
};
|
|
@@ -167,40 +171,40 @@ class WsClient {
|
|
|
167
171
|
if (!('onRequest' in this.options)) {
|
|
168
172
|
return;
|
|
169
173
|
}
|
|
170
|
-
const requestId = request.meta[
|
|
174
|
+
const requestId = request.meta[RPC_MESSAGE_REQUEST_ID];
|
|
171
175
|
const respond = (value) => {
|
|
172
|
-
this.sendRpcMessage(new
|
|
173
|
-
[
|
|
174
|
-
[
|
|
176
|
+
this.sendRpcMessage(new RpcValueWithMetaData(makeMetaMap({
|
|
177
|
+
[RPC_MESSAGE_CALLER_IDS]: request.meta[RPC_MESSAGE_CALLER_IDS],
|
|
178
|
+
[RPC_MESSAGE_REQUEST_ID]: requestId,
|
|
175
179
|
}), value));
|
|
176
180
|
};
|
|
177
181
|
const sendDelay = (progress) => {
|
|
178
|
-
respond(
|
|
179
|
-
[
|
|
182
|
+
respond(makeIMap({
|
|
183
|
+
[RPC_MESSAGE_DELAY]: new Double(progress),
|
|
180
184
|
}));
|
|
181
185
|
};
|
|
182
186
|
// eslint-disable-next-line unicorn/no-computed-property-existence-check
|
|
183
|
-
if (
|
|
187
|
+
if (RPC_MESSAGE_ABORT in request.value) {
|
|
184
188
|
const requestHandler = this.requestHandlers.get(requestId);
|
|
185
|
-
if (request.value[
|
|
189
|
+
if (request.value[RPC_MESSAGE_ABORT]) {
|
|
186
190
|
this.abortedRequests.add(requestId);
|
|
187
191
|
if (requestHandler?.options.onAbort !== undefined) {
|
|
188
192
|
requestHandler.options.onAbort();
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
|
-
if (!request.value[
|
|
195
|
+
if (!request.value[RPC_MESSAGE_ABORT] && requestHandler?.options.onProgressQuery !== undefined) {
|
|
192
196
|
const progress = requestHandler.options.onProgressQuery();
|
|
193
197
|
sendDelay(progress instanceof Promise ? await progress : progress);
|
|
194
198
|
}
|
|
195
199
|
return;
|
|
196
200
|
}
|
|
197
201
|
try {
|
|
198
|
-
const shvPath = request.meta[
|
|
199
|
-
const methodName = request.meta[
|
|
202
|
+
const shvPath = request.meta[RPC_MESSAGE_SHV_PATH];
|
|
203
|
+
const methodName = request.meta[RPC_MESSAGE_METHOD];
|
|
200
204
|
const unhandledMethod = () => {
|
|
201
|
-
throw new
|
|
205
|
+
throw new MethodNotFound(`No such path '${shvPath}' or method '${methodName}' or insufficient access rights.`);
|
|
202
206
|
};
|
|
203
|
-
const accessLevel = request.meta[
|
|
207
|
+
const accessLevel = request.meta[RPC_MESSAGE_ACCESS_LEVEL];
|
|
204
208
|
if (accessLevel === undefined) {
|
|
205
209
|
unhandledMethod();
|
|
206
210
|
return;
|
|
@@ -211,34 +215,34 @@ class WsClient {
|
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
213
217
|
const methodDefinition = (() => {
|
|
214
|
-
const dirEntry =
|
|
215
|
-
[
|
|
216
|
-
[
|
|
217
|
-
[
|
|
218
|
-
[
|
|
219
|
-
[
|
|
220
|
-
[
|
|
221
|
-
[
|
|
218
|
+
const dirEntry = makeIMap({
|
|
219
|
+
[DIR_NAME]: 'dir',
|
|
220
|
+
[DIR_FLAGS]: 0,
|
|
221
|
+
[DIR_PARAM]: 'DirParam',
|
|
222
|
+
[DIR_RESULT]: 'DirResult',
|
|
223
|
+
[DIR_ACCESS]: accessLevelFromAccessString('bws'),
|
|
224
|
+
[DIR_SIGNALS]: makeMap({}),
|
|
225
|
+
[DIR_EXTRA]: makeMap({}),
|
|
222
226
|
});
|
|
223
|
-
const lsEntry =
|
|
224
|
-
[
|
|
225
|
-
[
|
|
226
|
-
[
|
|
227
|
-
[
|
|
228
|
-
[
|
|
229
|
-
[
|
|
230
|
-
[
|
|
227
|
+
const lsEntry = makeIMap({
|
|
228
|
+
[DIR_NAME]: 'ls',
|
|
229
|
+
[DIR_FLAGS]: 0,
|
|
230
|
+
[DIR_PARAM]: 'LsParam',
|
|
231
|
+
[DIR_RESULT]: 'LsResult',
|
|
232
|
+
[DIR_ACCESS]: accessLevelFromAccessString('bws'),
|
|
233
|
+
[DIR_SIGNALS]: makeMap({}),
|
|
234
|
+
[DIR_EXTRA]: makeMap({}),
|
|
231
235
|
});
|
|
232
236
|
switch (methodName) {
|
|
233
237
|
case 'dir':
|
|
234
238
|
return {
|
|
235
239
|
entry: dirEntry,
|
|
236
|
-
paramValidator:
|
|
240
|
+
paramValidator: validateDirParam,
|
|
237
241
|
handler(_shvPath, _method, param) {
|
|
238
242
|
const methodsResult = [dirEntry, lsEntry, ...methods.dirEntries.map(method => method.entry)];
|
|
239
243
|
switch (true) {
|
|
240
244
|
case typeof param === 'string':
|
|
241
|
-
return methodsResult.find(method => method[
|
|
245
|
+
return methodsResult.find(method => method[DIR_NAME] === param) ?? false;
|
|
242
246
|
case typeof param === 'boolean':
|
|
243
247
|
case param === undefined:
|
|
244
248
|
console.log('methodsResult', '=', methodsResult);
|
|
@@ -249,34 +253,34 @@ class WsClient {
|
|
|
249
253
|
case 'ls':
|
|
250
254
|
return {
|
|
251
255
|
entry: lsEntry,
|
|
252
|
-
paramValidator:
|
|
256
|
+
paramValidator: validateLsParam,
|
|
253
257
|
handler(shvPath) {
|
|
254
258
|
return methods.ls(shvPath);
|
|
255
259
|
},
|
|
256
260
|
};
|
|
257
261
|
default:
|
|
258
|
-
return methods.dirEntries.find(method => method.entry[
|
|
262
|
+
return methods.dirEntries.find(method => method.entry[DIR_NAME] === methodName);
|
|
259
263
|
}
|
|
260
264
|
})();
|
|
261
265
|
if (methodDefinition === undefined) {
|
|
262
266
|
unhandledMethod();
|
|
263
267
|
return;
|
|
264
268
|
}
|
|
265
|
-
if (accessLevel < methodDefinition.entry[
|
|
269
|
+
if (accessLevel < methodDefinition.entry[DIR_ACCESS]) {
|
|
266
270
|
unhandledMethod();
|
|
267
271
|
return;
|
|
268
272
|
}
|
|
269
|
-
const requestParam = request.value[
|
|
273
|
+
const requestParam = request.value[RPC_MESSAGE_PARAMS];
|
|
270
274
|
if (methodDefinition.paramValidator !== undefined && !methodDefinition.paramValidator(requestParam)) {
|
|
271
|
-
throw new
|
|
275
|
+
throw new InvalidParams(`Unexpected param '${shvPath}:${methodName}': ${toCpon(requestParam)}`);
|
|
272
276
|
}
|
|
273
277
|
let result = methodDefinition.handler(shvPath, methodName, requestParam, sendDelay);
|
|
274
278
|
if (result instanceof RequestHandler) {
|
|
275
279
|
this.requestHandlers.set(requestId, result);
|
|
276
280
|
result = result.options.result;
|
|
277
281
|
}
|
|
278
|
-
const resultOrError =
|
|
279
|
-
[
|
|
282
|
+
const resultOrError = makeIMap({
|
|
283
|
+
[RPC_MESSAGE_RESULT]: result instanceof Promise ? await result : result,
|
|
280
284
|
});
|
|
281
285
|
if (!this.abortedRequests.has(requestId)) {
|
|
282
286
|
respond(resultOrError);
|
|
@@ -290,8 +294,8 @@ class WsClient {
|
|
|
290
294
|
}
|
|
291
295
|
catch (error) {
|
|
292
296
|
const sendError = (error) => {
|
|
293
|
-
respond(
|
|
294
|
-
[
|
|
297
|
+
respond(makeIMap({
|
|
298
|
+
[RPC_MESSAGE_ERROR]: error.err_info,
|
|
295
299
|
}));
|
|
296
300
|
};
|
|
297
301
|
switch (true) {
|
|
@@ -299,54 +303,54 @@ class WsClient {
|
|
|
299
303
|
sendError(error);
|
|
300
304
|
break;
|
|
301
305
|
case error instanceof Error:
|
|
302
|
-
sendError(new
|
|
306
|
+
sendError(new InternalError(error.message));
|
|
303
307
|
break;
|
|
304
308
|
default:
|
|
305
|
-
sendError(new
|
|
309
|
+
sendError(new InternalError('Unknown error'));
|
|
306
310
|
break;
|
|
307
311
|
}
|
|
308
312
|
}
|
|
309
313
|
};
|
|
310
314
|
const handleResponse = (response) => {
|
|
311
|
-
const requestId = response.meta[
|
|
315
|
+
const requestId = response.meta[RPC_MESSAGE_REQUEST_ID];
|
|
312
316
|
if (this.rpcHandlers[requestId] !== undefined) {
|
|
313
317
|
const handler = this.rpcHandlers[requestId];
|
|
314
318
|
clearTimeout(handler.timeout_handle);
|
|
315
319
|
// eslint-disable-next-line unicorn/no-computed-property-existence-check
|
|
316
|
-
if (
|
|
320
|
+
if (RPC_MESSAGE_DELAY in response.value) {
|
|
317
321
|
if (handler.delayCallback !== undefined) {
|
|
318
|
-
handler.delayCallback(response.value[
|
|
322
|
+
handler.delayCallback(response.value[RPC_MESSAGE_DELAY].value);
|
|
319
323
|
}
|
|
320
324
|
handler.timeout_handle = makeTimeout(this.timeout, handler.resolve);
|
|
321
325
|
return;
|
|
322
326
|
}
|
|
323
327
|
handler.resolve((() => {
|
|
324
328
|
// eslint-disable-next-line unicorn/no-computed-property-existence-check
|
|
325
|
-
if (
|
|
326
|
-
const code = response.value[
|
|
329
|
+
if (RPC_MESSAGE_ERROR in response.value) {
|
|
330
|
+
const code = response.value[RPC_MESSAGE_ERROR][ERROR_CODE];
|
|
327
331
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
328
332
|
const ErrorTypeCtor = (() => {
|
|
329
333
|
switch (code) {
|
|
330
|
-
case
|
|
331
|
-
case
|
|
332
|
-
case
|
|
333
|
-
case
|
|
334
|
-
case
|
|
335
|
-
case
|
|
336
|
-
case
|
|
337
|
-
case
|
|
338
|
-
case
|
|
339
|
-
case
|
|
340
|
-
case
|
|
341
|
-
case
|
|
342
|
-
default: return
|
|
334
|
+
case ErrorCode.InvalidRequest: return InvalidRequest;
|
|
335
|
+
case ErrorCode.MethodNotFound: return MethodNotFound;
|
|
336
|
+
case ErrorCode.InvalidParams: return InvalidParams;
|
|
337
|
+
case ErrorCode.InternalError: return InternalError;
|
|
338
|
+
case ErrorCode.ParseError: return ParseError;
|
|
339
|
+
case ErrorCode.MethodCallTimeout: return MethodCallTimeout;
|
|
340
|
+
case ErrorCode.MethodCallCancelled: return MethodCallCancelled;
|
|
341
|
+
case ErrorCode.MethodCallException: return MethodCallException;
|
|
342
|
+
case ErrorCode.Unknown: return Unknown;
|
|
343
|
+
case ErrorCode.LoginRequired: return LoginRequired;
|
|
344
|
+
case ErrorCode.UserIDRequired: return UserIDRequired;
|
|
345
|
+
case ErrorCode.NotImplemented: return NotImplemented;
|
|
346
|
+
default: return Unknown;
|
|
343
347
|
}
|
|
344
348
|
})();
|
|
345
|
-
return new ErrorTypeCtor(response.value[
|
|
349
|
+
return new ErrorTypeCtor(response.value[RPC_MESSAGE_ERROR]);
|
|
346
350
|
}
|
|
347
351
|
// eslint-disable-next-line unicorn/no-computed-property-existence-check
|
|
348
|
-
if (
|
|
349
|
-
return response.value[
|
|
352
|
+
if (RPC_MESSAGE_RESULT in response.value) {
|
|
353
|
+
return response.value[RPC_MESSAGE_RESULT];
|
|
350
354
|
}
|
|
351
355
|
// At this point, the RpcResponse either has:
|
|
352
356
|
// - no keys, which means that the response has an implicit Null result.
|
|
@@ -354,7 +358,7 @@ class WsClient {
|
|
|
354
358
|
// https://silicon-heaven.github.io/shv-doc/rpcmessage.html#response
|
|
355
359
|
const rpcMessageKeys = Object.keys(response.value);
|
|
356
360
|
if (rpcMessageKeys.length !== 0) {
|
|
357
|
-
return new
|
|
361
|
+
return new NotImplemented(`Got an RpcResponse with unsupported keys: ${rpcMessageKeys.join(' ')}`);
|
|
358
362
|
}
|
|
359
363
|
})());
|
|
360
364
|
// eslint-disable-next-line @typescript-eslint/no-array-delete, @typescript-eslint/no-dynamic-delete
|
|
@@ -366,14 +370,14 @@ class WsClient {
|
|
|
366
370
|
const rpcVal = dataToRpcValue(evt.data);
|
|
367
371
|
const parseMessage = this.options.parseMessage ?? defaultParseMessage;
|
|
368
372
|
const rpcMsg = parseMessage(rpcVal);
|
|
369
|
-
this.logDebug(`message received: ${
|
|
370
|
-
if (
|
|
371
|
-
handleSignal(rpcMsg);
|
|
373
|
+
this.logDebug(`message received: ${toCpon(rpcMsg)}`);
|
|
374
|
+
if (isSignal(rpcMsg)) {
|
|
375
|
+
await handleSignal(rpcMsg);
|
|
372
376
|
}
|
|
373
|
-
else if (
|
|
377
|
+
else if (isRequest(rpcMsg)) {
|
|
374
378
|
await handleRequest(rpcMsg);
|
|
375
379
|
}
|
|
376
|
-
else if (
|
|
380
|
+
else if (isResponse(rpcMsg)) {
|
|
377
381
|
handleResponse(rpcMsg);
|
|
378
382
|
}
|
|
379
383
|
});
|
|
@@ -433,10 +437,10 @@ class WsClient {
|
|
|
433
437
|
this.sendPing();
|
|
434
438
|
}, options.pingInterval ?? DEFAULT_PING_INTERVAL);
|
|
435
439
|
};
|
|
436
|
-
const makeLoginParams = (loginMap) =>
|
|
440
|
+
const makeLoginParams = (loginMap) => makeMap({
|
|
437
441
|
login: loginMap,
|
|
438
|
-
options:
|
|
439
|
-
device: typeof options.mountPoint === 'string' ?
|
|
442
|
+
options: makeMap({
|
|
443
|
+
device: typeof options.mountPoint === 'string' ? makeMap({ mountPoint: options.mountPoint }) : undefined,
|
|
440
444
|
}),
|
|
441
445
|
});
|
|
442
446
|
this.callRpcMethod(undefined, 'hello').then(response => {
|
|
@@ -447,13 +451,13 @@ class WsClient {
|
|
|
447
451
|
const makeLoginMap = () => {
|
|
448
452
|
switch (options.login.type) {
|
|
449
453
|
case 'PLAIN':
|
|
450
|
-
return
|
|
454
|
+
return makeMap({
|
|
451
455
|
password: options.login.password,
|
|
452
456
|
type: options.login.type,
|
|
453
457
|
user: options.login.user,
|
|
454
458
|
});
|
|
455
459
|
case 'TOKEN':
|
|
456
|
-
return
|
|
460
|
+
return makeMap({
|
|
457
461
|
token: options.login.token,
|
|
458
462
|
type: options.login.type,
|
|
459
463
|
});
|
|
@@ -473,19 +477,19 @@ class WsClient {
|
|
|
473
477
|
}
|
|
474
478
|
callRpcMethod(shvPath, method, params, options) {
|
|
475
479
|
const rqId = this.requestId++;
|
|
476
|
-
const makeRq = (value) => new
|
|
477
|
-
[
|
|
478
|
-
[
|
|
479
|
-
[
|
|
480
|
-
[
|
|
481
|
-
...(options?.requestUserId === true && { [
|
|
480
|
+
const makeRq = (value) => new RpcValueWithMetaData(makeMetaMap({
|
|
481
|
+
[RPC_MESSAGE_CALLER_IDS]: undefined,
|
|
482
|
+
[RPC_MESSAGE_REQUEST_ID]: rqId,
|
|
483
|
+
[RPC_MESSAGE_METHOD]: method ?? '',
|
|
484
|
+
[RPC_MESSAGE_SHV_PATH]: shvPath ?? '',
|
|
485
|
+
...(options?.requestUserId === true && { [RPC_MESSAGE_USER_ID]: '' }),
|
|
482
486
|
}), value);
|
|
483
487
|
// We will not add an explicit null as a param unless the user requests it. Receivers of RpcCalls should
|
|
484
488
|
// generally handle a missing param the same as if the param was Null.
|
|
485
489
|
const paramRaw = params !== undefined || (options?.explicitNullParam ?? false) ? {
|
|
486
|
-
[
|
|
490
|
+
[RPC_MESSAGE_PARAMS]: params,
|
|
487
491
|
} : {};
|
|
488
|
-
let rq = makeRq(
|
|
492
|
+
let rq = makeRq(makeIMap(paramRaw));
|
|
489
493
|
if (options?.beforeSendHook !== undefined) {
|
|
490
494
|
rq = options.beforeSendHook(rq);
|
|
491
495
|
}
|
|
@@ -493,12 +497,12 @@ class WsClient {
|
|
|
493
497
|
const promise = new RpcRequestPromise(resolve => {
|
|
494
498
|
this.rpcHandlers[rqId] = { resolve, timeout_handle: makeTimeout(this.timeout, resolve), delayCallback: options?.delayCallback };
|
|
495
499
|
}, () => {
|
|
496
|
-
this.sendRpcMessage(makeRq(
|
|
497
|
-
[
|
|
500
|
+
this.sendRpcMessage(makeRq(makeIMap({
|
|
501
|
+
[RPC_MESSAGE_ABORT]: true,
|
|
498
502
|
})));
|
|
499
503
|
}, () => {
|
|
500
|
-
this.sendRpcMessage(makeRq(
|
|
501
|
-
[
|
|
504
|
+
this.sendRpcMessage(makeRq(makeIMap({
|
|
505
|
+
[RPC_MESSAGE_ABORT]: false,
|
|
502
506
|
})));
|
|
503
507
|
});
|
|
504
508
|
return promise;
|
|
@@ -507,16 +511,16 @@ class WsClient {
|
|
|
507
511
|
if (this.websocket.readyState !== WebSocket.OPEN) {
|
|
508
512
|
return;
|
|
509
513
|
}
|
|
510
|
-
this.logDebug('sending rpc message:',
|
|
511
|
-
const msgData = new Uint8Array(
|
|
512
|
-
const wr = new
|
|
514
|
+
this.logDebug('sending rpc message:', toCpon(rpcMsg));
|
|
515
|
+
const msgData = new Uint8Array(toChainPack(rpcMsg));
|
|
516
|
+
const wr = new ChainPackWriter();
|
|
513
517
|
wr.writeUIntData(msgData.length + 1);
|
|
514
518
|
const dgram = new Uint8Array(wr.ctx.length + 1 + msgData.length);
|
|
515
519
|
let ix = 0;
|
|
516
520
|
for (let i = 0; i < wr.ctx.length; i++) {
|
|
517
521
|
dgram[ix++] = wr.ctx.data[i];
|
|
518
522
|
}
|
|
519
|
-
dgram[ix++] =
|
|
523
|
+
dgram[ix++] = CHAINPACK_PROTOCOL_TYPE;
|
|
520
524
|
for (const msgDatum of msgData) {
|
|
521
525
|
dgram[ix++] = msgDatum;
|
|
522
526
|
}
|
|
@@ -539,7 +543,7 @@ class WsClient {
|
|
|
539
543
|
return this.shvApiVersion;
|
|
540
544
|
}
|
|
541
545
|
async subscribe(subscriber, riGetter, callback) {
|
|
542
|
-
const ri = await
|
|
546
|
+
const ri = await resolveRI(riGetter);
|
|
543
547
|
const path = ri.path();
|
|
544
548
|
const signal = ri.signal();
|
|
545
549
|
if (this.subscriptions.some(val => val.subscriber === subscriber && val.path === path && val.signal === signal)) {
|
|
@@ -550,7 +554,7 @@ class WsClient {
|
|
|
550
554
|
if (this.subscriptions.every(val => !(val.path === path && val.signal === signal))) {
|
|
551
555
|
switch (await this.getShvApiVersion()) {
|
|
552
556
|
case ShvApiVersion.V2: {
|
|
553
|
-
const result = await this.callRpcMethod('.broker/app', 'subscribe',
|
|
557
|
+
const result = await this.callRpcMethod('.broker/app', 'subscribe', makeMap({ signal, path }));
|
|
554
558
|
if (result instanceof Error) {
|
|
555
559
|
this.logDebug(`Couldn't subscribe to ${path}, ${signal}`);
|
|
556
560
|
}
|
|
@@ -578,7 +582,7 @@ class WsClient {
|
|
|
578
582
|
});
|
|
579
583
|
}
|
|
580
584
|
async unsubscribe(subscriber, riGetter) {
|
|
581
|
-
const ri = await
|
|
585
|
+
const ri = await resolveRI(riGetter);
|
|
582
586
|
const path = ri.path();
|
|
583
587
|
const signal = ri.signal();
|
|
584
588
|
const idx = this.subscriptions.findIndex(val => val.subscriber === subscriber && val.path === path && val.signal === signal);
|
|
@@ -593,14 +597,14 @@ class WsClient {
|
|
|
593
597
|
}
|
|
594
598
|
switch (await this.getShvApiVersion()) {
|
|
595
599
|
case ShvApiVersion.V2: {
|
|
596
|
-
const result = await this.callRpcMethod('.broker/app', 'unsubscribe',
|
|
600
|
+
const result = await this.callRpcMethod('.broker/app', 'unsubscribe', makeMap({ signal, path }));
|
|
597
601
|
if (result instanceof Error) {
|
|
598
602
|
this.logDebug(`Couldn't unsubscribe ${path}, ${signal}`);
|
|
599
603
|
}
|
|
600
604
|
break;
|
|
601
605
|
}
|
|
602
606
|
case ShvApiVersion.V3: {
|
|
603
|
-
const result = await this.callRpcMethod('.broker/currentClient', 'unsubscribe',
|
|
607
|
+
const result = await this.callRpcMethod('.broker/currentClient', 'unsubscribe', makeMap({ ri: ri.asString(), ttl: undefined }));
|
|
604
608
|
if (result instanceof Error) {
|
|
605
609
|
this.logDebug(`Couldn't unsubscribe ${path}, ${signal}`);
|
|
606
610
|
}
|
|
@@ -629,4 +633,4 @@ class WsClient {
|
|
|
629
633
|
this.options.logDebug(...args);
|
|
630
634
|
}
|
|
631
635
|
}
|
|
632
|
-
|
|
636
|
+
export { WsClient };
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libshv-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "Typescript implementation of libshv",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"files": [
|
|
8
9
|
"dist"
|
|
9
10
|
],
|
|
@@ -45,5 +46,8 @@
|
|
|
45
46
|
"eslint-plugin-unicorn": "^73.0.0",
|
|
46
47
|
"typescript": "^6.0.2",
|
|
47
48
|
"typescript-eslint": "^8.46.1"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"minimatch": "^10.2.6"
|
|
48
52
|
}
|
|
49
53
|
}
|