libshv-js 5.1.0 → 7.0.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/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from './cpon';
5
5
  export * from './json';
6
6
  export * from './rpcmessage';
7
7
  export * from './rpcvalue';
8
+ export * from './shv-ri';
8
9
  export * from './ws-client';
package/dist/index.js CHANGED
@@ -21,4 +21,5 @@ __exportStar(require("./cpon"), exports);
21
21
  __exportStar(require("./json"), exports);
22
22
  __exportStar(require("./rpcmessage"), exports);
23
23
  __exportStar(require("./rpcvalue"), exports);
24
+ __exportStar(require("./shv-ri"), exports);
24
25
  __exportStar(require("./ws-client"), exports);
@@ -3,6 +3,7 @@ export declare const RPC_MESSAGE_REQUEST_ID = 8;
3
3
  export declare const RPC_MESSAGE_SHV_PATH = 9;
4
4
  export declare const RPC_MESSAGE_METHOD = 10;
5
5
  export declare const RPC_MESSAGE_CALLER_IDS = 11;
6
+ export declare const RPC_MESSAGE_USER_ID = 16;
6
7
  export declare const RPC_MESSAGE_ACCESS_LEVEL = 17;
7
8
  export declare const RPC_MESSAGE_PARAMS = 1;
8
9
  export declare const RPC_MESSAGE_RESULT = 2;
@@ -36,6 +37,7 @@ type RpcRequestMeta = MetaMap<{
36
37
  [RPC_MESSAGE_SHV_PATH]: string;
37
38
  [RPC_MESSAGE_METHOD]: string;
38
39
  [RPC_MESSAGE_CALLER_IDS]?: number | number[] | undefined;
40
+ [RPC_MESSAGE_USER_ID]?: string | undefined;
39
41
  [RPC_MESSAGE_ACCESS_LEVEL]?: number | undefined;
40
42
  }>;
