@iobroker/js-controller-adapter 7.2.3 → 8.0.0-alpha.2-20260923-99da17fcc

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 (108) 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 +1896 -177
  6. package/build/cjs/lib/adapter/adapter.js +788 -1035
  7. package/build/cjs/lib/adapter/adapter.js.map +2 -2
  8. package/build/cjs/lib/adapter/asyncAdapter.d.ts +167 -0
  9. package/build/cjs/lib/adapter/asyncAdapter.js +252 -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/ResourceManager.d.ts +69 -0
  33. package/build/cjs/lib/adapter/managers/ResourceManager.js +170 -0
  34. package/build/cjs/lib/adapter/managers/ResourceManager.js.map +7 -0
  35. package/build/cjs/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
  36. package/build/cjs/lib/adapter/managers/SubscriptionManager.js +349 -0
  37. package/build/cjs/lib/adapter/managers/SubscriptionManager.js.map +7 -0
  38. package/build/cjs/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
  39. package/build/cjs/lib/adapter/userInterfaceMessagingController.js +9 -2
  40. package/build/cjs/lib/adapter/userInterfaceMessagingController.js.map +2 -2
  41. package/build/cjs/lib/adapter/utils.js +15 -1
  42. package/build/cjs/lib/adapter/utils.js.map +2 -2
  43. package/build/cjs/lib/adapter/validator.d.ts +10 -5
  44. package/build/cjs/lib/adapter/validator.js +6 -5
  45. package/build/cjs/lib/adapter/validator.js.map +2 -2
  46. package/build/esm/index.d.ts +0 -1
  47. package/build/esm/index.d.ts.map +1 -1
  48. package/build/esm/index.js +0 -1
  49. package/build/esm/index.js.map +1 -1
  50. package/build/esm/lib/_Types.d.ts +394 -63
  51. package/build/esm/lib/_Types.d.ts.map +1 -1
  52. package/build/esm/lib/adapter/adapter.d.ts +1896 -177
  53. package/build/esm/lib/adapter/adapter.d.ts.map +1 -1
  54. package/build/esm/lib/adapter/adapter.js +900 -1215
  55. package/build/esm/lib/adapter/adapter.js.map +1 -1
  56. package/build/esm/lib/adapter/asyncAdapter.d.ts +167 -0
  57. package/build/esm/lib/adapter/asyncAdapter.d.ts.map +1 -0
  58. package/build/esm/lib/adapter/asyncAdapter.js +225 -0
  59. package/build/esm/lib/adapter/asyncAdapter.js.map +1 -0
  60. package/build/esm/lib/adapter/context.d.ts +34 -0
  61. package/build/esm/lib/adapter/context.d.ts.map +1 -0
  62. package/build/esm/lib/adapter/context.js +2 -0
  63. package/build/esm/lib/adapter/context.js.map +1 -0
  64. package/build/esm/lib/adapter/defaultObjs.d.ts +5 -0
  65. package/build/esm/lib/adapter/defaultObjs.d.ts.map +1 -1
  66. package/build/esm/lib/adapter/defaultObjs.js +5 -0
  67. package/build/esm/lib/adapter/defaultObjs.js.map +1 -1
  68. package/build/esm/lib/adapter/log.d.ts +25 -0
  69. package/build/esm/lib/adapter/log.d.ts.map +1 -1
  70. package/build/esm/lib/adapter/log.js +25 -0
  71. package/build/esm/lib/adapter/log.js.map +1 -1
  72. package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts +49 -0
  73. package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts.map +1 -0
  74. package/build/esm/lib/adapter/managers/AdapterContextBase.js +77 -0
  75. package/build/esm/lib/adapter/managers/AdapterContextBase.js.map +1 -0
  76. package/build/esm/lib/adapter/managers/AliasManager.d.ts +103 -0
  77. package/build/esm/lib/adapter/managers/AliasManager.d.ts.map +1 -0
  78. package/build/esm/lib/adapter/managers/AliasManager.js +269 -0
  79. package/build/esm/lib/adapter/managers/AliasManager.js.map +1 -0
  80. package/build/esm/lib/adapter/managers/CertificateManager.d.ts +50 -0
  81. package/build/esm/lib/adapter/managers/CertificateManager.d.ts.map +1 -0
  82. package/build/esm/lib/adapter/managers/CertificateManager.js +142 -0
  83. package/build/esm/lib/adapter/managers/CertificateManager.js.map +1 -0
  84. package/build/esm/lib/adapter/managers/MessagingManager.d.ts +58 -0
  85. package/build/esm/lib/adapter/managers/MessagingManager.d.ts.map +1 -0
  86. package/build/esm/lib/adapter/managers/MessagingManager.js +267 -0
  87. package/build/esm/lib/adapter/managers/MessagingManager.js.map +1 -0
  88. package/build/esm/lib/adapter/managers/ResourceManager.d.ts +69 -0
  89. package/build/esm/lib/adapter/managers/ResourceManager.d.ts.map +1 -0
  90. package/build/esm/lib/adapter/managers/ResourceManager.js +151 -0
  91. package/build/esm/lib/adapter/managers/ResourceManager.js.map +1 -0
  92. package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
  93. package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts.map +1 -0
  94. package/build/esm/lib/adapter/managers/SubscriptionManager.js +374 -0
  95. package/build/esm/lib/adapter/managers/SubscriptionManager.js.map +1 -0
  96. package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
  97. package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts.map +1 -1
  98. package/build/esm/lib/adapter/userInterfaceMessagingController.js +8 -2
  99. package/build/esm/lib/adapter/userInterfaceMessagingController.js.map +1 -1
  100. package/build/esm/lib/adapter/utils.d.ts.map +1 -1
  101. package/build/esm/lib/adapter/utils.js +1 -1
  102. package/build/esm/lib/adapter/utils.js.map +1 -1
  103. package/build/esm/lib/adapter/validator.d.ts +10 -5
  104. package/build/esm/lib/adapter/validator.d.ts.map +1 -1
  105. package/build/esm/lib/adapter/validator.js +8 -5
  106. package/build/esm/lib/adapter/validator.js.map +1 -1
  107. package/build/tsconfig.build.tsbuildinfo +1 -1
  108. 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,80 @@ 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);
