@iobroker/js-controller-adapter 7.2.3 → 8.0.0-alpha.1-20260921-e5941ca8a

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.
Files changed (101) hide show
  1. package/build/cjs/index.d.ts +0 -1
  2. package/build/cjs/index.js +5 -8
  3. package/build/cjs/index.js.map +2 -2
  4. package/build/cjs/lib/_Types.d.ts +394 -63
  5. package/build/cjs/lib/adapter/adapter.d.ts +1818 -177
  6. package/build/cjs/lib/adapter/adapter.js +715 -1035
  7. package/build/cjs/lib/adapter/adapter.js.map +2 -2
  8. package/build/cjs/lib/adapter/asyncAdapter.d.ts +124 -0
  9. package/build/cjs/lib/adapter/asyncAdapter.js +184 -0
  10. package/build/cjs/lib/adapter/asyncAdapter.js.map +7 -0
  11. package/build/cjs/lib/adapter/context.d.ts +34 -0
  12. package/build/cjs/lib/adapter/context.js +17 -0
  13. package/build/cjs/lib/adapter/context.js.map +7 -0
  14. package/build/cjs/lib/adapter/defaultObjs.d.ts +5 -0
  15. package/build/cjs/lib/adapter/defaultObjs.js +2 -0
  16. package/build/cjs/lib/adapter/defaultObjs.js.map +2 -2
  17. package/build/cjs/lib/adapter/log.d.ts +25 -0
  18. package/build/cjs/lib/adapter/log.js +29 -0
  19. package/build/cjs/lib/adapter/log.js.map +2 -2
  20. package/build/cjs/lib/adapter/managers/AdapterContextBase.d.ts +49 -0
  21. package/build/cjs/lib/adapter/managers/AdapterContextBase.js +98 -0
  22. package/build/cjs/lib/adapter/managers/AdapterContextBase.js.map +7 -0
  23. package/build/cjs/lib/adapter/managers/AliasManager.d.ts +103 -0
  24. package/build/cjs/lib/adapter/managers/AliasManager.js +293 -0
  25. package/build/cjs/lib/adapter/managers/AliasManager.js.map +7 -0
  26. package/build/cjs/lib/adapter/managers/CertificateManager.d.ts +50 -0
  27. package/build/cjs/lib/adapter/managers/CertificateManager.js +174 -0
  28. package/build/cjs/lib/adapter/managers/CertificateManager.js.map +7 -0
  29. package/build/cjs/lib/adapter/managers/MessagingManager.d.ts +58 -0
  30. package/build/cjs/lib/adapter/managers/MessagingManager.js +289 -0
  31. package/build/cjs/lib/adapter/managers/MessagingManager.js.map +7 -0
  32. package/build/cjs/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
  33. package/build/cjs/lib/adapter/managers/SubscriptionManager.js +349 -0
  34. package/build/cjs/lib/adapter/managers/SubscriptionManager.js.map +7 -0
  35. package/build/cjs/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
  36. package/build/cjs/lib/adapter/userInterfaceMessagingController.js +9 -2
  37. package/build/cjs/lib/adapter/userInterfaceMessagingController.js.map +2 -2
  38. package/build/cjs/lib/adapter/utils.js +15 -1
  39. package/build/cjs/lib/adapter/utils.js.map +2 -2
  40. package/build/cjs/lib/adapter/validator.d.ts +10 -5
  41. package/build/cjs/lib/adapter/validator.js +6 -5
  42. package/build/cjs/lib/adapter/validator.js.map +2 -2
  43. package/build/esm/index.d.ts +0 -1
  44. package/build/esm/index.d.ts.map +1 -1
  45. package/build/esm/index.js +0 -1
  46. package/build/esm/index.js.map +1 -1
  47. package/build/esm/lib/_Types.d.ts +394 -63
  48. package/build/esm/lib/_Types.d.ts.map +1 -1
  49. package/build/esm/lib/adapter/adapter.d.ts +1818 -177
  50. package/build/esm/lib/adapter/adapter.d.ts.map +1 -1
  51. package/build/esm/lib/adapter/adapter.js +827 -1215
  52. package/build/esm/lib/adapter/adapter.js.map +1 -1
  53. package/build/esm/lib/adapter/asyncAdapter.d.ts +124 -0
  54. package/build/esm/lib/adapter/asyncAdapter.d.ts.map +1 -0
  55. package/build/esm/lib/adapter/asyncAdapter.js +157 -0
  56. package/build/esm/lib/adapter/asyncAdapter.js.map +1 -0
  57. package/build/esm/lib/adapter/context.d.ts +34 -0
  58. package/build/esm/lib/adapter/context.d.ts.map +1 -0
  59. package/build/esm/lib/adapter/context.js +2 -0
  60. package/build/esm/lib/adapter/context.js.map +1 -0
  61. package/build/esm/lib/adapter/defaultObjs.d.ts +5 -0
  62. package/build/esm/lib/adapter/defaultObjs.d.ts.map +1 -1
  63. package/build/esm/lib/adapter/defaultObjs.js +5 -0
  64. package/build/esm/lib/adapter/defaultObjs.js.map +1 -1
  65. package/build/esm/lib/adapter/log.d.ts +25 -0
  66. package/build/esm/lib/adapter/log.d.ts.map +1 -1
  67. package/build/esm/lib/adapter/log.js +25 -0
  68. package/build/esm/lib/adapter/log.js.map +1 -1
  69. package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts +49 -0
  70. package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts.map +1 -0
  71. package/build/esm/lib/adapter/managers/AdapterContextBase.js +77 -0
  72. package/build/esm/lib/adapter/managers/AdapterContextBase.js.map +1 -0
  73. package/build/esm/lib/adapter/managers/AliasManager.d.ts +103 -0
  74. package/build/esm/lib/adapter/managers/AliasManager.d.ts.map +1 -0
  75. package/build/esm/lib/adapter/managers/AliasManager.js +269 -0
  76. package/build/esm/lib/adapter/managers/AliasManager.js.map +1 -0
  77. package/build/esm/lib/adapter/managers/CertificateManager.d.ts +50 -0
  78. package/build/esm/lib/adapter/managers/CertificateManager.d.ts.map +1 -0
  79. package/build/esm/lib/adapter/managers/CertificateManager.js +142 -0
  80. package/build/esm/lib/adapter/managers/CertificateManager.js.map +1 -0
  81. package/build/esm/lib/adapter/managers/MessagingManager.d.ts +58 -0
  82. package/build/esm/lib/adapter/managers/MessagingManager.d.ts.map +1 -0
  83. package/build/esm/lib/adapter/managers/MessagingManager.js +267 -0
  84. package/build/esm/lib/adapter/managers/MessagingManager.js.map +1 -0
  85. package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
  86. package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts.map +1 -0
  87. package/build/esm/lib/adapter/managers/SubscriptionManager.js +374 -0
  88. package/build/esm/lib/adapter/managers/SubscriptionManager.js.map +1 -0
  89. package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
  90. package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts.map +1 -1
  91. package/build/esm/lib/adapter/userInterfaceMessagingController.js +8 -2
  92. package/build/esm/lib/adapter/userInterfaceMessagingController.js.map +1 -1
  93. package/build/esm/lib/adapter/utils.d.ts.map +1 -1
  94. package/build/esm/lib/adapter/utils.js +1 -1
  95. package/build/esm/lib/adapter/utils.js.map +1 -1
  96. package/build/esm/lib/adapter/validator.d.ts +10 -5
  97. package/build/esm/lib/adapter/validator.d.ts.map +1 -1
  98. package/build/esm/lib/adapter/validator.js +8 -5
  99. package/build/esm/lib/adapter/validator.js.map +1 -1
  100. package/build/tsconfig.build.tsbuildinfo +1 -1
  101. package/package.json +25 -20
@@ -11,15 +11,19 @@ import extend from 'node.extend';
11
11
  import yargs from 'yargs/yargs';
12
12
  import fs from 'fs-extra';
13
13
  import * as url from 'node:url';
14
- import { PluginHandler } from '@iobroker/plugin-base';
15
14
  import { EXIT_CODES, getObjectsConstructor, getStatesConstructor, isInstalledFromNpm, logger, password, tools, } from '@iobroker/js-controller-common';
16
- import { decryptArray, encryptArray, getAdapterScopedPackageIdentifier, getSupportedFeatures, isMessageboxSupported, listInstalledNodeModules, requestModuleNameByUrl, deleteObjectAttribute, setObjectAttribute, getObjectAttribute, } from '../../lib/adapter/utils.js';
15
+ import { decryptArray, encryptArray, getAdapterScopedPackageIdentifier, getSupportedFeatures, isMessageboxSupported, listInstalledNodeModules, requestModuleNameByUrl, deleteObjectAttribute, getObjectAttribute, } from '../../lib/adapter/utils.js';
17
16
  // local version is always the same as controller version, since lerna exact: true is used
18
17
  import packJson from '@iobroker/js-controller-adapter/package.json' with { type: 'json' };
19
18
  import { Log } from '../../lib/adapter/log.js';
20
19
  import { Validator } from './validator.js';
21
20
  import { ACCESS_EVERY_READ, ACCESS_EVERY_WRITE, ACCESS_GROUP_READ, ACCESS_GROUP_WRITE, ACCESS_USER_READ, ACCESS_USER_WRITE, ALIAS_STARTS_WITH, DEFAULT_SECRET, ERROR_PERMISSION, NO_PROTECT_ADAPTERS, STATE_QUALITY, SYSTEM_ADMIN_GROUP, SYSTEM_ADMIN_USER, } from '../../lib/adapter/constants.js';
21
+ import { PluginHandler } from '@iobroker/plugin-base';
22
22
  import { UserInterfaceMessagingController } from '../../lib/adapter/userInterfaceMessagingController.js';
23
+ import { AsyncAdapter } from '../../lib/adapter/asyncAdapter.js';
24
+ import { CERTIFICATES_OBJECT_ID } from '../../lib/adapter/managers/CertificateManager.js';
25
+ import { AliasManager } from '../../lib/adapter/managers/AliasManager.js';
26
+ import { SubscriptionManager } from '../../lib/adapter/managers/SubscriptionManager.js';
23
27
  import { SYSTEM_ADAPTER_PREFIX, DEFAULT_OBJECTS_WARN_LIMIT } from '@iobroker/js-controller-common-db/constants';
24
28
  import { isLogLevel } from '@iobroker/js-controller-common-db/tools';
25
29
  const controllerVersion = packJson.version;
