libshv-js 3.3.6 → 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/.github/workflows/check-version-bump.yml +22 -0
- package/.github/workflows/lint.yml +2 -2
- package/.xo-config.json +22 -14
- package/package.json +3 -3
- package/src/chainpack.ts +124 -76
- package/src/cpcontext.ts +9 -4
- package/src/cpon.ts +145 -72
- package/src/rpcmessage.ts +57 -176
- package/src/rpcvalue.ts +11 -8
- package/src/ws-client.ts +114 -51
- package/src/zod.ts +27 -3
- package/test.ts +2 -0
package/src/rpcmessage.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {toCpon} from './cpon';
|
|
3
|
-
import {toChainPack} from './chainpack';
|
|
1
|
+
import * as z from './zod';
|
|
4
2
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
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
|
-
|
|
34
|
-
[ERROR_CODE]:
|
|
35
|
-
[ERROR_MESSAGE]
|
|
36
|
-
[ERROR_DATA]
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
return
|
|
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:
|
|
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
|
|
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
|
|
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,7 +1,7 @@
|
|
|
1
|
-
import {ChainPackReader,
|
|
2
|
-
import {type CponReader,
|
|
3
|
-
import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessage, type
|
|
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
7
|
const DEFAULT_PING_INTERVAL = 30 * 1000;
|
|
@@ -11,20 +11,23 @@ const dataToRpcValue = (buff: ArrayBuffer) => {
|
|
|
11
11
|
rd.readUIntData();
|
|
12
12
|
const proto = rd.ctx.getByte();
|
|
13
13
|
switch (proto) {
|
|
14
|
-
case
|
|
14
|
+
case CHAINPACK_PROTOCOL_TYPE:
|
|
15
15
|
break;
|
|
16
|
-
case
|
|
16
|
+
case CPON_PROTOCOL_TYPE:
|
|
17
17
|
throw new Error('CPON protocol type is not supported anymore');
|
|
18
18
|
default:
|
|
19
19
|
throw new Error(`Unsupported protocol type ${proto}`);
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
const rpcVal = rd.read();
|
|
23
|
+
return rpcVal;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
type SubscriptionCallback = (path: string, method: string, param?: RpcValue) => void;
|
|
26
27
|
|
|
27
|
-
type
|
|
28
|
+
type ResultOrError<T = RpcValue> = T | Error;
|
|
29
|
+
|
|
30
|
+
type RpcResponseResolver = (rpc_msg: ResultOrError) => void;
|
|
28
31
|
|
|
29
32
|
type Subscription = {
|
|
30
33
|
subscriber: string;
|
|
@@ -45,7 +48,7 @@ type WsClientOptions = {
|
|
|
45
48
|
onConnected: () => void;
|
|
46
49
|
onConnectionFailure: (error: Error) => void;
|
|
47
50
|
onDisconnected: () => void;
|
|
48
|
-
onRequest: (rpc_msg:
|
|
51
|
+
onRequest: (rpc_msg: RpcRequest) => void;
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
type LsResult = string[];
|
|
@@ -74,6 +77,31 @@ type DirResult = Array<IMap<{
|
|
|
74
77
|
[DIR_EXTRA]: ShvMap;
|
|
75
78
|
}>>;
|
|
76
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
|
+
|
|
77
105
|
class WsClient {
|
|
78
106
|
requestId = 1;
|
|
79
107
|
pingTimerId = -1;
|
|
@@ -127,11 +155,13 @@ class WsClient {
|
|
|
127
155
|
this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
|
|
128
156
|
this.onConnectionFailure(error);
|
|
129
157
|
};
|
|
158
|
+
|
|
130
159
|
this.callRpcMethod(undefined, 'hello').then(response => {
|
|
131
160
|
if (response instanceof Error) {
|
|
132
161
|
handleConnectionError(response);
|
|
133
162
|
return;
|
|
134
163
|
}
|
|
164
|
+
|
|
135
165
|
const params = makeMap({
|
|
136
166
|
login: makeMap({
|
|
137
167
|
password: this.password,
|
|
@@ -148,6 +178,7 @@ class WsClient {
|
|
|
148
178
|
handleConnectionError(response);
|
|
149
179
|
return;
|
|
150
180
|
}
|
|
181
|
+
|
|
151
182
|
this.logDebug('SUCCESS: connected to shv broker');
|
|
152
183
|
this.onConnected();
|
|
153
184
|
this.pingTimerId = window.setInterval(() => {
|
|
@@ -165,32 +196,58 @@ class WsClient {
|
|
|
165
196
|
window.clearInterval(this.pingTimerId);
|
|
166
197
|
});
|
|
167
198
|
|
|
168
|
-
this.websocket.addEventListener('message', evt => {
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
this.logDebug(`message received: ${
|
|
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)}`);
|
|
172
203
|
|
|
173
|
-
if (
|
|
204
|
+
if (isSignal(rpcMsg)) {
|
|
174
205
|
for (const sub of this.subscriptions) {
|
|
175
|
-
const
|
|
176
|
-
const method =
|
|
206
|
+
const shvPath = rpcMsg.meta[RPC_MESSAGE_SHV_PATH];
|
|
207
|
+
const method = rpcMsg.meta[RPC_MESSAGE_METHOD];
|
|
177
208
|
|
|
178
|
-
if (
|
|
179
|
-
sub.callback(
|
|
209
|
+
if (shvPath.startsWith(sub.path) && method === sub.method) {
|
|
210
|
+
sub.callback(shvPath, method, rpcMsg.value[RPC_MESSAGE_PARAMS]);
|
|
180
211
|
}
|
|
181
212
|
}
|
|
182
|
-
} else if (
|
|
183
|
-
this.onRequest(
|
|
184
|
-
} else if (
|
|
185
|
-
const requestId =
|
|
186
|
-
if (requestId === undefined) {
|
|
187
|
-
throw new Error('got RpcResponse without requestId');
|
|
188
|
-
}
|
|
213
|
+
} else if (isRequest(rpcMsg)) {
|
|
214
|
+
this.onRequest(rpcMsg);
|
|
215
|
+
} else if (isResponse(rpcMsg)) {
|
|
216
|
+
const requestId = rpcMsg.meta[RPC_MESSAGE_REQUEST_ID];
|
|
189
217
|
|
|
190
218
|
if (this.rpcHandlers[Number(requestId)] !== undefined) {
|
|
191
219
|
const handler = this.rpcHandlers[Number(requestId)];
|
|
192
220
|
clearTimeout(handler.timeout_handle);
|
|
193
|
-
handler.resolve(
|
|
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
|
+
})());
|
|
194
251
|
// eslint-disable-next-line @typescript-eslint/no-array-delete, @typescript-eslint/no-dynamic-delete
|
|
195
252
|
delete this.rpcHandlers[Number(requestId)];
|
|
196
253
|
}
|
|
@@ -203,28 +260,29 @@ class WsClient {
|
|
|
203
260
|
});
|
|
204
261
|
}
|
|
205
262
|
|
|
206
|
-
callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<
|
|
207
|
-
callRpcMethod(shv_path: string | undefined, method: 'dir', params?: RpcValue): Promise<
|
|
208
|
-
callRpcMethod(shv_path: string | undefined, method: 'ls', params?: RpcValue): Promise<
|
|
209
|
-
callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<
|
|
210
|
-
callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue) {
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
|
|
221
278
|
this.sendRpcMessage(rq);
|
|
222
279
|
|
|
223
|
-
const promise = new Promise<
|
|
224
|
-
this.rpcHandlers[
|
|
280
|
+
const promise = new Promise<ResultOrError>(resolve => {
|
|
281
|
+
this.rpcHandlers[rqId] = {resolve, timeout_handle: self.setTimeout(() => {
|
|
225
282
|
resolve(new MethodCallTimeout(makeIMap({
|
|
226
283
|
[ERROR_CODE]: ErrorCode.MethodCallTimeout,
|
|
227
284
|
[ERROR_MESSAGE]: `Shv call timeout after: ${this.timeout} msec.`,
|
|
285
|
+
[ERROR_DATA]: undefined,
|
|
228
286
|
})));
|
|
229
287
|
}, this.timeout)};
|
|
230
288
|
});
|
|
@@ -234,22 +292,23 @@ class WsClient {
|
|
|
234
292
|
|
|
235
293
|
sendRpcMessage(rpc_msg: RpcMessage) {
|
|
236
294
|
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
|
237
|
-
this.logDebug('sending rpc message:',
|
|
238
|
-
const
|
|
295
|
+
this.logDebug('sending rpc message:', toCpon(rpc_msg));
|
|
296
|
+
const msgData = new Uint8Array(toChainPack(rpc_msg));
|
|
239
297
|
|
|
240
298
|
const wr = new ChainPackWriter();
|
|
241
|
-
wr.writeUIntData(
|
|
242
|
-
const dgram = new Uint8Array(wr.ctx.length + 1 +
|
|
299
|
+
wr.writeUIntData(msgData.length + 1);
|
|
300
|
+
const dgram = new Uint8Array(wr.ctx.length + 1 + msgData.length);
|
|
243
301
|
let ix = 0;
|
|
244
302
|
for (let i = 0; i < wr.ctx.length; i++) {
|
|
245
303
|
dgram[ix++] = wr.ctx.data[i];
|
|
246
304
|
}
|
|
247
305
|
|
|
248
|
-
dgram[ix++] =
|
|
306
|
+
dgram[ix++] = CHAINPACK_PROTOCOL_TYPE;
|
|
249
307
|
|
|
250
|
-
for (const
|
|
251
|
-
dgram[ix++] =
|
|
308
|
+
for (const msgDatum of msgData) {
|
|
309
|
+
dgram[ix++] = msgDatum;
|
|
252
310
|
}
|
|
311
|
+
|
|
253
312
|
this.logDebug(`sending ${dgram.length} bytes of data`);
|
|
254
313
|
this.websocket.send(dgram.buffer);
|
|
255
314
|
}
|
|
@@ -260,6 +319,7 @@ class WsClient {
|
|
|
260
319
|
this.logDebug(`Already subscribed {$path}:${method} for subscriber ${subscriber}`);
|
|
261
320
|
return;
|
|
262
321
|
}
|
|
322
|
+
|
|
263
323
|
// If this path:method has not been subscribed on the broker, do it now
|
|
264
324
|
if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
|
|
265
325
|
this.callRpcMethod('.broker/app', 'subscribe', makeMap({
|
|
@@ -268,6 +328,7 @@ class WsClient {
|
|
|
268
328
|
this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
|
|
269
329
|
});
|
|
270
330
|
}
|
|
331
|
+
|
|
271
332
|
this.subscriptions.push({
|
|
272
333
|
subscriber,
|
|
273
334
|
path,
|
|
@@ -282,11 +343,13 @@ class WsClient {
|
|
|
282
343
|
this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
|
|
283
344
|
return;
|
|
284
345
|
}
|
|
346
|
+
|
|
285
347
|
this.subscriptions.splice(idx, 1);
|
|
286
348
|
// Unsubscribe on the broker only if there are no other subscriptions of this path:method
|
|
287
349
|
if (this.subscriptions.some(val => val.path === path && val.method === method)) {
|
|
288
350
|
return;
|
|
289
351
|
}
|
|
352
|
+
|
|
290
353
|
this.callRpcMethod('.broker/app', 'unsubscribe', makeMap({
|
|
291
354
|
method, path,
|
|
292
355
|
})).catch(() => {
|
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
|
|
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
|
|
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';
|
package/test.ts
CHANGED
|
@@ -7,6 +7,7 @@ const checkEq = (e1: string | number, e2: string | number) => {
|
|
|
7
7
|
throw new Error(`test check error: ${e1} === ${e2}`);
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
+
|
|
10
11
|
for (const lst of [
|
|
11
12
|
[((2 ** 31) - 1) + 'u', null],
|
|
12
13
|
[((2 ** 32) - 1) + 'u', null], // too big for JS bitwise operations
|
|
@@ -78,6 +79,7 @@ for (const lst of [
|
|
|
78
79
|
const cpn2 = toCpon(rv2);
|
|
79
80
|
checkEq(cpn1, cpn2);
|
|
80
81
|
}
|
|
82
|
+
|
|
81
83
|
// same points in time
|
|
82
84
|
const v1 = fromCpon('d"2017-05-03T18:30:00Z"') as DateTime;
|
|
83
85
|
const v2 = fromCpon('d"2017-05-03T22:30:00+04"') as DateTime;
|