libshv-js 3.12.2 → 3.14.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/package.json +3 -2
- package/src/rpcmessage.ts +2 -0
- package/src/ws-client.ts +100 -12
- package/src/zod.ts +0 -1
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libshv-js",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"description": "Typescript implementation of libshv",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
7
|
"lint": "eslint --format unix src/",
|
|
8
|
-
"fix": "npm run lint -- --fix"
|
|
8
|
+
"fix": "npm run lint -- --fix",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
9
10
|
},
|
|
10
11
|
"author": "Elektroline",
|
|
11
12
|
"repository": {
|
package/src/rpcmessage.ts
CHANGED
|
@@ -4,6 +4,7 @@ export const RPC_MESSAGE_REQUEST_ID = 8;
|
|
|
4
4
|
export const RPC_MESSAGE_SHV_PATH = 9;
|
|
5
5
|
export const RPC_MESSAGE_METHOD = 10;
|
|
6
6
|
export const RPC_MESSAGE_CALLER_IDS = 11;
|
|
7
|
+
export const RPC_MESSAGE_ACCESS_LEVEL = 17;
|
|
7
8
|
|
|
8
9
|
export const RPC_MESSAGE_PARAMS = 1;
|
|
9
10
|
export const RPC_MESSAGE_RESULT = 2;
|
|
@@ -43,6 +44,7 @@ const RpcRequestMetaZod = z.metamap({
|
|
|
43
44
|
[RPC_MESSAGE_REQUEST_ID]: z.number(),
|
|
44
45
|
[RPC_MESSAGE_METHOD]: z.string(),
|
|
45
46
|
[RPC_MESSAGE_SHV_PATH]: z.string(),
|
|
47
|
+
[RPC_MESSAGE_ACCESS_LEVEL]: z.int().gte(0).lte(63).optional(),
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
const RpcRequestValueZod = z.imap({
|
package/src/ws-client.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {accessLevelFromAccessString} from './access';
|
|
1
2
|
import {ChainPackReader, CHAINPACK_PROTOCOL_TYPE, ChainPackWriter, toChainPack} from './chainpack';
|
|
2
3
|
import {type CponReader, CPON_PROTOCOL_TYPE, toCpon} from './cpon';
|
|
3
|
-
import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessageZod, type RpcMessage, isSignal, isRequest, type RpcRequest, isResponse, ERROR_DATA, type ErrorMap, RPC_MESSAGE_METHOD, RPC_MESSAGE_SHV_PATH, RPC_MESSAGE_REQUEST_ID, RPC_MESSAGE_PARAMS, RPC_MESSAGE_ERROR, RPC_MESSAGE_RESULT, RPC_MESSAGE_CALLER_IDS, RpcResponseValue, RPC_MESSAGE_DELAY, RPC_MESSAGE_ABORT, RpcSignal, RpcResponse} from './rpcmessage';
|
|
4
|
+
import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessageZod, type RpcMessage, isSignal, isRequest, type RpcRequest, isResponse, ERROR_DATA, type ErrorMap, RPC_MESSAGE_METHOD, RPC_MESSAGE_SHV_PATH, RPC_MESSAGE_REQUEST_ID, RPC_MESSAGE_PARAMS, RPC_MESSAGE_ERROR, RPC_MESSAGE_RESULT, RPC_MESSAGE_CALLER_IDS, RpcResponseValue, RPC_MESSAGE_DELAY, RPC_MESSAGE_ABORT, RpcSignal, RpcResponse, RPC_MESSAGE_ACCESS_LEVEL} from './rpcmessage';
|
|
4
5
|
import {type RpcValue, type Null, type Int, type IMap, type ShvMap, makeMap, makeIMap, RpcValueWithMetaData, makeMetaMap, Double} from './rpcvalue';
|
|
6
|
+
import * as z from './zod';
|
|
5
7
|
|
|
6
8
|
const DEFAULT_TIMEOUT = 5000;
|
|
7
9
|
const DEFAULT_PING_INTERVAL = 30 * 1000;
|
|
@@ -60,6 +62,29 @@ export class RequestHandler {
|
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
export const LsParamZod = z.undefined().or(z.string());
|
|
66
|
+
export const DirParamZod = z.undefined().or(z.boolean()).or(z.string());
|
|
67
|
+
type DirParam = z.infer<typeof DirParamZod>;
|
|
68
|
+
|
|
69
|
+
export type DirEntry = IMap<{
|
|
70
|
+
[DIR_NAME]: string;
|
|
71
|
+
[DIR_FLAGS]: DirFlags;
|
|
72
|
+
[DIR_PARAM]: string | Null;
|
|
73
|
+
[DIR_RESULT]: string | Null;
|
|
74
|
+
[DIR_ACCESS]: Int;
|
|
75
|
+
[DIR_SIGNALS]: ShvMap<Record<string, string | Null>>;
|
|
76
|
+
[DIR_EXTRA]: ShvMap;
|
|
77
|
+
}>;
|
|
78
|
+
|
|
79
|
+
export type DirResult = DirEntry[];
|
|
80
|
+
|
|
81
|
+
export type MethodHandler = {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
paramParser: z.ZodType<any>,
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
handler: (shvPath: string, method: string, params: any, delay: (progress: number) => void) => Promise<RpcValue> | RpcValue | RequestHandler,
|
|
86
|
+
};
|
|
87
|
+
|
|
63
88
|
type WsClientOptionsLogin = WsClientOptionsCommon & {
|
|
64
89
|
mountPoint?: string;
|
|
65
90
|
login: Login;
|
|
@@ -68,7 +93,7 @@ type WsClientOptionsLogin = WsClientOptionsCommon & {
|
|
|
68
93
|
onConnected: () => void;
|
|
69
94
|
onConnectionFailure: (error: Error) => void;
|
|
70
95
|
onDisconnected: () => void;
|
|
71
|
-
onRequest: (shvPath: string
|
|
96
|
+
onRequest: (shvPath: string) => Array<{entry: DirEntry} & MethodHandler> | undefined;
|
|
72
97
|
};
|
|
73
98
|
|
|
74
99
|
type WsClientOptionsWorkflows = WsClientOptionsCommon & {
|
|
@@ -94,15 +119,6 @@ export const DIR_RESULT = 4;
|
|
|
94
119
|
export const DIR_ACCESS = 5;
|
|
95
120
|
export const DIR_SIGNALS = 6;
|
|
96
121
|
export const DIR_EXTRA = 63;
|
|
97
|
-
type DirResult = Array<IMap<{
|
|
98
|
-
[DIR_NAME]: string;
|
|
99
|
-
[DIR_FLAGS]: DirFlags;
|
|
100
|
-
[DIR_PARAM]: string | Null;
|
|
101
|
-
[DIR_RESULT]: string | Null;
|
|
102
|
-
[DIR_ACCESS]: Int;
|
|
103
|
-
[DIR_SIGNALS]: ShvMap<Record<string, string | Null>>;
|
|
104
|
-
[DIR_EXTRA]: ShvMap;
|
|
105
|
-
}>>;
|
|
106
122
|
|
|
107
123
|
class RpcError extends Error {
|
|
108
124
|
constructor(readonly err_info: ErrorMap) {
|
|
@@ -216,6 +232,7 @@ class WsClient {
|
|
|
216
232
|
}
|
|
217
233
|
};
|
|
218
234
|
|
|
235
|
+
// eslint-disable-next-line complexity
|
|
219
236
|
const handleRequest = async (request: RpcRequest) => {
|
|
220
237
|
if ('onRequest' in this.options) {
|
|
221
238
|
const requestId = request.meta[RPC_MESSAGE_REQUEST_ID];
|
|
@@ -252,7 +269,78 @@ class WsClient {
|
|
|
252
269
|
}
|
|
253
270
|
|
|
254
271
|
try {
|
|
255
|
-
|
|
272
|
+
const shvPath = request.meta[RPC_MESSAGE_SHV_PATH];
|
|
273
|
+
const methodName = request.meta[RPC_MESSAGE_METHOD];
|
|
274
|
+
const unhandledMethod = () => {
|
|
275
|
+
throw new MethodNotFound(`No such path '${shvPath}' or method '${methodName}' or insufficient access rights.`);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const accessLevel = request.meta[RPC_MESSAGE_ACCESS_LEVEL];
|
|
279
|
+
if (accessLevel === undefined) {
|
|
280
|
+
unhandledMethod();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const methods = this.options.onRequest(shvPath);
|
|
285
|
+
if (methods === undefined) {
|
|
286
|
+
unhandledMethod();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const methodDefinition = ((): {entry: DirEntry} & MethodHandler | undefined => {
|
|
291
|
+
const dirEntry = makeIMap({
|
|
292
|
+
[DIR_NAME]: 'dir',
|
|
293
|
+
[DIR_FLAGS]: 0,
|
|
294
|
+
[DIR_PARAM]: '',
|
|
295
|
+
[DIR_RESULT]: '',
|
|
296
|
+
[DIR_ACCESS]: accessLevelFromAccessString('rd'),
|
|
297
|
+
[DIR_SIGNALS]: makeMap({}),
|
|
298
|
+
[DIR_EXTRA]: makeMap({}),
|
|
299
|
+
});
|
|
300
|
+
switch (methodName) {
|
|
301
|
+
case 'dir':
|
|
302
|
+
return {
|
|
303
|
+
entry: dirEntry,
|
|
304
|
+
paramParser: DirParamZod,
|
|
305
|
+
handler(_shvPath, _method, param: DirParam) {
|
|
306
|
+
const methodsResult = [dirEntry, ...methods.map(method => method.entry)];
|
|
307
|
+
switch (true) {
|
|
308
|
+
case typeof param === 'string':
|
|
309
|
+
return methodsResult.find(method => method[DIR_NAME] === param) ?? false;
|
|
310
|
+
case typeof param === 'boolean':
|
|
311
|
+
case param === undefined:
|
|
312
|
+
console.log('methodsResult', '=', methodsResult);
|
|
313
|
+
return methodsResult;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
default:
|
|
318
|
+
return methods.find(method => method.entry[DIR_NAME] === methodName);
|
|
319
|
+
}
|
|
320
|
+
})();
|
|
321
|
+
|
|
322
|
+
if (methodDefinition === undefined) {
|
|
323
|
+
unhandledMethod();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (accessLevel < methodDefinition.entry[DIR_ACCESS]) {
|
|
328
|
+
unhandledMethod();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const requestParam = request.value[RPC_MESSAGE_PARAMS];
|
|
333
|
+
const parsedParam = methodDefinition.paramParser.safeParse(requestParam);
|
|
334
|
+
if (!parsedParam.success) {
|
|
335
|
+
throw new InvalidParams(`Unexpected param for method ${methodName} on path ${shvPath}: ${toCpon(requestParam)}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let result = methodDefinition.handler(
|
|
339
|
+
shvPath,
|
|
340
|
+
methodName,
|
|
341
|
+
parsedParam.data,
|
|
342
|
+
sendDelay,
|
|
343
|
+
);
|
|
256
344
|
if (result instanceof RequestHandler) {
|
|
257
345
|
this.requestHandlers.set(requestId, result);
|
|
258
346
|
result = result.options.result;
|
package/src/zod.ts
CHANGED
|
@@ -29,7 +29,6 @@ export const map = <T extends Record<string, ZodType<any>>>(schema: T) => implMa
|
|
|
29
29
|
export const imap = <T extends Record<number, ZodType<any>>>(schema: T) => implMakeMapParser(isIMap, 'imap', 'IMap', z.object(schema));
|
|
30
30
|
export const metamap = <T extends Record<string | number, ZodType<any>>>(schema: T) => implMakeMapParser(isMetaMap, 'metamap', 'MetaMap', z.object(schema));
|
|
31
31
|
export const recmap = <T extends ZodType<any>>(schema: T) => implMakeMapParser(val => isShvMap(val) || isIMap(val), 'map', 'ShvMap', z.record(z.any(), schema));
|
|
32
|
-
export const int = () => z.number();
|
|
33
32
|
|
|
34
33
|
export const uint = () => z.instanceof(UInt<number>);
|
|
35
34
|
export const double = () => z.instanceof(Double);
|