libshv-js 3.3.5 → 3.4.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/src/rpcmessage.ts CHANGED
@@ -1,15 +1,13 @@
1
- import {type IMap, type Int, isIMap, makeIMap, makeMetaMap, type MetaMap, type RpcValue, RpcValueWithMetaData} from './rpcvalue';
2
- import {toCpon} from './cpon';
3
- import {toChainPack} from './chainpack';
1
+ import * as z from './zod';
4
2
 
5
- const TagRequestId = 8;
6
- const TagShvPath = 9;
7
- const TagMethod = 10;
8
- const TagCallerIds = 11;
3
+ export const RPC_MESSAGE_REQUEST_ID = 8;
4
+ export const RPC_MESSAGE_SHV_PATH = 9;
5
+ export const RPC_MESSAGE_METHOD = 10;
6
+ export const RPC_MESSAGE_CALLER_IDS = 11;
9
7
 
10
- const KeyParams = 1;
11
- const KeyResult = 2;
12
- const KeyError = 3;
8
+ export const RPC_MESSAGE_PARAMS = 1;
9
+ export const RPC_MESSAGE_RESULT = 2;
10
+ export const RPC_MESSAGE_ERROR = 3;
13
11
 
14
12
  export const ERROR_CODE = 1;
15
13
  export const ERROR_MESSAGE = 2;
@@ -30,169 +28,52 @@ enum ErrorCode {
30
28
  NotImplemented = 12,
31
29
  }
32
30
 
