libshv-js 3.13.0 → 3.15.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ws-client.ts +113 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libshv-js",
3
- "version": "3.13.0",
3
+ "version": "3.15.0",
4
4
  "description": "Typescript implementation of libshv",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
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
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,12 +62,24 @@ export class RequestHandler {
60
62
  }
61
63
  }
62
64
 
63
- type RequestHandlerParams = {
64
- shvPath: string;
65
- method: string;
66
- param: RpcValue;
67
- accessLevel: number;
68
- delay: (progress: number) => void;
65
+ export const LsParamZod = z.undefined().or(z.string());
66
+ export const DirParamZod = z.undefined().or(z.boolean()).or(z.string());
67
+
68
+ export type DirEntry = IMap<{
69
+ [DIR_NAME]: string;
70
+ [DIR_FLAGS]: number;
71
+ [DIR_PARAM]: string | Null;
72
+ [DIR_RESULT]: string | Null;
73
+ [DIR_ACCESS]: Int;
74
+ [DIR_SIGNALS]: ShvMap<Record<string, string | Null>>;
75
+ [DIR_EXTRA]: ShvMap;
76
+ }>;
77
+
78
+ export type DirResult = DirEntry[];
79
+
80
+ export type MethodHandler = {
81
+ paramParser: z.ZodType<RpcValue>,
82
+ handler: (shvPath: string, method: string, params: RpcValue, delay: (progress: number) => void) => Promise<RpcValue> | RpcValue | RequestHandler,
69
83
  };
70
84
 
71
85
  type WsClientOptionsLogin = WsClientOptionsCommon & {
@@ -76,7 +90,10 @@ type WsClientOptionsLogin = WsClientOptionsCommon & {
76
90
  onConnected: () => void;
77
91
  onConnectionFailure: (error: Error) => void;
78
92
  onDisconnected: () => void;
79
- onRequest: (request: RequestHandlerParams) => RpcValue | Promise<RpcValue> | RequestHandler;
93
+ onRequest: (shvPath: string) => {
94
+ ls: (shvPath: string) => string[] | Promise<string[]>;
95
+ dirEntries: Array<{entry: DirEntry} & MethodHandler>;
96
+ } | undefined;
80
97
  };
81
98
 
82
99
  type WsClientOptionsWorkflows = WsClientOptionsCommon & {
@@ -102,15 +119,6 @@ export const DIR_RESULT = 4;
102
119
  export const DIR_ACCESS = 5;
103
120
  export const DIR_SIGNALS = 6;
104
121
  export const DIR_EXTRA = 63;
105
- export type DirResult = Array<IMap<{
106
- [DIR_NAME]: string;
107
- [DIR_FLAGS]: DirFlags;
108
- [DIR_PARAM]: string | Null;
109
- [DIR_RESULT]: string | Null;
110
- [DIR_ACCESS]: Int;
111
- [DIR_SIGNALS]: ShvMap<Record<string, string | Null>>;
112
- [DIR_EXTRA]: ShvMap;
113
- }>>;
114
122
 
115
123
  class RpcError extends Error {
116
124
  constructor(readonly err_info: ErrorMap) {
@@ -224,6 +232,7 @@ class WsClient {
224
232
  }
225
233
  };
226
234
 
235
+ // eslint-disable-next-line complexity
227
236
  const handleRequest = async (request: RpcRequest) => {
228
237
  if ('onRequest' in this.options) {
229
238
  const requestId = request.meta[RPC_MESSAGE_REQUEST_ID];
@@ -246,9 +255,12 @@ class WsClient {
246
255
  if (RPC_MESSAGE_ABORT in request.value) {
247
256
  const requestHandler = this.requestHandlers.get(requestId);
248
257
 
249
- if (request.value[RPC_MESSAGE_ABORT] === true && requestHandler?.options.onAbort !== undefined) {
258
+ if (request.value[RPC_MESSAGE_ABORT] === true) {
250
259
  this.abortedRequests.add(requestId);
251
- requestHandler.options.onAbort();
260
+
261
+ if (requestHandler?.options.onAbort !== undefined) {
262
+ requestHandler.options.onAbort();
263
+ }
252
264
  }
253
265
 
254
266
  if (request.value[RPC_MESSAGE_ABORT] === false && requestHandler?.options.onProgressQuery !== undefined) {
@@ -261,19 +273,94 @@ class WsClient {
261
273
 
262
274
  try {
263
275
  const shvPath = request.meta[RPC_MESSAGE_SHV_PATH];
264
- const method = request.meta[RPC_MESSAGE_METHOD];
276
+ const methodName = request.meta[RPC_MESSAGE_METHOD];
277
+ const unhandledMethod = () => {
278
+ throw new MethodNotFound(`No such path '${shvPath}' or method '${methodName}' or insufficient access rights.`);
279
+ };
280
+
265
281
  const accessLevel = request.meta[RPC_MESSAGE_ACCESS_LEVEL];
266
282
  if (accessLevel === undefined) {
267
- throw new MethodNotFound(`Unknown method ${method} on path ${shvPath}`);
283
+ unhandledMethod();
284
+ return;
285
+ }
286
+
287
+ const methods = this.options.onRequest(shvPath);
288
+ if (methods === undefined) {
289
+ unhandledMethod();
290
+ return;
268
291
  }
269
292
 
270
- let result = this.options.onRequest({
293
+ const methodDefinition = ((): {entry: DirEntry} & MethodHandler | undefined => {
294
+ const dirEntry = makeIMap({
295
+ [DIR_NAME]: 'dir',
296
+ [DIR_FLAGS]: 0,
297
+ [DIR_PARAM]: 'DirParam',
298
+ [DIR_RESULT]: 'DirResult',
299
+ [DIR_ACCESS]: accessLevelFromAccessString('bws'),
300
+ [DIR_SIGNALS]: makeMap({}),
301
+ [DIR_EXTRA]: makeMap({}),
302
+ });
303
+ const lsEntry = makeIMap({
304
+ [DIR_NAME]: 'ls',
305
+ [DIR_FLAGS]: 0,
306
+ [DIR_PARAM]: 'LsParam',
307
+ [DIR_RESULT]: 'LsResult',
308
+ [DIR_ACCESS]: accessLevelFromAccessString('bws'),
309
+ [DIR_SIGNALS]: makeMap({}),
310
+ [DIR_EXTRA]: makeMap({}),
311
+ });
312
+ switch (methodName) {
313
+ case 'dir':
314
+ return {
315
+ entry: dirEntry,
316
+ paramParser: DirParamZod,
317
+ handler(_shvPath, _method, param) {
318
+ const methodsResult = [dirEntry, lsEntry, ...methods.dirEntries.map(method => method.entry)];
319
+ switch (true) {
320
+ case typeof param === 'string':
321
+ return methodsResult.find(method => method[DIR_NAME] === param) ?? false;
322
+ case typeof param === 'boolean':
323
+ case param === undefined:
324
+ console.log('methodsResult', '=', methodsResult);
325
+ return methodsResult;
326
+ }
327
+ },
328
+ };
329
+ case 'ls':
330
+ return {
331
+ entry: lsEntry,
332
+ paramParser: LsParamZod,
333
+ handler(shvPath) {
334
+ return methods.ls(shvPath);
335
+ },
336
+ };
337
+ default:
338
+ return methods.dirEntries.find(method => method.entry[DIR_NAME] === methodName);
339
+ }
340
+ })();
341
+
342
+ if (methodDefinition === undefined) {
343
+ unhandledMethod();
344
+ return;
345
+ }
346
+
347
+ if (accessLevel < methodDefinition.entry[DIR_ACCESS]) {
348
+ unhandledMethod();
349
+ return;
350
+ }
351
+
352
+ const requestParam = request.value[RPC_MESSAGE_PARAMS];
353
+ const parsedParam = methodDefinition.paramParser.safeParse(requestParam);
354
+ if (!parsedParam.success) {
355
+ throw new InvalidParams(`Unexpected param for method ${methodName} on path ${shvPath}: ${toCpon(requestParam)}`);
356
+ }
357
+
358
+ let result = methodDefinition.handler(
271
359
  shvPath,
272
- method,
273
- param: request.value[RPC_MESSAGE_PARAMS],
274
- accessLevel,
275
- delay: sendDelay,
276
- });
360
+ methodName,
361
+ parsedParam.data,
362
+ sendDelay,
363
+ );
277
364
  if (result instanceof RequestHandler) {
278
365
  this.requestHandlers.set(requestId, result);
279
366
  result = result.options.result;