@@ -51,14 +55,10 @@ export class AdapterClass extends EventEmitter {
51
55
  startedInCompactMode;
52
56
  /** List of instances which want our logs */
53
57
  logList = new Set();
54
- aliases = new Map();
55
- aliasPatterns = new Set();
56
58
  enums = {};
57
59
  eventLoopLags = [];
58
60
  overwriteLogLevel = false;
59
61
  adapterReady = false;
60
- /** Callbacks from sendTo */
61
- messageCallbacks = new Map();
62
62
  /**
63
63
  * Contains a live cache of the adapter's states.
64
64
  * NOTE: This is only defined if the adapter was initialized with the option states: true.
@@ -70,7 +70,6 @@ export class AdapterClass extends EventEmitter {
70
70
  */
71
71
  oObjects;
72
72
  _stopInProgress = false;
73
- _callbackId = 1;
74
73
  _firstConnection = true;
75
74
  _timers = new Set();
76
75
  _intervals = new Set();
@@ -79,12 +78,14 @@ export class AdapterClass extends EventEmitter {
79
78
  log;
80
79
  performStrictObjectChecks;
81
80
  _logger;
82
- _restartScheduleJob;
81
+ _restartScheduleJob = null;
83
82
  _schedule;
84
83
  namespaceLog;
85
84
  namespace;
86
85
  name;
87
86
  _systemSecret;
87
+ /** Attributes of `encryptedNative`: their values in `this.config` are decrypted on start */
88
+ _decryptedNativeAttributes = new Array();
88
89
  /** Whether the adapter has already terminated */
89
90
  terminated = false;
90
91
  /** The cache of usernames */
@@ -97,8 +98,6 @@ export class AdapterClass extends EventEmitter {
97
98
  users = {}; // todo
98
99
  /** The cache of user groups */
99
100
  groups = {};
100
- /** An array of instances, that support auto subscribe */
101
- autoSubscribe = [];
102
101
  defaultHistory = null;
103
102
  pluginHandler;
104
103
  _reportInterval;
@@ -130,11 +129,9 @@ export class AdapterClass extends EventEmitter {
130
129
  /** latitude configured in system.config, only available if requested via AdapterOptions `useFormatDate`*/
131
130
  latitude;
132
131
  _defaultObjs;
133
- _aliasObjectsSubscribed;
134
132
  config = {};
135
133
  host;
136
134
  common;
137
- mboxSubscribed;
138
135
  /** Stop the adapter */
139
136
  stop;
140
137
  version;
@@ -162,6 +159,31 @@ export class AdapterClass extends EventEmitter {
162
159
  SUPPORTED_FEATURES = getSupportedFeatures();
163
160
  /** Controller for messaging related functionality */
164
161
  uiMessagingController;
162
+ /** Shared runtime context handed to managers */
163
+ #context;
164
+ #async;
165
+ #certWatchers = new Map();
166
+ #aliasInstance;
167
+ /** Lazily-constructed alias subscription manager. */
168
+ get #alias() {
169
+ return (this.#aliasInstance ??= new AliasManager(this.#context, (id, allowSystem, options) => this._utils.validateId(id, allowSystem, options)));
170
+ }
171
+ #subscriptionsInstance;
172
+ /** Lazily-constructed object/file/state subscription manager. */
173
+ get #subscriptions() {
174
+ return (this.#subscriptionsInstance ??= new SubscriptionManager(this.#context, (id, isPattern) => this._utils.fixId(id, isPattern), this.#alias, {
175
+ getForeignStateObjects: (pattern, options) =>
176
+ // @ts-expect-error adjust types
177
+ this.getForeignObjectsAsync(pattern, null, null, options),
178
+ }));
179
+ }
180
+ /** True once `system.certificates` is subscribed, i.e. certificates have been requested */
181
+ #certObjectSubscribed = false;
182
+ /** True once a certificate change scheduled a restart, so concurrent changes schedule only one */
183
+ #certRestartScheduled = false;
184
+ /**
185
+ * @param options Adapter options, or the adapter name as a string
186
+ */
165
187
  constructor(options) {
166
188
  super();
167
189
  // enable "const adapter = require(__dirname + '/../../lib/adapter.js')('adapterName');" call
@@ -299,6 +321,36 @@ export class AdapterClass extends EventEmitter {
299
321
  subscribeCallback: this._options.uiClientSubscribe,
300
322
  unsubscribeCallback: this._options.uiClientUnsubscribe,
301
323
  });
324
+ const self = this;
325
+ this.#context = {
326
+ logger: this._logger,
327
+ uiMessagingController: this.uiMessagingController,
328
+ countOutput: () => {
329
+ self.outputCount++;
330
+ },
331
+ get namespace() {
332
+ return self.namespace;
333
+ },
334
+ get namespaceLog() {
335
+ return self.namespaceLog;
336
+ },
337
+ get host() {
338
+ return self.host;
339
+ },
340
+ get states() {
341
+ return self.#states;
342
+ },
343
+ get objects() {
344
+ return self.#objects;
345
+ },
346
+ get common() {
347
+ return self.common;
348
+ },
349
+ get config() {
350
+ return self.config;
351
+ },
352
+ };
353
+ this.#async = new AsyncAdapter(this.#context);
302
354
  // Create dynamic methods
303
355
  /**
304
356
  * Promise-version of `Adapter.getPort`
@@ -320,10 +372,6 @@ export class AdapterClass extends EventEmitter {
320
372
  * Promise-version of `Adapter.calculatePermissions`
321
373
  */
322
374
  this.calculatePermissionsAsync = tools.promisifyNoError(this.calculatePermissions, this);
323
- /**
324
- * Promise-version of `Adapter.getCertificates`
325
- */
326
- this.getCertificatesAsync = tools.promisify(this.getCertificates, this);
327
375
  /**
328
376
  * Promise-version of `Adapter.setObject`
329
377
  */
@@ -508,10 +556,6 @@ export class AdapterClass extends EventEmitter {
508
556
  * Promise-version of `Adapter.fileExists`
509
557
  */
510
558
  this.fileExistsAsync = tools.promisify(this.fileExists, this);
511
- /**
512
- * Promise-version of `Adapter.sendToHost`
513
- */
514
- this.sendToHostAsync = tools.promisifyNoError(this.sendToHost, this);
515
559
  /**
516
560
  * Promise-version of `Adapter.setState`
517
561
  */
@@ -573,7 +617,7 @@ export class AdapterClass extends EventEmitter {
573
617
  */
574
618
  this.unsubscribeStatesAsync = tools.promisify(this.unsubscribeStates, this);
575
619
  this.setExecutableCapabilities = tools.setExecutableCapabilities;
576
- this._init();
620
+ this._init().catch(e => this._logger.error(`${this.namespaceLog} Cannot initialize adapter: ${e.message}`));
577
621
  }
578
622
  /**
579
623
  * Get the adapter scoped package identifier of a node module
@@ -664,6 +708,12 @@ export class AdapterClass extends EventEmitter {
664
708
  return tools.encrypt(secretVal, value);
665
709
  }
666
710
  // unknown guard implementation
711
+ /**
712
+ * Read a session from the states DB
713
+ *
714
+ * @param id the session id
715
+ * @param callback return result
716
+ */
667
717
  getSession(id, callback) {
668
718
  Validator.assertString(id, 'id');
669
719
  Validator.assertCallback(callback, 'callback');
@@ -674,11 +724,19 @@ export class AdapterClass extends EventEmitter {
674
724
  if (!this.#states) {
675
725
  // if states is no longer existing, we do not need to unsubscribe
676
726
  this._logger.info(`${this.namespaceLog} getSession not processed because States database not connected`);
677
- return tools.maybeCallbackWithError(options.callback, tools.ERRORS.ERROR_DB_CLOSED);
727
+ return tools.maybeCallback(options.callback, null);
678
728
  }
679
- this.#states.getSession(options.id, options.callback);
729
+ this.#states.getSession(options.id, (err, session) => tools.maybeCallback(options.callback, session || null));
680
730
  }
681
731
  // unknown implementation guards
732
+ /**
733
+ * Store a session in the states DB
734
+ *
735
+ * @param id the session id
736
+ * @param ttl time to live in seconds
737
+ * @param data the session data to store
738
+ * @param callback return result
739
+ */
682
740
  setSession(id, ttl, data, callback) {
683
741
  Validator.assertString(id, 'id');
684
742
  Validator.assertOptionalCallback(callback, 'callback');
@@ -693,8 +751,16 @@ export class AdapterClass extends EventEmitter {
693
751
  this._logger.info(`${this.namespaceLog} setSession not processed because States database not connected`);
694
752
  return tools.maybeCallbackWithError(options.callback, tools.ERRORS.ERROR_DB_CLOSED);
695
753
  }
696
- this.#states.setSession(options.id, options.ttl, options.data, options.callback);
754
+ return options.callback
755
+ ? this.#states.setSession(options.id, options.ttl, options.data, options.callback)
756
+ : this.#states.setSession(options.id, options.ttl, options.data);
697
757
  }
758
+ /**
759
+ * Destroy a session in the states DB
760
+ *
761
+ * @param id the session id
762
+ * @param callback return result
763
+ */
698
764
  destroySession(id, callback) {
699
765
  Validator.assertString(id, 'id');
700
766
  Validator.assertOptionalCallback(callback, 'callback');
@@ -706,12 +772,13 @@ export class AdapterClass extends EventEmitter {
706
772
  this._logger.info(`${this.namespaceLog} destroySession not processed because States database not connected`);
707
773
  return tools.maybeCallbackWithError(options.callback, tools.ERRORS.ERROR_DB_CLOSED);
708
774
  }
709
- this.#states.destroySession(options.id, options.callback);
775
+ return options.callback
776
+ ? this.#states.destroySession(options.id, options.callback)
777
+ : this.#states.destroySession(options.id);
710
778
  }
711
779
  async _getObjectsByArray(keys, options) {
712
780
  try {
713
- const res = await this.#objects.getObjects(keys, options);
714
- return res;
781
+ return await this.#objects.getObjects(keys, options);
715
782
  }
716
783
  catch (e) {
717
784
  this._logger.error(`Could not get objects by array: ${e.message}`);
@@ -1066,7 +1133,7 @@ export class AdapterClass extends EventEmitter {
1066
1133
  options.user = this.usernames[options.user].id;
1067
1134
  }
1068
1135
  }
1069
- this.getForeignObject(options.user, options, (err, obj) => {
1136
+ this.getForeignObject(options.user, options.options, (err, obj) => {
1070
1137
  if (err || !obj) {
1071
1138
  return tools.maybeCallbackWithError(options.callback, 'User does not exist');
1072
1139
  }
@@ -1156,11 +1223,11 @@ export class AdapterClass extends EventEmitter {
1156
1223
  if (options.group && !options.group.startsWith('system.group.')) {
1157
1224
  options.group = `system.group.${options.group}`;
1158
1225
  }
1159
- this.getForeignObject(options.user, options, (err, obj) => {
1226
+ this.getForeignObject(options.user, options.options, (err, obj) => {
1160
1227
  if (err || !obj) {
1161
1228
  return tools.maybeCallback(options.callback, false);
1162
1229
  }
1163
- this.getForeignObject(options.group, options, (err, obj) => {
1230
+ return this.getForeignObject(options.group, options.options, (err, obj) => {
1164
1231
  if (err || !obj) {
1165
1232
  return tools.maybeCallback(options.callback, false);
1166
1233
  }
@@ -1265,7 +1332,7 @@ export class AdapterClass extends EventEmitter {
1265
1332
  * ```
1266
1333
  */
1267
1334
  calculatePermissions(user, commandsPermissions, options, callback) {
1268
- user = user || '';
1335
+ user ||= '';
1269
1336
  if (typeof options === 'function') {
1270
1337
  callback = options;
1271
1338
  options = null;
@@ -1278,7 +1345,12 @@ export class AdapterClass extends EventEmitter {
1278
1345
  Validator.assertObject(options, 'options');
1279
1346
  }
1280
1347
  Validator.assertOptionalCallback(callback, 'callback');
1281
- return this._calculatePermissions({ user, commandsPermissions, options, callback });
1348
+ return this._calculatePermissions({
1349
+ user: user,
1350
+ commandsPermissions,
1351
+ options,
1352
+ callback,
1353
+ });
1282
1354
  }
1283
1355
  async _calculatePermissions(options) {
1284
1356
  const { user } = options;
@@ -1380,23 +1452,22 @@ export class AdapterClass extends EventEmitter {
1380
1452
  for (const type of Object.keys(gAcl)) {
1381
1453
  // fix bug. Some version have user instead of users.
1382
1454
  if (type === 'user') {
1383
- // @ts-expect-error fix it
1384
- acl.users = acl.users || {};
1455
+ acl.users ||= {};
1385
1456
  }
1386
1457
  else {
1387
- // @ts-expect-error fix it
1388
- acl[type] = acl[type] || {};
1458
+ // @ts-expect-error fix later
1459
+ acl[type] ||= {};
1389
1460
  }
1390
1461
  // @ts-expect-error fix it
1391
1462
  for (const op of Object.keys(gAcl[type])) {
1392
1463
  // fix error
1393
1464
  if (type === 'user') {
1394
- // @ts-expect-error fix it
1395
- acl.users[op] = acl.users[op] || gAcl.user[op];
1465
+ // @ts-expect-error fix later
1466
+ acl.users[op] ||= gAcl.user[op];
1396
1467
  }
1397
1468
  else {
1398
- // @ts-expect-error fix it
1399
- acl[type][op] = acl[type][op] || gAcl[type][op];
1469
+ // @ts-expect-error fix later
1470
+ acl[type][op] ||= gAcl[type][op];
1400
1471
  }
1401
1472
  }
1402
1473
  }
@@ -1443,10 +1514,8 @@ export class AdapterClass extends EventEmitter {
1443
1514
  this._delays.forEach(timer => clearTimeout(timer));
1444
1515
  this._delays.clear();
1445
1516
  }
1446
- if (this.messageCallbacks.size) {
1447
- this.messageCallbacks.forEach(callbackObj => clearTimeout(callbackObj.timer));
1448
- this.messageCallbacks.clear();
1449
- }
1517
+ this.#async.clearPending();
1518
+ await this.stopWatchingCertificates();
1450
1519
  if (this.#states && updateAliveState) {
1451
1520
  this.outputCount++;
1452
1521
  try {
@@ -1467,11 +1536,14 @@ export class AdapterClass extends EventEmitter {
1467
1536
  if (this._options.unload.length >= 1) {
1468
1537
  // The method takes (at least) a callback
1469
1538
  try {
1470
- this._options.unload(finishUnload);
1539
+ const unloadResult = this._options.unload(finishUnload);
1540
+ if (unloadResult instanceof Promise) {
1541
+ unloadResult.catch(e => this._logger.error(`${this.namespaceLog} Error in unload: ${e.message}`));
1542
+ }
1471
1543
  }
1472
1544
  catch {
1473
1545
  // Can only throw in unload logic, in this case the finishUnload() was not called
1474
- finishUnload();
1546
+ finishUnload().catch(e => this._logger.error(`${this.namespaceLog} Error in finishUnload: ${e.message}`));
1475
1547
  }
1476
1548
  }
1477
1549
  else {
@@ -1484,7 +1556,7 @@ export class AdapterClass extends EventEmitter {
1484
1556
  await unloadPromise;
1485
1557
  }
1486
1558
  finally {
1487
- finishUnload();
1559
+ finishUnload().catch(e => this._logger.error(`${this.namespaceLog} Error in finishUnload: ${e.message}`));
1488
1560
  }
1489
1561
  }
1490
1562
  else {
@@ -1514,30 +1586,105 @@ export class AdapterClass extends EventEmitter {
1514
1586
  }
1515
1587
  }
1516
1588
  /**
1517
- * Reads the file certificate from a given path and adds a file watcher to restart adapter on cert changes
1518
- * if a cert is passed it is returned as it is
1589
+ * Installs a restart-on-change watcher for each file-backed certificate path, deduped by path.
1590
+ * A change schedules a graceful restart so the adapter reloads the new certificate.
1519
1591
  *
1520
- * @param cert cert or path to cert
1592
+ * @param paths file-backed certificate paths returned by the certificate manager
1521
1593
  */
1522
- _readFileCertificate(cert) {
1523
- if (typeof cert === 'string') {
1594
+ #watchCertFiles(paths) {
1595
+ for (const certFile of paths) {
1596
+ if (this.#certWatchers.has(certFile)) {
1597
+ continue;
1598
+ }
1524
1599
  try {
1525
- // if length < 1024 it's no valid cert, so we assume a path to a valid certificate
1526
- if (cert.length < 1024 && fs.existsSync(cert)) {
1527
- const certFile = cert;
1528
- cert = fs.readFileSync(certFile, 'utf8');
1529
- // start watcher of this file
1530
- fs.watch(certFile, (eventType, filename) => {
1531
- this._logger.warn(`${this.namespaceLog} New certificate "${filename}" detected. Restart adapter`);
1532
- setTimeout(() => this._stop({ isPause: false, isScheduled: true }), 2_000);
1533
- });
1534
- }
1600
+ const watcher = fs.watch(certFile, (_eventType, filename) => {
1601
+ this.#scheduleCertificateRestart(`New certificate "${filename}" detected`);
1602
+ });
1603
+ this.#certWatchers.set(certFile, watcher);
1535
1604
  }
1536
1605
  catch (e) {
1537
- this._logger.error(`${this.namespaceLog} Could not read certificate from file ${cert}: ${e.message}`);
1606
+ this._logger.error(`${this.namespaceLog} Cannot watch certificate file ${certFile}: ${e.message}`);
1607
+ }
1608
+ }
1609
+ }
1610
+ /**
1611
+ * Schedules a graceful restart, so the adapter reloads its certificates. Called by both the
1612
+ * file watchers and the `system.certificates` subscription; the first caller wins, so a change
1613
+ * seen through both channels restarts the adapter only once.
1614
+ *
1615
+ * @param reason human-readable cause, logged as a warning
1616
+ */
1617
+ #scheduleCertificateRestart(reason) {
1618
+ if (this.#certRestartScheduled) {
1619
+ return;
1620
+ }
1621
+ this.#certRestartScheduled = true;
1622
+ this._logger.warn(`${this.namespaceLog} ${reason}. Restart adapter`);
1623
+ setTimeout(() => this._stop({ isPause: false, isScheduled: true }), 2_000);
1624
+ }
1625
+ /**
1626
+ * Subscribes to the `system.certificates` object, so inline (text) certificates are watched the
1627
+ * same way file-backed ones are watched on disk. Subscribing on the system level channel keeps
1628
+ * these events out of the adapter's own `objectChange` handler.
1629
+ *
1630
+ * Called after certificates have been requested, so only adapters actually using certificates
1631
+ * pay for the subscription.
1632
+ */
1633
+ async #subscribeCertObject() {
1634
+ if (this.#certObjectSubscribed || !this.#objects) {
1635
+ return;
1636
+ }
1637
+ // set before awaiting, so concurrent getCertificates calls subscribe only once
1638
+ this.#certObjectSubscribed = true;
1639
+ try {
1640
+ await this.#objects.subscribeAsync(CERTIFICATES_OBJECT_ID);
1641
+ // stopWatchingCertificates() may have run while we were awaiting; it cleared the flag but could
1642
+ // not unsubscribe a subscription that did not exist yet, so undo it now to avoid a leak.
1643
+ if (!this.#certObjectSubscribed && this.#objects) {
1644
+ await this.#objects.unsubscribeAsync(CERTIFICATES_OBJECT_ID);
1645
+ }
1646
+ }
1647
+ catch (e) {
1648
+ this.#certObjectSubscribed = false;
1649
+ this._logger.error(`${this.namespaceLog} Cannot watch certificate object ${CERTIFICATES_OBJECT_ID}: ${e.message}`);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Tears down the certificate watching installed by {@link getCertificatesAsync}: closes the file
1654
+ * watchers, unsubscribes from `system.certificates`, and forgets the certificates handed out by the
1655
+ * last call, so a later change no longer schedules a restart. Called on adapter stop.
1656
+ */
1657
+ async stopWatchingCertificates() {
1658
+ for (const watcher of this.#certWatchers.values()) {
1659
+ watcher.close();
1660
+ }
1661
+ this.#certWatchers.clear();
1662
+ this.#certRestartScheduled = false;
1663
+ this.#async.stopWatchingCertificates();
1664
+ if (this.#certObjectSubscribed && this.#objects) {
1665
+ this.#certObjectSubscribed = false;
1666
+ try {
1667
+ await this.#objects.unsubscribeAsync(CERTIFICATES_OBJECT_ID);
1668
+ }
1669
+ catch {
1670
+ // ignore, e.g. when the objects DB is already gone
1538
1671
  }
1539
1672
  }
1540
- return cert;
1673
+ }
1674
+ /**
1675
+ * Loads SSL certificates by name and installs restart-on-change watchers: file watchers for
1676
+ * file-backed certificate values, and a subscription to `system.certificates` covering inline
1677
+ * ones and repointed paths.
1678
+ *
1679
+ * @param publicName public certificate name (defaults to `config.certPublic`)
1680
+ * @param privateName private key name (defaults to `config.certPrivate`)
1681
+ * @param chainedName chained certificate name (defaults to `config.certChained`)
1682
+ */
1683
+ async getCertificatesAsync(publicName, privateName, chainedName) {
1684
+ const { certs, useLetsEncrypt, certFilePaths } = await this.#async.getCertificates(publicName, privateName, chainedName);
1685
+ this.#watchCertFiles(certFilePaths);
1686
+ await this.#subscribeCertObject();
1687
+ return [certs, useLetsEncrypt];
1541
1688
  }
1542
1689
  /**
1543
1690
  * returns SSL certificates by name
@@ -1571,68 +1718,16 @@ export class AdapterClass extends EventEmitter {
1571
1718
  callback = chainedName;
1572
1719
  chainedName = undefined;
1573
1720
  }
1574
- const config = this.config;
1575
- publicName = publicName || config.certPublic;
1576
- privateName = privateName || config.certPrivate;
1577
- chainedName = chainedName || config.certChained;
1578
- if (publicName !== undefined) {
1579
- Validator.assertString(publicName, 'publicName');
1580
- }
1581
- if (privateName !== undefined) {
1582
- Validator.assertString(privateName, 'privateName');
1583
- }
1584
- if (chainedName !== undefined) {
1585
- Validator.assertString(chainedName, 'chainedName');
1586
- }
1587
1721
  Validator.assertOptionalCallback(callback, 'callback');
1588
- return this._getCertificates({ publicName, privateName, chainedName, callback });
1589
- }
1590
- async _getCertificates(options) {
1591
- const { publicName, chainedName, privateName, callback } = options;
1592
- let obj;
1593
- if (!this.#objects) {
1594
- this._logger.info(`${this.namespaceLog} getCertificates not processed because Objects database not connected`);
1595
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
1596
- }
1597
- try {
1598
- // Load certificates
1599
- obj = await this.#objects.getObject('system.certificates');
1600
- }
1601
- catch {
1602
- // ignore
1603
- }
1604
- if (!obj ||
1605
- !obj.native.certificates ||
1606
- !publicName ||
1607
- !privateName ||
1608
- !obj.native.certificates[publicName] ||
1609
- !obj.native.certificates[privateName] ||
1610
- (chainedName && !obj.native.certificates[chainedName])) {
1611
- this._logger.error(`${this.namespaceLog} Cannot configure secure web server, because no certificates found: ${publicName}, ${privateName}, ${chainedName}`);
1612
- if (publicName === 'defaultPublic' || privateName === 'defaultPrivate') {
1613
- this._logger.info(`${this.namespaceLog} Default certificates seem to be configured but missing. You can execute "iobroker cert create" in your shell to create these.`);
1614
- }
1615
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_NOT_FOUND);
1616
- }
1617
- let ca;
1618
- if (chainedName) {
1619
- const chained = this._readFileCertificate(obj.native.certificates[chainedName]).split('-----END CERTIFICATE-----\r\n');
1620
- // it is still a file name, and the file maybe does not exist, but we can omit this error
1621
- if (chained.join('').length >= 512) {
1622
- const caArr = [];
1623
- for (const cert of chained) {
1624
- if (cert.replace(/(\r\n|\r|\n)/g, '').trim()) {
1625
- caArr.push(`${cert}-----END CERTIFICATE-----\r\n`);
1626
- }
1627
- }
1628
- ca = caArr.join('');
1629
- }
1722
+ const cb = callback;
1723
+ if (typeof cb === 'function') {
1724
+ return this.getCertificatesAsync(publicName, privateName, chainedName).then(([certs, useLetsEncrypt]) => {
1725
+ cb(null, certs, useLetsEncrypt);
1726
+ }, (err) => {
1727
+ cb(err);
1728
+ });
1630
1729
  }
1631
- return tools.maybeCallbackWithError(callback, null, {
1632
- key: this._readFileCertificate(obj.native.certificates[privateName]),
1633
- cert: this._readFileCertificate(obj.native.certificates[publicName]),
1634
- ca,
1635
- }, obj.native.letsEncrypt);
1730
+ return this.getCertificatesAsync(publicName, privateName, chainedName);
1636
1731
  }
1637
1732
  /**
1638
1733
  * Restarts an instance of the adapter.
@@ -1641,7 +1736,7 @@ export class AdapterClass extends EventEmitter {
1641
1736
  restart() {
1642
1737
  if (this.stop) {
1643
1738
  this._logger.warn(`${this.namespaceLog} Restart initiated`);
1644
- this.stop();
1739
+ this.stop().catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
1645
1740
  }
1646
1741
  else {
1647
1742
  this._logger.warn(`${this.namespaceLog} Cannot initiate restart, because this.stop is not defined`);
@@ -1667,7 +1762,7 @@ export class AdapterClass extends EventEmitter {
1667
1762
  const configObjId = `system.adapter.${this.namespace}`;
1668
1763
  let obj;
1669
1764
  try {
1670
- obj = await this.getForeignObjectAsync(configObjId);
1765
+ obj = (await this.getForeignObjectAsync(configObjId));
1671
1766
  }
1672
1767
  catch (e) {
1673
1768
  this._logger.error(`${this.namespaceLog} Updating the adapter config failed: ${e.message}`);
@@ -1711,7 +1806,7 @@ export class AdapterClass extends EventEmitter {
1711
1806
  return this.setForeignObjectAsync(configObjId, obj);
1712
1807
  }
1713
1808
  /**
1714
- * Reads the encrypted parameter from config.
1809
+ * Reads a parameter which is stored encrypted and returns its decrypted value.
1715
1810
  *
1716
1811
  * It returns promise if no callback is provided.
1717
1812
  *
@@ -1726,14 +1821,16 @@ export class AdapterClass extends EventEmitter {
1726
1821
  async _getEncryptedConfig(options) {
1727
1822
  const { attribute, callback } = options;
1728
1823
  const value = getObjectAttribute(this.config, attribute);
1824
+ // `encryptedNative` says which attributes are stored encrypted, and the start decrypted exactly
1825
+ // those in place - decrypting again cannot be detected, because a value carries no mark saying so
1826
+ if ((Array.isArray(value) || typeof value === 'string') &&
1827
+ this._decryptedNativeAttributes.includes(attribute)) {
1828
+ return tools.maybeCallbackWithError(callback, null, value);
1829
+ }
1729
1830
  if (Array.isArray(value)) {
1730
1831
  const secret = await this.getSystemSecret();
1731
- const result = [];
1732
- for (let i = 0; i < value.length; i++) {
1733
- if (typeof value[i] === 'string') {
1734
- result[i] = tools.decrypt(secret, value[i]);
1735
- }
1736
- }
1832
+ // entries that are not a string (e.g. an element without this attribute) are returned as they are
1833
+ const result = value.map(item => (typeof item === 'string' ? tools.decrypt(secret, item) : item));
1737
1834
  return tools.maybeCallbackWithError(callback, null, result);
1738
1835
  }
1739
1836
  else if (typeof value === 'string') {
@@ -2120,7 +2217,7 @@ export class AdapterClass extends EventEmitter {
2120
2217
  * }
2121
2218
  * ```
2122
2219
  *
2123
- * If following object will be passed as argument
2220
+ * If the following object will be passed as an argument
2124
2221
  *
2125
2222
  * ```js
2126
2223
  * {
@@ -2231,36 +2328,29 @@ export class AdapterClass extends EventEmitter {
2231
2328
  this._logger.error(`${this.namespaceLog} Object ${options.id} not exist!`);
2232
2329
  oldObj = {};
2233
2330
  }
2234
- if (options.obj.native &&
2235
- 'repositories' in options.obj.native &&
2236
- oldObj.native &&
2237
- oldObj.native.repositories) {
2238
- oldObj.native.repositories = [];
2331
+ if (options.obj.native && 'repositories' in options.obj.native && oldObj.native?.repositories) {
2332
+ oldObj.native.repositories = {};
2239
2333
  }
2240
- if (options.obj.common && 'members' in options.obj.common && oldObj.common && oldObj.common.members) {
2334
+ if (options.obj.common && 'members' in options.obj.common && oldObj.common?.members) {
2241
2335
  oldObj.common.members = [];
2242
2336
  }
2243
- if (options.obj.native &&
2244
- 'certificates' in options.obj.native &&
2245
- oldObj.native &&
2246
- oldObj.native.certificates) {
2247
- oldObj.native.certificates = [];
2337
+ if (options.obj.native && 'certificates' in options.obj.native && oldObj.native?.certificates) {
2338
+ oldObj.native.certificates = {};
2248
2339
  }
2249
- if (options.obj.native && 'devices' in options.obj.native && oldObj.native && oldObj.native.devices) {
2340
+ if (options.obj.native && 'devices' in options.obj.native && oldObj.native?.devices) {
2250
2341
  oldObj.native.devices = [];
2251
2342
  }
2252
- options.obj.from = options.obj.from || `system.adapter.${this.namespace}`;
2253
- options.obj.user = options.obj.user || (options.options ? options.options.user : '') || SYSTEM_ADMIN_USER;
2254
- options.obj.ts = options.obj.ts || Date.now();
2343
+ options.obj.from ||= `system.adapter.${this.namespace}`;
2344
+ options.obj.user ||= (options.options ? options.options.user : '') || SYSTEM_ADMIN_USER;
2345
+ options.obj.ts ||= Date.now();
2255
2346
  options.obj = extend(true, oldObj, options.obj);
2256
2347
  // @ts-expect-error TODO we are returning type Object for ease of use to devs, but formally these are AnyObjects, e.g. not guaranteed to have common
2257
2348
  return this.#objects.setObject(options.id, options.obj, options.options, options.callback);
2258
2349
  }
2259
- options.obj.from = options.obj.from || `system.adapter.${this.namespace}`;
2260
- options.obj.user = options.obj.user || (options.options ? options.options.user : '') || SYSTEM_ADMIN_USER;
2261
- options.obj.ts = options.obj.ts || Date.now();
2262
- if ((options.obj.type && options.obj.type === 'state') ||
2263
- (!options.obj.type && oldObj && oldObj.type === 'state')) {
2350
+ options.obj.from ||= `system.adapter.${this.namespace}`;
2351
+ options.obj.user ||= (options.options ? options.options.user : '') || SYSTEM_ADMIN_USER;
2352
+ options.obj.ts ||= Date.now();
2353
+ if (options.obj?.type === 'state' || (!options.obj.type && oldObj?.type === 'state')) {
2264
2354
  if (options.obj.common &&
2265
2355
  'custom' in options.obj.common &&
2266
2356
  options.obj.common.custom !== null &&
@@ -2276,17 +2366,17 @@ export class AdapterClass extends EventEmitter {
2276
2366
  }
2277
2367
  }
2278
2368
  if (!oldObj) {
2279
- // if old object is not existing we behave like setObject
2369
+ // if old object is not existing, we behave like setObject
2280
2370
  return this.setForeignObject(options.id, options.obj, options.options, options.callback);
2281
2371
  }
2282
2372
  try {
2283
- const cbObj = await this.#objects.extendObjectAsync(options.id, options.obj, options.options || {});
2373
+ const cbObj = await this.#objects.extendObject(options.id, options.obj, options.options);
2284
2374
  let defState;
2285
2375
  if (options.obj.type === 'state' || oldObj.type === 'state') {
2286
2376
  if (options.obj.common && 'def' in options.obj.common && options.obj.common.def !== undefined) {
2287
2377
  defState = options.obj.common.def;
2288
2378
  }
2289
- else if (oldObj.common && oldObj.common.def !== undefined) {
2379
+ else if (oldObj.common?.def !== undefined) {
2290
2380
  defState = oldObj.common.def;
2291
2381
  }
2292
2382
  }
@@ -2464,29 +2554,32 @@ export class AdapterClass extends EventEmitter {
2464
2554
  this._logger.error(`${this.namespaceLog} Object ${id} not exist!`);
2465
2555
  oldObj = {};
2466
2556
  }
2467
- if (obj.native && 'repositories' in obj.native && oldObj.native && oldObj.native.repositories) {
2468
- oldObj.native.repositories = [];
2557
+ if (obj.native &&
2558
+ 'repositories' in obj.native &&
2559
+ oldObj.native?.repositories) {
2560
+ oldObj.native.repositories = {};
2469
2561
  }
2470
- if (obj.common && 'members' in obj.common && oldObj.common && oldObj.common.members) {
2562
+ if (obj.common && 'members' in obj.common && oldObj.common?.members) {
2471
2563
  oldObj.common.members = [];
2472
2564
  }
2473
- if (obj.native && 'certificates' in obj.native && oldObj.native && oldObj.native.certificates) {
2474
- oldObj.native.certificates = [];
2565
+ if (obj.native && 'certificates' in obj.native && oldObj.native?.certificates) {
2566
+ oldObj.native.certificates = {};
2475
2567
  }
2476
- if (obj.native && 'devices' in obj.native && oldObj.native && oldObj.native.devices) {
2568
+ if (obj.native && 'devices' in obj.native && oldObj.native?.devices) {
2477
2569
  oldObj.native.devices = [];
2478
2570
  }
2479
- obj.from = obj.from || `system.adapter.${this.namespace}`;
2480
- obj.user = obj.user || options?.user || SYSTEM_ADMIN_USER;
2481
- obj.ts = obj.ts || Date.now();
2571
+ obj.from ||= `system.adapter.${this.namespace}`;
2572
+ obj.user ||= options?.user || SYSTEM_ADMIN_USER;
2573
+ obj.ts ||= Date.now();
2482
2574
  obj = extend(true, oldObj, obj);
2483
- // @ts-expect-error TODO we are returning type Object for ease of use to devs, but formally these are AnyObjects, e.g. not guaranteed to have common
2484
- return this.#objects.setObject(id, obj, options, callback);
2485
- }
2486
- obj.from = obj.from || `system.adapter.${this.namespace}`;
2487
- obj.user = obj.user || options?.user || SYSTEM_ADMIN_USER;
2488
- obj.ts = obj.ts || Date.now();
2489
- if ((obj.type && obj.type === 'state') || (!obj.type && oldObj && oldObj.type === 'state')) {
2575
+ return callback
2576
+ ? this.#objects.setObject(id, obj, options, callback)
2577
+ : this.#objects.setObject(id, obj, options);
2578
+ }
2579
+ obj.from ||= `system.adapter.${this.namespace}`;
2580
+ obj.user ||= options?.user || SYSTEM_ADMIN_USER;
2581
+ obj.ts ||= Date.now();
2582
+ if ((obj.type && obj.type === 'state') || (!obj.type && oldObj?.type === 'state')) {
2490
2583
  if (obj.common &&
2491
2584
  'custom' in obj.common &&
2492
2585
  obj.common.custom !== null &&
@@ -2506,13 +2599,13 @@ export class AdapterClass extends EventEmitter {
2506
2599
  return this.setForeignObject(id, obj, options, callback);
2507
2600
  }
2508
2601
  try {
2509
- const cbObj = await this.#objects.extendObjectAsync(id, obj, options || {});
2602
+ const cbObj = await this.#objects.extendObject(id, obj, options);
2510
2603
  if (cbObj?.value.type === 'state') {
2511
2604
  let defState;
2512
- if (obj.common && 'def' in obj.common && obj.common.def !== undefined) {
2605
+ if (obj.common && 'def' in obj.common && obj.common?.def !== undefined) {
2513
2606
  defState = obj.common.def;
2514
2607
  }
2515
- else if (oldObj.common && oldObj.common.def !== undefined) {
2608
+ else if (oldObj.common?.def !== undefined) {
2516
2609
  defState = oldObj.common.def;
2517
2610
  }
2518
2611
  if (defState !== undefined) {
@@ -2650,7 +2743,7 @@ export class AdapterClass extends EventEmitter {
2650
2743
  Validator.assertString(design, 'design');
2651
2744
  Validator.assertString(search, 'search');
2652
2745
  Validator.assertOptionalCallback(callback, 'callback');
2653
- params = params || {};
2746
+ params ||= {};
2654
2747
  Validator.assertObject(params, 'params');
2655
2748
  if (options !== null && options !== undefined) {
2656
2749
  Validator.assertObject(options, 'options');
@@ -2919,9 +3012,7 @@ export class AdapterClass extends EventEmitter {
2919
3012
  if (!parts[2]) {
2920
3013
  continue;
2921
3014
  }
2922
- if (!result[`${parts[0]}.${parts[1]}`]) {
2923
- result[`${parts[0]}.${parts[1]}`] = {};
2924
- }
3015
+ result[`${parts[0]}.${parts[1]}`] ||= {};
2925
3016
  result[`${parts[0]}.${parts[1]}`][row.id] = row.value;
2926
3017
  }
2927
3018
  }
@@ -3146,7 +3237,7 @@ export class AdapterClass extends EventEmitter {
3146
3237
  * @param options optional user context with language
3147
3238
  * @param options.user current user
3148
3239
  * @param options.language language in which the search must be done for multi-language names
3149
- * @result if the object was found by ID it will return id and may be the multi-language name it exists. If the object was found by name it will return id and the multi-language name. If the object was not found, it will return only name that was searched for.
3240
+ * @returns if the object was found by ID it will return id and may be the multi-language name it exists. If the object was found by name it will return id and the multi-language name. If the object was not found, it will return only name that was searched for.
3150
3241
  */
3151
3242
  findForeignObjectAsync(id, type, options) {
3152
3243
  return new Promise((resolve, reject) => this.findForeignObject(id, type, options || {}, (err, id, name) => {
@@ -3188,7 +3279,7 @@ export class AdapterClass extends EventEmitter {
3188
3279
  catch (e) {
3189
3280
  return tools.maybeCallbackWithError(callback, e);
3190
3281
  }
3191
- return this._getForeignObject({ id, options, callback });
3282
+ return this._getForeignObject({ id: id, options, callback });
3192
3283
  }
3193
3284
  async _getForeignObject(options) {
3194
3285
  if (!this.#objects) {
@@ -3196,14 +3287,12 @@ export class AdapterClass extends EventEmitter {
3196
3287
  return tools.maybeCallbackWithError(options.callback, tools.ERRORS.ERROR_DB_CLOSED);
3197
3288
  }
3198
3289
  try {
3199
- const obj = await this.#objects.getObjectAsync(options.id, options);
3290
+ const obj = await this.#objects.getObjectAsync(options.id, options.options);
3200
3291
  // remove protectedNative if not admin, not cloud or not own adapter
3201
- if (obj &&
3292
+ if (obj?.native &&
3202
3293
  'protectedNative' in obj &&
3203
3294
  Array.isArray(obj.protectedNative) &&
3204
- obj._id &&
3205
- obj._id.startsWith('system.adapter.') &&
3206
- obj.native &&
3295
+ obj._id?.startsWith('system.adapter.') &&
3207
3296
  !NO_PROTECT_ADAPTERS.includes(this.name) &&
3208
3297
  this.name !== obj._id.split('.')[2]) {
3209
3298
  for (const attr of obj.protectedNative) {
@@ -3240,31 +3329,35 @@ export class AdapterClass extends EventEmitter {
3240
3329
  // @ts-expect-error we have ensured that it is string for the rest the method will validate again
3241
3330
  this.delForeignObject(id, options, callback);
3242
3331
  }
3243
- _deleteObjects(tasks, options, cb) {
3244
- if (!tasks || !tasks.length) {
3332
+ async _deleteObjects(tasks, options, cb) {
3333
+ if (!tasks?.length) {
3245
3334
  return tools.maybeCallback(cb);
3246
3335
  }
3247
- const task = tasks.shift();
3248
- this.#objects.delObject(task.id, options, async (err) => {
3249
- if (err) {
3250
- return tools.maybeCallbackWithError(cb, err);
3251
- }
3252
- if (task.state) {
3336
+ try {
3337
+ for (const task of tasks) {
3338
+ await new Promise((resolve, reject) => this.#objects.delObject(task.id, options, err => {
3339
+ err ? reject(err) : resolve();
3340
+ }));
3341
+ if (task.state) {
3342
+ try {
3343
+ await this.delForeignStateAsync(task.id, options);
3344
+ }
3345
+ catch (e) {
3346
+ this._logger.warn(`${this.namespaceLog} Could not remove state of ${task.id}: ${e.message}`);
3347
+ }
3348
+ }
3253
3349
  try {
3254
- await this.delForeignStateAsync(task.id, options);
3350
+ await tools.removeIdFromAllEnums(this.#objects, task.id, this.enums);
3255
3351
  }
3256
3352
  catch (e) {
3257
- this._logger.warn(`${this.namespaceLog} Could not remove state of ${task.id}: ${e.message}`);
3353
+ this._logger.warn(`${this.namespaceLog} Could not remove ${task.id} from enums: ${e.message}`);
3258
3354
  }
3259
3355
  }
3260
- try {
3261
- await tools.removeIdFromAllEnums(this.#objects, task.id, this.enums);
3262
- }
3263
- catch (e) {
3264
- this._logger.warn(`${this.namespaceLog} Could not remove ${task.id} from enums: ${e.message}`);
3265
- }
3266
- setImmediate(() => this._deleteObjects(tasks, options, cb));
3267
- });
3356
+ }
3357
+ catch (err) {
3358
+ return tools.maybeCallbackWithError(cb, err);
3359
+ }
3360
+ return tools.maybeCallback(cb);
3268
3361
  }
3269
3362
  /**
3270
3363
  * Delete any object.
@@ -3316,11 +3409,10 @@ export class AdapterClass extends EventEmitter {
3316
3409
  const selector = { startkey: `${id}.`, endkey: `${id}.\u9999` };
3317
3410
  // read all underlying states
3318
3411
  this.#objects.getObjectList(selector, options, (err, res) => {
3319
- res &&
3320
- res.rows.forEach((item) => !tasks.find(task => task.id === item.id) &&
3321
- (!item.value || !item.value.common || !item.value.common.dontDelete) && // exclude objects with dontDelete flag
3322
- tasks.push({ id: item.id, state: item.value && item.value.type === 'state' }));
3323
- this._deleteObjects(tasks, options, callback);
3412
+ res?.rows.forEach((item) => !tasks.find(task => task.id === item.id) &&
3413
+ !item.value?.common?.dontDelete && // exclude objects with dontDelete flag
3414
+ tasks.push({ id: item.id, state: item.value?.type === 'state' }));
3415
+ return this._deleteObjects(tasks, options, callback);
3324
3416
  });
3325
3417
  });
3326
3418
  }
@@ -3329,7 +3421,7 @@ export class AdapterClass extends EventEmitter {
3329
3421
  if (err) {
3330
3422
  return tools.maybeCallbackWithError(callback, err);
3331
3423
  }
3332
- else if (obj) {
3424
+ if (obj) {
3333
3425
  // do not allow deletion of objects with dontDelete flag
3334
3426
  if (obj.common?.dontDelete) {
3335
3427
  return tools.maybeCallbackWithError(callback, new Error('not deletable'));
@@ -3381,17 +3473,10 @@ export class AdapterClass extends EventEmitter {
3381
3473
  if (options !== null && options !== undefined) {
3382
3474
  Validator.assertObject(options, 'options');
3383
3475
  }
3384
- if (!this.#objects) {
3385
- this._logger.info(`${this.namespaceLog} subscribeObjects not processed because Objects database not connected`);
3386
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
3387
- }
3388
- if (pattern === '*') {
3389
- this.#objects.subscribeUser(`${this.namespace}.*`, options, callback);
3390
- }
3391
- else {
3392
- const fixedPattern = Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true);
3393
- this.#objects.subscribeUser(fixedPattern, options, callback);
3394
- }
3476
+ const cb = callback;
3477
+ this.#subscriptions
3478
+ .subscribeObjects(pattern, options)
3479
+ .then(() => cb?.(), (err) => cb?.(err));
3395
3480
  }
3396
3481
  /**
3397
3482
  * Unsubscribe on the changes of objects in this instance.
@@ -3415,17 +3500,10 @@ export class AdapterClass extends EventEmitter {
3415
3500
  if (options !== null && options !== undefined) {
3416
3501
  Validator.assertObject(options, 'options');
3417
3502
  }
3418
- if (!this.#objects) {
3419
- this._logger.info(`${this.namespaceLog} unsubscribeObjects not processed because Objects database not connected`);
3420
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
3421
- }
3422
- if (pattern === '*') {
3423
- this.#objects.unsubscribeUser(`${this.namespace}.*`, options, callback);
3424
- }
3425
- else {
3426
- const fixedPattern = Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true);
3427
- this.#objects.unsubscribeUser(fixedPattern, options, callback);
3428
- }
3503
+ const cb = callback;
3504
+ this.#subscriptions
3505
+ .unsubscribeObjects(pattern, options)
3506
+ .then(() => cb?.(), (err) => cb?.(err));
3429
3507
  }
3430
3508
  /**
3431
3509
  * Subscribe for the changes of objects in any instance.
@@ -3449,11 +3527,10 @@ export class AdapterClass extends EventEmitter {
3449
3527
  if (options !== null && options !== undefined) {
3450
3528
  Validator.assertObject(options, 'options');
3451
3529
  }
3452
- if (!this.#objects) {
3453
- this._logger.info(`${this.namespaceLog} subscribeForeignObjects not processed because Objects database not connected`);
3454
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
3455
- }
3456
- this.#objects.subscribeUser(pattern, options, callback);
3530
+ const cb = callback;
3531
+ this.#subscriptions
3532
+ .subscribeForeignObjects(pattern, options)
3533
+ .then(() => cb?.(), (err) => cb?.(err));
3457
3534
  }
3458
3535
  /**
3459
3536
  * Unsubscribe for the patterns on all objects.
@@ -3480,11 +3557,10 @@ export class AdapterClass extends EventEmitter {
3480
3557
  if (options !== null && options !== undefined) {
3481
3558
  Validator.assertObject(options, 'options');
3482
3559
  }
3483
- if (!this.#objects) {
3484
- this._logger.info(`${this.namespaceLog} unsubscribeForeignObjects not processed because Objects database not connected`);
3485
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
3486
- }
3487
- this.#objects.unsubscribeUser(pattern, options, callback);
3560
+ const cb = callback;
3561
+ this.#subscriptions
3562
+ .unsubscribeForeignObjects(pattern, options)
3563
+ .then(() => cb?.(), (err) => cb?.(err));
3488
3564
  }
3489
3565
  /**
3490
3566
  * Subscribe for the changes of files in specific instance.
@@ -3494,16 +3570,12 @@ export class AdapterClass extends EventEmitter {
3494
3570
  * @param options optional user context
3495
3571
  */
3496
3572
  subscribeForeignFiles(id, pattern, options) {
3497
- if (!this.#objects) {
3498
- this._logger.info(`${this.namespaceLog} subscribeForeignFiles not processed because Objects database not connected`);
3499
- throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
3500
- }
3501
3573
  Validator.assertString(id, 'id');
3502
3574
  Validator.assertPattern(pattern, 'pattern');
3503
3575
  if (options !== null && options !== undefined) {
3504
3576
  Validator.assertObject(options, 'options');
3505
3577
  }
3506
- return this.#objects.subscribeUserFile(id, pattern, options);
3578
+ return this.#subscriptions.subscribeForeignFiles(id, pattern, options);
3507
3579
  }
3508
3580
  /**
3509
3581
  * Unsubscribe for the changes of files on specific instance.
@@ -3516,16 +3588,12 @@ export class AdapterClass extends EventEmitter {
3516
3588
  if (!pattern) {
3517
3589
  pattern = '*';
3518
3590
  }
3519
- if (!this.#objects) {
3520
- this._logger.info(`${this.namespaceLog} unsubscribeForeignFiles not processed because Objects database not connected`);
3521
- throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
3522
- }
3523
3591
  Validator.assertString(id, 'id');
3524
3592
  Validator.assertPattern(pattern, 'pattern');
3525
3593
  if (options !== null && options !== undefined) {
3526
3594
  Validator.assertObject(options, 'options');
3527
3595
  }
3528
- return this.#objects.unsubscribeUserFile(id, pattern, options);
3596
+ return this.#subscriptions.unsubscribeForeignFiles(id, pattern, options);
3529
3597
  }
3530
3598
  /**
3531
3599
  * Same as {@link AdapterClass.setObject}, but with check if the object exists.
@@ -3680,11 +3748,11 @@ export class AdapterClass extends EventEmitter {
3680
3748
  return id;
3681
3749
  }
3682
3750
  /**
3683
- * @param deviceName
3684
- * @param common
3685
- * @param _native
3686
- * @param options
3687
- * @param callback
3751
+ * @param deviceName the name of the device
3752
+ * @param common the common section of the device object
3753
+ * @param _native the native section of the device object
3754
+ * @param options optional user context
3755
+ * @param callback return result
3688
3756
  * @deprecated use `this.extendObject` instead
3689
3757
  */
3690
3758
  createDevice(deviceName, common, _native, options, callback) {
@@ -3715,31 +3783,34 @@ export class AdapterClass extends EventEmitter {
3715
3783
  deviceName,
3716
3784
  _native,
3717
3785
  callback,
3718
- options,
3786
+ options: options,
3719
3787
  });
3720
3788
  }
3721
3789
  _createDevice(_options) {
3722
3790
  let { common, deviceName, _native } = _options;
3723
3791
  const { callback, options } = _options;
3724
- common = common || {};
3725
- common.name = common.name || deviceName;
3792
+ common ||= {};
3793
+ common.name ||= deviceName;
3726
3794
  deviceName = deviceName.replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_');
3727
3795
  _native = _native || {};
3728
- this.setObjectNotExists(deviceName, {
3796
+ const createResult = this.setObjectNotExists(deviceName, {
3729
3797
  type: 'device',
3730
3798
  common: common,
3731
3799
  native: _native,
3732
3800
  }, options, callback);
3801
+ if (createResult instanceof Promise) {
3802
+ createResult.catch(e => this._logger.error(`${this.namespaceLog} Cannot create device: ${e.message}`));
3803
+ }
3733
3804
  }
3734
3805
  /**
3735
3806
  * Name of channel must be in format "channel"
3736
3807
  *
3737
- * @param parentDevice
3738
- * @param channelName
3739
- * @param roleOrCommon
3740
- * @param _native
3741
- * @param options
3742
- * @param callback
3808
+ * @param parentDevice the parent device name
3809
+ * @param channelName the name of the channel
3810
+ * @param roleOrCommon role string or the common section of the channel object
3811
+ * @param _native the native section of the channel object
3812
+ * @param options optional user context
3813
+ * @param callback return result
3743
3814
  * @deprecated use `this.extendObject` instead
3744
3815
  */
3745
3816
  createChannel(parentDevice, channelName, roleOrCommon, _native, options, callback) {
@@ -3773,29 +3844,30 @@ export class AdapterClass extends EventEmitter {
3773
3844
  Validator.assertString(channelName, 'channelName');
3774
3845
  Validator.assertString(parentDevice, 'parentDevice');
3775
3846
  Validator.assertOptionalCallback(callback, 'callback');
3776
- common.name = common.name || channelName;
3847
+ common.name ||= channelName;
3777
3848
  if (parentDevice) {
3778
3849
  parentDevice = parentDevice.replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_');
3779
3850
  }
3780
3851
  channelName = channelName.replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_');
3781
3852
  // @ts-expect-error ts somehow loses types here
3782
3853
  channelName = this._DCS2ID(parentDevice, channelName);
3783
- _native = _native || {};
3854
+ _native ||= {};
3784
3855
  const obj = {
3856
+ _id: channelName,
3785
3857
  type: 'channel',
3786
- common: common,
3858
+ common,
3787
3859
  native: _native,
3788
3860
  };
3789
- this.setObjectNotExists(channelName, obj, options, callback);
3861
+ return this.setObjectNotExists(channelName, obj, options, callback);
3790
3862
  }
3791
3863
  /**
3792
- * @param parentDevice
3793
- * @param parentChannel
3794
- * @param stateName
3795
- * @param roleOrCommon
3796
- * @param _native
3797
- * @param options
3798
- * @param callback
3864
+ * @param parentDevice the parent device name
3865
+ * @param parentChannel the parent channel name
3866
+ * @param stateName the name of the state
3867
+ * @param roleOrCommon role string or the common section of the state object
3868
+ * @param _native the native section of the state object
3869
+ * @param options optional user context
3870
+ * @param callback return result
3799
3871
  * @deprecated use `this.extendObject` instead
3800
3872
  */
3801
3873
  createState(parentDevice, parentChannel, stateName, roleOrCommon, _native, options, callback) {
@@ -3822,12 +3894,13 @@ export class AdapterClass extends EventEmitter {
3822
3894
  write: false,
3823
3895
  name: '',
3824
3896
  role: roleOrCommon,
3897
+ type: 'mixed',
3825
3898
  };
3826
3899
  }
3827
3900
  else if (tools.isObject(roleOrCommon)) {
3828
3901
  common = roleOrCommon;
3829
3902
  }
3830
- _native = _native || {};
3903
+ _native ||= {};
3831
3904
  Validator.assertObject(common, 'common');
3832
3905
  Validator.assertString(stateName, 'stateName');
3833
3906
  Validator.assertString(parentDevice, 'parentDevice');
@@ -3842,9 +3915,9 @@ export class AdapterClass extends EventEmitter {
3842
3915
  _createState(_options) {
3843
3916
  const { _native, common, callback, options } = _options;
3844
3917
  let { parentChannel, parentDevice, stateName } = _options;
3845
- common.name = common.name || stateName;
3846
- common.read = common.read === undefined ? true : common.read;
3847
- common.write = common.write === undefined ? false : common.write;
3918
+ common.name ||= stateName;
3919
+ common.read ??= true;
3920
+ common.write ??= false;
3848
3921
  if (!common.role) {
3849
3922
  this._logger.error(`${this.namespaceLog} Try to create state ${parentDevice ? `${parentDevice}.` : ''}${parentChannel}.${stateName} without role`);
3850
3923
  return;
@@ -3914,7 +3987,7 @@ export class AdapterClass extends EventEmitter {
3914
3987
  common.def = max;
3915
3988
  }
3916
3989
  }
3917
- this.setObjectNotExists(id, {
3990
+ const stateResult = this.setObjectNotExists(id, {
3918
3991
  type: 'state',
3919
3992
  common: common,
3920
3993
  native: _native,
@@ -3922,32 +3995,29 @@ export class AdapterClass extends EventEmitter {
3922
3995
  if (err) {
3923
3996
  return tools.maybeCallbackWithError(callback, err);
3924
3997
  }
3925
- else if (common.def !== undefined) {
3998
+ if (common.def !== undefined) {
3926
3999
  this.getState(id, null, (err, state) => {
3927
4000
  if (!state) {
3928
4001
  if (common.defAck !== undefined) {
3929
- this.setState(id, common.def, common.defAck, options, callback);
4002
+ return this.setState(id, common.def, common.defAck, options, callback);
3930
4003
  }
3931
- else {
3932
- this.setState(id, common.def, options, callback);
3933
- }
3934
- }
3935
- else {
3936
- return tools.maybeCallback(callback);
4004
+ return this.setState(id, common.def, options, callback);
3937
4005
  }
4006
+ return tools.maybeCallback(callback);
3938
4007
  });
3939
4008
  }
3940
4009
  else {
3941
4010
  this.getState(id, null, (err, state) => {
3942
4011
  if (!state) {
3943
- this.setState(id, null, true, options, callback);
3944
- }
3945
- else {
3946
- return tools.maybeCallback(callback);
4012
+ return this.setState(id, null, true, options, callback);
3947
4013
  }
4014
+ return tools.maybeCallback(callback);
3948
4015
  });
3949
4016
  }
3950
4017
  });
4018
+ if (stateResult instanceof Promise) {
4019
+ stateResult.catch(e => this._logger.error(`${this.namespaceLog} Cannot create state object: ${e.message}`));
4020
+ }
3951
4021
  }
3952
4022
  /**
3953
4023
  * Delete device with all its channels and states.
@@ -4005,6 +4075,16 @@ export class AdapterClass extends EventEmitter {
4005
4075
  }
4006
4076
  return tools.maybeCallback(callback);
4007
4077
  }
4078
+ /**
4079
+ * Add a channel to an enum.
4080
+ *
4081
+ * @param enumName the enum category (e.g. rooms, functions)
4082
+ * @param addTo the enum id to add the channel to
4083
+ * @param parentDevice the parent device name
4084
+ * @param channelName the name of the channel
4085
+ * @param options optional user context
4086
+ * @param callback return result
4087
+ */
4008
4088
  addChannelToEnum(enumName, addTo, parentDevice, channelName, options, callback) {
4009
4089
  if (typeof options === 'function') {
4010
4090
  callback = options;
@@ -4052,11 +4132,11 @@ export class AdapterClass extends EventEmitter {
4052
4132
  obj.from = `system.adapter.${this.namespace}`;
4053
4133
  obj.user = options?.user || SYSTEM_ADMIN_USER;
4054
4134
  obj.ts = Date.now();
4055
- this.#objects.setObject(obj._id, obj, options, callback);
4056
- }
4057
- else {
4058
- return tools.maybeCallback(callback);
4135
+ return callback
4136
+ ? this.#objects.setObject(obj._id, obj, options, callback)
4137
+ : this.#objects.setObject(obj._id, obj, options);
4059
4138
  }
4139
+ return tools.maybeCallback(callback);
4060
4140
  }
4061
4141
  });
4062
4142
  }
@@ -4076,28 +4156,40 @@ export class AdapterClass extends EventEmitter {
4076
4156
  obj.from = `system.adapter.${this.namespace}`;
4077
4157
  obj.user = options?.user || SYSTEM_ADMIN_USER;
4078
4158
  obj.ts = Date.now();
4079
- this.#objects.setObject(obj._id, obj, options, callback);
4080
- }
4081
- else {
4082
- return tools.maybeCallback(callback);
4159
+ return callback
4160
+ ? this.#objects.setObject(obj._id, obj, options, callback)
4161
+ : this.#objects.setObject(obj._id, obj, options);
4083
4162
  }
4163
+ return tools.maybeCallback(callback);
4084
4164
  }
4085
- else {
4086
- // Create enum
4087
- this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4088
- common: {
4089
- name: addTo,
4090
- members: [objId],
4091
- },
4165
+ // Create enum
4166
+ return callback
4167
+ ? this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4168
+ common: { name: addTo, members: [objId] },
4092
4169
  from: `system.adapter.${this.namespace}`,
4093
4170
  ts: Date.now(),
4094
4171
  type: 'enum',
4095
4172
  native: {},
4096
- }, options, callback);
4097
- }
4173
+ }, options, callback)
4174
+ : this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4175
+ common: { name: addTo, members: [objId] },
4176
+ from: `system.adapter.${this.namespace}`,
4177
+ ts: Date.now(),
4178
+ type: 'enum',
4179
+ native: {},
4180
+ }, options);
4098
4181
  });
4099
4182
  }
4100
4183
  }
4184
+ /**
4185
+ * Remove a channel from an enum.
4186
+ *
4187
+ * @param enumName the enum category (e.g. rooms, functions)
4188
+ * @param parentDevice the parent device name
4189
+ * @param channelName the name of the channel
4190
+ * @param options optional user context
4191
+ * @param callback return result
4192
+ */
4101
4193
  deleteChannelFromEnum(enumName, parentDevice, channelName, options, callback) {
4102
4194
  if (typeof options === 'function') {
4103
4195
  callback = options;
@@ -4258,11 +4350,11 @@ export class AdapterClass extends EventEmitter {
4258
4350
  return tools.maybeCallback(callback);
4259
4351
  }
4260
4352
  /**
4261
- * @param parentDevice
4262
- * @param parentChannel
4263
- * @param stateName
4264
- * @param options
4265
- * @param callback
4353
+ * @param parentDevice the parent device name
4354
+ * @param parentChannel the parent channel name
4355
+ * @param stateName the name of the state
4356
+ * @param options optional user context
4357
+ * @param callback return result
4266
4358
  * @deprecated use `this.delObject` instead
4267
4359
  */
4268
4360
  deleteState(parentDevice, parentChannel, stateName, options, callback) {
@@ -4346,6 +4438,12 @@ export class AdapterClass extends EventEmitter {
4346
4438
  const _name = this._DCS2ID(parentDevice, parentChannel, stateName);
4347
4439
  this.delObject(_name, options, callback);
4348
4440
  }
4441
+ /**
4442
+ * Get all devices of this adapter.
4443
+ *
4444
+ * @param options optional user context, or the callback
4445
+ * @param callback return result
4446
+ */
4349
4447
  getDevices(options, callback) {
4350
4448
  if (typeof options === 'function' && typeof callback === 'object') {
4351
4449
  const tmp = callback;
@@ -4384,6 +4482,13 @@ export class AdapterClass extends EventEmitter {
4384
4482
  return tools.maybeCallbackWithError(callback, null, res);
4385
4483
  });
4386
4484
  }
4485
+ /**
4486
+ * Get all channels of this adapter or of the given device.
4487
+ *
4488
+ * @param parentDevice the parent device name, or the callback
4489
+ * @param options optional user context, or the callback
4490
+ * @param callback return result
4491
+ */
4387
4492
  getChannelsOf(parentDevice, options, callback) {
4388
4493
  if (typeof options === 'function') {
4389
4494
  callback = options;
@@ -4431,6 +4536,14 @@ export class AdapterClass extends EventEmitter {
4431
4536
  return tools.maybeCallbackWithError(options.callback, null, res);
4432
4537
  });
4433
4538
  }
4539
+ /**
4540
+ * Get all states of this adapter or of the given device/channel.
4541
+ *
4542
+ * @param parentDevice the parent device name, or the callback
4543
+ * @param parentChannel the parent channel name, or the callback
4544
+ * @param options optional user context, or the callback
4545
+ * @param callback return result
4546
+ */
4434
4547
  getStatesOf(parentDevice, parentChannel, options, callback) {
4435
4548
  if (typeof options === 'function') {
4436
4549
  callback = options;
@@ -4509,6 +4622,17 @@ export class AdapterClass extends EventEmitter {
4509
4622
  }
4510
4623
  });
4511
4624
  }
4625
+ /**
4626
+ * Add a state to an enum.
4627
+ *
4628
+ * @param enumName the enum category (e.g. rooms, functions)
4629
+ * @param addTo the enum id to add the state to
4630
+ * @param parentDevice the parent device name
4631
+ * @param parentChannel the parent channel name
4632
+ * @param stateName the name of the state
4633
+ * @param options optional user context
4634
+ * @param callback return result
4635
+ */
4512
4636
  addStateToEnum(enumName, addTo, parentDevice, parentChannel, stateName, options, callback) {
4513
4637
  if (typeof options === 'function') {
4514
4638
  callback = options;
@@ -4568,11 +4692,11 @@ export class AdapterClass extends EventEmitter {
4568
4692
  obj.from = `system.adapter.${this.namespace}`;
4569
4693
  obj.user = options?.user || SYSTEM_ADMIN_USER;
4570
4694
  obj.ts = Date.now();
4571
- this.#objects.setObject(obj._id, obj, options, callback);
4572
- }
4573
- else {
4574
- return tools.maybeCallback(callback);
4695
+ return callback
4696
+ ? this.#objects.setObject(obj._id, obj, options, callback)
4697
+ : this.#objects.setObject(obj._id, obj, options);
4575
4698
  }
4699
+ return tools.maybeCallback(callback);
4576
4700
  });
4577
4701
  }
4578
4702
  else {
@@ -4587,31 +4711,44 @@ export class AdapterClass extends EventEmitter {
4587
4711
  obj.from = `system.adapter.${this.namespace}`;
4588
4712
  obj.user = options?.user || SYSTEM_ADMIN_USER;
4589
4713
  obj.ts = Date.now();
4590
- this.#objects.setObject(obj._id, obj, callback);
4591
- }
4592
- else {
4593
- return tools.maybeCallback(callback);
4714
+ return callback
4715
+ ? this.#objects.setObject(obj._id, obj, callback)
4716
+ : this.#objects.setObject(obj._id, obj);
4594
4717
  }
4718
+ return tools.maybeCallback(callback);
4595
4719
  }
4596
- else {
4597
- if (err) {
4598
- return tools.maybeCallbackWithError(callback, err);
4599
- }
4600
- // Create enum
4601
- this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4602
- common: {
4603
- name: addTo,
4604
- members: [objId],
4605
- },
4720
+ if (err) {
4721
+ return tools.maybeCallbackWithError(callback, err);
4722
+ }
4723
+ // Create enum
4724
+ return callback
4725
+ ? this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4726
+ common: { name: addTo, members: [objId] },
4606
4727
  from: `system.adapter.${this.namespace}`,
4607
4728
  ts: Date.now(),
4608
4729
  type: 'enum',
4609
4730
  native: {},
4610
- }, options, callback);
4611
- }
4731
+ }, options, callback)
4732
+ : this.#objects.setObject(`enum.${enumName}.${addTo}`, {
4733
+ common: { name: addTo, members: [objId] },
4734
+ from: `system.adapter.${this.namespace}`,
4735
+ ts: Date.now(),
4736
+ type: 'enum',
4737
+ native: {},
4738
+ }, options);
4612
4739
  });
4613
4740
  }
4614
4741
  }
4742
+ /**
4743
+ * Remove a state from an enum.
4744
+ *
4745
+ * @param enumName the enum category (e.g. rooms, functions)
4746
+ * @param parentDevice the parent device name
4747
+ * @param parentChannel the parent channel name
4748
+ * @param stateName the name of the state
4749
+ * @param options optional user context
4750
+ * @param callback return result
4751
+ */
4615
4752
  deleteStateFromEnum(enumName, parentDevice, parentChannel, stateName, options, callback) {
4616
4753
  if (typeof options === 'function') {
4617
4754
  callback = options;
@@ -4823,6 +4960,14 @@ export class AdapterClass extends EventEmitter {
4823
4960
  }
4824
4961
  this.#objects.readDir(_adapter, path, options, callback);
4825
4962
  }
4963
+ /**
4964
+ * Deletes a file in the DB.
4965
+ *
4966
+ * @param _adapter adapter name, or null for the current adapter
4967
+ * @param name path to the file
4968
+ * @param options optional user context
4969
+ * @param callback return result
4970
+ */
4826
4971
  unlink(_adapter, name, options, callback) {
4827
4972
  if (_adapter === null) {
4828
4973
  _adapter = this.name;
@@ -4843,6 +4988,15 @@ export class AdapterClass extends EventEmitter {
4843
4988
  }
4844
4989
  this.#objects.unlink(_adapter, name, options, callback);
4845
4990
  }
4991
+ /**
4992
+ * Renames a file in the DB.
4993
+ *
4994
+ * @param _adapter adapter name, or null for the current adapter
4995
+ * @param oldName current path to the file
4996
+ * @param newName new path to the file
4997
+ * @param options optional user context
4998
+ * @param callback return result
4999
+ */
4846
5000
  rename(_adapter, oldName, newName, options, callback) {
4847
5001
  if (_adapter === null) {
4848
5002
  _adapter = this.name;
@@ -4862,8 +5016,16 @@ export class AdapterClass extends EventEmitter {
4862
5016
  this._logger.info(`${this.namespaceLog} rename not processed because Objects database not connected`);
4863
5017
  return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
4864
5018
  }
4865
- this.#objects.rename(_adapter, oldName, newName, options, callback);
5019
+ return this.#objects.rename(_adapter, oldName, newName, options, callback);
4866
5020
  }
5021
+ /**
5022
+ * Creates a directory in the DB.
5023
+ *
5024
+ * @param _adapter adapter name, or null for the current adapter
5025
+ * @param dirname path to the directory
5026
+ * @param options optional user context
5027
+ * @param callback return result
5028
+ */
4867
5029
  mkdir(_adapter, dirname, options, callback) {
4868
5030
  if (_adapter === null) {
4869
5031
  _adapter = this.name;
@@ -5001,6 +5163,13 @@ export class AdapterClass extends EventEmitter {
5001
5163
  return tools.maybeCallbackWithError(callback, e);
5002
5164
  }
5003
5165
  }
5166
+ /**
5167
+ * Formats a numeric value using the given format string.
5168
+ *
5169
+ * @param value the value to format
5170
+ * @param decimals number of decimals to keep, or the format string
5171
+ * @param _format optional format string for the decimal and thousands separator
5172
+ */
5004
5173
  formatValue(value, decimals, _format) {
5005
5174
  if (typeof decimals !== 'number') {
5006
5175
  _format = decimals;
@@ -5030,6 +5199,13 @@ export class AdapterClass extends EventEmitter {
5030
5199
  // @ts-expect-error fix later
5031
5200
  .replace(/\B(?=(\d{3})+(?!\d))/g, format[0]);
5032
5201
  }
5202
+ /**
5203
+ * Formats a date or duration according to the given format string.
5204
+ *
5205
+ * @param dateObj the date to format
5206
+ * @param isDuration whether the value represents a duration, or the format string
5207
+ * @param _format optional format string
5208
+ */
5033
5209
  formatDate(dateObj, isDuration, _format) {
5034
5210
  if ((typeof isDuration === 'string' && isDuration.toLowerCase() === 'duration') || isDuration === true) {
5035
5211
  isDuration = true;
@@ -5201,6 +5377,7 @@ export class AdapterClass extends EventEmitter {
5201
5377
  message = command;
5202
5378
  command = 'send';
5203
5379
  }
5380
+ // Legacy contract: bad arguments throw synchronously on the callback-style API.
5204
5381
  Validator.assertString(instanceName, 'instanceName');
5205
5382
  Validator.assertString(command, 'command');
5206
5383
  if (!tools.isObject(callback)) {
@@ -5209,13 +5386,42 @@ export class AdapterClass extends EventEmitter {
5209
5386
  if (options !== undefined) {
5210
5387
  Validator.assertObject(options, 'options');
5211
5388
  }
5212
- return this._sendTo({
5213
- instanceName,
5214
- command,
5215
- message,
5216
- options,
5217
- callback: callback,
5218
- });
5389
+ if (tools.isObject(callback)) {
5390
+ this.#fireAndForget(this.#async.sendTo(instanceName, command, message, this.#withSendFlags(options, { callback: callback })), 'Error in sendTo');
5391
+ return;
5392
+ }
5393
+ const cb = callback;
5394
+ if (typeof cb === 'function') {
5395
+ this.sendToAsync(instanceName, command, message, options).then((reply) => cb(reply), (err) => cb(err));
5396
+ return;
5397
+ }
5398
+ this.#fireAndForget(this.#async.sendTo(instanceName, command, message, this.#withSendFlags(options, { expectReply: false })), 'Error in sendTo');
5399
+ }
5400
+ /**
5401
+ * Merges send-control flags into the caller-supplied options for delegation to the async facade.
5402
+ * A non-object `options` is forwarded unchanged so the facade's validation surfaces it.
5403
+ *
5404
+ * @param options caller-supplied send options (unvalidated)
5405
+ * @param flags control flags to apply
5406
+ * @param flags.expectReply when false, the facade sends fire-and-forget
5407
+ * @param flags.callback legacy callback-info header to attach to the message
5408
+ */
5409
+ #withSendFlags(options, flags) {
5410
+ if (options !== undefined && !tools.isObject(options)) {
5411
+ return options;
5412
+ }
5413
+ return { ...options, ...flags };
5414
+ }
5415
+ /**
5416
+ * Observes a fire-and-forget promise from the callback-compat layer, logging any rejection without
5417
+ * rethrowing. Used only where the legacy callback API had no channel to surface the error; async
5418
+ * callers on {@link AsyncAdapter} instead await and catch.
5419
+ *
5420
+ * @param promise the promise to observe
5421
+ * @param errorMessage log prefix describing the failed action
5422
+ */
5423
+ #fireAndForget(promise, errorMessage) {
5424
+ promise.catch((e) => this._logger.error(`${this.namespaceLog} ${errorMessage}: ${e instanceof Error ? e.message : String(e)}`));
5219
5425
  }
5220
5426
  /**
5221
5427
  * Async version of sendTo
@@ -5227,124 +5433,16 @@ export class AdapterClass extends EventEmitter {
5227
5433
  * @param options optional options to define a timeout. This allows to get an error callback if no answer received in time (only if target is specific instance)
5228
5434
  */
5229
5435
  sendToAsync(instanceName, command, message, options) {
5230
- return new Promise((resolve, reject) => {
5231
- const callback = resOrError => {
5232
- if (resOrError instanceof Error) {
5233
- reject(resOrError);
5234
- }
5235
- resolve(resOrError);
5236
- };
5237
- // validation takes place inside sendTo so skip here
5238
- this.sendTo(instanceName, command, message, callback, options);
5239
- });
5240
- }
5241
- async _sendTo(_options) {
5242
- const { command, message, callback, options } = _options;
5243
- let { instanceName } = _options;
5244
- const obj = {
5245
- command,
5246
- message,
5247
- from: `system.adapter.${this.namespace}`,
5248
- };
5249
- if (!instanceName) {
5250
- // @ts-expect-error TODO it could also be the cb object
5251
- return tools.maybeCallbackWithError(callback, 'No instanceName provided or not a string');
5252
- }
5253
- if (!instanceName.startsWith('system.adapter.')) {
5254
- instanceName = `system.adapter.${instanceName}`;
5255
- }
5256
- if (!this.#states) {
5257
- // if states is no longer existing, we do not need to unsubscribe
5258
- this._logger.info(`${this.namespaceLog} sendTo not processed because States database not connected`);
5259
- // @ts-expect-error TODO it could also be the cb object
5260
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
5261
- }
5262
- if (typeof message !== 'object') {
5263
- this._logger.silly(`${this.namespaceLog} sendTo "${command}" to ${instanceName} from system.adapter.${this.namespace}: ${message}`);
5264
- }
5265
- else {
5266
- this._logger.silly(`${this.namespaceLog} sendTo "${command}" to ${instanceName} from system.adapter.${this.namespace}`);
5267
- }
5268
- // If not specific instance
5269
- if (!instanceName.match(/\.[0-9]+$/)) {
5270
- if (!this.#objects) {
5271
- this._logger.info(`${this.namespaceLog} sendTo not processed because Objects database not connected`);
5272
- // @ts-expect-error TODO it could also be the cb object
5273
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
5274
- }
5275
- try {
5276
- // Send it to all instances of adapter
5277
- const res = await this.#objects.getObjectView('system', 'instance', {
5278
- startkey: `${instanceName}.`,
5279
- endkey: `${instanceName}.\u9999`,
5280
- });
5281
- if (res) {
5282
- for (const row of res.rows) {
5283
- try {
5284
- await this.#states.pushMessage(row.id, obj);
5285
- }
5286
- catch (e) {
5287
- // @ts-expect-error TODO it could also be the cb object
5288
- return tools.maybeCallbackWithError(callback, e);
5289
- }
5290
- }
5291
- }
5292
- }
5293
- catch {
5294
- //ignore
5295
- }
5436
+ if (typeof message === 'function') {
5437
+ options = undefined;
5438
+ message = command;
5439
+ command = 'send';
5296
5440
  }
5297
- else {
5298
- if (callback) {
5299
- if (typeof callback === 'function') {
5300
- // force subscribe even no messagebox enabled
5301
- if (!isMessageboxSupported(this.common) && !this.mboxSubscribed) {
5302
- this.mboxSubscribed = true;
5303
- this.#states.subscribeMessage(`system.adapter.${this.namespace}`);
5304
- }
5305
- obj.callback = {
5306
- message,
5307
- id: this._callbackId++,
5308
- ack: false,
5309
- time: Date.now(),
5310
- };
5311
- if (this._callbackId >= 0xffffffff) {
5312
- this._callbackId = 1;
5313
- }
5314
- const callbackId = obj.callback.id;
5315
- let timer;
5316
- if (options?.timeout) {
5317
- timer = setTimeout(() => {
5318
- const callbackObj = this.messageCallbacks.get(callbackId);
5319
- if (callbackObj) {
5320
- callbackObj.cb(new Error('Timeout exceeded'));
5321
- this.messageCallbacks.delete(callbackId);
5322
- }
5323
- }, options.timeout);
5324
- }
5325
- this.messageCallbacks.set(callbackId, { cb: callback, time: Date.now(), timer });
5326
- // delete too old callbacks IDs
5327
- const now = Date.now();
5328
- for (const [_id, cb] of this.messageCallbacks) {
5329
- if (now - cb.time > 3_600_000) {
5330
- this.messageCallbacks.delete(_id);
5331
- }
5332
- }
5333
- }
5334
- else {
5335
- // callback is an object
5336
- obj.callback = callback;
5337
- obj.callback.ack = true;
5338
- }
5339
- }
5340
- try {
5341
- await this.#states.pushMessage(instanceName, obj);
5342
- }
5343
- catch (e) {
5344
- // @ts-expect-error TODO it could also be the cb object
5345
- return tools.maybeCallbackWithError(callback, e);
5346
- }
5441
+ else if (typeof message === 'undefined') {
5442
+ message = command;
5443
+ command = 'send';
5347
5444
  }
5445
+ return this.#async.sendTo(instanceName, command, message, this.#withSendFlags(options, { expectReply: true }));
5348
5446
  }
5349
5447
  /**
5350
5448
  * Send message to specific host or to all hosts.
@@ -5364,10 +5462,15 @@ export class AdapterClass extends EventEmitter {
5364
5462
  * ```
5365
5463
  */
5366
5464
  sendToHost(hostName, command, message, callback) {
5465
+ if (typeof message === 'function' && typeof callback === 'undefined') {
5466
+ callback = message;
5467
+ message = undefined;
5468
+ }
5367
5469
  if (typeof message === 'undefined') {
5368
5470
  message = command;
5369
5471
  command = 'send';
5370
5472
  }
5473
+ // Legacy contract: bad arguments throw synchronously on the callback-style API.
5371
5474
  if (hostName !== null) {
5372
5475
  Validator.assertString(hostName, 'hostName');
5373
5476
  }
@@ -5375,91 +5478,34 @@ export class AdapterClass extends EventEmitter {
5375
5478
  if (!tools.isObject(callback)) {
5376
5479
  Validator.assertOptionalCallback(callback, 'callback');
5377
5480
  }
5378
- return this._sendToHost({
5379
- hostName,
5380
- command,
5381
- message,
5382
- callback: callback,
5383
- });
5384
- }
5385
- async _sendToHost(_options) {
5386
- const { command, message, callback } = _options;
5387
- let { hostName } = _options;
5388
- const obj = { command, message, from: `system.adapter.${this.namespace}` };
5389
- if (!this.#states) {
5390
- // if states is no longer existing, we do not need to unsubscribe
5391
- this._logger.info(`${this.namespaceLog} sendToHost not processed because States database not connected`);
5392
- // @ts-expect-error TODO it could also be the cb object
5393
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
5394
- }
5395
- if (hostName && !hostName.startsWith('system.host.')) {
5396
- hostName = `system.host.${hostName}`;
5481
+ if (tools.isObject(callback)) {
5482
+ this.#fireAndForget(this.#async.sendToHost(hostName, command, message, {
5483
+ callback: callback,
5484
+ }), 'Error in sendToHost');
5485
+ return;
5397
5486
  }
5398
- if (!hostName) {
5399
- if (!this.#objects) {
5400
- this._logger.info(`${this.namespaceLog} sendToHost not processed because Objects database not connected`);
5401
- // @ts-expect-error TODO it could also be the cb object
5402
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
5403
- }
5404
- // Send to all hosts
5405
- this.#objects.getObjectList({
5406
- startkey: 'system.host.',
5407
- endkey: `system.host.\u9999`,
5408
- }, null, async (err, res) => {
5409
- if (!this.#states) {
5410
- // if states is no longer existing, we do not need to unsubscribe
5411
- return;
5412
- }
5413
- if (!err && res?.rows.length) {
5414
- for (const row of res.rows) {
5415
- const parts = row.id.split('.');
5416
- // ignore system.host.name.alive and so on
5417
- if (parts.length === 3) {
5418
- try {
5419
- await this.#states.pushMessage(row.id, obj);
5420
- }
5421
- catch (e) {
5422
- // @ts-expect-error TODO it could also be the cb object
5423
- return tools.maybeCallbackWithError(callback, e);
5424
- }
5425
- }
5426
- }
5427
- }
5428
- });
5487
+ const cb = callback;
5488
+ // A broadcast (hostName === null) yields many replies, so the callback is ignored — matching legacy behavior.
5489
+ if (typeof cb === 'function' && hostName !== null) {
5490
+ this.sendToHostAsync(hostName, command, message).then((reply) => cb(reply), (err) => cb(err));
5491
+ return;
5429
5492
  }
5430
- else {
5431
- if (callback) {
5432
- if (typeof callback === 'function') {
5433
- // force subscribe even no messagebox enabled
5434
- if (!isMessageboxSupported(this.common) && !this.mboxSubscribed) {
5435
- this.mboxSubscribed = true;
5436
- this.#states.subscribeMessage(`system.adapter.${this.namespace}`);
5437
- }
5438
- obj.callback = {
5439
- message,
5440
- id: this._callbackId++,
5441
- ack: false,
5442
- time: Date.now(),
5443
- };
5444
- if (this._callbackId >= 0xffffffff) {
5445
- this._callbackId = 1;
5446
- }
5447
- this.messageCallbacks.set(obj.callback.id, { cb: callback, time: Date.now() });
5448
- }
5449
- else {
5450
- // callback is an object
5451
- obj.callback = callback;
5452
- obj.callback.ack = true;
5453
- }
5454
- }
5455
- try {
5456
- await this.#states.pushMessage(hostName, obj);
5457
- }
5458
- catch (e) {
5459
- // @ts-expect-error TODO it could also be the cb object
5460
- return tools.maybeCallbackWithError(callback, e);
5461
- }
5493
+ this.#fireAndForget(this.#async.sendToHost(hostName, command, message, { expectReply: false }), 'Error in sendToHost');
5494
+ }
5495
+ /**
5496
+ * Async version of sendToHost
5497
+ *
5498
+ * @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.
5499
+ * @param command command name. One of: "cmdExec", "getRepository", "getInstalled", "getVersion", "getDiagData", "getLocationOnDisk", "getDevList", "getLogs", "delLogs", "readDirAsZip", "writeDirAsZip", "readObjectsAsZip", "writeObjectsAsZip", "checkLogging". Commands can be checked in controller.js (function processMessage)
5500
+ * @param message object that will be given as argument for request
5501
+ */
5502
+ sendToHostAsync(hostName, command, message) {
5503
+ if (typeof message === 'undefined') {
5504
+ message = command;
5505
+ command = 'send';
5462
5506
  }
5507
+ // broadcast (null host) yields many responses → never wait for a reply
5508
+ return this.#async.sendToHost(hostName, command, message, { expectReply: hostName !== null });
5463
5509
  }
5464
5510
  /**
5465
5511
  * Send a message to an active UI Client
@@ -5467,22 +5513,7 @@ export class AdapterClass extends EventEmitter {
5467
5513
  * @param options clientId and data options
5468
5514
  */
5469
5515
  sendToUI(options) {
5470
- if (!this.#states) {
5471
- throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
5472
- }
5473
- const { clientId, data } = options;
5474
- if (clientId === undefined) {
5475
- return this.uiMessagingController.sendToAllClients({
5476
- data,
5477
- states: this.#states,
5478
- });
5479
- }
5480
- Validator.assertString(clientId, 'clientId');
5481
- return this.uiMessagingController.sendToClient({
5482
- clientId,
5483
- data,
5484
- states: this.#states,
5485
- });
5516
+ return this.#async.sendToUI(options);
5486
5517
  }
5487
5518
  /**
5488
5519
  * Send notification with given scope and category to host of this adapter
@@ -5493,31 +5524,7 @@ export class AdapterClass extends EventEmitter {
5493
5524
  * @param options - Additional options for the notification, currently `contextData` is supported
5494
5525
  */
5495
5526
  async registerNotification(scope, category, message, options) {
5496
- if (!this.#states) {
5497
- // if states is no longer existing, we do not need to set
5498
- this._logger.info(`${this.namespaceLog} registerNotification not processed because States database not connected`);
5499
- throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
5500
- }
5501
- Validator.assertString(scope, 'scope');
5502
- if (category !== null) {
5503
- Validator.assertString(category, 'category');
5504
- }
5505
- Validator.assertString(message, 'message');
5506
- if (options !== undefined) {
5507
- Validator.assertObject(options, 'options');
5508
- }
5509
- const obj = {
5510
- command: 'addNotification',
5511
- message: {
5512
- scope,
5513
- category,
5514
- message,
5515
- instance: this.namespace,
5516
- contextData: options?.contextData,
5517
- },
5518
- from: `system.adapter.${this.namespace}`,
5519
- };
5520
- await this.#states.pushMessage(`system.host.${this.host}`, obj);
5527
+ return this.#async.registerNotification(scope, category, message, options);
5521
5528
  }
5522
5529
  /**
5523
5530
  * Writes value into states DB.
@@ -5736,7 +5743,7 @@ export class AdapterClass extends EventEmitter {
5736
5743
  // ignore
5737
5744
  }
5738
5745
  if (!userAcl) {
5739
- // User does not exists
5746
+ // User does not exist
5740
5747
  this._logger.error(`${this.namespaceLog} unknown user "${options.user}"`);
5741
5748
  return options;
5742
5749
  }
@@ -5758,8 +5765,7 @@ export class AdapterClass extends EventEmitter {
5758
5765
  // read all groups for this user
5759
5766
  this.users[options.user] = {
5760
5767
  groups: options.groups,
5761
- // @ts-expect-error TODO: UserCommon has no acl
5762
- acl: userAcl.common?.acl || {},
5768
+ acl: {},
5763
5769
  };
5764
5770
  await this._getGroups(options.groups);
5765
5771
  // combine all rights
@@ -5770,9 +5776,9 @@ export class AdapterClass extends EventEmitter {
5770
5776
  }
5771
5777
  const group = this.groups[gName];
5772
5778
  if (group.common?.acl?.file) {
5773
- if (!user.acl || !user.acl.file) {
5774
- user.acl = user.acl || {};
5775
- user.acl.file = user.acl.file || {};
5779
+ if (!user.acl?.file) {
5780
+ user.acl ||= {};
5781
+ user.acl.file ||= {};
5776
5782
  user.acl.file.create = group.common.acl.file.create;
5777
5783
  user.acl.file.read = group.common.acl.file.read;
5778
5784
  user.acl.file.write = group.common.acl.file.write;
@@ -5780,17 +5786,17 @@ export class AdapterClass extends EventEmitter {
5780
5786
  user.acl.file.list = group.common.acl.file.list;
5781
5787
  }
5782
5788
  else {
5783
- user.acl.file.create = user.acl.file.create || group.common.acl.file.create;
5784
- user.acl.file.read = user.acl.file.read || group.common.acl.file.read;
5785
- user.acl.file.write = user.acl.file.write || group.common.acl.file.write;
5786
- user.acl.file.delete = user.acl.file.delete || group.common.acl.file.delete;
5787
- user.acl.file.list = user.acl.file.list || group.common.acl.file.list;
5789
+ user.acl.file.create ||= group.common.acl.file.create;
5790
+ user.acl.file.read ||= group.common.acl.file.read;
5791
+ user.acl.file.write ||= group.common.acl.file.write;
5792
+ user.acl.file.delete ||= group.common.acl.file.delete;
5793
+ user.acl.file.list ||= group.common.acl.file.list;
5788
5794
  }
5789
5795
  }
5790
5796
  if (group.common?.acl?.object) {
5791
5797
  if (!user.acl || !user.acl.object) {
5792
- user.acl = user.acl || {};
5793
- user.acl.object = user.acl.object || {};
5798
+ user.acl ||= {};
5799
+ user.acl.object ||= {};
5794
5800
  user.acl.object.create = group.common.acl.object.create;
5795
5801
  user.acl.object.read = group.common.acl.object.read;
5796
5802
  user.acl.object.write = group.common.acl.object.write;
@@ -5798,17 +5804,17 @@ export class AdapterClass extends EventEmitter {
5798
5804
  user.acl.object.list = group.common.acl.object.list;
5799
5805
  }
5800
5806
  else {
5801
- user.acl.object.create = user.acl.object.create || group.common.acl.object.create;
5802
- user.acl.object.read = user.acl.object.read || group.common.acl.object.read;
5803
- user.acl.object.write = user.acl.object.write || group.common.acl.object.write;
5804
- user.acl.object.delete = user.acl.object.delete || group.common.acl.object.delete;
5805
- user.acl.object.list = user.acl.object.list || group.common.acl.object.list;
5807
+ user.acl.object.create ||= group.common.acl.object.create;
5808
+ user.acl.object.read ||= group.common.acl.object.read;
5809
+ user.acl.object.write ||= group.common.acl.object.write;
5810
+ user.acl.object.delete ||= group.common.acl.object.delete;
5811
+ user.acl.object.list ||= group.common.acl.object.list;
5806
5812
  }
5807
5813
  }
5808
5814
  if (group.common?.acl?.users) {
5809
5815
  if (!user.acl || !user.acl.users) {
5810
- user.acl = user.acl || {};
5811
- user.acl.users = user.acl.users || {};
5816
+ user.acl ||= {};
5817
+ user.acl.users ||= {};
5812
5818
  user.acl.users.create = group.common.acl.users.create;
5813
5819
  user.acl.users.read = group.common.acl.users.read;
5814
5820
  user.acl.users.write = group.common.acl.users.write;
@@ -5816,17 +5822,17 @@ export class AdapterClass extends EventEmitter {
5816
5822
  user.acl.users.list = group.common.acl.users.list;
5817
5823
  }
5818
5824
  else {
5819
- user.acl.users.create = user.acl.users.create || group.common.acl.users.create;
5820
- user.acl.users.read = user.acl.users.read || group.common.acl.users.read;
5821
- user.acl.users.write = user.acl.users.write || group.common.acl.users.write;
5822
- user.acl.users.delete = user.acl.users.delete || group.common.acl.users.delete;
5823
- user.acl.users.list = user.acl.users.list || group.common.acl.users.list;
5825
+ user.acl.users.create ||= group.common.acl.users.create;
5826
+ user.acl.users.read ||= group.common.acl.users.read;
5827
+ user.acl.users.write ||= group.common.acl.users.write;
5828
+ user.acl.users.delete ||= group.common.acl.users.delete;
5829
+ user.acl.users.list ||= group.common.acl.users.list;
5824
5830
  }
5825
5831
  }
5826
5832
  if (group.common?.acl?.state) {
5827
5833
  if (!user.acl || !user.acl.state) {
5828
- user.acl = user.acl || {};
5829
- user.acl.state = user.acl.state || {};
5834
+ user.acl ||= {};
5835
+ user.acl.state ||= {};
5830
5836
  user.acl.state.create = group.common.acl.state.create;
5831
5837
  user.acl.state.read = group.common.acl.state.read;
5832
5838
  user.acl.state.write = group.common.acl.state.write;
@@ -5834,11 +5840,11 @@ export class AdapterClass extends EventEmitter {
5834
5840
  user.acl.state.list = group.common.acl.state.list;
5835
5841
  }
5836
5842
  else {
5837
- user.acl.state.create = user.acl.state.create || group.common.acl.state.create;
5838
- user.acl.state.read = user.acl.state.read || group.common.acl.state.read;
5839
- user.acl.state.write = user.acl.state.write || group.common.acl.state.write;
5840
- user.acl.state.delete = user.acl.state.delete || group.common.acl.state.delete;
5841
- user.acl.state.list = user.acl.state.list || group.common.acl.state.list;
5843
+ user.acl.state.create ||= group.common.acl.state.create;
5844
+ user.acl.state.read ||= group.common.acl.state.read;
5845
+ user.acl.state.write ||= group.common.acl.state.write;
5846
+ user.acl.state.delete ||= group.common.acl.state.delete;
5847
+ user.acl.state.list ||= group.common.acl.state.list;
5842
5848
  }
5843
5849
  }
5844
5850
  }
@@ -5846,10 +5852,18 @@ export class AdapterClass extends EventEmitter {
5846
5852
  return options;
5847
5853
  }
5848
5854
  _checkState(obj, options, command) {
5855
+ // Members of the administrator group are not restricted by the ACL of a state, the same as for objects and files
5856
+ if (options.groups?.includes(SYSTEM_ADMIN_GROUP)) {
5857
+ return true;
5858
+ }
5849
5859
  const limitToOwnerRights = options.limitToOwnerRights === true;
5850
5860
  if (obj?.acl) {
5851
- obj.acl.state = obj.acl.state || obj.acl.object;
5861
+ obj.acl.state ||= obj.acl.object;
5852
5862
  if (obj.acl.state) {
5863
+ if (!options.acl) {
5864
+ this._logger.warn(`${this.namespaceLog} Permission error for user "${options.user} on "${obj._id}": ${command}`);
5865
+ return false;
5866
+ }
5853
5867
  // If user is owner
5854
5868
  if (options.user === obj.acl.owner) {
5855
5869
  if (command === 'setState' || command === 'delState') {
@@ -5876,7 +5890,7 @@ export class AdapterClass extends EventEmitter {
5876
5890
  this._logger.warn(`${this.namespaceLog} Called unknown command on "${obj._id}": ${command}`);
5877
5891
  }
5878
5892
  }
5879
- else if (options.groups.includes(obj.acl.ownerGroup) && !limitToOwnerRights) {
5893
+ else if (options.groups?.includes(obj.acl.ownerGroup) && !limitToOwnerRights) {
5880
5894
  if (command === 'setState' || command === 'delState') {
5881
5895
  if (command === 'delState' && !options.acl.state.delete) {
5882
5896
  this._logger.warn(`${this.namespaceLog} Permission error for user "${options.user} on "${obj._id}": ${command}`);
@@ -6166,7 +6180,6 @@ export class AdapterClass extends EventEmitter {
6166
6180
  return tools.maybeCallbackWithError(callback, null, res.id, res.notChanged);
6167
6181
  }
6168
6182
  const res = await this._setStateChangedHelper(fixedId, stateObj);
6169
- // @ts-expect-error todo fix it
6170
6183
  return tools.maybeCallbackWithError(callback, null, res.id, res.notChanged);
6171
6184
  }
6172
6185
  /**
@@ -6252,7 +6265,9 @@ export class AdapterClass extends EventEmitter {
6252
6265
  }
6253
6266
  // if state.from provided, we use it else, we set default property
6254
6267
  state.from =
6255
- typeof state.from === 'string' && state.from !== '' ? state.from : `system.adapter.${this.namespace}`;
6268
+ typeof state.from === 'string' && state.from !== ''
6269
+ ? state.from
6270
+ : `system.adapter.${this.namespace}`;
6256
6271
  state.user = options?.user || SYSTEM_ADMIN_USER;
6257
6272
  if (!id || typeof id !== 'string') {
6258
6273
  const warn = id ? `ID can be only string and not "${typeof id}"` : `Empty ID: ${JSON.stringify(state)}`;
@@ -6260,10 +6275,12 @@ export class AdapterClass extends EventEmitter {
6260
6275
  return tools.maybeCallbackWithError(callback, warn);
6261
6276
  }
6262
6277
  id = this.fixForbiddenCharsInId(id);
6263
- if (options?.user && options.user !== SYSTEM_ADMIN_USER) {
6278
+ if (options?.user &&
6279
+ options.user !== SYSTEM_ADMIN_USER) {
6264
6280
  let obj;
6265
6281
  try {
6266
- obj = (await this._checkStates(id, options, 'setState')).objs[0];
6282
+ obj = (await this._checkStates(id, options, 'setState'))
6283
+ .objs[0];
6267
6284
  }
6268
6285
  catch (e) {
6269
6286
  return tools.maybeCallbackWithError(callback, e);
@@ -6311,8 +6328,8 @@ export class AdapterClass extends EventEmitter {
6311
6328
  this.outputCount++;
6312
6329
  this.#states.setState(aliasId, tools.formatAliasValue({
6313
6330
  sourceCommon: obj?.common,
6314
- targetCommon: targetObj && targetObj.common,
6315
- state,
6331
+ targetCommon: targetObj?.common,
6332
+ state: state,
6316
6333
  logger: this._logger,
6317
6334
  logNamespace: this.namespaceLog,
6318
6335
  sourceId: obj?._id,
@@ -6375,7 +6392,7 @@ export class AdapterClass extends EventEmitter {
6375
6392
  this.#states.setState(targetId, tools.formatAliasValue({
6376
6393
  sourceCommon: obj.common,
6377
6394
  targetCommon: targetObj?.common,
6378
- state,
6395
+ state: state,
6379
6396
  logger: this._logger,
6380
6397
  logNamespace: this.namespaceLog,
6381
6398
  sourceId: obj._id,
@@ -6475,7 +6492,8 @@ export class AdapterClass extends EventEmitter {
6475
6492
  // wrap non-object values in a state object
6476
6493
  state = state !== undefined ? { val: state } : {};
6477
6494
  }
6478
- if (state.val === undefined && !Object.keys(state).length) {
6495
+ if (state.val === undefined &&
6496
+ !Object.keys(state).length) {
6479
6497
  // undefined is not allowed as state.val -> return
6480
6498
  this._logger.info(`${this.namespaceLog} undefined is not a valid state value for id "${id}"`);
6481
6499
  // TODO: reactivate line below + test in in next controller version (02.05.2021)
@@ -6486,10 +6504,14 @@ export class AdapterClass extends EventEmitter {
6486
6504
  }
6487
6505
  // if state.from provided, we use it else, we set default property
6488
6506
  state.from =
6489
- typeof state.from === 'string' && state.from !== '' ? state.from : `system.adapter.${this.namespace}`;
6490
- state.user = options?.user || SYSTEM_ADMIN_USER;
6507
+ typeof state.from === 'string' && state.from !== ''
6508
+ ? state.from
6509
+ : `system.adapter.${this.namespace}`;
6510
+ state.user =
6511
+ options?.user || SYSTEM_ADMIN_USER;
6491
6512
  id = this.fixForbiddenCharsInId(id);
6492
- if (options?.user && options.user !== SYSTEM_ADMIN_USER) {
6513
+ if (options?.user &&
6514
+ options.user !== SYSTEM_ADMIN_USER) {
6493
6515
  try {
6494
6516
  await this._checkStates(id, options, 'setState');
6495
6517
  }
@@ -6517,7 +6539,7 @@ export class AdapterClass extends EventEmitter {
6517
6539
  * }
6518
6540
  * ```
6519
6541
  *
6520
- * See possible attributes of the state in @setState explanation
6542
+ * See possible attributes of the state in `setState` explanation
6521
6543
  */
6522
6544
  getState(id, options, callback) {
6523
6545
  // we use any types here, because validation takes place in foreign method
@@ -6542,7 +6564,7 @@ export class AdapterClass extends EventEmitter {
6542
6564
  * }
6543
6565
  * ```
6544
6566
  *
6545
- * See possible attributes of the state in @setState explanation
6567
+ * See possible attributes of the state in `setState` explanation
6546
6568
  */
6547
6569
  getForeignState(id, options, callback) {
6548
6570
  if (typeof options === 'function') {
@@ -6646,7 +6668,7 @@ export class AdapterClass extends EventEmitter {
6646
6668
  if (this.oStates && this.oStates[id]) {
6647
6669
  return tools.maybeCallbackWithError(callback, null, this.oStates[id]);
6648
6670
  }
6649
- return this.#states.getState(id, callback);
6671
+ return callback ? this.#states.getState(id, callback) : this.#states.getState(id);
6650
6672
  }
6651
6673
  }
6652
6674
  // find out default history instance
@@ -6731,7 +6753,7 @@ export class AdapterClass extends EventEmitter {
6731
6753
  * }
6732
6754
  * ```
6733
6755
  *
6734
- * See possible attributes of the state in @setState explanation
6756
+ * See possible attributes of the state in `setState` explanation
6735
6757
  */
6736
6758
  getHistory(id, options, callback) {
6737
6759
  if (typeof options === 'function') {
@@ -6753,13 +6775,12 @@ export class AdapterClass extends EventEmitter {
6753
6775
  this._utils.validateId(id, true, null);
6754
6776
  }
6755
6777
  catch (e) {
6756
- // @ts-expect-error
6757
6778
  return tools.maybeCallbackWithError(callback, e);
6758
6779
  }
6759
- options = options || {};
6760
- options.end = options.end || Date.now() + 5000000;
6780
+ options ||= {};
6781
+ options.end ||= Date.now() + 5000000;
6761
6782
  if (!options.count && !options.start) {
6762
- options.start = options.start || Date.now() - 604800000; // - 1 week
6783
+ options.start ||= Date.now() - 604800000; // - 1 week
6763
6784
  }
6764
6785
  if (!options.instance) {
6765
6786
  if (!this.defaultHistory) {
@@ -6774,12 +6795,10 @@ export class AdapterClass extends EventEmitter {
6774
6795
  await this._checkStates(id, options, 'getState');
6775
6796
  }
6776
6797
  catch (e) {
6777
- // @ts-expect-error
6778
6798
  return tools.maybeCallbackWithError(callback, e);
6779
6799
  }
6780
6800
  }
6781
- this.sendTo(options.instance || 'history.0', 'getHistory', { id, options }, res => {
6782
- // @ts-expect-error
6801
+ this.sendTo(options.instance || 'history.0', 'getHistory', { id, options }, (res) => {
6783
6802
  tools.maybeCallbackWithError(callback, res.error, res.result, res.step, res.sessionId);
6784
6803
  });
6785
6804
  }
@@ -6806,7 +6825,7 @@ export class AdapterClass extends EventEmitter {
6806
6825
  }
6807
6826
  /**
6808
6827
  * Deletes a state of this instance.
6809
- * The object will NOT be deleted. If you want to delete it too, use @delObject instead.
6828
+ * The object will NOT be deleted. If you want to delete it too, use `delObject` instead.
6810
6829
  *
6811
6830
  * It is not required to provice the adapter namespace, because it will automatically be added.
6812
6831
  * E.g. to delete "adapterName.X.myObject", only "myObject" is required as ID.
@@ -6849,7 +6868,7 @@ export class AdapterClass extends EventEmitter {
6849
6868
  }
6850
6869
  /**
6851
6870
  * Deletes a state of any adapter.
6852
- * The object is NOT deleted. If you want to delete it too, use @delForeignObject instead.
6871
+ * The object is NOT deleted. If you want to delete it too, use `delForeignObject` instead.
6853
6872
  *
6854
6873
  * No error is returned if state does not exist.
6855
6874
  *
@@ -6893,7 +6912,12 @@ export class AdapterClass extends EventEmitter {
6893
6912
  return tools.maybeCallbackWithError(callback, e);
6894
6913
  }
6895
6914
  }
6896
- await this.#states.delState(id, callback);
6915
+ if (callback) {
6916
+ this.#states.delState(id, callback);
6917
+ }
6918
+ else {
6919
+ await this.#states.delState(id);
6920
+ }
6897
6921
  }
6898
6922
  /**
6899
6923
  * Read all states of this adapter, that pass the pattern
@@ -6928,7 +6952,7 @@ export class AdapterClass extends EventEmitter {
6928
6952
  const arr = await this.#states.getStates(keys);
6929
6953
  const result = {};
6930
6954
  for (let i = 0; i < keys.length; i++) {
6931
- const obj = targetObjs && targetObjs[i];
6955
+ const obj = targetObjs?.[i];
6932
6956
  if (obj?.common?.alias && srcObjs) {
6933
6957
  const srcObj = srcObjs[i];
6934
6958
  if (srcObj) {
@@ -7041,12 +7065,12 @@ export class AdapterClass extends EventEmitter {
7041
7065
  async _getForeignStates(_options) {
7042
7066
  const { options, pattern, callback } = _options;
7043
7067
  if (!this.#states) {
7044
- // if states is no longer existing, we do not need to unsubscribe
7068
+ // if states are no longer existing, we do not need to unsubscribe
7045
7069
  this._logger.info(`${this.namespaceLog} getForeignStates not processed because States database not connected`);
7046
7070
  return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7047
7071
  }
7048
7072
  if (!this.#objects) {
7049
- // if states is no longer existing, we do not need to unsubscribe
7073
+ // if states are no longer existing, we do not need to unsubscribe
7050
7074
  this._logger.info(`${this.namespaceLog} getForeignStates not processed because Objects database not connected`);
7051
7075
  return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7052
7076
  }
@@ -7063,7 +7087,7 @@ export class AdapterClass extends EventEmitter {
7063
7087
  }
7064
7088
  }
7065
7089
  else {
7066
- const res = await this._processStates(pattern, options?._objects);
7090
+ const res = await this._processStates(pattern, options._objects);
7067
7091
  return tools.maybeCallbackWithError(callback, null, res);
7068
7092
  }
7069
7093
  }
@@ -7131,90 +7155,6 @@ export class AdapterClass extends EventEmitter {
7131
7155
  }
7132
7156
  }
7133
7157
  }
7134
- /**
7135
- * Add subscription for given alias, if it is not a state it will be ignored
7136
- *
7137
- * @param aliasObj the alias object
7138
- * @param pattern pattern to subscribe for
7139
- */
7140
- async _addAliasSubscribe(aliasObj, pattern) {
7141
- if (aliasObj.type !== 'state') {
7142
- // no state types do not need to be subscribed
7143
- return;
7144
- }
7145
- if (!aliasObj.common?.alias?.id) {
7146
- // if state and no id given
7147
- this._logger.warn(`${this.namespaceLog} Alias ${aliasObj._id} has no target 5`);
7148
- throw new Error(`Alias ${aliasObj._id} has no target`);
7149
- }
7150
- // id can be string or can have attribute read
7151
- const sourceId = tools.isObject(aliasObj.common.alias.id)
7152
- ? aliasObj.common.alias.id.read
7153
- : aliasObj.common.alias.id;
7154
- // validate here because we use objects/states db directly
7155
- try {
7156
- this._utils.validateId(sourceId, true, null);
7157
- }
7158
- catch (e) {
7159
- throw new Error(`Error validating alias id of ${aliasObj._id}: ${e.message}`);
7160
- }
7161
- const targetEntry = {
7162
- alias: deepClone(aliasObj.common.alias),
7163
- id: aliasObj._id,
7164
- pattern,
7165
- type: aliasObj.common.type,
7166
- max: aliasObj.common.max,
7167
- min: aliasObj.common.min,
7168
- unit: aliasObj.common.unit,
7169
- };
7170
- let aliasDetails;
7171
- if (!this.aliases.has(sourceId)) {
7172
- aliasDetails = { targets: [] };
7173
- // add the alias before doing anything async, so if a delete comes in between we can detect it
7174
- this.aliases.set(sourceId, aliasDetails);
7175
- }
7176
- else {
7177
- aliasDetails = this.aliases.get(sourceId);
7178
- }
7179
- if (!aliasDetails.source) {
7180
- await this.#states.subscribe(sourceId);
7181
- // we ignore permissions on the source object and thus get it as admin user
7182
- const sourceObj = await this.#objects.getObject(sourceId, { user: SYSTEM_ADMIN_USER });
7183
- // if we have a common and the alias has not been removed in-between
7184
- if (sourceObj?.common && this.aliases.has(sourceObj._id)) {
7185
- aliasDetails.source = {
7186
- min: sourceObj.common.min,
7187
- max: sourceObj.common.max,
7188
- type: sourceObj.common.type,
7189
- unit: sourceObj.common.unit,
7190
- };
7191
- }
7192
- }
7193
- // add the alias target after we have ensured that we have the source set
7194
- aliasDetails.targets.push(targetEntry);
7195
- }
7196
- /**
7197
- * Remove an alias subscribe
7198
- *
7199
- * @param sourceId id of the source object
7200
- * @param aliasObjOrIdx the alias target or the index of the targets array
7201
- */
7202
- async _removeAliasSubscribe(sourceId, aliasObjOrIdx) {
7203
- if (!this.aliases.has(sourceId)) {
7204
- return;
7205
- }
7206
- const alias = this.aliases.get(sourceId);
7207
- // remove from targets array
7208
- const pos = typeof aliasObjOrIdx === 'number' ? aliasObjOrIdx : alias.targets.indexOf(aliasObjOrIdx);
7209
- if (pos !== -1) {
7210
- alias.targets.splice(pos, 1);
7211
- // unsubscribe if no more aliases exists
7212
- if (!alias.targets.length) {
7213
- this.aliases.delete(sourceId);
7214
- await this.#states.unsubscribe(sourceId);
7215
- }
7216
- }
7217
- }
7218
7158
  /**
7219
7159
  * Subscribe for changes on all states of all adapters (and system states), that pass the pattern
7220
7160
  *
@@ -7241,167 +7181,10 @@ export class AdapterClass extends EventEmitter {
7241
7181
  Validator.assertObject(options, 'options');
7242
7182
  }
7243
7183
  Validator.assertPattern(pattern, 'pattern');
7244
- return this._subscribeForeignStates({ pattern, options, callback });
7245
- }
7246
- async _subscribeForeignStates(_options) {
7247
- const { pattern, options, callback } = _options;
7248
- // Todo check rights for options
7249
- await this._autoSubscribeOn();
7250
- if (!this.#states) {
7251
- // if states is no longer existing, we do not need to unsubscribe
7252
- this._logger.info(`${this.namespaceLog} subscribeForeignStates not processed because States database not connected`);
7253
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7254
- }
7255
- if (!this.#objects) {
7256
- this._logger.info(`${this.namespaceLog} subscribeForeignStates not processed because Objects database not connected`);
7257
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7258
- }
7259
- // compare if this pattern for one of auto-subscribe adapters
7260
- for (const autoSubEntry of this.autoSubscribe) {
7261
- if (typeof pattern === 'string' && (pattern === '*' || pattern.startsWith(`${autoSubEntry}.`))) {
7262
- // put this pattern into adapter list
7263
- let state;
7264
- try {
7265
- state = await this.#states.getState(`system.adapter.${autoSubEntry}.subscribes`);
7266
- }
7267
- catch {
7268
- // ignore
7269
- }
7270
- state = state || { val: '{}' };
7271
- state.val = state.val || '{}';
7272
- let subs;
7273
- try {
7274
- subs = JSON.parse(state.val);
7275
- }
7276
- catch {
7277
- this._logger.error(`${this.namespaceLog} Cannot parse subscribes for "${autoSubEntry}.subscribes"`);
7278
- }
7279
- // validate that correct structure read from state.val
7280
- if (!tools.isObject(subs)) {
7281
- subs = {};
7282
- }
7283
- if (!tools.isObject(subs[pattern])) {
7284
- subs[pattern] = {};
7285
- }
7286
- if (typeof subs[pattern][this.namespace] !== 'number') {
7287
- subs[pattern][this.namespace] = 0;
7288
- }
7289
- subs[pattern][this.namespace]++;
7290
- this.outputCount++;
7291
- await this.#states.setState(`system.adapter.${autoSubEntry}.subscribes`, JSON.stringify(subs));
7292
- }
7293
- }
7294
- if (Array.isArray(pattern)) {
7295
- // get all aliases
7296
- const aliasesIds = pattern
7297
- .map(id => (id.startsWith(ALIAS_STARTS_WITH) ? id : null))
7298
- .filter(id => id);
7299
- // get all non aliases
7300
- const nonAliasesIds = pattern
7301
- .map(id => (!id.startsWith(ALIAS_STARTS_WITH) ? id : null))
7302
- .filter(id => id);
7303
- for (const aliasPattern of pattern) {
7304
- if ((aliasPattern.startsWith(ALIAS_STARTS_WITH) || aliasPattern.includes('*')) &&
7305
- !this.aliasPatterns.has(aliasPattern)) {
7306
- // it's a new alias conform pattern to store
7307
- this.aliasPatterns.add(aliasPattern);
7308
- }
7309
- }
7310
- const promises = [];
7311
- if (aliasesIds.length) {
7312
- if (!this._aliasObjectsSubscribed) {
7313
- this._aliasObjectsSubscribed = true;
7314
- this.#objects.subscribe(`${ALIAS_STARTS_WITH}*`);
7315
- }
7316
- const aliasObjs = await this._getObjectsByArray(aliasesIds, options);
7317
- for (const aliasObj of aliasObjs) {
7318
- if (aliasObj) {
7319
- promises.push(this._addAliasSubscribe(aliasObj, aliasObj._id));
7320
- }
7321
- }
7322
- }
7323
- if (nonAliasesIds.length) {
7324
- for (const id of nonAliasesIds) {
7325
- promises.push(new Promise(resolve => this.#states.subscribeUser(id, resolve)));
7326
- }
7327
- }
7328
- try {
7329
- await Promise.all(promises);
7330
- }
7331
- catch (e) {
7332
- this._logger.error(`${this.namespaceLog} Error on "subscribeForeignStates": ${e.message}`);
7333
- }
7334
- return tools.maybeCallback(callback);
7335
- }
7336
- else if (pattern.includes('*')) {
7337
- if (pattern === '*' || pattern.startsWith(ALIAS_STARTS_WITH)) {
7338
- if (!this._aliasObjectsSubscribed) {
7339
- this._aliasObjectsSubscribed = true;
7340
- this.#objects.subscribe(`${ALIAS_STARTS_WITH}*`);
7341
- }
7342
- // read all aliases
7343
- try {
7344
- // @ts-expect-error adjust types
7345
- const objs = await this.getForeignObjectsAsync(pattern, null, null, options);
7346
- const promises = [];
7347
- if (!this.aliasPatterns.has(pattern)) {
7348
- // it's a new pattern to store
7349
- this.aliasPatterns.add(pattern);
7350
- }
7351
- for (const id of Object.keys(objs)) {
7352
- // If alias
7353
- if (id.startsWith(ALIAS_STARTS_WITH)) {
7354
- const aliasObj = objs[id];
7355
- promises.push(this._addAliasSubscribe(aliasObj, pattern));
7356
- }
7357
- }
7358
- try {
7359
- await Promise.all(promises);
7360
- }
7361
- catch (e) {
7362
- this._logger.error(`${this.namespaceLog} Error on "subscribeForeignStates": ${e.message}`);
7363
- }
7364
- if (!this.#states) {
7365
- // if states is no longer existing, we do not need to unsubscribe
7366
- this._logger.info(`${this.namespaceLog} subscribeForeignStates not processed because States database not connected`);
7367
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7368
- }
7369
- if (promises.length && pattern !== '*') {
7370
- return tools.maybeCallback(callback);
7371
- }
7372
- // no alias objects found or pattern *
7373
- this.#states.subscribeUser(pattern, callback);
7374
- }
7375
- catch (e) {
7376
- this._logger.warn(`${this.namespaceLog} Cannot subscribe to ${pattern}: ${e.message}`);
7377
- return tools.maybeCallbackWithError(callback, e);
7378
- }
7379
- }
7380
- else {
7381
- this.#states.subscribeUser(pattern, callback);
7382
- }
7383
- }
7384
- else if (pattern.startsWith(ALIAS_STARTS_WITH)) {
7385
- if (!this._aliasObjectsSubscribed) {
7386
- this._aliasObjectsSubscribed = true;
7387
- this.#objects.subscribe(`${ALIAS_STARTS_WITH}*`);
7388
- }
7389
- // just read one alias Object
7390
- try {
7391
- const aliasObj = await this.#objects.getObject(pattern, options);
7392
- if (aliasObj) {
7393
- await this._addAliasSubscribe(aliasObj, pattern);
7394
- return tools.maybeCallback(callback);
7395
- }
7396
- return tools.maybeCallback(callback);
7397
- }
7398
- catch (e) {
7399
- this._logger.warn(`${this.namespaceLog} cannot subscribe on alias "${pattern}": ${e.message}`);
7400
- }
7401
- }
7402
- else {
7403
- this.#states.subscribeUser(pattern, callback);
7404
- }
7184
+ const cb = callback;
7185
+ this.#subscriptions
7186
+ .subscribeForeignStates(pattern, options)
7187
+ .then(() => cb?.(), (err) => cb?.(err));
7405
7188
  }
7406
7189
  /**
7407
7190
  * Unsubscribe for changes for given pattern
@@ -7435,102 +7218,10 @@ export class AdapterClass extends EventEmitter {
7435
7218
  if (options !== null && options !== undefined) {
7436
7219
  Validator.assertObject(options, 'options');
7437
7220
  }
7438
- return this._unsubscribeForeignStates({ pattern, options, callback });
7439
- }
7440
- async _unsubscribeForeignStates(_options) {
7441
- const { pattern, callback } = _options;
7442
- if (!this.#states) {
7443
- // if states is no longer existing, we do not need to unsubscribe
7444
- this._logger.info(`${this.namespaceLog} unsubscrubeForeignStates not processed because States database not connected`);
7445
- return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7446
- }
7447
- if (this.autoSubscribe && typeof pattern === 'string') {
7448
- for (const autoSub of this.autoSubscribe) {
7449
- if (pattern === '*' || pattern.substring(0, autoSub.length + 1) === `${autoSub}.`) {
7450
- // remove this pattern from adapter list
7451
- let state;
7452
- try {
7453
- state = await this.#states.getState(`system.adapter.${autoSub}.subscribes`);
7454
- }
7455
- catch {
7456
- // ignore
7457
- }
7458
- if (!state || !state.val) {
7459
- continue;
7460
- }
7461
- let subs;
7462
- try {
7463
- subs = JSON.parse(state.val);
7464
- }
7465
- catch {
7466
- this._logger.error(`${this.namespaceLog} Cannot parse subscribes for "${autoSub}.subscribes"`);
7467
- continue;
7468
- }
7469
- if (!tools.isObject(subs) ||
7470
- !tools.isObject(subs[pattern]) ||
7471
- subs[pattern][this.namespace] === undefined) {
7472
- // check subs is a valid object, because it comes from state.val
7473
- continue;
7474
- }
7475
- if (typeof subs[pattern][this.namespace] === 'number') {
7476
- subs[pattern][this.namespace]--;
7477
- if (subs[pattern][this.namespace] <= 0) {
7478
- delete subs[pattern][this.namespace];
7479
- }
7480
- }
7481
- else {
7482
- // corrupted info, we can only delete
7483
- delete subs[pattern][this.namespace];
7484
- }
7485
- // if no other subs are there
7486
- if (!Object.keys(subs[pattern]).length) {
7487
- delete subs[pattern];
7488
- }
7489
- this.outputCount++;
7490
- await this.#states.setState(`system.adapter.${autoSub}.subscribes`, JSON.stringify(subs));
7491
- }
7492
- }
7493
- }
7494
- let aliasPattern;
7495
- const promises = [];
7496
- if (Array.isArray(pattern)) {
7497
- // process every entry as single unsubscribe
7498
- for (const _pattern of pattern) {
7499
- promises.push(this.unsubscribeForeignStatesAsync(_pattern));
7500
- }
7501
- }
7502
- else if (pattern.includes('*') || pattern.startsWith(ALIAS_STARTS_WITH)) {
7503
- if (pattern === '*' || pattern.startsWith(ALIAS_STARTS_WITH)) {
7504
- aliasPattern = pattern; // check all aliases
7505
- if (pattern === '*') {
7506
- promises.push(this.#states.unsubscribeUser(pattern));
7507
- }
7508
- }
7509
- else {
7510
- promises.push(this.#states.unsubscribeUser(pattern));
7511
- }
7512
- }
7513
- else {
7514
- promises.push(this.#states.unsubscribeUser(pattern));
7515
- }
7516
- if (aliasPattern) {
7517
- // if pattern known, remove it from alias patterns to not subscribe to further matching aliases
7518
- this.aliasPatterns.delete(aliasPattern);
7519
- for (const [sourceId, alias] of this.aliases) {
7520
- for (let i = alias.targets.length - 1; i >= 0; i--) {
7521
- if (alias.targets[i].pattern === aliasPattern) {
7522
- promises.push(this._removeAliasSubscribe(sourceId, i));
7523
- }
7524
- }
7525
- }
7526
- }
7527
- await Promise.all(promises);
7528
- // if no alias subscribed any longer, remove subscription
7529
- if (!this.aliases.size && this._aliasObjectsSubscribed) {
7530
- this._aliasObjectsSubscribed = false;
7531
- this.#objects.unsubscribe(`${ALIAS_STARTS_WITH}*`);
7532
- }
7533
- return tools.maybeCallback(callback);
7221
+ const cb = callback;
7222
+ this.#subscriptions
7223
+ .unsubscribeForeignStates(pattern, options)
7224
+ .then(() => cb?.(), (err) => cb?.(err));
7534
7225
  }
7535
7226
  /**
7536
7227
  * Subscribe for changes on all states of this instance, that pass the pattern
@@ -7554,11 +7245,10 @@ export class AdapterClass extends EventEmitter {
7554
7245
  if (options !== null && options !== undefined) {
7555
7246
  Validator.assertObject(options, 'options');
7556
7247
  }
7557
- return this._subscribeForeignStates({
7558
- pattern: Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true),
7559
- options,
7560
- callback,
7561
- });
7248
+ const cb = callback;
7249
+ this.#subscriptions
7250
+ .subscribeForeignStates(Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true), options)
7251
+ .then(() => cb?.(), (err) => cb?.(err));
7562
7252
  }
7563
7253
  /**
7564
7254
  * Unsubscribe for changes for given pattern for own states.
@@ -7584,11 +7274,10 @@ export class AdapterClass extends EventEmitter {
7584
7274
  if (options !== null && options !== undefined) {
7585
7275
  Validator.assertObject(options, 'options');
7586
7276
  }
7587
- return this._unsubscribeForeignStates({
7588
- pattern: Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true),
7589
- options,
7590
- callback,
7591
- });
7277
+ const cb = callback;
7278
+ this.#subscriptions
7279
+ .unsubscribeForeignStates(Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true), options)
7280
+ .then(() => cb?.(), (err) => cb?.(err));
7592
7281
  }
7593
7282
  /**
7594
7283
  * Return plugin instance
@@ -7616,31 +7305,6 @@ export class AdapterClass extends EventEmitter {
7616
7305
  Validator.assertString(name, 'name');
7617
7306
  return this.pluginHandler.getPluginConfig(name);
7618
7307
  }
7619
- async _autoSubscribeOn() {
7620
- if (!this.autoSubscribe && this.#objects) {
7621
- try {
7622
- // collect all
7623
- const res = await this.#objects.getObjectViewAsync('system', 'instance', {
7624
- startkey: 'system.adapter.',
7625
- endkey: 'system.adapter.\u9999',
7626
- });
7627
- this.autoSubscribe = [];
7628
- for (const row of res.rows) {
7629
- if (row.value?.common.subscribable) {
7630
- const _id = row.id.substring(15); // cut system.adapter.
7631
- if (!this.autoSubscribe.includes(_id)) {
7632
- this.autoSubscribe.push(_id);
7633
- }
7634
- }
7635
- }
7636
- // because of autoSubscribe
7637
- await this.#objects.subscribeAsync('system.adapter.*');
7638
- }
7639
- catch {
7640
- // ignore
7641
- }
7642
- }
7643
- }
7644
7308
  /**
7645
7309
  * This method returns the list of license that can be used by this adapter
7646
7310
  *
@@ -7769,20 +7433,24 @@ export class AdapterClass extends EventEmitter {
7769
7433
  /** Time after which states regularly set by the status report expire in seconds */
7770
7434
  const reportStatusExpirySec = Math.floor(this._config.system.statisticsInterval / 1_000) + 10;
7771
7435
  const id = `system.adapter.${this.namespace}`;
7772
- this.#states.setState(`${id}.alive`, {
7436
+ this.#states
7437
+ .setState(`${id}.alive`, {
7773
7438
  val: true,
7774
7439
  ack: true,
7775
7440
  expire: reportStatusExpirySec,
7776
7441
  from: id,
7777
- });
7442
+ })
7443
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set alive state: ${e.message}`));
7778
7444
  this.outputCount++;
7779
7445
  if (this.connected) {
7780
- this.#states.setState(`${id}.connected`, {
7446
+ this.#states
7447
+ .setState(`${id}.connected`, {
7781
7448
  val: true,
7782
7449
  ack: true,
7783
7450
  expire: reportStatusExpirySec,
7784
7451
  from: id,
7785
- });
7452
+ })
7453
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set connected state: ${e.message}`));
7786
7454
  this.outputCount++;
7787
7455
  }
7788
7456
  if (!this.startedInCompactMode) {
@@ -7797,47 +7465,59 @@ export class AdapterClass extends EventEmitter {
7797
7465
  // timestamp: 864000000 // ms since epoch
7798
7466
  // }
7799
7467
  pidUsage(process.pid, (err, stats) => {
7800
- // sometimes adapter is stopped, but this is still running
7801
- if (!err && this && this.#states && this.#states.setState && stats) {
7802
- this.#states.setState(`${id}.cpu`, {
7468
+ // sometimes adapter is stopped, but this callback is still running: once
7469
+ // terminating, the states DB connection may already be closing — skip the write to
7470
+ // avoid a "Connection is closed" warning (setState does a read-before-write).
7471
+ if (!err && this && this.#states && this.#states.setState && stats && !this.terminated) {
7472
+ this.#states
7473
+ .setState(`${id}.cpu`, {
7803
7474
  ack: true,
7804
7475
  from: id,
7805
7476
  val: Math.round(100 * stats.cpu) / 100,
7806
7477
  expire: reportStatusExpirySec,
7807
- });
7808
- this.#states.setState(`${id}.cputime`, {
7478
+ })
7479
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set cpu state: ${e.message}`));
7480
+ this.#states
7481
+ .setState(`${id}.cputime`, {
7809
7482
  ack: true,
7810
7483
  from: id,
7811
7484
  val: stats.ctime / 1_000,
7812
7485
  expire: reportStatusExpirySec,
7813
- });
7486
+ })
7487
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set cputime state: ${e.message}`));
7814
7488
  this.outputCount += 2;
7815
7489
  }
7816
7490
  });
7817
7491
  try {
7818
7492
  //RSS is the resident set size, the portion of the process's memory held in RAM (as opposed to the swap space or the part held in the filesystem).
7819
7493
  const mem = process.memoryUsage();
7820
- this.#states.setState(`${id}.memRss`, {
7494
+ this.#states
7495
+ .setState(`${id}.memRss`, {
7821
7496
  val: parseFloat((mem.rss / 1048576) /* 1MB */
7822
7497
  .toFixed(2)),
7823
7498
  ack: true,
7824
7499
  from: id,
7825
7500
  expire: reportStatusExpirySec,
7826
- });
7827
- this.#states.setState(`${id}.memHeapTotal`, {
7501
+ })
7502
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memRss state: ${e.message}`));
7503
+ this.#states
7504
+ .setState(`${id}.memHeapTotal`, {
7828
7505
  val: parseFloat((mem.heapTotal / 1048576) /* 1MB */
7829
7506
  .toFixed(2)),
7830
7507
  ack: true,
7831
7508
  from: id,
7832
7509
  expire: reportStatusExpirySec,
7833
- });
7834
- this.#states.setState(`${id}.memHeapUsed`, {
7510
+ })
7511
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memHeapTotal state: ${e.message}`));
7512
+ this.#states
7513
+ .setState(`${id}.memHeapUsed`, {
7835
7514
  val: parseFloat((mem.heapUsed / 1048576) /* 1MB */
7836
7515
  .toFixed(2)),
7837
7516
  ack: true,
7838
7517
  from: id,
7839
7518
  expire: reportStatusExpirySec,
7840
- });
7519
+ })
7520
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memHeapUsed state: ${e.message}`));
7841
7521
  }
7842
7522
  catch (e) {
7843
7523
  this._logger.warn(`${this.namespaceLog} Could not query used process memory: ${e.message}`);
@@ -7845,35 +7525,43 @@ export class AdapterClass extends EventEmitter {
7845
7525
  this.outputCount += 3;
7846
7526
  if (this.eventLoopLags.length) {
7847
7527
  const eventLoopLag = Math.ceil(this.eventLoopLags.reduce((a, b) => a + b) / this.eventLoopLags.length);
7848
- this.#states.setState(`${id}.eventLoopLag`, {
7528
+ this.#states
7529
+ .setState(`${id}.eventLoopLag`, {
7849
7530
  val: eventLoopLag,
7850
7531
  ack: true,
7851
7532
  from: id,
7852
7533
  expire: reportStatusExpirySec,
7853
- }); // average of measured values
7534
+ }) // average of measured values
7535
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set eventLoopLag state: ${e.message}`));
7854
7536
  this.eventLoopLags = [];
7855
7537
  this.outputCount++;
7856
7538
  }
7857
7539
  }
7858
7540
  this.outputCount += 3;
7859
- this.#states.setState(`${id}.uptime`, {
7541
+ this.#states
7542
+ .setState(`${id}.uptime`, {
7860
7543
  val: parseInt(process.uptime().toFixed(), 10),
7861
7544
  ack: true,
7862
7545
  from: id,
7863
7546
  expire: reportStatusExpirySec,
7864
- });
7865
- this.#states.setState(`${id}.inputCount`, {
7547
+ })
7548
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set uptime state: ${e.message}`));
7549
+ this.#states
7550
+ .setState(`${id}.inputCount`, {
7866
7551
  val: this.inputCount,
7867
7552
  ack: true,
7868
7553
  from: id,
7869
7554
  expire: reportStatusExpirySec,
7870
- });
7871
- this.#states.setState(`${id}.outputCount`, {
7555
+ })
7556
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set inputCount state: ${e.message}`));
7557
+ this.#states
7558
+ .setState(`${id}.outputCount`, {
7872
7559
  val: this.outputCount,
7873
7560
  ack: true,
7874
7561
  from: id,
7875
7562
  expire: reportStatusExpirySec,
7876
- });
7563
+ })
7564
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set outputCount state: ${e.message}`));
7877
7565
  this.inputCount = 0;
7878
7566
  this.outputCount = 0;
7879
7567
  }
@@ -7960,7 +7648,9 @@ export class AdapterClass extends EventEmitter {
7960
7648
  else if (this.#states?.pushLog) {
7961
7649
  // Send it to all adapters, that required logs
7962
7650
  for (const instanceId of this.logList) {
7963
- this.#states.pushLog(instanceId, info);
7651
+ this.#states
7652
+ .pushLog(instanceId, info)
7653
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot push log: ${e.message}`));
7964
7654
  }