33
- export type ErrorMap = {
34
- [ERROR_CODE]: ErrorCode;
35
- [ERROR_MESSAGE]?: string;
36
- [ERROR_DATA]?: RpcValue;
37
- };
38
-
39
- class RpcError extends Error {
40
- constructor(private readonly err_info: ErrorMap) {
41
- super(err_info[ERROR_MESSAGE] ?? 'Unknown RpcError');
42
- }
43
-
44
- data() {
45
- return this.err_info[ERROR_DATA];
46
- }
47
- }
48
-
49
- export class ProtocolError extends Error {}
50
-
51
- export class InvalidRequest extends RpcError {}
52
- export class MethodNotFound extends RpcError {}
53
- export class InvalidParams extends RpcError {}
54
- export class InternalError extends RpcError {}
55
- export class ParseError extends RpcError {}
56
- export class MethodCallTimeout extends RpcError {}
57
- export class MethodCallCancelled extends RpcError {}
58
- export class MethodCallException extends RpcError {}
59
- export class Unknown extends RpcError {}
60
- export class LoginRequired extends RpcError {}
61
- export class UserIDRequired extends RpcError {}
62
- export class NotImplemented extends RpcError {}
63
-
64
- class RpcMessage {
65
- value: IMap;
66
- meta: MetaMap;
67
- constructor(rpc_val?: RpcValue) {
68
- if (rpc_val === undefined) {
69
- this.value = makeIMap({});
70
- this.meta = makeMetaMap({});
71
- return;
72
- }
73
-
74
- if (!(rpc_val instanceof RpcValueWithMetaData && isIMap(rpc_val.value))) {
75
- throw new TypeError(`RpcMessage initialized with a non-IMap: ${toCpon(rpc_val)}`);
76
- }
77
-
78
- this.value = rpc_val.value;
79
- this.meta = rpc_val.meta;
80
- }
81
-
82
- isValid() {
83
- return this.shvPath() && (this.isRequest() || this.isResponse || this.isSignal());
84
- }
85
-
86
- isRequest(): boolean {
87
- return this.requestId() !== undefined && this.method() !== undefined;
88
- }
89
-
90
- isResponse(): boolean {
91
- return this.requestId() !== undefined && this.method() === undefined;
92
- }
93
-
94
- isSignal(): boolean {
95
- return this.requestId() === undefined && this.method() !== undefined;
96
- }
97
-
98
- requestId(): Int | undefined {
99
- return (this.meta[TagRequestId] as Int);
100
- }
101
-
102
- setRequestId(id: number) {
103
- this.meta[TagRequestId] = id;
104
- }
105
-
106
- callerIds(): RpcValue[] | undefined {
107
- return this.meta[TagCallerIds] as RpcValue[];
108
- }
109
-
110
- setCallerIds(ids: RpcValue[]) {
111
- this.meta[TagCallerIds] = ids;
112
- }
113
-
114
- shvPath(): string | undefined {
115
- return (this.meta[TagShvPath] as string);
116
- }
117
-
118
- setShvPath(val: string) {
119
- this.meta[TagShvPath] = val;
120
- }
121
-
122
- method(): string | undefined {
123
- return (this.meta[TagMethod] as string);
124
- }
125
-
126
- setMethod(val: string) {
127
- this.meta[TagMethod] = val;
128
- }
129
-
130
- params() {
131
- return this.value[KeyParams];
132
- }
133
-
134
- setParams(params: RpcValue) {
135
- this.value[KeyParams] = params;
136
- }
137
-
138
- resultOrError() {
139
- if (Object.hasOwn(this.value, KeyError)) {
140
- if (!isIMap(this.value[KeyError])) {
141
- return new ProtocolError('Response had an error, but this error was not a map');
142
- }
143
-
144
- const error_map = this.value[KeyError];
145
- if (typeof error_map[ERROR_CODE] !== 'number') {
146
- return new ProtocolError('Response had an error, but this error did not contain at least an error code');
147
- }
148
-
149
- const code = error_map[ERROR_CODE] as unknown;
150
-
151
- const ErrorType = (() => {
152
- switch (code) {
153
- case ErrorCode.InvalidRequest: return InvalidRequest;
154
- case ErrorCode.MethodNotFound: return MethodNotFound;
155
- case ErrorCode.InvalidParams: return InvalidParams;
156
- case ErrorCode.InternalError: return InternalError;
157
- case ErrorCode.ParseError: return ParseError;
158
- case ErrorCode.MethodCallTimeout: return MethodCallTimeout;
159
- case ErrorCode.MethodCallCancelled: return MethodCallCancelled;
160
- case ErrorCode.MethodCallException: return MethodCallException;
161
- case ErrorCode.Unknown: return Unknown;
162
- case ErrorCode.LoginRequired: return LoginRequired;
163
- case ErrorCode.UserIDRequired: return UserIDRequired;
164
- case ErrorCode.NotImplemented: return NotImplemented;
165
- default: return Unknown;
166
- }
167
- })();
168
-
169
- return new ErrorType(this.value[KeyError] as unknown as ErrorMap);
170
- }
171
-
172
- if (Object.hasOwn(this.value, KeyResult)) {
173
- return this.value[KeyResult];
174
- }
175
-
176
- return new ProtocolError('Response included neither result nor error');
177
- }
178
-
179
- setResult(result: RpcValue) {
180
- this.value[KeyResult] = result;
181
- }
182
-
183
- setError(error: string) {
184
- this.value[KeyError] = error;
185
- }
186
-
187
- toCpon() {
188
- return toCpon(new RpcValueWithMetaData(this.value, this.meta));
189
- }
190
-
191
- toChainPack() {
192
- return toChainPack(new RpcValueWithMetaData(this.value, this.meta));
193
- }
194
- }
195
-
196
- export type RpcResponse<T = RpcValue> = T | Error;
197
-
198
- export {RpcMessage, RpcError, ErrorCode};
31
+ const ErrorMapZod = z.imap({
32
+ [ERROR_CODE]: z.number(),
33
+ [ERROR_MESSAGE]: z.string().optional(),
34
+ [ERROR_DATA]: z.rpcvalue().optional(),
35
+ });
36
+
37
+ export type ErrorMap = z.infer<typeof ErrorMapZod>;
38
+
39
+ const RpcRequestMetaZod = z.metamap({
40
+ [RPC_MESSAGE_REQUEST_ID]: z.number(),
41
+ [RPC_MESSAGE_METHOD]: z.string(),
42
+ [RPC_MESSAGE_SHV_PATH]: z.string(),
43
+ });
44
+
45
+ const RpcRequestValueZod = z.imap({
46
+ [RPC_MESSAGE_PARAMS]: z.rpcvalue().optional(),
47
+ });
48
+
49
+ const RpcResponseMetaZod = z.metamap({
50
+ [RPC_MESSAGE_REQUEST_ID]: z.number(),
51
+ });
52
+ const RpcResponseValueZod = z.imap({
53
+ [RPC_MESSAGE_RESULT]: z.rpcvalue(),
54
+ }).or(z.imap({
55
+ [RPC_MESSAGE_ERROR]: ErrorMapZod,
56
+ }));
57
+
58
+ const RpcSignalMetaZod = z.metamap({
59
+ [RPC_MESSAGE_SHV_PATH]: z.string(),
60
+ [RPC_MESSAGE_METHOD]: z.string(),
61
+ });
62
+ const RpcSignalValueZod = z.imap({
63
+ [RPC_MESSAGE_PARAMS]: z.rpcvalue().optional(),
64
+ });
65
+
66
+ const RpcRequestZod = z.withMeta(RpcRequestMetaZod, RpcRequestValueZod);
67
+ const RpcResponseZod = z.withMeta(RpcResponseMetaZod, RpcResponseValueZod);
68
+ const RpcSignalZod = z.withMeta(RpcSignalMetaZod, RpcSignalValueZod);
69
+ const RpcMessageZod = z.union([RpcRequestZod, RpcResponseZod, RpcSignalZod]);
70
+ export type RpcRequest = z.infer<typeof RpcRequestZod>;
71
+ export type RpcResponse = z.infer<typeof RpcResponseZod>;
72
+ export type RpcSignal = z.infer<typeof RpcSignalZod>;
73
+ export type RpcMessage = z.infer<typeof RpcMessageZod>;
74
+
75
+ export const isSignal = (message: RpcMessage): message is RpcSignal => !(RPC_MESSAGE_REQUEST_ID in message.meta) && RPC_MESSAGE_METHOD in message.meta;
76
+ export const isRequest = (message: RpcMessage): message is RpcRequest => RPC_MESSAGE_REQUEST_ID in message.meta && RPC_MESSAGE_METHOD in message.meta;
77
+ export const isResponse = (message: RpcMessage): message is RpcResponse => RPC_MESSAGE_REQUEST_ID in message.meta && !(RPC_MESSAGE_METHOD in message.meta);
78
+
79
+ export {RpcMessageZod, ErrorCode};
package/src/rpcvalue.ts CHANGED
@@ -50,10 +50,11 @@ export type Blob = ArrayBuffer;
50
50
  export type ShvString = string;
