@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.
- package/build/cjs/index.d.ts +0 -1
- package/build/cjs/index.js +5 -8
- package/build/cjs/index.js.map +2 -2
- package/build/cjs/lib/_Types.d.ts +394 -63
- package/build/cjs/lib/adapter/adapter.d.ts +1896 -177
- package/build/cjs/lib/adapter/adapter.js +788 -1035
- package/build/cjs/lib/adapter/adapter.js.map +2 -2
- package/build/cjs/lib/adapter/asyncAdapter.d.ts +167 -0
- package/build/cjs/lib/adapter/asyncAdapter.js +252 -0
- package/build/cjs/lib/adapter/asyncAdapter.js.map +7 -0
- package/build/cjs/lib/adapter/context.d.ts +34 -0
- package/build/cjs/lib/adapter/context.js +17 -0
- package/build/cjs/lib/adapter/context.js.map +7 -0
- package/build/cjs/lib/adapter/defaultObjs.d.ts +5 -0
- package/build/cjs/lib/adapter/defaultObjs.js +2 -0
- package/build/cjs/lib/adapter/defaultObjs.js.map +2 -2
- package/build/cjs/lib/adapter/log.d.ts +25 -0
- package/build/cjs/lib/adapter/log.js +29 -0
- package/build/cjs/lib/adapter/log.js.map +2 -2
- package/build/cjs/lib/adapter/managers/AdapterContextBase.d.ts +49 -0
- package/build/cjs/lib/adapter/managers/AdapterContextBase.js +98 -0
- package/build/cjs/lib/adapter/managers/AdapterContextBase.js.map +7 -0
- package/build/cjs/lib/adapter/managers/AliasManager.d.ts +103 -0
- package/build/cjs/lib/adapter/managers/AliasManager.js +293 -0
- package/build/cjs/lib/adapter/managers/AliasManager.js.map +7 -0
- package/build/cjs/lib/adapter/managers/CertificateManager.d.ts +50 -0
- package/build/cjs/lib/adapter/managers/CertificateManager.js +174 -0
- package/build/cjs/lib/adapter/managers/CertificateManager.js.map +7 -0
- package/build/cjs/lib/adapter/managers/MessagingManager.d.ts +58 -0
- package/build/cjs/lib/adapter/managers/MessagingManager.js +289 -0
- package/build/cjs/lib/adapter/managers/MessagingManager.js.map +7 -0
- package/build/cjs/lib/adapter/managers/ResourceManager.d.ts +69 -0
- package/build/cjs/lib/adapter/managers/ResourceManager.js +170 -0
- package/build/cjs/lib/adapter/managers/ResourceManager.js.map +7 -0
- package/build/cjs/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
- package/build/cjs/lib/adapter/managers/SubscriptionManager.js +349 -0
- package/build/cjs/lib/adapter/managers/SubscriptionManager.js.map +7 -0
- package/build/cjs/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
- package/build/cjs/lib/adapter/userInterfaceMessagingController.js +9 -2
- package/build/cjs/lib/adapter/userInterfaceMessagingController.js.map +2 -2
- package/build/cjs/lib/adapter/utils.js +15 -1
- package/build/cjs/lib/adapter/utils.js.map +2 -2
- package/build/cjs/lib/adapter/validator.d.ts +10 -5
- package/build/cjs/lib/adapter/validator.js +6 -5
- package/build/cjs/lib/adapter/validator.js.map +2 -2
- package/build/esm/index.d.ts +0 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +0 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/lib/_Types.d.ts +394 -63
- package/build/esm/lib/_Types.d.ts.map +1 -1
- package/build/esm/lib/adapter/adapter.d.ts +1896 -177
- package/build/esm/lib/adapter/adapter.d.ts.map +1 -1
- package/build/esm/lib/adapter/adapter.js +900 -1215
- package/build/esm/lib/adapter/adapter.js.map +1 -1
- package/build/esm/lib/adapter/asyncAdapter.d.ts +167 -0
- package/build/esm/lib/adapter/asyncAdapter.d.ts.map +1 -0
- package/build/esm/lib/adapter/asyncAdapter.js +225 -0
- package/build/esm/lib/adapter/asyncAdapter.js.map +1 -0
- package/build/esm/lib/adapter/context.d.ts +34 -0
- package/build/esm/lib/adapter/context.d.ts.map +1 -0
- package/build/esm/lib/adapter/context.js +2 -0
- package/build/esm/lib/adapter/context.js.map +1 -0
- package/build/esm/lib/adapter/defaultObjs.d.ts +5 -0
- package/build/esm/lib/adapter/defaultObjs.d.ts.map +1 -1
- package/build/esm/lib/adapter/defaultObjs.js +5 -0
- package/build/esm/lib/adapter/defaultObjs.js.map +1 -1
- package/build/esm/lib/adapter/log.d.ts +25 -0
- package/build/esm/lib/adapter/log.d.ts.map +1 -1
- package/build/esm/lib/adapter/log.js +25 -0
- package/build/esm/lib/adapter/log.js.map +1 -1
- package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts +49 -0
- package/build/esm/lib/adapter/managers/AdapterContextBase.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/AdapterContextBase.js +77 -0
- package/build/esm/lib/adapter/managers/AdapterContextBase.js.map +1 -0
- package/build/esm/lib/adapter/managers/AliasManager.d.ts +103 -0
- package/build/esm/lib/adapter/managers/AliasManager.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/AliasManager.js +269 -0
- package/build/esm/lib/adapter/managers/AliasManager.js.map +1 -0
- package/build/esm/lib/adapter/managers/CertificateManager.d.ts +50 -0
- package/build/esm/lib/adapter/managers/CertificateManager.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/CertificateManager.js +142 -0
- package/build/esm/lib/adapter/managers/CertificateManager.js.map +1 -0
- package/build/esm/lib/adapter/managers/MessagingManager.d.ts +58 -0
- package/build/esm/lib/adapter/managers/MessagingManager.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/MessagingManager.js +267 -0
- package/build/esm/lib/adapter/managers/MessagingManager.js.map +1 -0
- package/build/esm/lib/adapter/managers/ResourceManager.d.ts +69 -0
- package/build/esm/lib/adapter/managers/ResourceManager.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/ResourceManager.js +151 -0
- package/build/esm/lib/adapter/managers/ResourceManager.js.map +1 -0
- package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts +107 -0
- package/build/esm/lib/adapter/managers/SubscriptionManager.d.ts.map +1 -0
- package/build/esm/lib/adapter/managers/SubscriptionManager.js +374 -0
- package/build/esm/lib/adapter/managers/SubscriptionManager.js.map +1 -0
- package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts +10 -0
- package/build/esm/lib/adapter/userInterfaceMessagingController.d.ts.map +1 -1
- package/build/esm/lib/adapter/userInterfaceMessagingController.js +8 -2
- package/build/esm/lib/adapter/userInterfaceMessagingController.js.map +1 -1
- package/build/esm/lib/adapter/utils.d.ts.map +1 -1
- package/build/esm/lib/adapter/utils.js +1 -1
- package/build/esm/lib/adapter/utils.js.map +1 -1
- package/build/esm/lib/adapter/validator.d.ts +10 -5
- package/build/esm/lib/adapter/validator.d.ts.map +1 -1
- package/build/esm/lib/adapter/validator.js +8 -5
- package/build/esm/lib/adapter/validator.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- 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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
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
|
-
|
|
1384
|
-
acl.users = acl.users || {};
|
|
1455
|
+
acl.users ||= {};
|
|
1385
1456
|
}
|
|
1386
1457
|
else {
|
|
1387
|
-
// @ts-expect-error fix
|
|
1388
|
-
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
|
|
1395
|
-
acl.users[op]
|
|
1465
|
+
// @ts-expect-error fix later
|
|
1466
|
+
acl.users[op] ||= gAcl.user[op];
|
|
1396
1467
|
}
|
|
1397
1468
|
else {
|
|
1398
|
-
// @ts-expect-error fix
|
|
1399
|
-
acl[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
|
-
|
|
1447
|
-
|
|
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
|
-
*
|
|
1518
|
-
*
|
|
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
|
|
1592
|
+
* @param paths file-backed certificate paths returned by the certificate manager
|
|
1521
1593
|
*/
|
|
1522
|
-
|
|
1523
|
-
|
|
1594
|
+
#watchCertFiles(paths) {
|
|
1595
|
+
for (const certFile of paths) {
|
|
1596
|
+
if (this.#certWatchers.has(certFile)) {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1524
1599
|
try {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1732
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2340
|
+
if (options.obj.native && 'devices' in options.obj.native && oldObj.native?.devices) {
|
|
2250
2341
|
oldObj.native.devices = [];
|
|
2251
2342
|
}
|
|
2252
|
-
options.obj.from
|
|
2253
|
-
options.obj.user
|
|
2254
|
-
options.obj.ts
|
|
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
|
|
2260
|
-
options.obj.user
|
|
2261
|
-
options.obj.ts
|
|
2262
|
-
if (
|
|
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.
|
|
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
|
|
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 &&
|
|
2468
|
-
|
|
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
|
|
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
|
|
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
|
|
2568
|
+
if (obj.native && 'devices' in obj.native && oldObj.native?.devices) {
|
|
2477
2569
|
oldObj.native.devices = [];
|
|
2478
2570
|
}
|
|
2479
|
-
obj.from
|
|
2480
|
-
obj.user
|
|
2481
|
-
obj.ts
|
|
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
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
obj.
|
|
2488
|
-
obj.
|
|
2489
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
|
3332
|
+
async _deleteObjects(tasks, options, cb) {
|
|
3333
|
+
if (!tasks?.length) {
|
|
3245
3334
|
return tools.maybeCallback(cb);
|
|
3246
3335
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
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
|
|
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
|
|
3353
|
+
this._logger.warn(`${this.namespaceLog} Could not remove ${task.id} from enums: ${e.message}`);
|
|
3258
3354
|
}
|
|
3259
3355
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
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
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
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
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
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
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
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
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
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.#
|
|
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.#
|
|
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
|
|
3725
|
-
common.name
|
|
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
|
|
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
|
|
3854
|
+
_native ||= {};
|
|
3784
3855
|
const obj = {
|
|
3856
|
+
_id: channelName,
|
|
3785
3857
|
type: 'channel',
|
|
3786
|
-
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
|
|
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
|
|
3846
|
-
common.read
|
|
3847
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
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
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
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
|
-
|
|
4086
|
-
|
|
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
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
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
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
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
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
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
|
-
|
|
5213
|
-
instanceName,
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
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
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
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
|
-
|
|
5299
|
-
|
|
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
|
-
|
|
5379
|
-
hostName,
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
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
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
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
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
5774
|
-
user.acl
|
|
5775
|
-
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
|
|
5784
|
-
user.acl.file.read
|
|
5785
|
-
user.acl.file.write
|
|
5786
|
-
user.acl.file.delete
|
|
5787
|
-
user.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
|
|
5793
|
-
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
|
|
5802
|
-
user.acl.object.read
|
|
5803
|
-
user.acl.object.write
|
|
5804
|
-
user.acl.object.delete
|
|
5805
|
-
user.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
|
|
5811
|
-
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
|
|
5820
|
-
user.acl.users.read
|
|
5821
|
-
user.acl.users.write
|
|
5822
|
-
user.acl.users.delete
|
|
5823
|
-
user.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
|
|
5829
|
-
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
|
|
5838
|
-
user.acl.state.read
|
|
5839
|
-
user.acl.state.write
|
|
5840
|
-
user.acl.state.delete
|
|
5841
|
-
user.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
|
|
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
|
|
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 !== ''
|
|
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 &&
|
|
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'))
|
|
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
|
|
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 &&
|
|
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 !== ''
|
|
6490
|
-
|
|
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 &&
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6760
|
-
options.end
|
|
6853
|
+
options ||= {};
|
|
6854
|
+
options.end ||= Date.now() + 5000000;
|
|
6761
6855
|
if (!options.count && !options.start) {
|
|
6762
|
-
options.start
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
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
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
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
|
-
|
|
7558
|
-
|
|
7559
|
-
options
|
|
7560
|
-
|
|
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
|
-
|
|
7588
|
-
|
|
7589
|
-
options
|
|
7590
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
7802
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
7601
|
+
this.#states
|
|
7602
|
+
.setState(`${id}.eventLoopLag`, {
|
|
7849
7603
|
val: eventLoopLag,
|
|
7850
7604
|
ack: true,
|
|
7851
7605
|
from: id,
|
|
7852
7606
|
expire: reportStatusExpirySec,
|
|
7853
|
-
})
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
8181
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
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
|
-
|
|
8318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8535
|
-
this.autoSubscribe.push(_id);
|
|
8536
|
-
}
|
|
8211
|
+
this.#subscriptions.addSubscribableInstance(_id);
|
|
8537
8212
|
}
|
|
8538
8213
|
else {
|
|
8539
|
-
|
|
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
|
|
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
|
|
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
|
|
8781
|
-
|
|
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
|
|
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
|
-
//
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
this.#states.setState(`${id}.
|
|
8878
|
-
this.#states.setState(`${id}.
|
|
8879
|
-
this.#states.setState(`${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
|
|
8815
|
+
if (!tasks?.length) {
|
|
9131
8816
|
return tools.maybeCallback(callback);
|
|
9132
8817
|
}
|
|
9133
8818
|
const task = tasks.shift();
|