5528
+ }
5529
+ /**
5530
+ * Register an exclusive resource (serial port, TCP/UDP port, USB device, ...) as used by this instance.
5531
+ *
5532
+ * Exclusive resources are the ones that cannot be occupied by more than one instance at the same time.
5533
+ * The information is forwarded to the host this instance runs on and stored under
5534
+ * `system.host.<hostname>.usedResources.<type>`, so the user gets an overview of the occupied resources
5535
+ * and can pick a free one when configuring a new instance.
5536
+ *
5537
+ * Registering is **additive**: call it once per occupied resource, in any order and from any number of
5538
+ * async init paths. The host drops what this instance registered before whenever the instance starts, so
5539
+ * a stale registration from a previous configuration cannot survive a restart.
5540
+ *
5541
+ * @param type the kind of resource, e.g. "serialPort" or "tcpPort"
5542
+ * @param data the strictly typed payload describing the resource, e.g. `{ port: '/dev/ttyUSB0' }`
5543
+ * @throws {Error} when the host refuses the registration - e.g. because `common.declareUsedResources` is not `true` - or does not answer
5544
+ */
5545
+ async registerUsedResource(type, data) {
5546
+ return this.#async.registerUsedResource(type, data);
5547
+ }
5548
+ /**
5549
+ * Ask whether another instance on this host currently holds an exclusive resource, without
5550
+ * registering anything.
5551
+ *
5552
+ * Call this **before** opening the port or the device - afterwards the operating system has
5553
+ * already decided the conflict and this can only improve the error message.
5554
+ *
5555
+ * The answer is a hint, not a permission: the registry knows what adapters declare, so an empty
5556
+ * result does not promise the resource is free.
5557
+ *
5558
+ * @param type the kind of resource, e.g. "serialPort" or "tcpPort"
5559
+ * @param data description of the resource that is about to be used, e.g. `{ port: 1883 }`
5560
+ * @returns the entries of other instances that currently hold it, newest registration first
5561
+ * @throws {Error} when the host refuses the request or does not answer
5562
+ */
5563
+ async checkUsedResource(type, data) {
5564
+ return this.#async.checkUsedResource(type, data);
5565
+ }
5566
+ /**
5567
+ * Free all exclusive resources this instance registered, across all types.
5568
+ *
5569
+ * This is not needed on start-up (the host already resets the registrations of a starting instance) nor
5570
+ * on shutdown (the host marks them as no longer held). Use it when the instance drops everything it
5571
+ * occupied while it keeps running, e.g. on a reconfiguration.
5572
+ *
5573
+ * @throws {Error} when the host refuses the command or does not answer
5574
+ */
5575
+ async clearUsedResources() {
5576
+ return this.#async.clearUsedResources();
5577
+ }
5578
+ /**
5579
+ * Free previously registered exclusive resources of this instance.
5580
+ *
5581
+ * `data` is a **filter, not the exact payload**: every field it names must match, fields it does not name
5582
+ * are ignored. `freeUsedResource('tcpPort', { port: 8080 })` therefore also frees an entry registered as
5583
+ * `{ port: 8080, bind: '0.0.0.0' }`, and if `data` is omitted, all registered resources of the given
5584
+ * `type` for this instance are freed. The change is forwarded to the host this instance runs on and
5585
+ * reflected in `system.host.<hostname>.usedResources.<type>`; a filter that matches nothing is logged by
5586
+ * the host.
5587
+ *
5588
+ * @param type the kind of resource, e.g. "serialPort" or "tcpPort"
5589
+ * @param data the fields identifying the resources to free; if omitted, all resources of `type` are freed
5590
+ * @throws {Error} when the host refuses the command or does not answer
5591
+ */
5592
+ async freeUsedResource(type, data) {
5593
+ return this.#async.freeUsedResource(type, data);
5594
+ }
5595
+ /**
5596
+ * @param type resource type to read; if omitted, the resources of every type are returned
5597
+ * @returns the list of registered resources (across all instances of this host)
5598
+ */
5599
+ async getHostUsedResources(type) {
5600
+ return type === undefined ? this.#async.getHostUsedResources() : this.#async.getHostUsedResources(type);
5521
5601
  }