51
51
  export type DateTime = Date & {utc_offset?: number};
52
52
  const withOffset = (date: Date, utc_offset?: number) => {
53
- const cloned_date: DateTime = new Date(date.getTime());
54
- cloned_date.utc_offset = utc_offset;
55
- return cloned_date;
53
+ const clonedDate: DateTime = new Date(date.getTime());
54
+ clonedDate.utc_offset = utc_offset;
55
+ return clonedDate;
56
56
  };
57
+
57
58
  export type List = RpcValue[];
58
59
 
59
60
  const shvMapType = Symbol('shvMapType');
@@ -92,9 +93,11 @@ type MetaMap<T extends Record<string | number, RpcValue> = Record<string | numbe
92
93
  [shvMapType]: 'metamap';
93
94
  };
94
95
 
95
- const isShvMap = (x: RpcValue): x is ShvMap => typeof x === 'object' && (x as ShvMap)[shvMapType] === 'map';
96
+ const isShvMap = (x: unknown): x is ShvMap => typeof x === 'object' && (x as ShvMap)[shvMapType] === 'map';
97
+
98
+ const isIMap = (x: unknown): x is IMap => typeof x === 'object' && (x as IMap)[shvMapType] === 'imap';
96
99
 
97
- const isIMap = (x: RpcValue): x is IMap => typeof x === 'object' && (x as IMap)[shvMapType] === 'imap';
100
+ const isMetaMap = (x: unknown): x is IMap => typeof x === 'object' && (x as MetaMap)[shvMapType] === 'metamap';
98
101
 