7965
7655
  }
7966
7656
  });
@@ -7986,7 +7676,9 @@ export class AdapterClass extends EventEmitter {
7986
7676
  if (this.logList.size && messages?.length && this.#states) {
7987
7677
  for (const message of messages) {
7988
7678
  for (const instanceId of this.logList) {
7989
- this.#states.pushLog(instanceId, message);
7679
+ this.#states
7680
+ .pushLog(instanceId, message)
7681
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot push log: ${e.message}`));
7990
7682
  }
7991
7683
  }
7992
7684
  }
@@ -8056,7 +7748,9 @@ export class AdapterClass extends EventEmitter {
8056
7748
  this.emit('log', msg);
8057
7749
  }
8058
7750
  };
8059
- this.#states.subscribeLog(`system.adapter.${this.namespace}`);
7751
+ this.#states
7752
+ .subscribeLog(`system.adapter.${this.namespace}`)
7753
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to log: ${e.message}`));
8060
7754
  }
8061
7755
  else {
8062
7756
  this.requireLog = isActive => {
@@ -8100,13 +7794,19 @@ export class AdapterClass extends EventEmitter {
8100
7794
  }
8101
7795
  if (!this._config.isInstall) {
8102
7796
  // Subscribe for process exit signal
8103
- this.#states.subscribe(`system.adapter.${this.namespace}.sigKill`);
7797
+ this.#states
7798
+ .subscribe(`system.adapter.${this.namespace}.sigKill`)
7799
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to sigKill: ${e.message}`));
8104
7800
  // Subscribe for loglevel
8105
- this.#states.subscribe(`system.adapter.${this.namespace}.logLevel`);
7801
+ this.#states
7802
+ .subscribe(`system.adapter.${this.namespace}.logLevel`)
7803
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to logLevel: ${e.message}`));
8106
7804
  }
8107
7805
  if (this._options.subscribable) {
8108
7806
  // subscribe on if other instance wants to have states of this adapter
8109
- this.#states.subscribe(`system.adapter.${this.namespace}.subscribes`);
7807
+ this.#states
7808
+ .subscribe(`system.adapter.${this.namespace}.subscribes`)
7809
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to subscribes: ${e.message}`));
8110
7810
  // read actual autosubscribe requests
8111
7811
  let state;
8112
7812
  try {
@@ -8160,7 +7860,7 @@ export class AdapterClass extends EventEmitter {
8160
7860
  isScheduled: false,
8161
7861
  exitCode: EXIT_CODES.ADAPTER_REQUESTED_TERMINATION,
8162
7862
  updateAliveState: false,
8163
- });
7863
+ }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8164
7864
  }
8165
7865
  }
8166
7866
  }
@@ -8171,14 +7871,11 @@ export class AdapterClass extends EventEmitter {
8171
7871
  if (state.val && state.val !== currentLevel && isLogLevel(newLogLevel)) {
8172
7872
  this.overwriteLogLevel = true;
8173
7873
  this._config.log.level = newLogLevel;
8174
- for (const transport in this._logger.transports) {
8175
- if (!Object.prototype.hasOwnProperty.call(this._logger.transports, transport)) {
8176
- continue;
8177
- }
7874
+ for (const transport of this._logger.transports) {
8178
7875
  // set the loglevel on transport only if no loglevel was pinned in log config
8179
7876
  // @ts-expect-error it is our own modification
8180
- if (!this._logger.transports[transport]._defaultConfigLoglevel) {
8181
- this._logger.transports[transport].level = newLogLevel;
7877
+ if (!transport._defaultConfigLoglevel) {
7878
+ transport.level = newLogLevel;
8182
7879
  }
8183
7880
  }
8184
7881
  this._logger.info(`${this.namespaceLog} Loglevel changed from "${currentLevel}" to "${state.val}"`);
@@ -8189,11 +7886,13 @@ export class AdapterClass extends EventEmitter {
8189
7886
  }
8190
7887
  this.outputCount++;
8191
7888
  this.#states &&
8192
- this.#states.setState(`system.adapter.${this.namespace}.logLevel`, {
7889
+ this.#states
7890
+ .setState(`system.adapter.${this.namespace}.logLevel`, {
8193
7891
  val: currentLevel,
8194
7892
  ack: true,
8195
7893
  from: `system.adapter.${this.namespace}`,
8196
- });
7894
+ })
7895
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set logLevel state: ${e.message}`));
8197
7896
  }