5522
5602
  /**
5523
5603
  * Writes value into states DB.
@@ -5736,7 +5816,7 @@ export class AdapterClass extends EventEmitter {
5736
5816
  // ignore
5737
5817
  }
5738
5818
  if (!userAcl) {
5739
- // User does not exists
5819
+ // User does not exist
5740
5820
  this._logger.error(`${this.namespaceLog} unknown user "${options.user}"`);
5741
5821
  return options;
5742
5822
  }
@@ -5758,8 +5838,7 @@ export class AdapterClass extends EventEmitter {
5758
5838
  // read all groups for this user
5759
5839
  this.users[options.user] = {
5760
5840
  groups: options.groups,
5761
- // @ts-expect-error TODO: UserCommon has no acl
5762
- acl: userAcl.common?.acl || {},
5841
+ acl: {},
5763
5842
  };
5764
5843
  await this._getGroups(options.groups);
5765
5844
  // combine all rights
@@ -5770,9 +5849,9 @@ export class AdapterClass extends EventEmitter {
5770
5849
  }
5771
5850
  const group = this.groups[gName];
5772
5851
  if (group.common?.acl?.file) {
5773
- if (!user.acl || !user.acl.file) {
5774
- user.acl = user.acl || {};
5775
- user.acl.file = user.acl.file || {};
5852
+ if (!user.acl?.file) {
5853
+ user.acl ||= {};
5854
+ user.acl.file ||= {};
5776
5855
  user.acl.file.create = group.common.acl.file.create;
5777
5856
  user.acl.file.read = group.common.acl.file.read;
5778
5857
  user.acl.file.write = group.common.acl.file.write;
@@ -5780,17 +5859,17 @@ export class AdapterClass extends EventEmitter {
5780
5859
  user.acl.file.list = group.common.acl.file.list;
5781
5860
  }
5782
5861
  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;
5862
+ user.acl.file.create ||= group.common.acl.file.create;
5863
+ user.acl.file.read ||= group.common.acl.file.read;
5864
+ user.acl.file.write ||= group.common.acl.file.write;
5865
+ user.acl.file.delete ||= group.common.acl.file.delete;
5866
+ user.acl.file.list ||= group.common.acl.file.list;
5788
5867
  }
5789
5868
  }
5790
5869
  if (group.common?.acl?.object) {
5791
5870
  if (!user.acl || !user.acl.object) {
5792
- user.acl = user.acl || {};
5793
- user.acl.object = user.acl.object || {};
5871
+ user.acl ||= {};
5872
+ user.acl.object ||= {};
5794
5873
  user.acl.object.create = group.common.acl.object.create;
5795
5874
  user.acl.object.read = group.common.acl.object.read;
5796
5875
  user.acl.object.write = group.common.acl.object.write;
@@ -5798,17 +5877,17 @@ export class AdapterClass extends EventEmitter {
5798
5877
  user.acl.object.list = group.common.acl.object.list;
5799
5878
  }
5800
5879
  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;
5880
+ user.acl.object.create ||= group.common.acl.object.create;
5881
+ user.acl.object.read ||= group.common.acl.object.read;
5882
+ user.acl.object.write ||= group.common.acl.object.write;
5883
+ user.acl.object.delete ||= group.common.acl.object.delete;
5884
+ user.acl.object.list ||= group.common.acl.object.list;
5806
5885
  }
5807
5886
  }
5808
5887
  if (group.common?.acl?.users) {
5809
5888
  if (!user.acl || !user.acl.users) {
5810
- user.acl = user.acl || {};
5811
- user.acl.users = user.acl.users || {};
5889
+ user.acl ||= {};
5890
+ user.acl.users ||= {};
5812
5891
  user.acl.users.create = group.common.acl.users.create;
5813
5892
  user.acl.users.read = group.common.acl.users.read;
5814
5893
  user.acl.users.write = group.common.acl.users.write;
@@ -5816,17 +5895,17 @@ export class AdapterClass extends EventEmitter {
5816
5895
  user.acl.users.list = group.common.acl.users.list;
5817
5896
  }
5818
5897
  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;
5898
+ user.acl.users.create ||= group.common.acl.users.create;
5899
+ user.acl.users.read ||= group.common.acl.users.read;
5900
+ user.acl.users.write ||= group.common.acl.users.write;
5901
+ user.acl.users.delete ||= group.common.acl.users.delete;
5902
+ user.acl.users.list ||= group.common.acl.users.list;
5824
5903
  }
5825
5904
  }
5826
5905
  if (group.common?.acl?.state) {
5827
5906
  if (!user.acl || !user.acl.state) {
5828
- user.acl = user.acl || {};
5829
- user.acl.state = user.acl.state || {};
5907
+ user.acl ||= {};
5908
+ user.acl.state ||= {};
5830
5909
  user.acl.state.create = group.common.acl.state.create;
5831
5910
  user.acl.state.read = group.common.acl.state.read;
5832
5911
  user.acl.state.write = group.common.acl.state.write;
@@ -5834,11 +5913,11 @@ export class AdapterClass extends EventEmitter {
5834
5913
  user.acl.state.list = group.common.acl.state.list;
5835
5914
  }
5836
5915
  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;
5916
+ user.acl.state.create ||= group.common.acl.state.create;
5917
+ user.acl.state.read ||= group.common.acl.state.read;
5918
+ user.acl.state.write ||= group.common.acl.state.write;
5919
+ user.acl.state.delete ||= group.common.acl.state.delete;
5920
+ user.acl.state.list ||= group.common.acl.state.list;
5842
5921
  }
5843
5922
  }
5844
5923
  }
@@ -5846,10 +5925,18 @@ export class AdapterClass extends EventEmitter {
5846
5925
  return options;
5847
5926
  }
5848
5927
  _checkState(obj, options, command) {
5928
+ // Members of the administrator group are not restricted by the ACL of a state, the same as for objects and files
5929
+ if (options.groups?.includes(SYSTEM_ADMIN_GROUP)) {
5930
+ return true;
5931
+ }
5849
5932
  const limitToOwnerRights = options.limitToOwnerRights === true;
5850
5933
  if (obj?.acl) {
5851
- obj.acl.state = obj.acl.state || obj.acl.object;
5934
+ obj.acl.state ||= obj.acl.object;
5852
5935
  if (obj.acl.state) {
5936
+ if (!options.acl) {
5937
+ this._logger.warn(`${this.namespaceLog} Permission error for user "${options.user} on "${obj._id}": ${command}`);
5938
+ return false;
5939
+ }
5853
5940
  // If user is owner
5854
5941
  if (options.user === obj.acl.owner) {
5855
5942
  if (command === 'setState' || command === 'delState') {
@@ -5876,7 +5963,7 @@ export class AdapterClass extends EventEmitter {
5876
5963
  this._logger.warn(`${this.namespaceLog} Called unknown command on "${obj._id}": ${command}`);
5877
5964
  }
5878
5965
  }
5879
- else if (options.groups.includes(obj.acl.ownerGroup) && !limitToOwnerRights) {
5966
+ else if (options.groups?.includes(obj.acl.ownerGroup) && !limitToOwnerRights) {
5880
5967
  if (command === 'setState' || command === 'delState') {
5881
5968
  if (command === 'delState' && !options.acl.state.delete) {
5882
5969
  this._logger.warn(`${this.namespaceLog} Permission error for user "${options.user} on "${obj._id}": ${command}`);
@@ -6166,7 +6253,6 @@ export class AdapterClass extends EventEmitter {
6166
6253
  return tools.maybeCallbackWithError(callback, null, res.id, res.notChanged);
6167
6254
  }
6168
6255
  const res = await this._setStateChangedHelper(fixedId, stateObj);
6169
- // @ts-expect-error todo fix it
6170
6256
  return tools.maybeCallbackWithError(callback, null, res.id, res.notChanged);
6171
6257
  }
6172
6258
  /**
@@ -6252,7 +6338,9 @@ export class AdapterClass extends EventEmitter {
6252
6338
  }
6253
6339
  // if state.from provided, we use it else, we set default property
6254
6340
  state.from =
6255
- typeof state.from === 'string' && state.from !== '' ? state.from : `system.adapter.${this.namespace}`;
6341
+ typeof state.from === 'string' && state.from !== ''
6342
+ ? state.from
6343
+ : `system.adapter.${this.namespace}`;
6256
6344
  state.user = options?.user || SYSTEM_ADMIN_USER;
6257
6345
  if (!id || typeof id !== 'string') {
6258
6346
  const warn = id ? `ID can be only string and not "${typeof id}"` : `Empty ID: ${JSON.stringify(state)}`;
@@ -6260,10 +6348,12 @@ export class AdapterClass extends EventEmitter {
6260
6348
  return tools.maybeCallbackWithError(callback, warn);
6261
6349
  }
6262
6350
  id = this.fixForbiddenCharsInId(id);
6263
- if (options?.user && options.user !== SYSTEM_ADMIN_USER) {
6351
+ if (options?.user &&
6352
+ options.user !== SYSTEM_ADMIN_USER) {
6264
6353
  let obj;
6265
6354
  try {
6266
- obj = (await this._checkStates(id, options, 'setState')).objs[0];
6355
+ obj = (await this._checkStates(id, options, 'setState'))
6356
+ .objs[0];
6267
6357
  }
6268
6358
  catch (e) {
6269
6359
  return tools.maybeCallbackWithError(callback, e);
@@ -6311,8 +6401,8 @@ export class AdapterClass extends EventEmitter {
6311
6401
  this.outputCount++;
6312
6402
  this.#states.setState(aliasId, tools.formatAliasValue({
6313
6403
  sourceCommon: obj?.common,
6314
- targetCommon: targetObj && targetObj.common,
6315
- state,
6404
+ targetCommon: targetObj?.common,
6405
+ state: state,
6316
6406
  logger: this._logger,
6317
6407
  logNamespace: this.namespaceLog,
6318
6408
  sourceId: obj?._id,
@@ -6375,7 +6465,7 @@ export class AdapterClass extends EventEmitter {
6375
6465
  this.#states.setState(targetId, tools.formatAliasValue({
6376
6466
  sourceCommon: obj.common,
6377
6467
  targetCommon: targetObj?.common,
6378
- state,
6468
+ state: state,
6379
6469
  logger: this._logger,
6380
6470
  logNamespace: this.namespaceLog,
6381
6471
  sourceId: obj._id,
@@ -6475,7 +6565,8 @@ export class AdapterClass extends EventEmitter {
6475
6565
  // wrap non-object values in a state object
6476
6566
  state = state !== undefined ? { val: state } : {};
6477
6567
  }
6478
- if (state.val === undefined && !Object.keys(state).length) {
6568
+ if (state.val === undefined &&
6569
+ !Object.keys(state).length) {
6479
6570
  // undefined is not allowed as state.val -> return
6480
6571
  this._logger.info(`${this.namespaceLog} undefined is not a valid state value for id "${id}"`);
6481
6572
  // TODO: reactivate line below + test in in next controller version (02.05.2021)
@@ -6486,10 +6577,14 @@ export class AdapterClass extends EventEmitter {
6486
6577
  }
6487
6578
  // if state.from provided, we use it else, we set default property
6488
6579
  state.from =
6489
- typeof state.from === 'string' && state.from !== '' ? state.from : `system.adapter.${this.namespace}`;
6490
- state.user = options?.user || SYSTEM_ADMIN_USER;
6580
+ typeof state.from === 'string' && state.from !== ''
6581
+ ? state.from
6582
+ : `system.adapter.${this.namespace}`;
6583
+ state.user =
6584
+ options?.user || SYSTEM_ADMIN_USER;
6491
6585
  id = this.fixForbiddenCharsInId(id);
6492
- if (options?.user && options.user !== SYSTEM_ADMIN_USER) {
6586
+ if (options?.user &&
6587
+ options.user !== SYSTEM_ADMIN_USER) {
6493
6588
  try {
6494
6589
  await this._checkStates(id, options, 'setState');
6495
6590
  }
@@ -6517,7 +6612,7 @@ export class AdapterClass extends EventEmitter {
6517
6612
  * }
6518
6613
  * ```
6519
6614
  *
6520
- * See possible attributes of the state in @setState explanation
6615
+ * See possible attributes of the state in `setState` explanation
6521
6616
  */