99
102
  const makeMetaMap = <T extends Record<string | number, RpcValue> = Record<string | number, RpcValue>, U extends Record<number, RpcValue> = Omit<T, typeof shvMapType>>(x: U = {} as U): MetaMap<U> => ({
100
103
  ...x,
@@ -111,10 +114,10 @@ const makeMap = <T extends Record<string, RpcValue> = Record<string, RpcValue>,
111
114
  [shvMapType]: 'map',
112
115
  });
113
116
 
114
- class RpcValueWithMetaData {
115
- constructor(public value: RpcValueType, public meta: MetaMap) {}
117
+ class RpcValueWithMetaData<MetaSchema extends MetaMap = MetaMap, ValueSchema extends RpcValueType = RpcValueType> {
118
+ constructor(public meta: MetaSchema, public value: ValueSchema) {}
116
119
  }
117
120
 
118
121
  export type RpcValue = RpcValueType | RpcValueWithMetaData;
119
122
 
120
- export {shvMapType, Decimal, Double, type IMap, type MetaMap, RpcValueWithMetaData, type ShvMap, UInt, withOffset, makeMap, makeIMap, makeMetaMap, isIMap, isShvMap};
123
+ export {shvMapType, Decimal, Double, type IMap, type MetaMap, RpcValueWithMetaData, type ShvMap, UInt, withOffset, makeMap, makeIMap, makeMetaMap, isIMap, isMetaMap, isShvMap};
package/src/ws-client.ts CHANGED
@@ -1,29 +1,33 @@
1
- import {ChainPackReader, ChainpackProtocolType, ChainPackWriter} from './chainpack';
2
- import {type CponReader, CponProtocolType} from './cpon';
3
- import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessage, type RpcResponse, MethodCallTimeout} from './rpcmessage';
4
- import {type RpcValue, type Null, type Int, type IMap, type ShvMap, makeMap, makeIMap} from './rpcvalue';
1
+ import {ChainPackReader, CHAINPACK_PROTOCOL_TYPE, ChainPackWriter, toChainPack} from './chainpack';
2
+ 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} from './rpcmessage';
4
+ import {type RpcValue, type Null, type Int, type IMap, type ShvMap, makeMap, makeIMap, RpcValueWithMetaData, makeMetaMap} from './rpcvalue';
5
5
 
6
6
  const DEFAULT_TIMEOUT = 5000;
7
+ const DEFAULT_PING_INTERVAL = 30 * 1000;
7
8
 
8
9
  const dataToRpcValue = (buff: ArrayBuffer) => {
9
10
  const rd: ChainPackReader | CponReader = new ChainPackReader(buff);
10
11
  rd.readUIntData();
11
12
  const proto = rd.ctx.getByte();
12
13
  switch (proto) {
13
- case ChainpackProtocolType:
14
+ case CHAINPACK_PROTOCOL_TYPE:
14
15
  break;
15
- case CponProtocolType:
16
+ case CPON_PROTOCOL_TYPE:
16
17
  throw new Error('CPON protocol type is not supported anymore');
17
18
  default:
18
19
  throw new Error(`Unsupported protocol type ${proto}`);
19
20
  }
20
- const rpc_val = rd.read();
21
- return rpc_val;
21
+
22
+ const rpcVal = rd.read();
23
+ return rpcVal;
22
24
  };
23
25
 
24
26
  type SubscriptionCallback = (path: string, method: string, param?: RpcValue) => void;
25
27
 
26
- type RpcResponseResolver = (rpc_msg: RpcResponse) => void;
28
+ type ResultOrError<T = RpcValue> = T | Error;
29
+
30
+ type RpcResponseResolver = (rpc_msg: ResultOrError) => void;
27
31
 
