libshv-js 3.16.1 → 3.20.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/dist/access.d.ts +69 -0
- package/dist/access.js +98 -0
- package/dist/chainpack.d.ts +39 -0
- package/{src/chainpack.ts → dist/chainpack.js} +120 -218
- package/dist/cpcontext.d.ts +18 -0
- package/{src/cpcontext.ts → dist/cpcontext.js} +18 -27
- package/dist/cpon.d.ts +55 -0
- package/{src/cpon.ts → dist/cpon.js} +192 -292
- package/dist/index.d.ts +10 -0
- package/dist/index.js +26 -0
- package/dist/json.d.ts +3 -0
- package/{src/json.ts → dist/json.js} +31 -49
- package/dist/rpcmessage.d.ts +285 -0
- package/dist/rpcmessage.js +111 -0
- package/dist/rpcvalue.d.ts +56 -0
- package/dist/rpcvalue.js +100 -0
- package/dist/vue-shv.d.ts +91 -0
- package/{src/vue-shv.ts → dist/vue-shv.js} +104 -164
- package/dist/ws-client.d.ts +232 -0
- package/dist/ws-client.js +586 -0
- package/dist/zod.d.ts +30 -0
- package/dist/zod.js +112 -0
- package/package.json +21 -13
- package/.github/workflows/check-version-bump.yml +0 -22
- package/.github/workflows/lint.yml +0 -33
- package/.github/workflows/publish.yml +0 -24
- package/.github/workflows/test.yml +0 -31
- package/eslint.config.js +0 -103
- package/examples/websocketosaurus.html +0 -44
- package/examples/websocketosaurus.js +0 -94
- package/src/access.ts +0 -103
- package/src/rpcmessage.ts +0 -91
- package/src/rpcvalue.ts +0 -147
- package/src/ws-client.ts +0 -708
- package/src/zod.ts +0 -95
- package/test.ts +0 -110
- package/test_json.ts +0 -44
- package/test_zod.ts +0 -24
- package/tsconfig.json +0 -108
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WsClient = exports.NotImplemented = exports.UserIDRequired = exports.LoginRequired = exports.Unknown = exports.MethodCallException = exports.MethodCallCancelled = exports.MethodCallTimeout = exports.ParseError = exports.InternalError = exports.InvalidParams = exports.MethodNotFound = exports.InvalidRequest = exports.DIR_EXTRA = exports.DIR_SIGNALS = exports.DIR_ACCESS = exports.DIR_RESULT = exports.DIR_PARAM = exports.DIR_FLAGS = exports.DIR_NAME = exports.DirFlags = exports.DirParamZod = exports.LsParamZod = exports.RequestHandler = void 0;
|
|
37
|
+
const access_1 = require("./access");
|
|
38
|
+
const chainpack_1 = require("./chainpack");
|
|
39
|
+
const cpon_1 = require("./cpon");
|
|
40
|
+
const rpcmessage_1 = require("./rpcmessage");
|
|
41
|
+
const rpcvalue_1 = require("./rpcvalue");
|
|
42
|
+
const z = __importStar(require("./zod"));
|
|
43
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
44
|
+
const DEFAULT_PING_INTERVAL = 30 * 1000;
|
|
45
|
+
const dataToRpcValue = (buff) => {
|
|
46
|
+
const rd = new chainpack_1.ChainPackReader(buff);
|
|
47
|
+
rd.readUIntData();
|
|
48
|
+
const proto = rd.ctx.getByte();
|
|
49
|
+
switch (proto) {
|
|
50
|
+
case chainpack_1.CHAINPACK_PROTOCOL_TYPE:
|
|
51
|
+
break;
|
|
52
|
+
case cpon_1.CPON_PROTOCOL_TYPE:
|
|
53
|
+
throw new Error('CPON protocol type is not supported anymore');
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Unsupported protocol type ${proto}`);
|
|
56
|
+
}
|
|
57
|
+
const rpcVal = rd.read();
|
|
58
|
+
return rpcVal;
|
|
59
|
+
};
|
|
60
|
+
class RequestHandler {
|
|
61
|
+
options;
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
/* nothing */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.RequestHandler = RequestHandler;
|
|
68
|
+
exports.LsParamZod = z.undefined().or(z.string());
|
|
69
|
+
exports.DirParamZod = z.undefined().or(z.boolean()).or(z.string());
|
|
70
|
+
var DirFlags;
|
|
71
|
+
(function (DirFlags) {
|
|
72
|
+
DirFlags[DirFlags["Reserved"] = 1] = "Reserved";
|
|
73
|
+
DirFlags[DirFlags["Getter"] = 2] = "Getter";
|
|
74
|
+
DirFlags[DirFlags["Setter"] = 4] = "Setter";
|
|
75
|
+
DirFlags[DirFlags["LargeResultHint"] = 8] = "LargeResultHint";
|
|
76
|
+
DirFlags[DirFlags["NotIdempotent"] = 16] = "NotIdempotent";
|
|
77
|
+
DirFlags[DirFlags["RequiresClientId"] = 32] = "RequiresClientId";
|
|
78
|
+
})(DirFlags || (exports.DirFlags = DirFlags = {}));
|
|
79
|
+
exports.DIR_NAME = 1;
|
|
80
|
+
exports.DIR_FLAGS = 2;
|
|
81
|
+
exports.DIR_PARAM = 3;
|
|
82
|
+
exports.DIR_RESULT = 4;
|
|
83
|
+
exports.DIR_ACCESS = 5;
|
|
84
|
+
exports.DIR_SIGNALS = 6;
|
|
85
|
+
exports.DIR_EXTRA = 63;
|
|
86
|
+
class RpcError extends Error {
|
|
87
|
+
err_info;
|
|
88
|
+
constructor(err_info) {
|
|
89
|
+
const errorCodeToMessage = (code) => {
|
|
90
|
+
switch (code) {
|
|
91
|
+
case rpcmessage_1.ErrorCode.InvalidRequest: return 'InvalidRequest';
|
|
92
|
+
case rpcmessage_1.ErrorCode.MethodNotFound: return 'MethodNotFound';
|
|
93
|
+
case rpcmessage_1.ErrorCode.InvalidParams: return 'InvalidParams';
|
|
94
|
+
case rpcmessage_1.ErrorCode.InternalError: return 'InternalError';
|
|
95
|
+
case rpcmessage_1.ErrorCode.ParseError: return 'ParseError';
|
|
96
|
+
case rpcmessage_1.ErrorCode.MethodCallTimeout: return 'MethodCallTimeout';
|
|
97
|
+
case rpcmessage_1.ErrorCode.MethodCallCancelled: return 'MethodCallCancelled';
|
|
98
|
+
case rpcmessage_1.ErrorCode.MethodCallException: return 'MethodCallException';
|
|
99
|
+
case rpcmessage_1.ErrorCode.Unknown: return 'Unknown';
|
|
100
|
+
case rpcmessage_1.ErrorCode.LoginRequired: return 'LoginRequired';
|
|
101
|
+
case rpcmessage_1.ErrorCode.UserIDRequired: return 'UserIDRequired';
|
|
102
|
+
case rpcmessage_1.ErrorCode.NotImplemented: return 'NotImplemented';
|
|
103
|
+
default: return 'Unknown RpcError';
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
super(err_info[rpcmessage_1.ERROR_MESSAGE] ?? `${errorCodeToMessage(err_info[rpcmessage_1.ERROR_CODE])} (${err_info[rpcmessage_1.ERROR_CODE]})`);
|
|
107
|
+
this.err_info = err_info;
|
|
108
|
+
}
|
|
109
|
+
data() {
|
|
110
|
+
return this.err_info[rpcmessage_1.ERROR_DATA];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const makeErrInfo = (errInfoOrMsg, code) => {
|
|
114
|
+
if (typeof errInfoOrMsg === 'object') {
|
|
115
|
+
return errInfoOrMsg;
|
|
116
|
+
}
|
|
117
|
+
return (0, rpcvalue_1.makeIMap)({
|
|
118
|
+
[rpcmessage_1.ERROR_CODE]: code,
|
|
119
|
+
[rpcmessage_1.ERROR_MESSAGE]: errInfoOrMsg,
|
|
120
|
+
[rpcmessage_1.ERROR_DATA]: undefined,
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
const createErrorClass = (code) => class extends RpcError {
|
|
124
|
+
constructor(errInfoOrMsg) {
|
|
125
|
+
super(makeErrInfo(errInfoOrMsg, code));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
129
|
+
exports.InvalidRequest = createErrorClass(rpcmessage_1.ErrorCode.InvalidRequest);
|
|
130
|
+
exports.MethodNotFound = createErrorClass(rpcmessage_1.ErrorCode.MethodNotFound);
|
|
131
|
+
exports.InvalidParams = createErrorClass(rpcmessage_1.ErrorCode.InvalidParams);
|
|
132
|
+
exports.InternalError = createErrorClass(rpcmessage_1.ErrorCode.InternalError);
|
|
133
|
+
exports.ParseError = createErrorClass(rpcmessage_1.ErrorCode.ParseError);
|
|
134
|
+
exports.MethodCallTimeout = createErrorClass(rpcmessage_1.ErrorCode.MethodCallTimeout);
|
|
135
|
+
exports.MethodCallCancelled = createErrorClass(rpcmessage_1.ErrorCode.MethodCallCancelled);
|
|
136
|
+
exports.MethodCallException = createErrorClass(rpcmessage_1.ErrorCode.MethodCallException);
|
|
137
|
+
exports.Unknown = createErrorClass(rpcmessage_1.ErrorCode.Unknown);
|
|
138
|
+
exports.LoginRequired = createErrorClass(rpcmessage_1.ErrorCode.LoginRequired);
|
|
139
|
+
exports.UserIDRequired = createErrorClass(rpcmessage_1.ErrorCode.UserIDRequired);
|
|
140
|
+
exports.NotImplemented = createErrorClass(rpcmessage_1.ErrorCode.NotImplemented);
|
|
141
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
142
|
+
const makeTimeout = (ms, resolve) => globalThis.setTimeout(() => {
|
|
143
|
+
resolve(new exports.MethodCallTimeout((0, rpcvalue_1.makeIMap)({
|
|
144
|
+
[rpcmessage_1.ERROR_CODE]: rpcmessage_1.ErrorCode.MethodCallTimeout,
|
|
145
|
+
[rpcmessage_1.ERROR_MESSAGE]: `Shv call timeout after: ${ms} msec.`,
|
|
146
|
+
[rpcmessage_1.ERROR_DATA]: undefined,
|
|
147
|
+
})));
|
|
148
|
+
}, ms);
|
|
149
|
+
class RpcRequestPromise extends Promise {
|
|
150
|
+
abort;
|
|
151
|
+
progressQuery;
|
|
152
|
+
constructor(executor, abort, progressQuery) {
|
|
153
|
+
super(executor);
|
|
154
|
+
this.abort = abort;
|
|
155
|
+
this.progressQuery = progressQuery;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
class WsClient {
|
|
159
|
+
requestId = 1;
|
|
160
|
+
pingTimerId = undefined;
|
|
161
|
+
rpcHandlers = [];
|
|
162
|
+
requestHandlers = new Map();
|
|
163
|
+
abortedRequests = new Set();
|
|
164
|
+
subscriptions = [];
|
|
165
|
+
websocket;
|
|
166
|
+
options;
|
|
167
|
+
timeout;
|
|
168
|
+
constructor(options) {
|
|
169
|
+
if (typeof options !== 'object') {
|
|
170
|
+
throw new TypeError('No options object supplied');
|
|
171
|
+
}
|
|
172
|
+
this.options = options;
|
|
173
|
+
this.timeout = 'timeout' in options && options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT;
|
|
174
|
+
this.websocket = new WebSocket(options.wsUri);
|
|
175
|
+
this.websocket.binaryType = 'arraybuffer';
|
|
176
|
+
const handleSignal = (signal) => {
|
|
177
|
+
for (const sub of this.subscriptions) {
|
|
178
|
+
const shvPath = signal.meta[rpcmessage_1.RPC_MESSAGE_SHV_PATH];
|
|
179
|
+
const method = signal.meta[rpcmessage_1.RPC_MESSAGE_METHOD];
|
|
180
|
+
if (shvPath.startsWith(sub.path) && method === sub.method) {
|
|
181
|
+
sub.callback(shvPath, method, signal.value[rpcmessage_1.RPC_MESSAGE_PARAMS]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
// eslint-disable-next-line complexity
|
|
186
|
+
const handleRequest = async (request) => {
|
|
187
|
+
if ('onRequest' in this.options) {
|
|
188
|
+
const requestId = request.meta[rpcmessage_1.RPC_MESSAGE_REQUEST_ID];
|
|
189
|
+
const respond = (value) => {
|
|
190
|
+
this.sendRpcMessage(new rpcvalue_1.RpcValueWithMetaData((0, rpcvalue_1.makeMetaMap)({
|
|
191
|
+
[rpcmessage_1.RPC_MESSAGE_CALLER_IDS]: request.meta[rpcmessage_1.RPC_MESSAGE_CALLER_IDS],
|
|
192
|
+
[rpcmessage_1.RPC_MESSAGE_REQUEST_ID]: requestId,
|
|
193
|
+
}), value));
|
|
194
|
+
};
|
|
195
|
+
const sendDelay = (progress) => {
|
|
196
|
+
respond((0, rpcvalue_1.makeIMap)({
|
|
197
|
+
[rpcmessage_1.RPC_MESSAGE_DELAY]: new rpcvalue_1.Double(progress),
|
|
198
|
+
}));
|
|
199
|
+
};
|
|
200
|
+
if (rpcmessage_1.RPC_MESSAGE_ABORT in request.value) {
|
|
201
|
+
const requestHandler = this.requestHandlers.get(requestId);
|
|
202
|
+
if (request.value[rpcmessage_1.RPC_MESSAGE_ABORT] === true) {
|
|
203
|
+
this.abortedRequests.add(requestId);
|
|
204
|
+
if (requestHandler?.options.onAbort !== undefined) {
|
|
205
|
+
requestHandler.options.onAbort();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (request.value[rpcmessage_1.RPC_MESSAGE_ABORT] === false && requestHandler?.options.onProgressQuery !== undefined) {
|
|
209
|
+
const progress = requestHandler.options.onProgressQuery();
|
|
210
|
+
sendDelay(progress instanceof Promise ? await progress : progress);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const shvPath = request.meta[rpcmessage_1.RPC_MESSAGE_SHV_PATH];
|
|
216
|
+
const methodName = request.meta[rpcmessage_1.RPC_MESSAGE_METHOD];
|
|
217
|
+
const unhandledMethod = () => {
|
|
218
|
+
throw new exports.MethodNotFound(`No such path '${shvPath}' or method '${methodName}' or insufficient access rights.`);
|
|
219
|
+
};
|
|
220
|
+
const accessLevel = request.meta[rpcmessage_1.RPC_MESSAGE_ACCESS_LEVEL];
|
|
221
|
+
if (accessLevel === undefined) {
|
|
222
|
+
unhandledMethod();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const methods = this.options.onRequest(shvPath);
|
|
226
|
+
if (methods === undefined) {
|
|
227
|
+
unhandledMethod();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const methodDefinition = (() => {
|
|
231
|
+
const dirEntry = (0, rpcvalue_1.makeIMap)({
|
|
232
|
+
[exports.DIR_NAME]: 'dir',
|
|
233
|
+
[exports.DIR_FLAGS]: 0,
|
|
234
|
+
[exports.DIR_PARAM]: 'DirParam',
|
|
235
|
+
[exports.DIR_RESULT]: 'DirResult',
|
|
236
|
+
[exports.DIR_ACCESS]: (0, access_1.accessLevelFromAccessString)('bws'),
|
|
237
|
+
[exports.DIR_SIGNALS]: (0, rpcvalue_1.makeMap)({}),
|
|
238
|
+
[exports.DIR_EXTRA]: (0, rpcvalue_1.makeMap)({}),
|
|
239
|
+
});
|
|
240
|
+
const lsEntry = (0, rpcvalue_1.makeIMap)({
|
|
241
|
+
[exports.DIR_NAME]: 'ls',
|
|
242
|
+
[exports.DIR_FLAGS]: 0,
|
|
243
|
+
[exports.DIR_PARAM]: 'LsParam',
|
|
244
|
+
[exports.DIR_RESULT]: 'LsResult',
|
|
245
|
+
[exports.DIR_ACCESS]: (0, access_1.accessLevelFromAccessString)('bws'),
|
|
246
|
+
[exports.DIR_SIGNALS]: (0, rpcvalue_1.makeMap)({}),
|
|
247
|
+
[exports.DIR_EXTRA]: (0, rpcvalue_1.makeMap)({}),
|
|
248
|
+
});
|
|
249
|
+
switch (methodName) {
|
|
250
|
+
case 'dir':
|
|
251
|
+
return {
|
|
252
|
+
entry: dirEntry,
|
|
253
|
+
paramParser: exports.DirParamZod,
|
|
254
|
+
handler(_shvPath, _method, param) {
|
|
255
|
+
const methodsResult = [dirEntry, lsEntry, ...methods.dirEntries.map(method => method.entry)];
|
|
256
|
+
switch (true) {
|
|
257
|
+
case typeof param === 'string':
|
|
258
|
+
return methodsResult.find(method => method[exports.DIR_NAME] === param) ?? false;
|
|
259
|
+
case typeof param === 'boolean':
|
|
260
|
+
case param === undefined:
|
|
261
|
+
console.log('methodsResult', '=', methodsResult);
|
|
262
|
+
return methodsResult;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
case 'ls':
|
|
267
|
+
return {
|
|
268
|
+
entry: lsEntry,
|
|
269
|
+
paramParser: exports.LsParamZod,
|
|
270
|
+
handler(shvPath) {
|
|
271
|
+
return methods.ls(shvPath);
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
default:
|
|
275
|
+
return methods.dirEntries.find(method => method.entry[exports.DIR_NAME] === methodName);
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
if (methodDefinition === undefined) {
|
|
279
|
+
unhandledMethod();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (accessLevel < methodDefinition.entry[exports.DIR_ACCESS]) {
|
|
283
|
+
unhandledMethod();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const requestParam = request.value[rpcmessage_1.RPC_MESSAGE_PARAMS];
|
|
287
|
+
const parsedParam = methodDefinition.paramParser.safeParse(requestParam);
|
|
288
|
+
if (!parsedParam.success) {
|
|
289
|
+
throw new exports.InvalidParams(`Unexpected param for method ${methodName} on path '${shvPath}': ${(0, cpon_1.toCpon)(requestParam)}`);
|
|
290
|
+
}
|
|
291
|
+
let result = methodDefinition.handler(shvPath, methodName, parsedParam.data, sendDelay);
|
|
292
|
+
if (result instanceof RequestHandler) {
|
|
293
|
+
this.requestHandlers.set(requestId, result);
|
|
294
|
+
result = result.options.result;
|
|
295
|
+
}
|
|
296
|
+
const resultOrError = (0, rpcvalue_1.makeIMap)({
|
|
297
|
+
[rpcmessage_1.RPC_MESSAGE_RESULT]: result instanceof Promise ? await result : result,
|
|
298
|
+
});
|
|
299
|
+
if (!this.abortedRequests.has(requestId)) {
|
|
300
|
+
respond(resultOrError);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.abortedRequests.delete(requestId);
|
|
304
|
+
}
|
|
305
|
+
if (requestId in this.requestHandlers) {
|
|
306
|
+
this.requestHandlers.delete(requestId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
const sendError = (error) => {
|
|
311
|
+
respond((0, rpcvalue_1.makeIMap)({
|
|
312
|
+
[rpcmessage_1.RPC_MESSAGE_ERROR]: error.err_info,
|
|
313
|
+
}));
|
|
314
|
+
};
|
|
315
|
+
switch (true) {
|
|
316
|
+
case error instanceof RpcError:
|
|
317
|
+
sendError(error);
|
|
318
|
+
break;
|
|
319
|
+
case error instanceof Error:
|
|
320
|
+
sendError(new exports.InternalError(error.message));
|
|
321
|
+
break;
|
|
322
|
+
default:
|
|
323
|
+
sendError(new exports.InternalError('Unknown error'));
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const handleResponse = (response) => {
|
|
330
|
+
const requestId = response.meta[rpcmessage_1.RPC_MESSAGE_REQUEST_ID];
|
|
331
|
+
if (this.rpcHandlers[Number(requestId)] !== undefined) {
|
|
332
|
+
const handler = this.rpcHandlers[Number(requestId)];
|
|
333
|
+
clearTimeout(handler.timeout_handle);
|
|
334
|
+
if (rpcmessage_1.RPC_MESSAGE_DELAY in response.value) {
|
|
335
|
+
if (handler.delayCallback !== undefined) {
|
|
336
|
+
handler.delayCallback(response.value[rpcmessage_1.RPC_MESSAGE_DELAY].value);
|
|
337
|
+
}
|
|
338
|
+
handler.timeout_handle = makeTimeout(this.timeout, handler.resolve);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
handler.resolve((() => {
|
|
342
|
+
if (rpcmessage_1.RPC_MESSAGE_ERROR in response.value) {
|
|
343
|
+
const code = response.value[rpcmessage_1.RPC_MESSAGE_ERROR][rpcmessage_1.ERROR_CODE];
|
|
344
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
345
|
+
const ErrorTypeCtor = (() => {
|
|
346
|
+
switch (code) {
|
|
347
|
+
case rpcmessage_1.ErrorCode.InvalidRequest: return exports.InvalidRequest;
|
|
348
|
+
case rpcmessage_1.ErrorCode.MethodNotFound: return exports.MethodNotFound;
|
|
349
|
+
case rpcmessage_1.ErrorCode.InvalidParams: return exports.InvalidParams;
|
|
350
|
+
case rpcmessage_1.ErrorCode.InternalError: return exports.InternalError;
|
|
351
|
+
case rpcmessage_1.ErrorCode.ParseError: return exports.ParseError;
|
|
352
|
+
case rpcmessage_1.ErrorCode.MethodCallTimeout: return exports.MethodCallTimeout;
|
|
353
|
+
case rpcmessage_1.ErrorCode.MethodCallCancelled: return exports.MethodCallCancelled;
|
|
354
|
+
case rpcmessage_1.ErrorCode.MethodCallException: return exports.MethodCallException;
|
|
355
|
+
case rpcmessage_1.ErrorCode.Unknown: return exports.Unknown;
|
|
356
|
+
case rpcmessage_1.ErrorCode.LoginRequired: return exports.LoginRequired;
|
|
357
|
+
case rpcmessage_1.ErrorCode.UserIDRequired: return exports.UserIDRequired;
|
|
358
|
+
case rpcmessage_1.ErrorCode.NotImplemented: return exports.NotImplemented;
|
|
359
|
+
default: return exports.Unknown;
|
|
360
|
+
}
|
|
361
|
+
})();
|
|
362
|
+
return new ErrorTypeCtor(response.value[rpcmessage_1.RPC_MESSAGE_ERROR]);
|
|
363
|
+
}
|
|
364
|
+
if (rpcmessage_1.RPC_MESSAGE_RESULT in response.value) {
|
|
365
|
+
return response.value[rpcmessage_1.RPC_MESSAGE_RESULT];
|
|
366
|
+
}
|
|
367
|
+
// At this point, the RpcResponse either has:
|
|
368
|
+
// - no keys, which means that the response has an implicit Null result.
|
|
369
|
+
// - has some unknown keys, in which case we will produce an error for the client.
|
|
370
|
+
// https://silicon-heaven.github.io/shv-doc/rpcmessage.html#response
|
|
371
|
+
const rpcMessageKeys = Object.keys(response.value);
|
|
372
|
+
if (rpcMessageKeys.length !== 0) {
|
|
373
|
+
return new exports.NotImplemented(`Got an RpcResponse with unsupported keys: ${rpcMessageKeys.join(' ')}`);
|
|
374
|
+
}
|
|
375
|
+
})());
|
|
376
|
+
// eslint-disable-next-line @typescript-eslint/no-array-delete
|
|
377
|
+
delete this.rpcHandlers[Number(requestId)];
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
this.websocket.addEventListener('message', async (evt) => {
|
|
381
|
+
const rpcVal = dataToRpcValue(evt.data);
|
|
382
|
+
const rpcMsg = rpcmessage_1.RpcMessageZod.parse(rpcVal);
|
|
383
|
+
this.logDebug(`message received: ${(0, cpon_1.toCpon)(rpcMsg)}`);
|
|
384
|
+
if ((0, rpcmessage_1.isSignal)(rpcMsg)) {
|
|
385
|
+
handleSignal(rpcMsg);
|
|
386
|
+
}
|
|
387
|
+
else if ((0, rpcmessage_1.isRequest)(rpcMsg)) {
|
|
388
|
+
await handleRequest(rpcMsg);
|
|
389
|
+
}
|
|
390
|
+
else if ((0, rpcmessage_1.isResponse)(rpcMsg)) {
|
|
391
|
+
handleResponse(rpcMsg);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
this.websocket.addEventListener('error', evt => {
|
|
395
|
+
console.log('WebSocket ERROR', evt);
|
|
396
|
+
this.logDebug('WebSocket ERROR');
|
|
397
|
+
});
|
|
398
|
+
if ('onWorkflows' in this.options) {
|
|
399
|
+
this.workflowsProcedure(this.options);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.loginProcedure(this.options);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
callRpcMethod(shvPath, method, params, delayCallback) {
|
|
406
|
+
const rqId = this.requestId++;
|
|
407
|
+
const makeRq = (value) => new rpcvalue_1.RpcValueWithMetaData((0, rpcvalue_1.makeMetaMap)({
|
|
408
|
+
[rpcmessage_1.RPC_MESSAGE_CALLER_IDS]: undefined,
|
|
409
|
+
[rpcmessage_1.RPC_MESSAGE_REQUEST_ID]: rqId,
|
|
410
|
+
[rpcmessage_1.RPC_MESSAGE_METHOD]: method ?? '',
|
|
411
|
+
[rpcmessage_1.RPC_MESSAGE_SHV_PATH]: shvPath ?? '',
|
|
412
|
+
}), value);
|
|
413
|
+
const rq = makeRq((0, rpcvalue_1.makeIMap)({
|
|
414
|
+
[rpcmessage_1.RPC_MESSAGE_PARAMS]: params,
|
|
415
|
+
}));
|
|
416
|
+
this.sendRpcMessage(rq);
|
|
417
|
+
const promise = new RpcRequestPromise(resolve => {
|
|
418
|
+
this.rpcHandlers[rqId] = { resolve, timeout_handle: makeTimeout(this.timeout, resolve), delayCallback };
|
|
419
|
+
}, () => {
|
|
420
|
+
this.sendRpcMessage(makeRq((0, rpcvalue_1.makeIMap)({
|
|
421
|
+
[rpcmessage_1.RPC_MESSAGE_ABORT]: true,
|
|
422
|
+
})));
|
|
423
|
+
}, () => {
|
|
424
|
+
this.sendRpcMessage(makeRq((0, rpcvalue_1.makeIMap)({
|
|
425
|
+
[rpcmessage_1.RPC_MESSAGE_ABORT]: false,
|
|
426
|
+
})));
|
|
427
|
+
});
|
|
428
|
+
return promise;
|
|
429
|
+
}
|
|
430
|
+
sendRpcMessage(rpcMsg) {
|
|
431
|
+
if (this.websocket.readyState === WebSocket.OPEN) {
|
|
432
|
+
this.logDebug('sending rpc message:', (0, cpon_1.toCpon)(rpcMsg));
|
|
433
|
+
const msgData = new Uint8Array((0, chainpack_1.toChainPack)(rpcMsg));
|
|
434
|
+
const wr = new chainpack_1.ChainPackWriter();
|
|
435
|
+
wr.writeUIntData(msgData.length + 1);
|
|
436
|
+
const dgram = new Uint8Array(wr.ctx.length + 1 + msgData.length);
|
|
437
|
+
let ix = 0;
|
|
438
|
+
for (let i = 0; i < wr.ctx.length; i++) {
|
|
439
|
+
dgram[ix++] = wr.ctx.data[i];
|
|
440
|
+
}
|
|
441
|
+
dgram[ix++] = chainpack_1.CHAINPACK_PROTOCOL_TYPE;
|
|
442
|
+
for (const msgDatum of msgData) {
|
|
443
|
+
dgram[ix++] = msgDatum;
|
|
444
|
+
}
|
|
445
|
+
this.websocket.send(dgram.buffer);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
subscribe(subscriber, path, method, callback) {
|
|
449
|
+
if (this.subscriptions.some(val => val.subscriber === subscriber && val.path === path && val.method === method)) {
|
|
450
|
+
this.logDebug(`Already subscribed {$path}:${method} for subscriber ${subscriber}`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
// If this path:method has not been subscribed on the broker, do it now
|
|
454
|
+
if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
|
|
455
|
+
this.callRpcMethod('.broker/app', 'subscribe', (0, rpcvalue_1.makeMap)({
|
|
456
|
+
method, path,
|
|
457
|
+
})).catch(() => {
|
|
458
|
+
this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
this.subscriptions.push({
|
|
462
|
+
subscriber,
|
|
463
|
+
path,
|
|
464
|
+
method,
|
|
465
|
+
callback,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
unsubscribe(subscriber, path, method) {
|
|
469
|
+
const idx = this.subscriptions.findIndex(val => val.subscriber === subscriber && val.path === path && val.method === method);
|
|
470
|
+
if (idx === -1) {
|
|
471
|
+
this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
this.subscriptions.splice(idx, 1);
|
|
475
|
+
// Unsubscribe on the broker only if there are no other subscriptions of this path:method
|
|
476
|
+
if (this.subscriptions.some(val => val.path === path && val.method === method)) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this.callRpcMethod('.broker/app', 'unsubscribe', (0, rpcvalue_1.makeMap)({
|
|
480
|
+
method, path,
|
|
481
|
+
})).catch(() => {
|
|
482
|
+
this.logDebug(`Couldn't unsubscribe ${path}, ${method}`);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
sendPing() {
|
|
486
|
+
this.callRpcMethod('.broker/app', 'ping').catch((error) => {
|
|
487
|
+
console.log('Failed to send ping:', error);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
accessGrantForMethodCall(path, method) {
|
|
491
|
+
return this.callRpcMethod('.broker/currentClient', 'accessGrantForMethodCall', [path, method]);
|
|
492
|
+
}
|
|
493
|
+
close() {
|
|
494
|
+
this.websocket.close();
|
|
495
|
+
}
|
|
496
|
+
logDebug(...args) {
|
|
497
|
+
if (this.options.logDebug === undefined) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
this.options.logDebug(...args);
|
|
501
|
+
}
|
|
502
|
+
workflowsProcedure(options) {
|
|
503
|
+
let workflowsObtained = false;
|
|
504
|
+
this.websocket.addEventListener('open', () => {
|
|
505
|
+
this.options.logDebug('CONNECTED');
|
|
506
|
+
const handleConnectionError = (error) => {
|
|
507
|
+
this.logDebug('FAILURE: couldn\'t retrieve workflows', error.message);
|
|
508
|
+
options.onWorkflowsFailed();
|
|
509
|
+
};
|
|
510
|
+
this.callRpcMethod(undefined, 'workflows').then(response => {
|
|
511
|
+
if (response instanceof Error) {
|
|
512
|
+
handleConnectionError(response);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
workflowsObtained = true;
|
|
516
|
+
options.onWorkflows(response);
|
|
517
|
+
this.close();
|
|
518
|
+
}).catch(() => {
|
|
519
|
+
options.onWorkflowsFailed();
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
this.websocket.addEventListener('close', () => {
|
|
523
|
+
this.logDebug('DISCONNECTED');
|
|
524
|
+
if (!workflowsObtained) {
|
|
525
|
+
options.onWorkflowsFailed();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
loginProcedure(options) {
|
|
530
|
+
this.websocket.addEventListener('open', () => {
|
|
531
|
+
this.options.logDebug('CONNECTED');
|
|
532
|
+
const handleConnectionError = (error) => {
|
|
533
|
+
this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
|
|
534
|
+
options.onConnectionFailure(error);
|
|
535
|
+
};
|
|
536
|
+
const handleLoginResponse = (response) => {
|
|
537
|
+
if (response instanceof Error) {
|
|
538
|
+
handleConnectionError(response);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
this.logDebug('SUCCESS: connected to shv broker');
|
|
542
|
+
options.onConnected();
|
|
543
|
+
this.pingTimerId = globalThis.setInterval(() => {
|
|
544
|
+
this.sendPing();
|
|
545
|
+
}, options.pingInterval ?? DEFAULT_PING_INTERVAL);
|
|
546
|
+
};
|
|
547
|
+
const makeLoginParams = (loginMap) => (0, rpcvalue_1.makeMap)({
|
|
548
|
+
login: loginMap,
|
|
549
|
+
options: (0, rpcvalue_1.makeMap)({
|
|
550
|
+
device: typeof options.mountPoint === 'string' ? (0, rpcvalue_1.makeMap)({ mountPoint: options.mountPoint }) : undefined,
|
|
551
|
+
}),
|
|
552
|
+
});
|
|
553
|
+
this.callRpcMethod(undefined, 'hello').then(response => {
|
|
554
|
+
if (response instanceof Error) {
|
|
555
|
+
handleConnectionError(response);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const makeLoginMap = () => {
|
|
559
|
+
switch (options.login.type) {
|
|
560
|
+
case 'PLAIN':
|
|
561
|
+
return (0, rpcvalue_1.makeMap)({
|
|
562
|
+
password: options.login.password,
|
|
563
|
+
type: options.login.type,
|
|
564
|
+
user: options.login.user,
|
|
565
|
+
});
|
|
566
|
+
case 'TOKEN':
|
|
567
|
+
return (0, rpcvalue_1.makeMap)({
|
|
568
|
+
token: options.login.token,
|
|
569
|
+
type: options.login.type,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
return this.callRpcMethod(undefined, 'login', makeLoginParams(makeLoginMap()));
|
|
574
|
+
}).then(handleLoginResponse).catch(() => {
|
|
575
|
+
this.logDebug('FAILURE: couldn\' connected to shv broker');
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
this.websocket.addEventListener('close', () => {
|
|
579
|
+
this.logDebug('DISCONNECTED');
|
|
580
|
+
this.subscriptions.length = 0;
|
|
581
|
+
options.onDisconnected();
|
|
582
|
+
globalThis.clearInterval(this.pingTimerId);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
exports.WsClient = WsClient;
|
package/dist/zod.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z, type ZodType } from 'zod/v4';
|
|
2
|
+
import { Decimal, Double, type MetaMap, RpcValue, type RpcValueType, RpcValueWithMetaData, shvMapType, UInt } from './rpcvalue';
|
|
3
|
+
export declare const map: <T extends Record<string, ZodType<any>>>(schema: T) => z.ZodCustom<z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
4
|
+
[shvMapType]: "map";
|
|
5
|
+
}, z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
6
|
+
[shvMapType]: "map";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const imap: <T extends Record<number, ZodType<any>>>(schema: T) => z.ZodCustom<z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
9
|
+
[shvMapType]: "imap";
|
|
10
|
+
}, z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
11
|
+
[shvMapType]: "imap";
|
|
12
|
+
}>;
|
|
13
|
+
export declare const metamap: <T extends Record<string | number, ZodType<any>>>(schema: T) => z.ZodCustom<z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
14
|
+
[shvMapType]: "metamap";
|
|
15
|
+
}, z.core.$InferObjectOutput<{ -readonly [P in keyof T]: T[P]; }, {}> & {
|
|
16
|
+
[shvMapType]: "metamap";
|
|
17
|
+
}>;
|
|
18
|
+
export declare const recmap: <T extends ZodType<any>>(schema: T) => z.ZodCustom<Record<any, z.core.output<T>> & {
|
|
19
|
+
[shvMapType]: "map";
|
|
20
|
+
}, Record<any, z.core.output<T>> & {
|
|
21
|
+
[shvMapType]: "map";
|
|
22
|
+
}>;
|
|
23
|
+
export declare const uint: () => z.ZodCustom<UInt<number>, UInt<number>>;
|
|
24
|
+
export declare const double: () => z.ZodCustom<Double, Double>;
|
|
25
|
+
export declare const decimal: () => z.ZodCustom<Decimal, Decimal>;
|
|
26
|
+
export declare const blob: () => z.ZodCustom<ArrayBuffer, ArrayBuffer>;
|
|
27
|
+
export declare const list: () => z.ZodArray<z.ZodLazy<z.ZodType<RpcValue>>>;
|
|
28
|
+
export declare const rpcvalue: () => ZodType<RpcValue>;
|
|
29
|
+
export declare const withMeta: <MetaSchema extends MetaMap, ValueSchema extends RpcValueType>(metaParser: ZodType<MetaSchema>, valueParser: ZodType<ValueSchema>) => z.ZodCustom<RpcValueWithMetaData<MetaSchema, ValueSchema>, RpcValueWithMetaData<MetaSchema, ValueSchema>>;
|
|
30
|
+
export * from 'zod/v4';
|