6522
6617
  getState(id, options, callback) {
6523
6618
  // we use any types here, because validation takes place in foreign method
@@ -6542,7 +6637,7 @@ export class AdapterClass extends EventEmitter {
6542
6637
  * }
6543
6638
  * ```
6544
6639
  *
6545
- * See possible attributes of the state in @setState explanation
6640
+ * See possible attributes of the state in `setState` explanation
6546
6641
  */
6547
6642
  getForeignState(id, options, callback) {
6548
6643
  if (typeof options === 'function') {
@@ -6646,7 +6741,7 @@ export class AdapterClass extends EventEmitter {
6646
6741
  if (this.oStates && this.oStates[id]) {
6647
6742
  return tools.maybeCallbackWithError(callback, null, this.oStates[id]);
6648
6743
  }
6649
- return this.#states.getState(id, callback);
6744
+ return callback ? this.#states.getState(id, callback) : this.#states.getState(id);
6650
6745
  }
6651
6746
  }
6652
6747
  // find out default history instance
@@ -6731,7 +6826,7 @@ export class AdapterClass extends EventEmitter {
6731
6826
  * }
6732
6827
  * ```
6733
6828
  *
6734
- * See possible attributes of the state in @setState explanation
6829
+ * See possible attributes of the state in `setState` explanation
6735
6830
  */
6736
6831
  getHistory(id, options, callback) {
6737
6832
  if (typeof options === 'function') {
@@ -6753,13 +6848,12 @@ export class AdapterClass extends EventEmitter {
6753
6848
  this._utils.validateId(id, true, null);
6754
6849
  }
6755
6850
  catch (e) {
6756
- // @ts-expect-error
6757
6851
  return tools.maybeCallbackWithError(callback, e);
6758
6852
  }
6759
- options = options || {};
6760
- options.end = options.end || Date.now() + 5000000;
6853
+ options ||= {};
6854
+ options.end ||= Date.now() + 5000000;
6761
6855
  if (!options.count && !options.start) {
6762
- options.start = options.start || Date.now() - 604800000; // - 1 week
6856
+ options.start ||= Date.now() - 604800000; // - 1 week
6763
6857
  }
6764
6858
  if (!options.instance) {
6765
6859
  if (!this.defaultHistory) {
@@ -6774,12 +6868,10 @@ export class AdapterClass extends EventEmitter {
6774
6868
  await this._checkStates(id, options, 'getState');
6775
6869
  }
6776
6870
  catch (e) {
6777
- // @ts-expect-error
6778
6871
  return tools.maybeCallbackWithError(callback, e);
6779
6872
  }
6780
6873
  }
6781
- this.sendTo(options.instance || 'history.0', 'getHistory', { id, options }, res => {
6782
- // @ts-expect-error
6874
+ this.sendTo(options.instance || 'history.0', 'getHistory', { id, options }, (res) => {
6783
6875
  tools.maybeCallbackWithError(callback, res.error, res.result, res.step, res.sessionId);
6784
6876
  });
6785
6877
  }
@@ -6806,7 +6898,7 @@ export class AdapterClass extends EventEmitter {
6806
6898
  }
6807
6899
  /**
6808
6900
  * Deletes a state of this instance.
6809
- * The object will NOT be deleted. If you want to delete it too, use @delObject instead.
6901
+ * The object will NOT be deleted. If you want to delete it too, use `delObject` instead.
6810
6902
  *
6811
6903
  * It is not required to provice the adapter namespace, because it will automatically be added.
6812
6904
  * E.g. to delete "adapterName.X.myObject", only "myObject" is required as ID.
@@ -6849,7 +6941,7 @@ export class AdapterClass extends EventEmitter {
6849
6941
  }
6850
6942
  /**
6851
6943
  * Deletes a state of any adapter.
6852
- * The object is NOT deleted. If you want to delete it too, use @delForeignObject instead.
6944
+ * The object is NOT deleted. If you want to delete it too, use `delForeignObject` instead.
6853
6945
  *
6854
6946
  * No error is returned if state does not exist.
6855
6947
  *
@@ -6893,7 +6985,12 @@ export class AdapterClass extends EventEmitter {
6893
6985
  return tools.maybeCallbackWithError(callback, e);
6894
6986
  }
6895
6987
  }
6896
- await this.#states.delState(id, callback);
6988
+ if (callback) {
6989
+ this.#states.delState(id, callback);
6990
+ }
6991
+ else {
6992
+ await this.#states.delState(id);
6993
+ }
6897
6994
  }
6898
6995
  /**
6899
6996
  * Read all states of this adapter, that pass the pattern
@@ -6928,7 +7025,7 @@ export class AdapterClass extends EventEmitter {
6928
7025
  const arr = await this.#states.getStates(keys);
6929
7026
  const result = {};
6930
7027
  for (let i = 0; i < keys.length; i++) {
6931
- const obj = targetObjs && targetObjs[i];
7028
+ const obj = targetObjs?.[i];
6932
7029
  if (obj?.common?.alias && srcObjs) {
6933
7030
  const srcObj = srcObjs[i];
6934
7031
  if (srcObj) {
@@ -7041,12 +7138,12 @@ export class AdapterClass extends EventEmitter {
7041
7138
  async _getForeignStates(_options) {
7042
7139
  const { options, pattern, callback } = _options;
7043
7140
  if (!this.#states) {
7044
- // if states is no longer existing, we do not need to unsubscribe
7141
+ // if states are no longer existing, we do not need to unsubscribe
7045
7142
  this._logger.info(`${this.namespaceLog} getForeignStates not processed because States database not connected`);
7046
7143
  return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7047
7144
  }
7048
7145
  if (!this.#objects) {
7049
- // if states is no longer existing, we do not need to unsubscribe
7146
+ // if states are no longer existing, we do not need to unsubscribe
7050
7147
  this._logger.info(`${this.namespaceLog} getForeignStates not processed because Objects database not connected`);
7051
7148
  return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
7052
7149
  }
@@ -7063,7 +7160,7 @@ export class AdapterClass extends EventEmitter {
7063
7160
  }
7064
7161
  }
7065
7162
  else {
7066
- const res = await this._processStates(pattern, options?._objects);
7163
+ const res = await this._processStates(pattern, options._objects);
7067
7164
  return tools.maybeCallbackWithError(callback, null, res);
7068
7165
  }
7069
7166
  }
@@ -7131,90 +7228,6 @@ export class AdapterClass extends EventEmitter {
7131
7228
  }
7132
7229
  }
7133
7230
  }
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
7231
  /**
7219
7232
  * Subscribe for changes on all states of all adapters (and system states), that pass the pattern
7220
7233
  *
@@ -7241,167 +7254,10 @@ export class AdapterClass extends EventEmitter {
7241
7254
  Validator.assertObject(options, 'options');
7242
7255
  }
7243
7256
  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
- }
7257
+ const cb = callback;
7258
+ this.#subscriptions
7259
+ .subscribeForeignStates(pattern, options)
7260
+ .then(() => cb?.(), (err) => cb?.(err));
7405
7261
  }
7406
7262
  /**
7407
7263
  * Unsubscribe for changes for given pattern
@@ -7435,102 +7291,10 @@ export class AdapterClass extends EventEmitter {
7435
7291
  if (options !== null && options !== undefined) {
7436
7292
  Validator.assertObject(options, 'options');
7437
7293
  }
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);
7294
+ const cb = callback;
7295
+ this.#subscriptions
7296
+ .unsubscribeForeignStates(pattern, options)
7297
+ .then(() => cb?.(), (err) => cb?.(err));
7534
7298
  }
7535
7299
  /**
7536
7300
  * Subscribe for changes on all states of this instance, that pass the pattern
@@ -7554,11 +7318,10 @@ export class AdapterClass extends EventEmitter {
7554
7318
  if (options !== null && options !== undefined) {
7555
7319
  Validator.assertObject(options, 'options');
7556
7320
  }
7557
- return this._subscribeForeignStates({
7558
- pattern: Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true),
7559
- options,
7560
- callback,
7561
- });
7321
+ const cb = callback;
7322
+ this.#subscriptions
7323
+ .subscribeForeignStates(Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true), options)
7324
+ .then(() => cb?.(), (err) => cb?.(err));
7562
7325
  }
7563
7326
  /**
7564
7327
  * Unsubscribe for changes for given pattern for own states.
@@ -7584,11 +7347,10 @@ export class AdapterClass extends EventEmitter {
7584
7347
  if (options !== null && options !== undefined) {
7585
7348
  Validator.assertObject(options, 'options');
7586
7349
  }
7587
- return this._unsubscribeForeignStates({
7588
- pattern: Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true),
7589
- options,
7590
- callback,
7591
- });
7350
+ const cb = callback;
7351
+ this.#subscriptions
7352
+ .unsubscribeForeignStates(Array.isArray(pattern) ? pattern : this._utils.fixId(pattern, true), options)
7353
+ .then(() => cb?.(), (err) => cb?.(err));
7592
7354
  }
7593
7355
  /**
7594
7356
  * Return plugin instance
@@ -7616,31 +7378,6 @@ export class AdapterClass extends EventEmitter {
7616
7378
  Validator.assertString(name, 'name');
7617
7379
  return this.pluginHandler.getPluginConfig(name);
7618
7380
  }
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
7381
  /**
7645
7382
  * This method returns the list of license that can be used by this adapter
7646
7383
  *
@@ -7769,20 +7506,24 @@ export class AdapterClass extends EventEmitter {
7769
7506
  /** Time after which states regularly set by the status report expire in seconds */
7770
7507
  const reportStatusExpirySec = Math.floor(this._config.system.statisticsInterval / 1_000) + 10;
7771
7508
  const id = `system.adapter.${this.namespace}`;
7772
- this.#states.setState(`${id}.alive`, {
7509
+ this.#states
7510
+ .setState(`${id}.alive`, {
7773
7511
  val: true,
7774
7512
  ack: true,
7775
7513
  expire: reportStatusExpirySec,
7776
7514
  from: id,
7777
- });
7515
+ })
7516
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set alive state: ${e.message}`));
7778
7517
  this.outputCount++;
7779
7518
  if (this.connected) {
7780
- this.#states.setState(`${id}.connected`, {
7519
+ this.#states
7520
+ .setState(`${id}.connected`, {
7781
7521
  val: true,
7782
7522
  ack: true,
7783
7523
  expire: reportStatusExpirySec,
7784
7524
  from: id,
7785
- });
7525
+ })
7526
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set connected state: ${e.message}`));
7786
7527
  this.outputCount++;
7787
7528
  }