28
32
  type Subscription = {
29
33
  subscriber: string;
@@ -39,11 +43,12 @@ type WsClientOptions = {
39
43
  password: string;
40
44
  loginType?: 'PLAIN' | 'AZURE';
41
45
  timeout?: number;
46
+ pingInterval?: number;
42
47
  wsUri: string;
43
48
  onConnected: () => void;
44
49
  onConnectionFailure: (error: Error) => void;
45
50
  onDisconnected: () => void;
46
- onRequest: (rpc_msg: RpcMessage) => void;
51
+ onRequest: (rpc_msg: RpcRequest) => void;
47
52
  };
48
53
 
49
54
  type LsResult = string[];
@@ -72,8 +77,35 @@ type DirResult = Array<IMap<{
72
77
  [DIR_EXTRA]: ShvMap;
73
78
  }>>;
74
79
 
80
+ class RpcError extends Error {
81
+ constructor(private readonly err_info: ErrorMap) {
82
+ super(err_info[ERROR_MESSAGE] ?? 'Unknown RpcError');
83
+ }
84
+
85
+ data() {
86
+ return this.err_info[ERROR_DATA];
87
+ }
88
+ }
89
+
90
+ class ProtocolError extends Error {}
91
+
92
+ class InvalidRequest extends RpcError {}
93
+ class MethodNotFound extends RpcError {}
94
+ class InvalidParams extends RpcError {}
95
+ class InternalError extends RpcError {}
96
+ class ParseError extends RpcError {}
97
+ class MethodCallTimeout extends RpcError {}
98
+ class MethodCallCancelled extends RpcError {}
99
+ class MethodCallException extends RpcError {}
100
+ class Unknown extends RpcError {}
101
+ class LoginRequired extends RpcError {}
102
+ class UserIDRequired extends RpcError {}
103
+ class NotImplemented extends RpcError {}
104
+
75
105
  class WsClient {
76
106
  requestId = 1;
107
+ pingTimerId = -1;
108
+
77
109
  rpcHandlers: Array<{
78
110
  resolve: RpcResponseResolver;
79
111
  timeout_handle: number;
@@ -92,6 +124,7 @@ class WsClient {
92
124
  onDisconnected: WsClientOptions['onDisconnected'];
93
125
  onRequest: WsClientOptions['onRequest'];
94
126
  timeout: WsClientOptions['timeout'];
127
+ pingInterval: WsClientOptions['pingInterval'];
95
128
 
96
129
  constructor(options: WsClientOptions) {
97
130
  if (typeof options !== 'object') {
@@ -108,6 +141,7 @@ class WsClient {
108
141
  this.websocket = new WebSocket(options.wsUri);
109
142
  this.websocket.binaryType = 'arraybuffer';
110
143
 
144
+ this.pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
111
145
  this.onConnected = options.onConnected ?? (() => {/* nothing */});
112
146
  this.onConnectionFailure = options.onConnectionFailure ?? (() => {/* nothing */});
113
147
  this.onDisconnected = options.onDisconnected ?? (() => {/* nothing */});
@@ -121,11 +155,13 @@ class WsClient {
121
155
  this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
122
156
  this.onConnectionFailure(error);
123
157
  };
158
+
124
159
  this.callRpcMethod(undefined, 'hello').then(response => {
125
160
  if (response instanceof Error) {
126
161
  handleConnectionError(response);
127
162
  return;
128
163
  }
164
+
129
165
  const params = makeMap({
130
166
  login: makeMap({
131
167
  password: this.password,
@@ -142,8 +178,12 @@ class WsClient {
142
178
  handleConnectionError(response);
143
179
  return;
144
180
  }
181
+
145
182
  this.logDebug('SUCCESS: connected to shv broker');
146
183
  this.onConnected();
184
+ this.pingTimerId = window.setInterval(() => {
185
+ this.sendPing();
186
+ }, this.pingInterval);
147
187
  }).catch(() => {
148
188
  this.logDebug('FAILURE: couldn\' connected to shv broker');
149
189
  });
@@ -153,34 +193,61 @@ class WsClient {
153
193
  this.logDebug('DISCONNECTED');
154
194
  this.subscriptions.length = 0;
155
195
  this.onDisconnected();
196
+ window.clearInterval(this.pingTimerId);
156
197
  });
157
198
 
158
- this.websocket.addEventListener('message', evt => {
159
- const rpc_val = dataToRpcValue(evt.data);
160
- const rpc_msg = new RpcMessage(rpc_val);
161
- this.logDebug(`message received: ${rpc_msg.toCpon()}`);
199
+ this.websocket.addEventListener('message', (evt: MessageEvent<ArrayBuffer>) => {
200
+ const rpcVal = dataToRpcValue(evt.data);
201
+ const rpcMsg = RpcMessageZod.parse(rpcVal);
202
+ this.logDebug(`message received: ${toCpon(rpcMsg)}`);
162
203
 
163
- if (rpc_msg.isSignal()) {
204
+ if (isSignal(rpcMsg)) {
164
205
  for (const sub of this.subscriptions) {
165
- const shv_path = rpc_msg.shvPath();
166
- const method = rpc_msg.method();
206
+ const shvPath = rpcMsg.meta[RPC_MESSAGE_SHV_PATH];
207
+ const method = rpcMsg.meta[RPC_MESSAGE_METHOD];
167
208
 
168
- if (shv_path?.startsWith(sub.path) && method === sub.method) {
169
- sub.callback(shv_path, method, rpc_msg.params());
209
+ if (shvPath.startsWith(sub.path) && method === sub.method) {
210
+ sub.callback(shvPath, method, rpcMsg.value[RPC_MESSAGE_PARAMS]);
170
211
  }
171
212
  }
172
- } else if (rpc_msg.isRequest()) {
173
- this.onRequest(rpc_msg);
174
- } else if (rpc_msg.isResponse()) {
175
- const requestId = rpc_msg.requestId();
176
- if (requestId === undefined) {
177
- throw new Error('got RpcResponse without requestId');
178
- }
213
+ } else if (isRequest(rpcMsg)) {
214
+ this.onRequest(rpcMsg);
215
+ } else if (isResponse(rpcMsg)) {
216
+ const requestId = rpcMsg.meta[RPC_MESSAGE_REQUEST_ID];
179
217
 
180
218
  if (this.rpcHandlers[Number(requestId)] !== undefined) {
181
219
  const handler = this.rpcHandlers[Number(requestId)];
182
220
  clearTimeout(handler.timeout_handle);
183
- handler.resolve(rpc_msg.resultOrError());
221
+ handler.resolve((() => {
222
+ if (RPC_MESSAGE_ERROR in rpcMsg.value) {
223
+ const code = rpcMsg.value[RPC_MESSAGE_ERROR][ERROR_CODE] as unknown;
224
+ const ErrorTypeCtor = (() => {
225
+ switch (code) {
226
+ case ErrorCode.InvalidRequest: return InvalidRequest;
227
+ case ErrorCode.MethodNotFound: return MethodNotFound;
228
+ case ErrorCode.InvalidParams: return InvalidParams;
229
+ case ErrorCode.InternalError: return InternalError;
230
+ case ErrorCode.ParseError: return ParseError;
231
+ case ErrorCode.MethodCallTimeout: return MethodCallTimeout;
232
+ case ErrorCode.MethodCallCancelled: return MethodCallCancelled;
233
+ case ErrorCode.MethodCallException: return MethodCallException;
234
+ case ErrorCode.Unknown: return Unknown;
235
+ case ErrorCode.LoginRequired: return LoginRequired;
236
+ case ErrorCode.UserIDRequired: return UserIDRequired;
237
+ case ErrorCode.NotImplemented: return NotImplemented;
238
+ default: return Unknown;
239
+ }
240
+ })();
241
+
242
+ return new ErrorTypeCtor(rpcMsg.value[RPC_MESSAGE_ERROR]);
243
+ }
244
+
245
+ if (RPC_MESSAGE_RESULT in rpcMsg.value) {
246
+ return rpcMsg.value[RPC_MESSAGE_RESULT];
247
+ }
248
+
249
+ return new ProtocolError('Response included neither result nor error');
250
+ })());
184
251
  // eslint-disable-next-line @typescript-eslint/no-array-delete, @typescript-eslint/no-dynamic-delete
185
252
  delete this.rpcHandlers[Number(requestId)];
186
253
  }
@@ -193,28 +260,29 @@ class WsClient {
193
260
  });
194
261
  }
195
262
 
196
- callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<RpcResponse<string>>;
197
- callRpcMethod(shv_path: string | undefined, method: 'dir', params?: RpcValue): Promise<RpcResponse<DirResult>>;
198
- callRpcMethod(shv_path: string | undefined, method: 'ls', params?: RpcValue): Promise<RpcResponse<LsResult>>;
199
- callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<RpcResponse>;
200
- callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue) {
201
- const rq = new RpcMessage();
202
- const rq_id = this.requestId++;
203
- rq.setRequestId(rq_id);
204
- if (shv_path !== undefined) {
205
- rq.setShvPath(shv_path);
206
- }
207
- rq.setMethod(method);
208
- if (params !== undefined) {
209
- rq.setParams(params);
210
- }
263
+ callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<ResultOrError<string>>;
264
+ callRpcMethod(shv_path: string | undefined, method: 'dir', params?: RpcValue): Promise<ResultOrError<DirResult>>;
265
+ callRpcMethod(shv_path: string | undefined, method: 'ls', params?: RpcValue): Promise<ResultOrError<LsResult>>;
266
+ callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<ResultOrError>;
267
+ callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<ResultOrError> {
268
+ const rqId = this.requestId++;
269
+ const rq: RpcRequest = new RpcValueWithMetaData(makeMetaMap({
270
+ [RPC_MESSAGE_REQUEST_ID]: rqId,
271
+ [RPC_MESSAGE_METHOD]: method ?? '',
272
+ [RPC_MESSAGE_SHV_PATH]: shv_path ?? '',
273
+ }), makeIMap({
274
+ [RPC_MESSAGE_PARAMS]: params,
275
+ }),
276
+ );
277
+
211
278
  this.sendRpcMessage(rq);
212
279
 
213
- const promise = new Promise<RpcResponse>(resolve => {
214
- this.rpcHandlers[rq_id] = {resolve, timeout_handle: self.setTimeout(() => {
280
+ const promise = new Promise<ResultOrError>(resolve => {
281
+ this.rpcHandlers[rqId] = {resolve, timeout_handle: self.setTimeout(() => {
215
282
  resolve(new MethodCallTimeout(makeIMap({
216
283
  [ERROR_CODE]: ErrorCode.MethodCallTimeout,
217
284
  [ERROR_MESSAGE]: `Shv call timeout after: ${this.timeout} msec.`,
285
+ [ERROR_DATA]: undefined,
218
286
  })));
219
287
  }, this.timeout)};
220
288
  });
@@ -223,23 +291,24 @@ class WsClient {
223
291
  }
224
292
 
225
293
  sendRpcMessage(rpc_msg: RpcMessage) {
226
- if (this.websocket && this.websocket.readyState === 1) {
227
- this.logDebug('sending rpc message:', rpc_msg.toCpon());
228
- const msg_data = new Uint8Array(rpc_msg.toChainPack());
294
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
295
+ this.logDebug('sending rpc message:', toCpon(rpc_msg));
296
+ const msgData = new Uint8Array(toChainPack(rpc_msg));
229
297
 
230
298
  const wr = new ChainPackWriter();
231
- wr.writeUIntData(msg_data.length + 1);
232
- const dgram = new Uint8Array(wr.ctx.length + 1 + msg_data.length);
299
+ wr.writeUIntData(msgData.length + 1);
300
+ const dgram = new Uint8Array(wr.ctx.length + 1 + msgData.length);
233
301
  let ix = 0;
234
302
  for (let i = 0; i < wr.ctx.length; i++) {
235
303
  dgram[ix++] = wr.ctx.data[i];
236
304
  }
237
305
 
238
- dgram[ix++] = ChainpackProtocolType;
306
+ dgram[ix++] = CHAINPACK_PROTOCOL_TYPE;
239
307
 
240
- for (const msg_datum of msg_data) {
241
- dgram[ix++] = msg_datum;
308
+ for (const msgDatum of msgData) {
309
+ dgram[ix++] = msgDatum;
242
310
  }
311
+
243
312
  this.logDebug(`sending ${dgram.length} bytes of data`);
244
313
  this.websocket.send(dgram.buffer);
245
314
  }
@@ -250,6 +319,7 @@ class WsClient {
250
319
  this.logDebug(`Already subscribed {$path}:${method} for subscriber ${subscriber}`);
251
320
  return;
252
321
  }
322
+
253
323
  // If this path:method has not been subscribed on the broker, do it now
254
324
  if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
255
325
  this.callRpcMethod('.broker/app', 'subscribe', makeMap({
@@ -258,6 +328,7 @@ class WsClient {
258
328
  this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
259
329
  });
260
330
  }
331
+
261
332
  this.subscriptions.push({
262
333
  subscriber,
263
334
  path,
@@ -272,11 +343,13 @@ class WsClient {
272
343
  this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
273
344
  return;
274
345
  }
346
+
275
347
  this.subscriptions.splice(idx, 1);
276
348
  // Unsubscribe on the broker only if there are no other subscriptions of this path:method
277
349
  if (this.subscriptions.some(val => val.path === path && val.method === method)) {
278
350
  return;
279
351
  }
352
+
280
353
  this.callRpcMethod('.broker/app', 'unsubscribe', makeMap({
281
354
  method, path,
282
355
  })).catch(() => {
@@ -284,6 +357,12 @@ class WsClient {
284
357
  });
285
358
  }
286
359
 
360
+ sendPing() {
361
+ this.callRpcMethod('.broker/app', 'ping').catch((error: unknown) => {
362
+ console.log('Failed to send ping:', error);
363
+ });
364
+ }
365
+
287
366
  accessGrantForMethodCall(path: string, method: string) {
288
367
  return this.callRpcMethod('.broker/currentClient', 'accessGrantForMethodCall', [path, method]);
289
368
  }
package/src/zod.ts CHANGED
@@ -1,11 +1,35 @@
1
1
  import {z, type ZodType} from 'zod';
2
- import {type IMap, isIMap, isShvMap, type RpcValue, type ShvMap, UInt} from './rpcvalue';
2
+ import {Decimal, Double, type IMap, isIMap, isMetaMap, isShvMap, type MetaMap, type RpcValue, type RpcValueType, RpcValueWithMetaData, type ShvMap, UInt} from './rpcvalue';
3
3
 
4
- export const map = <T extends Record<string, ZodType<any, any, any>>>(schema: T) => z.custom<ShvMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isShvMap(data) && z.object(schema).safeParse(data).success);
4
+ export const map = <T extends Record<string, ZodType<any, any, any>>>(schema?: T) => z.custom<ShvMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isShvMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
5
5
  export const recmap = <T extends ZodType<any, any, any>>(schema: T) => z.custom<ShvMap<Record<string, z.infer<T>>>>((data: RpcValue) => isShvMap(data) && z.record(z.string(), schema).safeParse(data).success);
6
- export const imap = <T extends Record<string, ZodType<any, any, any>>>(schema: T) => z.custom<IMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isIMap(data) && z.object(schema).safeParse(data).success);
6
+ export const imap = <T extends Record<string, ZodType<any, any, any>>>(schema?: T) => z.custom<IMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isIMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
7
+ export const metamap = <T extends Record<string | number, ZodType<any, any, any>>>(schema?: T) => z.custom<MetaMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isMetaMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
7
8
  export const int = () => z.number();
8
9
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments -- Zod needs the default argument, otherwise it'll infer as UInt<unknown>
9
10
  export const uint = () => z.instanceof(UInt<number>);
10
11
 
12
+ const withMetaInstanceParser = z.instanceof(RpcValueWithMetaData);
13
+ export const rpcvalue = () => z.union([
14
+ z.undefined(),
15
+ z.boolean(),
16
+ z.number(),
17
+ uint(),
18
+ z.instanceof(Double),
19
+ z.instanceof(Decimal),
20
+ z.instanceof(ArrayBuffer),
21
+ z.string(),
22
+ z.date(),
23
+ z.array(z.any()),
24
+ map(),
25
+ imap(),
26
+ withMetaInstanceParser,
27
+ ]);
28
+
29
+ export const withMeta = <MetaSchema extends MetaMap, ValueSchema extends RpcValueType>(metaParser: ZodType<MetaSchema, any, any>, valueParser: ZodType<ValueSchema, any, any>) =>
30
+ z.custom<RpcValueWithMetaData<z.infer<typeof metaParser>, z.infer<typeof valueParser>>>((data: any) => withMetaInstanceParser.and(z.object({
31
+ meta: metaParser,
32
+ value: valueParser,
33
+ })).safeParse(data).success);
34
+
11
35
  export * from 'zod';