8198
7897
  }
8199
7898
  // todo remove it as an error with log will be found
@@ -8236,29 +7935,7 @@ export class AdapterClass extends EventEmitter {
8236
7935
  // If this is messagebox
8237
7936
  const obj = state;
8238
7937
  if (obj) {
8239
- let callbackObj;
8240
- if (obj.callback?.id) {
8241
- callbackObj = this.messageCallbacks.get(obj.callback.id);
8242
- }
8243
- // If callback stored for this request
8244
- if (obj.callback?.ack && obj.callback.id && callbackObj) {
8245
- // Call callback function
8246
- if (typeof callbackObj.cb === 'function') {
8247
- callbackObj.cb(obj.message);
8248
- if (callbackObj.timer) {
8249
- clearTimeout(callbackObj.timer);
8250
- }
8251
- this.messageCallbacks.delete(obj.callback.id);
8252
- }
8253
- // delete too old callbacks IDs, like garbage collector
8254
- const now = Date.now();
8255
- for (const [_id, callback] of this.messageCallbacks) {
8256
- if (now - callback.time > 3_600_000) {
8257
- this.messageCallbacks.delete(_id);
8258
- }
8259
- }
8260
- }
8261
- else if (!this._stopInProgress) {
7938
+ if (!this.#async.resolveReply(obj) && !this._stopInProgress) {
8262
7939
  if (obj.command === 'clientSubscribe') {
8263
7940
  const res = await this.uiMessagingController.registerClientSubscribeByMessage(obj);
8264
7941
  this.sendTo(obj.from, obj.command, res, obj.callback);
@@ -8269,7 +7946,7 @@ export class AdapterClass extends EventEmitter {
8269
7946
  }
8270
7947
  if (this._options.message) {
8271
7948
  // Else inform about a new message the adapter
8272
- this._options.message(obj);
7949
+ Promise.resolve(this._options.message(obj)).catch(e => this._logger.error(`${this.namespaceLog} Error in message handler: ${e.message}`));
8273
7950
  }
8274
7951
  this.emit('message', obj);
8275
7952
  }
@@ -8304,37 +7981,13 @@ export class AdapterClass extends EventEmitter {
8304
7981
  }
8305
7982
  }
8306
7983
  }
8307
- else if (!this._stopInProgress && this.adapterReady && this.aliases.has(id)) {
8308
- // If adapter is ready and for this ID exist some alias links
8309
- const alias = this.aliases.get(id);
8310
- /** Prevent multiple publishes if multiple patterns contain this alias id */
8311
- const uniqueTargets = new Set();
8312
- for (const target of alias.targets) {
8313
- const targetId = target.id;
8314
- if (uniqueTargets.has(targetId)) {
8315
- continue;
7984
+ else if (!this._stopInProgress && this.adapterReady && this.#alias.hasSource(id)) {
7985
+ for (const { targetId, state: aState } of this.#alias.resolveSourceChange(id, state)) {
7986
+ if (typeof this._options.stateChange === 'function') {
7987
+ Promise.resolve(this._options.stateChange(targetId, aState)).catch(e => this._logger.error(`${this.namespaceLog} Error in stateChange handler: ${e.message}`));
8316
7988
  }
8317
- uniqueTargets.add(targetId);
8318
- const source = alias.source;
8319
- const aState = state
8320
- ? tools.formatAliasValue({
8321
- sourceCommon: source,
8322
- targetCommon: target,
8323
- state: deepClone(state),
8324
- logger: this._logger,
8325
- logNamespace: this.namespaceLog,
8326
- sourceId: id,
8327
- targetId,
8328
- })
8329
- : null;
8330
- if (aState || !state) {
8331
- if (typeof this._options.stateChange === 'function') {
8332
- this._options.stateChange(targetId, aState);
8333
- }
8334
- else {
8335
- // emit 'stateChange' event instantly
8336
- setImmediate(() => this.emit('stateChange', targetId, aState));
8337
- }
7989
+ else {
7990
+ setImmediate(() => this.emit('stateChange', targetId, aState));
8338
7991
  }
8339
7992
  }
8340
7993
  }
@@ -8373,7 +8026,7 @@ export class AdapterClass extends EventEmitter {
8373
8026
  return;
8374
8027
  } // If reconnected in the meantime, do not terminate
8375
8028
  this._logger.warn(`${this.namespaceLog} Cannot connect/reconnect to states DB. Stopping adapter.`);
8376
- this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false });
8029
+ this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8377
8030
  }, 5000);
8378
8031
  },
8379
8032
  });