7788
7529
  if (!this.startedInCompactMode) {
@@ -7797,47 +7538,59 @@ export class AdapterClass extends EventEmitter {
7797
7538
  // timestamp: 864000000 // ms since epoch
7798
7539
  // }
7799
7540
  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`, {
7541
+ // sometimes adapter is stopped, but this callback is still running: once
7542
+ // terminating, the states DB connection may already be closing — skip the write to
7543
+ // avoid a "Connection is closed" warning (setState does a read-before-write).
7544
+ if (!err && this && this.#states && this.#states.setState && stats && !this.terminated) {
7545
+ this.#states
7546
+ .setState(`${id}.cpu`, {
7803
7547
  ack: true,
7804
7548
  from: id,
7805
7549
  val: Math.round(100 * stats.cpu) / 100,
7806
7550
  expire: reportStatusExpirySec,
7807
- });
7808
- this.#states.setState(`${id}.cputime`, {
7551
+ })
7552
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set cpu state: ${e.message}`));
7553
+ this.#states
7554
+ .setState(`${id}.cputime`, {
7809
7555
  ack: true,
7810
7556
  from: id,
7811
7557
  val: stats.ctime / 1_000,
7812
7558
  expire: reportStatusExpirySec,
7813
- });
7559
+ })
7560
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set cputime state: ${e.message}`));
7814
7561
  this.outputCount += 2;
7815
7562
  }
7816
7563
  });
7817
7564
  try {
7818
7565
  //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
7566
  const mem = process.memoryUsage();
7820
- this.#states.setState(`${id}.memRss`, {
7567
+ this.#states
7568
+ .setState(`${id}.memRss`, {
7821
7569
  val: parseFloat((mem.rss / 1048576) /* 1MB */
7822
7570
  .toFixed(2)),
7823
7571
  ack: true,
7824
7572
  from: id,
7825
7573
  expire: reportStatusExpirySec,
7826
- });
7827
- this.#states.setState(`${id}.memHeapTotal`, {
7574
+ })
7575
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memRss state: ${e.message}`));
7576
+ this.#states
7577
+ .setState(`${id}.memHeapTotal`, {
7828
7578
  val: parseFloat((mem.heapTotal / 1048576) /* 1MB */
7829
7579
  .toFixed(2)),
7830
7580
  ack: true,
7831
7581
  from: id,
7832
7582
  expire: reportStatusExpirySec,
7833
- });
7834
- this.#states.setState(`${id}.memHeapUsed`, {
7583
+ })
7584
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memHeapTotal state: ${e.message}`));
7585
+ this.#states
7586
+ .setState(`${id}.memHeapUsed`, {
7835
7587
  val: parseFloat((mem.heapUsed / 1048576) /* 1MB */
7836
7588
  .toFixed(2)),
7837
7589
  ack: true,
7838
7590
  from: id,
7839
7591
  expire: reportStatusExpirySec,
7840
- });
7592
+ })
7593
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set memHeapUsed state: ${e.message}`));
7841
7594
  }
