libshv-js 6.0.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);
@@ -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';
@@ -209,6 +209,10 @@ export type CallRpcMethodOptions = {
209
209
  delayCallback?: (progress: number) => void;
210
210
  requestUserId?: boolean;
211
211
  };
212
+ declare enum ShvApiVersion {
213
+ V2 = 0,
214
+ V3 = 1
215
+ }
212
216
  declare class WsClient {
213
217
  private requestId;
214
218
  private pingTimerId;
@@ -219,6 +223,7 @@ declare class WsClient {
219
223
  private readonly websocket;
220
224
  private readonly options;
221
225
  private readonly timeout;
226
+ private shvApiVersion;
222
227
  constructor(options: WsClientOptions<MethodHandler>);
223
228
  callRpcMethod(shvPath: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string], options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<string>>;
224
229
  callRpcMethod(shvPath: '.broker/currentClient', method: 'accessLevelForMethodCall', params: [string, string], options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<number>>;
@@ -226,8 +231,9 @@ declare class WsClient {
226
231
  callRpcMethod(shvPath: string | undefined, method: 'ls', params?: RpcValue, options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError<LsResult>>;
227
232
  callRpcMethod(shvPath: string | undefined, method: string, params?: RpcValue, options?: CallRpcMethodOptions): RpcRequestPromise<ResultOrError>;
228
233
  sendRpcMessage(rpcMsg: RpcMessage): void;
229
- subscribe(subscriber: string, pathGetter: StringGetter, method: string, callback: SubscriptionCallback): Promise<void>;
230
- 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>;
231
237
  sendPing(): void;
232
238
  accessGrantForMethodCall(path: string, method: string): RpcRequestPromise<ResultOrError<string>>;
233
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
  };
@@ -420,44 +426,91 @@ class WsClient {
420
426
  this.websocket.send(dgram.buffer);
421
427
  }
422
428
  }
423
- async subscribe(subscriber, pathGetter, method, callback) {
424
- const path = await (0, utils_1.resolveString)(pathGetter);
425
- if (this.subscriptions.some(val => val.subscriber === subscriber && val.path === path && val.method === method)) {
426
- 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}`);
427
451
  return;
428
452
  }
429
- // If this path:method has not been subscribed on the broker, do it now
430
- if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
431
- this.callRpcMethod('.broker/app', 'subscribe', (0, rpcvalue_1.makeMap)({
432
- method, path,
433
- })).catch(() => {
434
- this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
435
- });
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
+ }
436
476
  }
437
477
  this.subscriptions.push({
438
478
  subscriber,
439
479
  path,
440
- method,
480
+ signal,
441
481
  callback,
442
482
  });
443
483
  }
444
- async unsubscribe(subscriber, pathGetter, method) {
445
- const path = await (0, utils_1.resolveString)(pathGetter);
446
- 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);
447
489
  if (idx === -1) {
448
- this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
490
+ this.logDebug(`No such subscription ${path}:${signal} for subscriber ${subscriber}`);
449
491
  return;
450
492
  }
451
493
  this.subscriptions.splice(idx, 1);
452
- // Unsubscribe on the broker only if there are no other subscriptions of this path:method
453
- 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)) {
454
496
  return;
455
497
  }
456
- this.callRpcMethod('.broker/app', 'unsubscribe', (0, rpcvalue_1.makeMap)({
457
- method, path,
458
- })).catch(() => {
459
- this.logDebug(`Couldn't unsubscribe ${path}, ${method}`);
460
- });
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
+ }
461
514
  }
462
515
  sendPing() {
463
516
  this.callRpcMethod('.app', 'ping').catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libshv-js",
3
- "version": "6.0.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",