@iobroker/js-controller-adapter 7.2.3 → 7.2.5

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.
@@ -388,6 +388,8 @@ export interface InternalAddChannelToEnumOptions {
388
388
  export interface SendToOptions {
389
389
  /** Method throws or calls error cb, if callback not called in time, works for single targets only */
390
390
  timeout?: number;
391
+ /** The user on whose behalf the message is sent */
392
+ user?: ioBroker.ObjectIDs.User;
391
393
  }
392
394
  export interface InternalSendToOptions {
393
395
  instanceName: string;
@@ -411,6 +413,7 @@ export interface InternalSendToHostOptions {
411
413
  command: string;
412
414
  message: any;
413
415
  callback?: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo;
416
+ options?: SendToOptions;
414
417
  }
415
418
  export interface InternalGetStateOptions {
416
419
  id: string;
@@ -162,7 +162,7 @@ export interface AdapterClass {
162
162
  * Sends a message to a specific host or all hosts.
163
163
  */
164
164
  sendToHostAsync(hostName: string, message: ioBroker.MessagePayload): Promise<ioBroker.Message | undefined>;
165
- sendToHostAsync(hostName: string, command: string, message: ioBroker.MessagePayload): Promise<ioBroker.Message | undefined>;
165
+ sendToHostAsync(hostName: string, command: string, message: ioBroker.MessagePayload, options?: SendToOptions): Promise<ioBroker.Message | undefined>;
166
166
  /**
167
167
  * Sends a message to a specific instance or all instances of some specific adapter.
168
168
  */
@@ -338,6 +338,8 @@ export declare class AdapterClass extends EventEmitter {
338
338
  namespace: `${string}.${number}`;
339
339
  name: string;
340
340
  private _systemSecret?;
341
+ /** Attributes of `encryptedNative`: their values in `this.config` are decrypted on start */
342
+ private _decryptedNativeAttributes;
341
343
  /** Whether the adapter has already terminated */
342
344
  private terminated;
343
345
  /** The cache of usernames */
@@ -491,6 +493,15 @@ export declare class AdapterClass extends EventEmitter {
491
493
  *
492
494
  */
493
495
  disable(): ioBroker.SetObjectPromise;
496
+ /**
497
+ * Reads a parameter which is stored encrypted and returns its decrypted value.
498
+ *
499
+ * An attribute listed in `encryptedNative` is already decrypted in `this.config` when the adapter
500
+ * starts, and is returned as it is.
501
+ *
502
+ * @param attribute the config attribute to decrypt
503
+ * @param callback return result
504
+ */
494
505
  getEncryptedConfig(attribute: string, callback?: GetEncryptedConfigCallback): Promise<string | string[] | void>;
495
506
  private _getEncryptedConfig;
496
507
  /**
@@ -711,7 +722,7 @@ export declare class AdapterClass extends EventEmitter {
711
722
  sendTo(instanceName: string, command: string, message: any, callback?: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo, options?: SendToOptions): void;
712
723
  private _sendTo;
713
724
  sendToHost(hostName: string | null, message: any, callback?: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo): void;
714
- sendToHost(hostName: string | null, command: string, message: any, callback?: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo): void;
725
+ sendToHost(hostName: string | null, command: string, message: any, callback?: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo, options?: SendToOptions): void;
715
726
  private _sendToHost;
716
727
  sendToUI(options: SendToUserInterfaceClientOptions): Promise<void>;
717
728
  registerNotification<Scope extends keyof ioBroker.NotificationScopes>(scope: Scope, category: ioBroker.NotificationScopes[Scope] | null, message: string, options?: NotificationOptions): Promise<void>;
@@ -110,6 +110,8 @@ class AdapterClass extends import_node_events.EventEmitter {
110
110
  namespace;
111
111
  name;
112
112
  _systemSecret;
113
+ /** Attributes of `encryptedNative`: their values in `this.config` are decrypted on start */
114
+ _decryptedNativeAttributes = new Array();
113
115
  /** Whether the adapter has already terminated */
114
116
  terminated = false;
115
117
  /** The cache of usernames */
@@ -361,7 +363,6 @@ class AdapterClass extends import_node_events.EventEmitter {
361
363
  this.readFileAsync = import_js_controller_common.tools.promisify(this.readFile, this, ["file", "mimeType"]);
362
364
  this.writeFileAsync = import_js_controller_common.tools.promisify(this.writeFile, this);
363
365
  this.fileExistsAsync = import_js_controller_common.tools.promisify(this.fileExists, this);
364
- this.sendToHostAsync = import_js_controller_common.tools.promisifyNoError(this.sendToHost, this);
365
366
  this.setStateAsync = import_js_controller_common.tools.promisify(this.setState, this);
366
367
  this.setStateChangedAsync = import_js_controller_common.tools.promisify(this.setStateChanged, this, ["id", "notChanged"]);
367
368
  this.setForeignStateAsync = import_js_controller_common.tools.promisify(this.setForeignState, this);
@@ -479,7 +480,12 @@ class AdapterClass extends import_node_events.EventEmitter {
479
480
  this._logger.info(`${this.namespaceLog} getSession not processed because States database not connected`);
480
481
  return import_js_controller_common.tools.maybeCallbackWithError(options.callback, import_js_controller_common.tools.ERRORS.ERROR_DB_CLOSED);
481
482
  }
482
- this.#states.getSession(options.id, options.callback);
483
+ this.#states.getSession(options.id, (err, session) => {
484
+ if (err) {
485
+ this._logger.error(`${this.namespaceLog} Cannot read session "${options.id}": ${err.message}`);
486
+ }
487
+ return import_js_controller_common.tools.maybeCallback(options.callback, session || null);
488
+ });
483
489
  }
484
490
  // unknown implementation guards
485
491
  setSession(id, ttl, data, callback) {
@@ -1389,7 +1395,7 @@ class AdapterClass extends import_node_events.EventEmitter {
1389
1395
  return this.setForeignObjectAsync(configObjId, obj);
1390
1396
  }
1391
1397
  /**
1392
- * Reads the encrypted parameter from config.
1398
+ * Reads a parameter which is stored encrypted and returns its decrypted value.
1393
1399
  *
1394
1400
  * It returns promise if no callback is provided.
1395
1401
  *
@@ -1404,14 +1410,12 @@ class AdapterClass extends import_node_events.EventEmitter {
1404
1410
  async _getEncryptedConfig(options) {
1405
1411
  const { attribute, callback } = options;
1406
1412
  const value = (0, import_utils.getObjectAttribute)(this.config, attribute);
1413
+ if ((Array.isArray(value) || typeof value === "string") && this._decryptedNativeAttributes.includes(attribute)) {
1414
+ return import_js_controller_common.tools.maybeCallbackWithError(callback, null, value);
1415
+ }
1407
1416
  if (Array.isArray(value)) {
1408
1417
  const secret = await this.getSystemSecret();
1409
- const result = [];
1410
- for (let i = 0; i < value.length; i++) {
1411
- if (typeof value[i] === "string") {
1412
- result[i] = import_js_controller_common.tools.decrypt(secret, value[i]);
1413
- }
1414
- }
1418
+ const result = value.map((item) => typeof item === "string" ? import_js_controller_common.tools.decrypt(secret, item) : item);
1415
1419
  return import_js_controller_common.tools.maybeCallbackWithError(callback, null, result);
1416
1420
  } else if (typeof value === "string") {
1417
1421
  const secret = await this.getSystemSecret();
@@ -4706,13 +4710,27 @@ class AdapterClass extends import_node_events.EventEmitter {
4706
4710
  this.sendTo(instanceName, command, message, callback, options);
4707
4711
  });
4708
4712
  }
4713
+ /**
4714
+ * The `user` field of an outbound message, from the `user` of the send options.
4715
+ *
4716
+ * Spread into the envelope, so a send without a user leaves the field out entirely instead of
4717
+ * putting an empty one in: a receiver that checks `obj.user` must be able to tell "nobody named a
4718
+ * user" from "the user is known", and an empty string is neither.
4719
+ *
4720
+ * @param options the send options the caller passed, if any
4721
+ */
4722
+ #userOf(options) {
4723
+ const user = options?.user;
4724
+ return typeof user === "string" && user ? { user } : {};
4725
+ }
4709
4726
  async _sendTo(_options) {
4710
4727
  const { command, message, callback, options } = _options;
4711
4728
  let { instanceName } = _options;
4712
4729
  const obj = {
4713
4730
  command,
4714
4731
  message,
4715
- from: `system.adapter.${this.namespace}`
4732
+ from: `system.adapter.${this.namespace}`,
4733
+ ...this.#userOf(options)
4716
4734
  };
4717
4735
  if (!instanceName) {
4718
4736
  return import_js_controller_common.tools.maybeCallbackWithError(callback, "No instanceName provided or not a string");
@@ -4812,8 +4830,10 @@ class AdapterClass extends import_node_events.EventEmitter {
4812
4830
  * if (!result) adapter.log.error('No response received');
4813
4831
  * }
4814
4832
  * ```
4833
+ * @param options optional send options: a `timeout` for the answer, or the `user` this message is
4834
+ * sent on behalf of - the host sees it as `obj.user`
4815
4835
  */
4816
- sendToHost(hostName, command, message, callback) {
4836
+ sendToHost(hostName, command, message, callback, options) {
4817
4837
  if (typeof message === "undefined") {
4818
4838
  message = command;
4819
4839
  command = "send";
@@ -4825,17 +4845,57 @@ class AdapterClass extends import_node_events.EventEmitter {
4825
4845
  if (!import_js_controller_common.tools.isObject(callback)) {
4826
4846
  import_validator.Validator.assertOptionalCallback(callback, "callback");
4827
4847
  }
4848
+ if (options !== void 0) {
4849
+ import_validator.Validator.assertObject(options, "options");
4850
+ }
4828
4851
  return this._sendToHost({
4829
4852
  hostName,
4830
4853
  command,
4831
4854
  message,
4855
+ options,
4832
4856
  callback
4833
4857
  });
4834
4858
  }
4859
+ /**
4860
+ * Async version of sendToHost
4861
+ *
4862
+ * Resolves with whatever the host answered - like the promisified version it replaces, an error
4863
+ * of the host reaches the caller as the resolved value, and that is left alone: an adapter that
4864
+ * awaits this today must not start seeing rejections.
4865
+ *
4866
+ * It rejects in one case: a `timeout` was asked for in the send options and elapsed, which is
4867
+ * how `sendToAsync` reports the same thing. Whoever passes that option is writing against an
4868
+ * option that did not exist before and can expect it; whoever passes none keeps the old
4869
+ * behaviour, including a host that never answers and a caller that waits for ever.
4870
+ *
4871
+ * @param hostName name of the host where the message must be sent to. E.g. "myPC" or "system.host.myPC". If argument is null, the message will be sent to all hosts.
4872
+ * @param command command name. One of: "cmdExec", "getRepository", "getInstalled", ...
4873
+ * @param message object that will be given as argument for request
4874
+ * @param options optional send options: a `timeout` for the answer, or the `user` the message is sent for
4875
+ */
4876
+ sendToHostAsync(hostName, command, message, options) {
4877
+ const timeout = options?.timeout;
4878
+ return new Promise((resolve, reject) => {
4879
+ this.sendToHost(
4880
+ hostName,
4881
+ command,
4882
+ message,
4883
+ // The answer of a host travels through the states database and is therefore never an
4884
+ // `Error` instance; one can only come from here, and only where a budget was named.
4885
+ (result) => timeout && result instanceof Error ? reject(result) : resolve(result),
4886
+ options
4887
+ );
4888
+ });
4889
+ }
4835
4890
  async _sendToHost(_options) {
4836
- const { command, message, callback } = _options;
4891
+ const { command, message, callback, options } = _options;
4837
4892
  let { hostName } = _options;
4838
- const obj = { command, message, from: `system.adapter.${this.namespace}` };
4893
+ const obj = {
4894
+ command,
4895
+ message,
4896
+ from: `system.adapter.${this.namespace}`,
4897
+ ...this.#userOf(options)
4898
+ };
4839
4899
  if (!this.#states) {
4840
4900
  this._logger.info(`${this.namespaceLog} sendToHost not processed because States database not connected`);
4841
4901
  return import_js_controller_common.tools.maybeCallbackWithError(callback, import_js_controller_common.tools.ERRORS.ERROR_DB_CLOSED);
@@ -4884,7 +4944,18 @@ class AdapterClass extends import_node_events.EventEmitter {
4884
4944
  if (this._callbackId >= 4294967295) {
4885
4945
  this._callbackId = 1;
4886
4946
  }
4887
- this.messageCallbacks.set(obj.callback.id, { cb: callback, time: Date.now() });
4947
+ const callbackId = obj.callback.id;
4948
+ let timer;
4949
+ if (options?.timeout) {
4950
+ timer = setTimeout(() => {
4951
+ const callbackObj = this.messageCallbacks.get(callbackId);
4952
+ if (callbackObj) {
4953
+ callbackObj.cb(new Error("Timeout exceeded"));
4954
+ this.messageCallbacks.delete(callbackId);
4955
+ }
4956
+ }, options.timeout);
4957
+ }
4958
+ this.messageCallbacks.set(callbackId, { cb: callback, time: Date.now(), timer });
4888
4959
  } else {
4889
4960
  obj.callback = callback;
4890
4961
  obj.callback.ack = true;
@@ -5238,6 +5309,9 @@ class AdapterClass extends import_node_events.EventEmitter {
5238
5309
  return options;
5239
5310
  }
5240
5311
  _checkState(obj, options, command) {
5312
+ if (options.groups?.includes(import_constants.SYSTEM_ADMIN_GROUP)) {
5313
+ return true;
5314
+ }
5241
5315
  const limitToOwnerRights = options.limitToOwnerRights === true;
5242
5316
  if (obj?.acl) {
5243
5317
  obj.acl.state = obj.acl.state || obj.acl.object;
@@ -7746,12 +7820,15 @@ class AdapterClass extends import_node_events.EventEmitter {
7746
7820
  this.oObjects = await this.getAdapterObjectsAsync();
7747
7821
  await this.subscribeObjectsAsync("*");
7748
7822
  }
7749
- await this.getSystemSecret();
7750
- const promises = [];
7823
+ const secret = await this.getSystemSecret();
7751
7824
  if (Array.isArray(adapterConfig.encryptedNative)) {
7825
+ this._decryptedNativeAttributes = new Array();
7752
7826
  for (const attr of adapterConfig.encryptedNative) {
7753
- if (typeof this.config[attr] === "string") {
7754
- promises.push(this.getEncryptedConfig(attr).then((decryptedValue) => (0, import_utils.setObjectAttribute)(this.config, attr, decryptedValue)).catch((e) => this._logger.error(`${this.namespaceLog} Can not decrypt attribute ${attr}: ${e.message}`)));
7827
+ try {
7828
+ (0, import_utils.decryptArray)({ obj: this.config, secret, keys: [attr] });
7829
+ this._decryptedNativeAttributes.push(attr);
7830
+ } catch (e) {
7831
+ this._logger.error(`${this.namespaceLog} Can not decrypt attribute ${attr}: ${e.message}`);
7755
7832
  }
7756
7833
  }
7757
7834
  } else {
@@ -7760,7 +7837,6 @@ class AdapterClass extends import_node_events.EventEmitter {
7760
7837
  this.SUPPORTED_FEATURES.splice(idx, 1);
7761
7838
  }
7762
7839
  }
7763
- await Promise.all(promises);
7764
7840
  if (!this.#states) {
7765
7841
  return;
7766
7842
  }