7842
7595
  catch (e) {
7843
7596
  this._logger.warn(`${this.namespaceLog} Could not query used process memory: ${e.message}`);
@@ -7845,35 +7598,43 @@ export class AdapterClass extends EventEmitter {
7845
7598
  this.outputCount += 3;
7846
7599
  if (this.eventLoopLags.length) {
7847
7600
  const eventLoopLag = Math.ceil(this.eventLoopLags.reduce((a, b) => a + b) / this.eventLoopLags.length);
7848
- this.#states.setState(`${id}.eventLoopLag`, {
7601
+ this.#states
7602
+ .setState(`${id}.eventLoopLag`, {
7849
7603
  val: eventLoopLag,
7850
7604
  ack: true,
7851
7605
  from: id,
7852
7606
  expire: reportStatusExpirySec,
7853
- }); // average of measured values
7607
+ }) // average of measured values
7608
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set eventLoopLag state: ${e.message}`));
7854
7609
  this.eventLoopLags = [];
7855
7610
  this.outputCount++;
7856
7611
  }
7857
7612
  }
7858
7613
  this.outputCount += 3;
7859
- this.#states.setState(`${id}.uptime`, {
7614
+ this.#states
7615
+ .setState(`${id}.uptime`, {
7860
7616
  val: parseInt(process.uptime().toFixed(), 10),
7861
7617
  ack: true,
7862
7618
  from: id,
7863
7619
  expire: reportStatusExpirySec,
7864
- });
7865
- this.#states.setState(`${id}.inputCount`, {
7620
+ })
7621
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set uptime state: ${e.message}`));
7622
+ this.#states
7623
+ .setState(`${id}.inputCount`, {
7866
7624
  val: this.inputCount,
7867
7625
  ack: true,
7868
7626
  from: id,
7869
7627
  expire: reportStatusExpirySec,
7870
- });
7871
- this.#states.setState(`${id}.outputCount`, {
7628
+ })
7629
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set inputCount state: ${e.message}`));
7630
+ this.#states
7631
+ .setState(`${id}.outputCount`, {
7872
7632
  val: this.outputCount,
7873
7633
  ack: true,
7874
7634
  from: id,
7875
7635
  expire: reportStatusExpirySec,
7876
- });
7636
+ })
7637
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set outputCount state: ${e.message}`));
7877
7638
  this.inputCount = 0;
7878
7639
  this.outputCount = 0;
7879
7640
  }
@@ -7960,7 +7721,9 @@ export class AdapterClass extends EventEmitter {
7960
7721
  else if (this.#states?.pushLog) {
7961
7722
  // Send it to all adapters, that required logs
7962
7723
  for (const instanceId of this.logList) {
7963
- this.#states.pushLog(instanceId, info);
7724
+ this.#states
7725
+ .pushLog(instanceId, info)
7726
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot push log: ${e.message}`));
7964
7727
  }
7965
7728
  }
7966
7729
  });
@@ -7986,7 +7749,9 @@ export class AdapterClass extends EventEmitter {
7986
7749
  if (this.logList.size && messages?.length && this.#states) {
7987
7750
  for (const message of messages) {
7988
7751
  for (const instanceId of this.logList) {
7989
- this.#states.pushLog(instanceId, message);
7752
+ this.#states
7753
+ .pushLog(instanceId, message)
7754
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot push log: ${e.message}`));
7990
7755
  }
7991
7756
  }
7992
7757
  }
@@ -8056,7 +7821,9 @@ export class AdapterClass extends EventEmitter {
8056
7821
  this.emit('log', msg);
8057
7822
  }
8058
7823
  };
8059
- this.#states.subscribeLog(`system.adapter.${this.namespace}`);
7824
+ this.#states
7825
+ .subscribeLog(`system.adapter.${this.namespace}`)
7826
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to log: ${e.message}`));
8060
7827
  }
8061
7828
  else {
8062
7829
  this.requireLog = isActive => {
@@ -8100,13 +7867,19 @@ export class AdapterClass extends EventEmitter {
8100
7867
  }
8101
7868
  if (!this._config.isInstall) {
8102
7869
  // Subscribe for process exit signal
8103
- this.#states.subscribe(`system.adapter.${this.namespace}.sigKill`);
7870
+ this.#states
7871
+ .subscribe(`system.adapter.${this.namespace}.sigKill`)
7872
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to sigKill: ${e.message}`));
8104
7873
  // Subscribe for loglevel
8105
- this.#states.subscribe(`system.adapter.${this.namespace}.logLevel`);
7874
+ this.#states
7875
+ .subscribe(`system.adapter.${this.namespace}.logLevel`)
7876
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to logLevel: ${e.message}`));
8106
7877
  }
8107
7878
  if (this._options.subscribable) {
8108
7879
  // subscribe on if other instance wants to have states of this adapter
8109
- this.#states.subscribe(`system.adapter.${this.namespace}.subscribes`);
7880
+ this.#states
7881
+ .subscribe(`system.adapter.${this.namespace}.subscribes`)
7882
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to subscribes: ${e.message}`));
8110
7883
  // read actual autosubscribe requests
8111
7884
  let state;
8112
7885
  try {
@@ -8160,7 +7933,7 @@ export class AdapterClass extends EventEmitter {
8160
7933
  isScheduled: false,
8161
7934
  exitCode: EXIT_CODES.ADAPTER_REQUESTED_TERMINATION,
8162
7935
  updateAliveState: false,
8163
- });
7936
+ }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8164
7937
  }
8165
7938
  }
8166
7939
  }