@@ -8440,7 +8093,7 @@ export class AdapterClass extends EventEmitter {
8440
8093
  return;
8441
8094
  } // If reconnected in the meantime, do not terminate
8442
8095
  this._logger.warn(`${this.namespaceLog} Cannot connect/reconnect to objects DB. Stopping adapter.`);
8443
- this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false });
8096
+ this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8444
8097
  }, 4000);
8445
8098
  },
8446
8099
  change: async (id, obj) => {
@@ -8452,7 +8105,14 @@ export class AdapterClass extends EventEmitter {
8452
8105
  // If desired, that adapter must be terminated
8453
8106
  if (id === `system.adapter.${this.namespace}` && obj?.common?.enabled === false) {
8454
8107
  this._logger.info(`${this.namespaceLog} Adapter is disabled => stop`);
8455
- this._stop();
8108
+ this._stop().catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8109
+ return;
8110
+ }
8111
+ // a certificate we are using has been changed => restart to reload it
8112
+ if (id === CERTIFICATES_OBJECT_ID) {
8113
+ if (this.#async.hasRelevantCertificateChange(obj)) {
8114
+ this.#scheduleCertificateRestart('Changed certificate detected');
8115
+ }
8456
8116
  return;
8457
8117
  }
8458
8118
  // update language, dateFormat and comma
@@ -8468,78 +8128,17 @@ export class AdapterClass extends EventEmitter {
8468
8128
  }
8469
8129
  // if alias
8470
8130
  if (id.startsWith(ALIAS_STARTS_WITH)) {
8471
- // if `this.aliases` is empty, or no target found, it's a new alias
8472
- let isNewAlias = true;
8473
- for (const [sourceId, alias] of this.aliases) {
8474
- const targetAlias = alias.targets.find(entry => entry.id === id);
8475
- // Find entry for this alias
8476
- if (targetAlias) {
8477
- isNewAlias = false;
8478
- // new sourceId or same
8479
- if (obj?.common?.alias?.id) {
8480
- // check if id.read or id
8481
- const newSourceId = typeof obj.common.alias.id.read === 'string'
8482
- ? obj.common.alias.id.read
8483
- : obj.common.alias.id;
8484
- // if linked ID changed
8485
- if (newSourceId !== sourceId) {
8486
- await this._removeAliasSubscribe(sourceId, targetAlias);
8487
- try {
8488
- await this._addAliasSubscribe(obj, targetAlias.pattern);
8489
- }
8490
- catch (e) {
8491
- this._logger.error(`${this.namespaceLog} Could not add alias subscription: ${e.message}`);
8492
- }
8493
- }
8494
- else {
8495
- // update attributes
8496
- targetAlias.min = obj.common.min;
8497
- targetAlias.max = obj.common.max;
8498
- targetAlias.type = obj.common.type;
8499
- targetAlias.alias = deepClone(obj.common.alias);
8500
- }
8501
- }
8502
- else {
8503
- // link was deleted
8504
- // remove from targets array
8505
- await this._removeAliasSubscribe(sourceId, targetAlias);
8506
- }
8507
- }
8508
- }
8509
- // it's a new alias, we add it to our subscription
8510
- if (isNewAlias && obj) {
8511
- for (const aliasPattern of this.aliasPatterns) {
8512
- // check if it's in our subs range, if so add it
8513
- const testPattern = aliasPattern.slice(-1) === '*'
8514
- ? new RegExp(tools.pattern2RegEx(aliasPattern))
8515
- : aliasPattern;
8516
- if ((typeof testPattern === 'string' && aliasPattern === id) ||
8517
- (testPattern instanceof RegExp && testPattern.test(id))) {
8518
- try {
8519
- await this._addAliasSubscribe(obj, id);
8520
- }
8521
- catch (e) {
8522
- this._logger.warn(`${this.namespaceLog} Could not add alias subscription: ${e.message}`);
8523
- }
8524
- break;
8525
- }
8526
- }
8527
- }
8131
+ await this.#alias.handleAliasObjectChange(id, obj);
8528
8132
  }
8529
8133
  // process auto-subscribe adapters
8530
8134
  if (id.startsWith('system.adapter.')) {
8531
8135
  if (obj?.common?.subscribable) {
8532
8136
  const _id = id.substring(15); // 'system.adapter.'.length
8533
8137
  if (obj.common.enabled) {
8534
- if (!this.autoSubscribe.includes(_id)) {
8535
- this.autoSubscribe.push(_id);
8536
- }
8138
+ this.#subscriptions.addSubscribableInstance(_id);
8537
8139
  }
8538
8140
  else {
8539
- const pos = this.autoSubscribe.indexOf(_id);
8540
- if (pos !== -1) {
8541
- this.autoSubscribe.splice(pos, 1);
8542
- }
8141
+ this.#subscriptions.removeSubscribableInstance(_id);
8543
8142
  }
8544
8143
  }
8545
8144
  }
@@ -8677,7 +8276,9 @@ export class AdapterClass extends EventEmitter {
8677
8276
  // if adapterState was destroyed, we should not continue
8678
8277
  return;
8679
8278
  }
8680
- this.#states.subscribe(`system.adapter.${this.namespace}.plugins.*`);
8279
+ this.#states
8280
+ .subscribe(`system.adapter.${this.namespace}.plugins.*`)
8281
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to plugins: ${e.message}`));
8681
8282
  if (this._options.instance === undefined) {
8682
8283
  if (!adapterConfig || !('common' in adapterConfig) || !adapterConfig.common.enabled) {
8683
8284
  if (adapterConfig && 'common' in adapterConfig && adapterConfig.common.enabled !== undefined) {
@@ -8769,7 +8370,9 @@ export class AdapterClass extends EventEmitter {
8769
8370
  this.stop = params => this._stop({ ...params, isPause: false });
8770
8371
  }
8771
8372
  // Monitor logging state
8772
- this.#states.subscribe(`${SYSTEM_ADAPTER_PREFIX}*.logging`);
8373
+ this.#states
8374
+ .subscribe(`${SYSTEM_ADAPTER_PREFIX}*.logging`)
8375
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to logging: ${e.message}`));
8773
8376
  if (typeof this._options.message === 'function' &&
8774
8377
  // @ts-expect-error, we should infer correctly that this is an InstanceObject in this case
8775
8378
  !isMessageboxSupported(adapterConfig.common)) {
@@ -8777,8 +8380,9 @@ export class AdapterClass extends EventEmitter {
8777
8380
  // @ts-expect-error we should infer adapterConfig correctly
8778
8381
  }
8779
8382
  else if (isMessageboxSupported(adapterConfig.common)) {
8780
- this.mboxSubscribed = true;
8781
- this.#states.subscribeMessage(`system.adapter.${this.namespace}`);
8383
+ this.#states
8384
+ .subscribeMessage(`system.adapter.${this.namespace}`)
8385
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to messages: ${e.message}`));
8782
8386
  }
8783
8387
  }
8784
8388
  else {
@@ -8795,7 +8399,7 @@ export class AdapterClass extends EventEmitter {
8795
8399
  this.host = this.common?.host || tools.getHostName();
8796
8400
  }
8797
8401
  this.adapterConfig = adapterConfig;
8798
- this._utils = new Validator(this.#objects, this.#states, this.namespaceLog, this._logger, this.namespace, this._namespaceRegExp);
8402
+ this._utils = new Validator(this.#objects, this.namespaceLog, this._logger, this.namespace, this._namespaceRegExp);
8799
8403
  this.log = new Log(this.namespaceLog, this._config.log.level, this._logger);
8800
8404
  await this._createInstancesObjects(adapterConfig);
8801
8405
  // auto oObjects
@@ -8804,19 +8408,22 @@ export class AdapterClass extends EventEmitter {
8804
8408
  await this.subscribeObjectsAsync('*');
8805
8409
  }
8806
8410
  // initialize the system secret
8807
- await this.getSystemSecret();
8411
+ const secret = await this.getSystemSecret();
8808
8412
  // Decrypt all attributes of encryptedNative
8809
- const promises = [];
8810
8413
  // @ts-expect-error
8811
8414
  if (Array.isArray(adapterConfig.encryptedNative)) {
8415
+ this._decryptedNativeAttributes = new Array();
8812
8416
  // @ts-expect-error
8813
8417
  for (const attr of adapterConfig.encryptedNative) {
8814
- // we can only decrypt strings
8815
- // @ts-expect-error
8816
- if (typeof this.config[attr] === 'string') {
8817
- promises.push(this.getEncryptedConfig(attr)
8818
- .then(decryptedValue => setObjectAttribute(this.config, attr, decryptedValue))
8819
- .catch(e => this._logger.error(`${this.namespaceLog} Can not decrypt attribute ${attr}: ${e.message}`)));
8418
+ // one attribute at a time, so a value that cannot be decrypted does not stop the others
8419
+ try {
8420
+ decryptArray({ obj: this.config, secret, keys: [attr] });
8421
+ // only after it worked: a failed decrypt leaves the encrypted value in place, and
8422
+ // reading it back must decrypt it rather than hand out the cipher text
8423
+ this._decryptedNativeAttributes.push(attr);
8424
+ }
8425
+ catch (e) {
8426
+ this._logger.error(`${this.namespaceLog} Can not decrypt attribute ${attr}: ${e.message}`);
8820
8427
  }
8821
8428
  }
8822
8429
  }
@@ -8827,19 +8434,19 @@ export class AdapterClass extends EventEmitter {
8827
8434
  this.SUPPORTED_FEATURES.splice(idx, 1);
8828
8435
  }
8829
8436
  }
8830
- // Wait till all attributes decrypted
8831
- await Promise.all(promises);
8832
8437
  if (!this.#states) {
8833
8438
  // if this.adapterStates was destroyed, we should not continue
8834
8439
  return;
8835
8440
  }
8836
8441
  this.outputCount++;
8837
8442
  // set current loglevel
8838
- this.#states.setState(`system.adapter.${this.namespace}.logLevel`, {
8443
+ this.#states
8444
+ .setState(`system.adapter.${this.namespace}.logLevel`, {
8839
8445
  val: this._config.log.level,
8840
8446
  ack: true,
8841
8447
  from: `system.adapter.${this.namespace}`,
8842
- });
8448
+ })
8449
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set logLevel state: ${e.message}`));
8843
8450
  try {
8844
8451
  await this.#checkObjectsWarnLimit();
8845
8452
  }