41
43
  type RpcRequestValue = IMap<{
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isResponse = exports.isRequest = exports.isSignal = exports.ErrorCode = exports.ERROR_DATA = exports.ERROR_MESSAGE = exports.ERROR_CODE = exports.RPC_MESSAGE_ABORT = exports.RPC_MESSAGE_DELAY = exports.RPC_MESSAGE_ERROR = exports.RPC_MESSAGE_RESULT = exports.RPC_MESSAGE_PARAMS = exports.RPC_MESSAGE_ACCESS_LEVEL = exports.RPC_MESSAGE_CALLER_IDS = exports.RPC_MESSAGE_METHOD = exports.RPC_MESSAGE_SHV_PATH = exports.RPC_MESSAGE_REQUEST_ID = void 0;
3
+ exports.isResponse = exports.isRequest = exports.isSignal = exports.ErrorCode = exports.ERROR_DATA = exports.ERROR_MESSAGE = exports.ERROR_CODE = exports.RPC_MESSAGE_ABORT = exports.RPC_MESSAGE_DELAY = exports.RPC_MESSAGE_ERROR = exports.RPC_MESSAGE_RESULT = exports.RPC_MESSAGE_PARAMS = exports.RPC_MESSAGE_ACCESS_LEVEL = exports.RPC_MESSAGE_USER_ID = exports.RPC_MESSAGE_CALLER_IDS = exports.RPC_MESSAGE_METHOD = exports.RPC_MESSAGE_SHV_PATH = exports.RPC_MESSAGE_REQUEST_ID = void 0;
4
4
  exports.RPC_MESSAGE_REQUEST_ID = 8;
5
5
  exports.RPC_MESSAGE_SHV_PATH = 9;
6
6
  exports.RPC_MESSAGE_METHOD = 10;
7
7
  exports.RPC_MESSAGE_CALLER_IDS = 11;
8
+ exports.RPC_MESSAGE_USER_ID = 16;
8
9
  exports.RPC_MESSAGE_ACCESS_LEVEL = 17;
9
10
  exports.RPC_MESSAGE_PARAMS = 1;
10
11
  exports.RPC_MESSAGE_RESULT = 2;
@@ -0,0 +1,12 @@
1
+ export declare class ShvRI {
2
+ private ri;
3
+ private methodSepIx;
4
+ private signalSepIx?;
5
+ constructor(ri: string);
6
+ path(): string;
7
+ method(): string;
8
+ signal(): string | undefined;
9
+ hasSignal(): boolean;
10
+ asString(): string;
11
+ static fromPathMethodSignal(path: string, method: string, signal?: string): ShvRI;
12
+ }
package/dist/shv-ri.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ShvRI = void 0;
4
+ class ShvRI {
5
+ ri;
6
+ methodSepIx;
7
+ signalSepIx;
8
+ constructor(ri) {
9
+ const methodSepIx = ri.indexOf(':');
10
+ if (methodSepIx === -1) {
11
+ throw new Error('Method separator ":" is missing.');
12
+ }
13
+ const signalSepRelative = ri.indexOf(':', methodSepIx + 1);
14
+ const signalSepIx = signalSepRelative !== -1 ? signalSepRelative : undefined;
15
+ this.ri = ri;
16
+ this.methodSepIx = methodSepIx;
17
+ this.signalSepIx = signalSepIx;
18
+ const method = this.method();
19
+ if (method.length === 0) {
20
+ throw new Error('Method must not be empty.');
21
+ }
22
+ const signal = this.signal();
23
+ if (signal?.length === 0) {
24
+ throw new Error('Signal, if present, must not be empty.');
25
+ }
26
+ }
27
+ path() {
28
+ return this.ri.slice(0, this.methodSepIx);
29
+ }
30
+ method() {
31
+ if (this.signalSepIx !== undefined) {
32
+ return this.ri.slice(this.methodSepIx + 1, this.signalSepIx);
33
+ }
34
+ return this.ri.slice(this.methodSepIx + 1);
35
+ }
36
+ signal() {
37
+ if (this.signalSepIx !== undefined) {
38
+ return this.ri.slice(this.signalSepIx + 1);
39
+ }
40
+ }
41
+ hasSignal() {
42
+ return this.signalSepIx !== undefined;
43
+ }
44
+ asString() {
45
+ return this.ri;
46
+ }
47
+ static fromPathMethodSignal(path, method, signal) {
48
+ const m = method.length === 0 ? '*' : method;
49
+ const ri = signal !== undefined
50
+ ? `${path}:${m}:${signal}`
51
+ : `${path}:${m}`;
52
+ return new ShvRI(ri);
53
+ }
54
+ }
55
+ exports.ShvRI = ShvRI;
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,5 @@
1
+ import { ShvRI } from './shv-ri';
1
2
  export type StringGetter = string | Promise<string> | (() => string | Promise<string>);
2
3
  export declare const resolveString: (input: StringGetter) => string | Promise<string>;
4
+ export type RIGetter = ShvRI | Promise<ShvRI> | (() => ShvRI | Promise<ShvRI>);
5
+ export declare const resolveRI: (input: RIGetter) => ShvRI | Promise<ShvRI>;
package/dist/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveString = void 0;
3
+ exports.resolveRI = exports.resolveString = void 0;
4
4
  const resolveString = (input) => {
5
5
  if (typeof input === 'function') {
6
6
  input = input();
@@ -8,3 +8,10 @@ const resolveString = (input) => {
8
8
  return input;
9
9
  };
10
10
  exports.resolveString = resolveString;
11
+ const resolveRI = (input) => {
12
+ if (typeof input === 'function') {
13
+ input = input();
14
+ }
15
+ return input;
16
+ };
17
+ exports.resolveRI = resolveRI;
@@ -1,7 +1,7 @@
1
1
  import { type RpcMessage, type ErrorMap } from './rpcmessage';
2
2
  import { type RpcValue, type Null, type Int, type IMap, type ShvMap } from './rpcvalue';
3
- import { StringGetter } from './utils';
4
- type SubscriptionCallback = (path: string, method: string, param?: RpcValue) => void;
3
+ import { RIGetter } from './utils';
4
+ type SubscriptionCallback = (path: string, signal: string, param?: RpcValue) => void;
5
5
  type ResultOrError<T = RpcValue> = T | Error;
6
6
  type Login = {
7
7
  type: 'PLAIN';
@@ -205,6 +205,14 @@ declare class RpcRequestPromise<Result> extends Promise<Result> {
205
205
  readonly progressQuery: () => void;
206
206
  constructor(executor: (resolve: (value: Result | PromiseLike<Result>) => void, reject: (reason?: unknown) => void) => void, abort: () => void, progressQuery: () => void);
207
207
  }
208
+ export type CallRpcMethodOptions = {
209
+ delayCallback?: (progress: number) => void;
210
+ requestUserId?: boolean;
211
+ };
212
+ declare enum ShvApiVersion {
213
+ V2 = 0,
214
+ V3 = 1
215
+ }
208
216
  declare class WsClient {
209
217
  private requestId;
210
218
  private pingTimerId;
@@ -215,15 +223,17 @@ declare class WsClient {
215
223
  private readonly websocket;
216
224
  private readonly options;
217
225
  private readonly timeout;
226
+ private shvApiVersion;
218
227
  constructor(options: WsClientOptions<MethodHandler>);
219
- callRpcMethod(shvPath: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string], delayCallback?: (progress: number) => void): RpcRequestPromise<ResultOrError<string>>;
220
- callRpcMethod(shvPath: '.broker/currentClient', method: 'accessLevelForMethodCall', params: [string, string], delayCallback?: (progress: number) => void): RpcRequestPromise<ResultOrError<number>>;
221
- callRpcMethod(shvPath: string | undefined, method: 'dir', params?: RpcValue, delayCallback?: (progress: number) => void): RpcRequestPromise<ResultOrError<DirResult>>;
222
- callRpcMethod(shvPath: string | undefined, method: 'ls', params?: RpcValue, delayCallback?: (progress: number) => void): RpcRequestPromise<ResultOrError<LsResult>>;
223
- callRpcMethod(shvPath: string | undefined, method: string, params?: RpcValue, delayCallback?: (progress: number) => void): RpcRequestPromise<ResultOrError>;
228
+ callRpcMethod(shvPath: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string], options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<string>>;
229
+ callRpcMethod(shvPath: '.broker/currentClient', method: 'accessLevelForMethodCall', params: [string, string], options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<number>>;
230
+ callRpcMethod(shvPath: string | undefined, method: 'dir', params?: RpcValue, options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<DirResult>>;
231
+ callRpcMethod(shvPath: string | undefined, method: 'ls', params?: RpcValue, options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<LsResult>>;
232
+ callRpcMethod(shvPath: string | undefined, method: string, params?: RpcValue, options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError>;
224
233
  sendRpcMessage(rpcMsg: RpcMessage): void;
225
- subscribe(subscriber: string, pathGetter: StringGetter, method: string, callback: SubscriptionCallback): Promise<void>;
226
- unsubscribe(subscriber: string, pathGetter: StringGetter, method: string): Promise<void>;
234
+ getShvApiVersion(): Promise<ShvApiVersion>;
235
+ subscribe(subscriber: string, riGetter: RIGetter, callback: SubscriptionCallback): Promise<void>;
236
+ unsubscribe(subscriber: string, riGetter: RIGetter): Promise<void>;
227
237
  sendPing(): void;
228
238
  accessGrantForMethodCall(path: string, method: string): RpcRequestPromise<ResultOrError<string>>;
229
239
  accessLevelForMethodCall(path: string, method: string): RpcRequestPromise<ResultOrError<number>>;
package/dist/ws-client.js CHANGED
@@ -129,6 +129,11 @@ class RpcRequestPromise extends Promise {
129
129
  this.progressQuery = progressQuery;
130
130
  }
131
131
  }
132
+ var ShvApiVersion;
133
+ (function (ShvApiVersion) {
134
+ ShvApiVersion[ShvApiVersion["V2"] = 0] = "V2";
135
+ ShvApiVersion[ShvApiVersion["V3"] = 1] = "V3";
136
+ })(ShvApiVersion || (ShvApiVersion = {}));
132
137
  class WsClient {
133
138
  requestId = 1;
134
139
  pingTimerId = undefined;
@@ -139,6 +144,7 @@ class WsClient {
139
144
  websocket;
140
145
  options;
141
146
  timeout;
147
+ shvApiVersion = undefined;
142
148
  constructor(options) {
143
149
  if (typeof options !== 'object') {
144
150
  throw new TypeError('No options object supplied');
@@ -150,9 +156,9 @@ class WsClient {
150
156
  const handleSignal = (signal) => {
151
157
  for (const sub of this.subscriptions) {
152
158
  const shvPath = signal.meta[rpcmessage_1.RPC_MESSAGE_SHV_PATH];
153
- const method = signal.meta[rpcmessage_1.RPC_MESSAGE_METHOD];
154
- if (shvPath.startsWith(sub.path) && method === sub.method) {
155
- sub.callback(shvPath, method, signal.value[rpcmessage_1.RPC_MESSAGE_PARAMS]);
159
+ const signalName = signal.meta[rpcmessage_1.RPC_MESSAGE_METHOD];
160
+ if (shvPath.startsWith(sub.path) && signalName === sub.signal) {
161
+ sub.callback(shvPath, signalName, signal.value[rpcmessage_1.RPC_MESSAGE_PARAMS]);
156
162
  }
157
163
  }
158
164
  };
@@ -376,20 +382,21 @@ class WsClient {
376
382
  this.loginProcedure(this.options);
377
383
  }
378
384
  }
379
- callRpcMethod(shvPath, method, params, delayCallback) {
385
+ callRpcMethod(shvPath, method, params, options) {
380
386
  const rqId = this.requestId++;
381
387
  const makeRq = (value) => new rpcvalue_1.RpcValueWithMetaData((0, rpcvalue_1.makeMetaMap)({
382
388
  [rpcmessage_1.RPC_MESSAGE_CALLER_IDS]: undefined,
383
389
  [rpcmessage_1.RPC_MESSAGE_REQUEST_ID]: rqId,
384
390
  [rpcmessage_1.RPC_MESSAGE_METHOD]: method ?? '',
385
391
  [rpcmessage_1.RPC_MESSAGE_SHV_PATH]: shvPath ?? '',
392
+ ...(options?.requestUserId === true ? { [rpcmessage_1.RPC_MESSAGE_USER_ID]: '' } : {}),
386
393
  }), value);
387
394
  const rq = makeRq((0, rpcvalue_1.makeIMap)({
388
395
  [rpcmessage_1.RPC_MESSAGE_PARAMS]: params,
389
396
  }));
390
397
  this.sendRpcMessage(rq);
391
398
  const promise = new RpcRequestPromise(resolve => {
392
- this.rpcHandlers[rqId] = { resolve, timeout_handle: makeTimeout(this.timeout, resolve), delayCallback };
399
+ this.rpcHandlers[rqId] = { resolve, timeout_handle: makeTimeout(this.timeout, resolve), delayCallback: options?.delayCallback };
393
400
  }, () => {
394
401
  this.sendRpcMessage(makeRq((0, rpcvalue_1.makeIMap)({
395
402
  [rpcmessage_1.RPC_MESSAGE_ABORT]: true,
@@ -419,44 +426,91 @@ class WsClient {
419
426
  this.websocket.send(dgram.buffer);
420
427
  }
421
428
  }
422
- async subscribe(subscriber, pathGetter, method, callback) {
423
- const path = await (0, utils_1.resolveString)(pathGetter);
424
- if (this.subscriptions.some(val => val.subscriber === subscriber && val.path === path && val.method === method)) {
425
- this.logDebug(`Already subscribed {$path}:${method} for subscriber ${subscriber}`);
429
+ async getShvApiVersion() {
430
+ if (this.shvApiVersion === undefined) {
431
+ const lsResult = await this.callRpcMethod('.broker', 'ls');
432
+ if (lsResult instanceof Error) {
433
+ console.warn('Cannot detect SHV API version. Using version 2 as a fallback.');
434
+ this.shvApiVersion = ShvApiVersion.V2;
435
+ }
436
+ else if (lsResult.includes('client')) {
437
+ this.shvApiVersion = ShvApiVersion.V3;
438
+ }
439
+ else {
440
+ this.shvApiVersion = ShvApiVersion.V2;
441
+ }
442
+ }
443
+ return this.shvApiVersion;
444
+ }
445
+ async subscribe(subscriber, riGetter, callback) {
446
+ const ri = await (0, utils_1.resolveRI)(riGetter);
447
+ const path = ri.path();
448
+ const signal = ri.signal();
449
+ if (this.subscriptions.some(val => val.subscriber === subscriber && val.path === path && val.signal === signal)) {
450
+ this.logDebug(`Already subscribed {$path}:${signal} for subscriber ${subscriber}`);
426
451
  return;
427
452
  }
428
- // If this path:method has not been subscribed on the broker, do it now
429
- if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
430
- this.callRpcMethod('.broker/app', 'subscribe', (0, rpcvalue_1.makeMap)({
431
- method, path,
432
- })).catch(() => {
433
- this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
434
- });
453
+ // If this path:signal has not been subscribed on the broker, do it now
454
+ if (!this.subscriptions.some(val => val.path === path && val.signal === signal)) {
455
+ switch (await this.getShvApiVersion()) {
456
+ case ShvApiVersion.V2: {
457
+ const result = await this.callRpcMethod('.broker/app', 'subscribe', (0, rpcvalue_1.makeMap)({ signal, path }));
458
+ if (result instanceof Error) {
459
+ this.logDebug(`Couldn't subscribe to ${path}, ${signal}`);
460
+ }
461
+ if (result instanceof Error) {
462
+ this.logDebug(`Couldn't subscribe to ${path}, ${signal}`);
463
+ return;
464
+ }
465
+ break;
466
+ }
467
+ case ShvApiVersion.V3: {
468
+ const result = await this.callRpcMethod('.broker/currentClient', 'subscribe', [ri.asString(), undefined]);
469
+ if (result instanceof Error) {
470
+ this.logDebug(`Couldn't subscribe to ${path}, ${signal}`);
471
+ return;
472
+ }
473
+ break;
474
+ }
475
+ }
435
476
  }
436
477
  this.subscriptions.push({
437
478
  subscriber,
438
479
  path,
439
- method,
480
+ signal,
440
481
  callback,
441
482
  });
442
483
  }
443
- async unsubscribe(subscriber, pathGetter, method) {
444
- const path = await (0, utils_1.resolveString)(pathGetter);
445
- const idx = this.subscriptions.findIndex(val => val.subscriber === subscriber && val.path === path && val.method === method);
484
+ async unsubscribe(subscriber, riGetter) {
485
+ const ri = await (0, utils_1.resolveRI)(riGetter);
486
+ const path = ri.path();
487
+ const signal = ri.signal();
488
+ const idx = this.subscriptions.findIndex(val => val.subscriber === subscriber && val.path === path && val.signal === signal);
446
489
  if (idx === -1) {
447
- this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
490
+ this.logDebug(`No such subscription ${path}:${signal} for subscriber ${subscriber}`);
448
491
  return;
449
492
  }
450
493
  this.subscriptions.splice(idx, 1);
451
- // Unsubscribe on the broker only if there are no other subscriptions of this path:method
452
- if (this.subscriptions.some(val => val.path === path && val.method === method)) {
494
+ // Unsubscribe on the broker only if there are no other subscriptions of this path:signal
495
+ if (this.subscriptions.some(val => val.path === path && val.signal === signal)) {
453
496
  return;
454
497
  }
455
- this.callRpcMethod('.broker/app', 'unsubscribe', (0, rpcvalue_1.makeMap)({
456
- method, path,
457
- })).catch(() => {
458
- this.logDebug(`Couldn't unsubscribe ${path}, ${method}`);
459
- });
498
+ switch (await this.getShvApiVersion()) {
499
+ case ShvApiVersion.V2: {
500
+ const result = await this.callRpcMethod('.broker/app', 'unsubscribe', (0, rpcvalue_1.makeMap)({ signal, path }));
501
+ if (result instanceof Error) {
502
+ this.logDebug(`Couldn't unsubscribe ${path}, ${signal}`);
503
+ }
504
+ break;
505
+ }
506
+ case ShvApiVersion.V3: {
507
+ const result = await this.callRpcMethod('.broker/currentClient', 'unsubscribe', (0, rpcvalue_1.makeMap)({ ri: ri.asString(), ttl: undefined }));
508
+ if (result instanceof Error) {
509
+ this.logDebug(`Couldn't unsubscribe ${path}, ${signal}`);
510
+ }
511
+ break;
512
+ }
513
+ }
460
514
  }
461
515
  sendPing() {
462
516
  this.callRpcMethod('.app', 'ping').catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libshv-js",
3
- "version": "5.1.0",
3
+ "version": "7.0.0",
4
4
  "description": "Typescript implementation of libshv",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",