@@ -8171,14 +7944,11 @@ export class AdapterClass extends EventEmitter {
8171
7944
  if (state.val && state.val !== currentLevel && isLogLevel(newLogLevel)) {
8172
7945
  this.overwriteLogLevel = true;
8173
7946
  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
- }
7947
+ for (const transport of this._logger.transports) {
8178
7948
  // set the loglevel on transport only if no loglevel was pinned in log config
8179
7949
  // @ts-expect-error it is our own modification
8180
- if (!this._logger.transports[transport]._defaultConfigLoglevel) {
8181
- this._logger.transports[transport].level = newLogLevel;
7950
+ if (!transport._defaultConfigLoglevel) {
7951
+ transport.level = newLogLevel;
8182
7952
  }
8183
7953
  }
8184
7954
  this._logger.info(`${this.namespaceLog} Loglevel changed from "${currentLevel}" to "${state.val}"`);
@@ -8189,11 +7959,13 @@ export class AdapterClass extends EventEmitter {
8189
7959
  }
8190
7960
  this.outputCount++;
8191
7961
  this.#states &&
8192
- this.#states.setState(`system.adapter.${this.namespace}.logLevel`, {
7962
+ this.#states
7963
+ .setState(`system.adapter.${this.namespace}.logLevel`, {
8193
7964
  val: currentLevel,
8194
7965
  ack: true,
8195
7966
  from: `system.adapter.${this.namespace}`,
8196
- });
7967
+ })
7968
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set logLevel state: ${e.message}`));
8197
7969
  }
8198
7970
  }
8199
7971
  // todo remove it as an error with log will be found
@@ -8236,29 +8008,7 @@ export class AdapterClass extends EventEmitter {
8236
8008
  // If this is messagebox
8237
8009
  const obj = state;
8238
8010
  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) {
8011
+ if (!this.#async.resolveReply(obj) && !this._stopInProgress) {
8262
8012
  if (obj.command === 'clientSubscribe') {
8263
8013
  const res = await this.uiMessagingController.registerClientSubscribeByMessage(obj);
8264
8014
  this.sendTo(obj.from, obj.command, res, obj.callback);
@@ -8269,7 +8019,7 @@ export class AdapterClass extends EventEmitter {
8269
8019
  }
8270
8020
  if (this._options.message) {
8271
8021
  // Else inform about a new message the adapter
8272
- this._options.message(obj);
8022
+ Promise.resolve(this._options.message(obj)).catch(e => this._logger.error(`${this.namespaceLog} Error in message handler: ${e.message}`));
8273
8023
  }
8274
8024
  this.emit('message', obj);
8275
8025
  }
@@ -8304,37 +8054,13 @@ export class AdapterClass extends EventEmitter {
8304
8054
  }
8305
8055
  }
8306
8056
  }
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;
8057
+ else if (!this._stopInProgress && this.adapterReady && this.#alias.hasSource(id)) {
8058
+ for (const { targetId, state: aState } of this.#alias.resolveSourceChange(id, state)) {
8059
+ if (typeof this._options.stateChange === 'function') {
8060
+ Promise.resolve(this._options.stateChange(targetId, aState)).catch(e => this._logger.error(`${this.namespaceLog} Error in stateChange handler: ${e.message}`));
8316
8061
  }
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
- }
8062
+ else {
8063
+ setImmediate(() => this.emit('stateChange', targetId, aState));
8338
8064
  }
8339
8065
  }
8340
8066
  }
@@ -8373,7 +8099,7 @@ export class AdapterClass extends EventEmitter {
8373
8099
  return;
8374
8100
  } // If reconnected in the meantime, do not terminate
8375
8101
  this._logger.warn(`${this.namespaceLog} Cannot connect/reconnect to states DB. Stopping adapter.`);
8376
- this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false });
8102
+ this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8377
8103
  }, 5000);
8378
8104
  },
8379
8105
  });
@@ -8440,7 +8166,7 @@ export class AdapterClass extends EventEmitter {
8440
8166
  return;
8441
8167
  } // If reconnected in the meantime, do not terminate
8442
8168
  this._logger.warn(`${this.namespaceLog} Cannot connect/reconnect to objects DB. Stopping adapter.`);
8443
- this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false });
8169
+ this._stop({ exitCode: EXIT_CODES.NO_ERROR, updateAliveState: false }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8444
8170
  }, 4000);
8445
8171
  },
8446
8172
  change: async (id, obj) => {
@@ -8452,7 +8178,14 @@ export class AdapterClass extends EventEmitter {
8452
8178
  // If desired, that adapter must be terminated
8453
8179
  if (id === `system.adapter.${this.namespace}` && obj?.common?.enabled === false) {
8454
8180
  this._logger.info(`${this.namespaceLog} Adapter is disabled => stop`);
8455
- this._stop();
8181
+ this._stop().catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8182
+ return;
8183
+ }
8184
+ // a certificate we are using has been changed => restart to reload it
8185
+ if (id === CERTIFICATES_OBJECT_ID) {
8186
+ if (this.#async.hasRelevantCertificateChange(obj)) {
8187
+ this.#scheduleCertificateRestart('Changed certificate detected');
8188
+ }
8456
8189
  return;
8457
8190
  }
8458
8191
  // update language, dateFormat and comma
@@ -8468,78 +8201,17 @@ export class AdapterClass extends EventEmitter {
8468
8201
  }
8469
8202
  // if alias
8470
8203
  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
- }
8204
+ await this.#alias.handleAliasObjectChange(id, obj);
8528
8205
  }
8529
8206
  // process auto-subscribe adapters
8530
8207
  if (id.startsWith('system.adapter.')) {
8531
8208
  if (obj?.common?.subscribable) {
8532
8209
  const _id = id.substring(15); // 'system.adapter.'.length
8533
8210
  if (obj.common.enabled) {
8534
- if (!this.autoSubscribe.includes(_id)) {
8535
- this.autoSubscribe.push(_id);
8536
- }
8211
+ this.#subscriptions.addSubscribableInstance(_id);
8537
8212
  }
8538
8213
  else {
8539
- const pos = this.autoSubscribe.indexOf(_id);
8540
- if (pos !== -1) {
8541
- this.autoSubscribe.splice(pos, 1);
8542
- }
8214
+ this.#subscriptions.removeSubscribableInstance(_id);
8543
8215
  }
8544
8216
  }
8545
8217
  }
@@ -8677,7 +8349,9 @@ export class AdapterClass extends EventEmitter {
8677
8349
  // if adapterState was destroyed, we should not continue
8678
8350
  return;
8679
8351
  }
8680
- this.#states.subscribe(`system.adapter.${this.namespace}.plugins.*`);
8352
+ this.#states
8353
+ .subscribe(`system.adapter.${this.namespace}.plugins.*`)
8354
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to plugins: ${e.message}`));
8681
8355
  if (this._options.instance === undefined) {
8682
8356
  if (!adapterConfig || !('common' in adapterConfig) || !adapterConfig.common.enabled) {
8683
8357
  if (adapterConfig && 'common' in adapterConfig && adapterConfig.common.enabled !== undefined) {
@@ -8769,7 +8443,9 @@ export class AdapterClass extends EventEmitter {
8769
8443
  this.stop = params => this._stop({ ...params, isPause: false });
8770
8444
  }
8771
8445
  // Monitor logging state
8772
- this.#states.subscribe(`${SYSTEM_ADAPTER_PREFIX}*.logging`);
8446
+ this.#states
8447
+ .subscribe(`${SYSTEM_ADAPTER_PREFIX}*.logging`)
8448
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to logging: ${e.message}`));
8773
8449
  if (typeof this._options.message === 'function' &&
8774
8450
  // @ts-expect-error, we should infer correctly that this is an InstanceObject in this case
8775
8451
  !isMessageboxSupported(adapterConfig.common)) {
@@ -8777,8 +8453,9 @@ export class AdapterClass extends EventEmitter {
8777
8453
  // @ts-expect-error we should infer adapterConfig correctly
8778
8454
  }
8779
8455
  else if (isMessageboxSupported(adapterConfig.common)) {
8780
- this.mboxSubscribed = true;
8781
- this.#states.subscribeMessage(`system.adapter.${this.namespace}`);
8456
+ this.#states
8457
+ .subscribeMessage(`system.adapter.${this.namespace}`)
8458
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot subscribe to messages: ${e.message}`));
8782
8459
  }
8783
8460
  }