@@ -8864,19 +8471,24 @@ export class AdapterClass extends EventEmitter {
8864
8471
  this._reportInterval = setInterval(() => this._reportStatus(), this._config.system.statisticsInterval);
8865
8472
  this._reportStatus();
8866
8473
  const id = `system.adapter.${this.namespace}`;
8867
- this.#states.setState(`${id}.compactMode`, {
8474
+ this.#states
8475
+ .setState(`${id}.compactMode`, {
8868
8476
  ack: true,
8869
8477
  from: id,
8870
8478
  val: !!this.startedInCompactMode,
8871
- });
8479
+ })
8480
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set compactMode state: ${e.message}`));
8872
8481
  this.outputCount++;
8873
8482
  if (this.startedInCompactMode) {
8874
- this.#states.setState(`${id}.cpu`, { ack: true, from: id, val: 0 });
8875
- this.#states.setState(`${id}.cputime`, { ack: true, from: id, val: 0 });
8876
- this.#states.setState(`${id}.memRss`, { val: 0, ack: true, from: id });
8877
- this.#states.setState(`${id}.memHeapTotal`, { val: 0, ack: true, from: id });
8878
- this.#states.setState(`${id}.memHeapUsed`, { val: 0, ack: true, from: id });
8879
- this.#states.setState(`${id}.eventLoopLag`, { val: 0, ack: true, from: id });
8483
+ const onErr = (e) => {
8484
+ this._logger.error(`${this.namespaceLog} Cannot reset compact metric: ${e.message}`);
8485
+ };
8486
+ this.#states.setState(`${id}.cpu`, { ack: true, from: id, val: 0 }).catch(onErr);
8487
+ this.#states.setState(`${id}.cputime`, { ack: true, from: id, val: 0 }).catch(onErr);
8488
+ this.#states.setState(`${id}.memRss`, { val: 0, ack: true, from: id }).catch(onErr);
8489
+ this.#states.setState(`${id}.memHeapTotal`, { val: 0, ack: true, from: id }).catch(onErr);
8490
+ this.#states.setState(`${id}.memHeapUsed`, { val: 0, ack: true, from: id }).catch(onErr);
8491
+ this.#states.setState(`${id}.eventLoopLag`, { val: 0, ack: true, from: id }).catch(onErr);
8880
8492
  this.outputCount += 6;
8881
8493
  }
8882
8494
  else {
@@ -8899,7 +8511,7 @@ export class AdapterClass extends EventEmitter {
8899
8511
  this._logger.debug(`${this.namespaceLog} Schedule restart: ${adapterConfig.common.restartSchedule}`);
8900
8512
  this._restartScheduleJob = this._schedule.scheduleJob(adapterConfig.common.restartSchedule, () => {
8901
8513
  this._logger.info(`${this.namespaceLog} Scheduled restart.`);
8902
- this._stop({ isPause: false, isScheduled: true });
8514
+ this._stop({ isPause: false, isScheduled: true }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8903
8515
  });
8904
8516
  }
8905
8517
  }
@@ -8930,13 +8542,13 @@ export class AdapterClass extends EventEmitter {
8930
8542
  if (this._config.isInstall &&
8931
8543
  (typeof this._options.install === 'function' || this.listeners('install').length)) {
8932
8544
  if (typeof this._options.install === 'function') {
8933
- this._options.install();
8545
+ Promise.resolve(this._options.install()).catch(e => this._logger.error(`${this.namespaceLog} Error in install handler: ${e.message}`));
8934
8546
  }
8935
8547
  this.emit('install');
8936
8548
  }
8937
8549
  else {
8938
8550
  if (typeof this._options.ready === 'function') {
8939
- this._options.ready();
8551
+ Promise.resolve(this._options.ready()).catch(e => this._logger.error(`${this.namespaceLog} Error in ready handler: ${e.message}`));
8940
8552
  }
8941
8553
  this.emit('ready');
8942
8554
  }
@@ -8990,7 +8602,7 @@ export class AdapterClass extends EventEmitter {
8990
8602
  isScheduled: false,
8991
8603
  exitCode: EXIT_CODES.UNCAUGHT_EXCEPTION,
8992
8604
  updateAliveState: false,
8993
- });
8605
+ }).catch(err => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${err.message}`));
8994
8606
  }
8995
8607
  catch (e) {
8996
8608
  this._logger.error(`${this.namespaceLog} exception by stop: ${e ? e.message : e}`);
@@ -9077,7 +8689,7 @@ export class AdapterClass extends EventEmitter {
9077
8689
  });
9078
8690
  }
9079
8691
  return new Promise(resolve => {
9080
- this._extendObjects(objs, resolve);
8692
+ return this._extendObjects(objs, resolve);
9081
8693
  });
9082
8694
  }
9083
8695
  /**
@@ -9127,7 +8739,7 @@ export class AdapterClass extends EventEmitter {
9127
8739
  return this.#states.setState(id, state);
9128
8740
  }
9129
8741
  async _extendObjects(tasks, callback) {
9130
- if (!tasks || !tasks.length) {
8742
+ if (!tasks?.length) {
9131
8743
  return tools.maybeCallback(callback);
9132
8744
  }
9133
8745
  const task = tasks.shift();