8784
8461
  else {
@@ -8795,7 +8472,7 @@ export class AdapterClass extends EventEmitter {
8795
8472
  this.host = this.common?.host || tools.getHostName();
8796
8473
  }
8797
8474
  this.adapterConfig = adapterConfig;
8798
- this._utils = new Validator(this.#objects, this.#states, this.namespaceLog, this._logger, this.namespace, this._namespaceRegExp);
8475
+ this._utils = new Validator(this.#objects, this.namespaceLog, this._logger, this.namespace, this._namespaceRegExp);
8799
8476
  this.log = new Log(this.namespaceLog, this._config.log.level, this._logger);
8800
8477
  await this._createInstancesObjects(adapterConfig);
8801
8478
  // auto oObjects
@@ -8804,19 +8481,22 @@ export class AdapterClass extends EventEmitter {
8804
8481
  await this.subscribeObjectsAsync('*');
8805
8482
  }
8806
8483
  // initialize the system secret
8807
- await this.getSystemSecret();
8484
+ const secret = await this.getSystemSecret();
8808
8485
  // Decrypt all attributes of encryptedNative
8809
- const promises = [];
8810
8486
  // @ts-expect-error
8811
8487
  if (Array.isArray(adapterConfig.encryptedNative)) {
8488
+ this._decryptedNativeAttributes = new Array();
8812
8489
  // @ts-expect-error
8813
8490
  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}`)));
8491
+ // one attribute at a time, so a value that cannot be decrypted does not stop the others
8492
+ try {
8493
+ decryptArray({ obj: this.config, secret, keys: [attr] });
8494
+ // only after it worked: a failed decrypt leaves the encrypted value in place, and
8495
+ // reading it back must decrypt it rather than hand out the cipher text
8496
+ this._decryptedNativeAttributes.push(attr);
8497
+ }
8498
+ catch (e) {
8499
+ this._logger.error(`${this.namespaceLog} Can not decrypt attribute ${attr}: ${e.message}`);
8820
8500
  }
8821
8501
  }
8822
8502
  }
@@ -8827,19 +8507,19 @@ export class AdapterClass extends EventEmitter {
8827
8507
  this.SUPPORTED_FEATURES.splice(idx, 1);
8828
8508
  }
8829
8509
  }
8830
- // Wait till all attributes decrypted
8831
- await Promise.all(promises);
8832
8510
  if (!this.#states) {
8833
8511
  // if this.adapterStates was destroyed, we should not continue
8834
8512
  return;
8835
8513
  }
8836
8514
  this.outputCount++;
8837
8515
  // set current loglevel
8838
- this.#states.setState(`system.adapter.${this.namespace}.logLevel`, {
8516
+ this.#states
8517
+ .setState(`system.adapter.${this.namespace}.logLevel`, {
8839
8518
  val: this._config.log.level,
8840
8519
  ack: true,
8841
8520
  from: `system.adapter.${this.namespace}`,
8842
- });
8521
+ })
8522
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set logLevel state: ${e.message}`));
8843
8523
  try {
8844
8524
  await this.#checkObjectsWarnLimit();
8845
8525
  }
@@ -8864,19 +8544,24 @@ export class AdapterClass extends EventEmitter {
8864
8544
  this._reportInterval = setInterval(() => this._reportStatus(), this._config.system.statisticsInterval);
8865
8545
  this._reportStatus();
8866
8546
  const id = `system.adapter.${this.namespace}`;
8867
- this.#states.setState(`${id}.compactMode`, {
8547
+ this.#states
8548
+ .setState(`${id}.compactMode`, {
8868
8549
  ack: true,
8869
8550
  from: id,
8870
8551
  val: !!this.startedInCompactMode,
8871
- });
8552
+ })
8553
+ .catch(e => this._logger.error(`${this.namespaceLog} Cannot set compactMode state: ${e.message}`));
8872
8554
  this.outputCount++;
8873
8555
  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 });
8556
+ const onErr = (e) => {
8557
+ this._logger.error(`${this.namespaceLog} Cannot reset compact metric: ${e.message}`);
8558
+ };
8559
+ this.#states.setState(`${id}.cpu`, { ack: true, from: id, val: 0 }).catch(onErr);
8560
+ this.#states.setState(`${id}.cputime`, { ack: true, from: id, val: 0 }).catch(onErr);
8561
+ this.#states.setState(`${id}.memRss`, { val: 0, ack: true, from: id }).catch(onErr);
8562
+ this.#states.setState(`${id}.memHeapTotal`, { val: 0, ack: true, from: id }).catch(onErr);
8563
+ this.#states.setState(`${id}.memHeapUsed`, { val: 0, ack: true, from: id }).catch(onErr);
8564
+ this.#states.setState(`${id}.eventLoopLag`, { val: 0, ack: true, from: id }).catch(onErr);
8880
8565
  this.outputCount += 6;
8881
8566
  }
8882
8567
  else {
@@ -8899,7 +8584,7 @@ export class AdapterClass extends EventEmitter {
8899
8584
  this._logger.debug(`${this.namespaceLog} Schedule restart: ${adapterConfig.common.restartSchedule}`);
8900
8585
  this._restartScheduleJob = this._schedule.scheduleJob(adapterConfig.common.restartSchedule, () => {
8901
8586
  this._logger.info(`${this.namespaceLog} Scheduled restart.`);
8902
- this._stop({ isPause: false, isScheduled: true });
8587
+ this._stop({ isPause: false, isScheduled: true }).catch(e => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${e.message}`));
8903
8588
  });
8904
8589
  }
8905
8590
  }
@@ -8930,13 +8615,13 @@ export class AdapterClass extends EventEmitter {
8930
8615
  if (this._config.isInstall &&
8931
8616
  (typeof this._options.install === 'function' || this.listeners('install').length)) {
8932
8617
  if (typeof this._options.install === 'function') {
8933
- this._options.install();
8618
+ Promise.resolve(this._options.install()).catch(e => this._logger.error(`${this.namespaceLog} Error in install handler: ${e.message}`));
8934
8619
  }
8935
8620
  this.emit('install');
8936
8621
  }
8937
8622
  else {
8938
8623
  if (typeof this._options.ready === 'function') {
8939
- this._options.ready();
8624
+ Promise.resolve(this._options.ready()).catch(e => this._logger.error(`${this.namespaceLog} Error in ready handler: ${e.message}`));
8940
8625
  }
8941
8626
  this.emit('ready');
8942
8627
  }
@@ -8990,7 +8675,7 @@ export class AdapterClass extends EventEmitter {
8990
8675
  isScheduled: false,
8991
8676
  exitCode: EXIT_CODES.UNCAUGHT_EXCEPTION,
8992
8677
  updateAliveState: false,
8993
- });
8678
+ }).catch(err => this._logger.error(`${this.namespaceLog} Cannot stop adapter: ${err.message}`));
8994
8679
  }
8995
8680
  catch (e) {
8996
8681
  this._logger.error(`${this.namespaceLog} exception by stop: ${e ? e.message : e}`);
@@ -9077,7 +8762,7 @@ export class AdapterClass extends EventEmitter {
9077
8762
  });
9078
8763
  }
9079
8764
  return new Promise(resolve => {
9080
- this._extendObjects(objs, resolve);
8765
+ return this._extendObjects(objs, resolve);
9081
8766
  });
9082
8767
  }
9083
8768
  /**
@@ -9127,7 +8812,7 @@ export class AdapterClass extends EventEmitter {
9127
8812
  return this.#states.setState(id, state);
9128
8813
  }
9129
8814
  async _extendObjects(tasks, callback) {
9130
- if (!tasks || !tasks.length) {
8815
+ if (!tasks?.length) {
9131
8816
  return tools.maybeCallback(callback);
9132
8817
  }
9133
8818
  const task = tasks.shift();