@iobroker/socket-classes 1.6.2 → 2.0.2

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.
@@ -0,0 +1,1586 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SocketCommandsAdmin = void 0;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const node_path_1 = require("node:path");
10
+ const node_fs_1 = require("node:fs");
11
+ const socketCommands_1 = require("./socketCommands");
12
+ const ACL_READ = 4;
13
+ // const ACL_WRITE = 2;
14
+ class SocketCommandsAdmin extends socketCommands_1.SocketCommands {
15
+ static ALLOW_CACHE = [
16
+ 'getRepository',
17
+ 'getInstalled',
18
+ 'getInstalledAdapter',
19
+ 'getVersion',
20
+ 'getDiagData',
21
+ 'getLocationOnDisk',
22
+ 'getDevList',
23
+ 'getLogs',
24
+ 'getHostInfo',
25
+ ];
26
+ states;
27
+ objects;
28
+ thresholdInterval = null;
29
+ cmdSessions = {};
30
+ eventsThreshold;
31
+ cache = {};
32
+ cacheGB = null; // cache garbage collector
33
+ onThresholdChanged = null;
34
+ secret = '';
35
+ constructor(adapter, updateSession, context, objects, states) {
36
+ super(adapter, updateSession, context);
37
+ this.objects = objects;
38
+ this.states = states;
39
+ this.eventsThreshold = {
40
+ count: 0,
41
+ timeActivated: 0,
42
+ active: false,
43
+ accidents: 0,
44
+ repeatSeconds: 3, // how many seconds continuously must be number of events > value
45
+ value: parseInt(adapter.config.thresholdValue, 10) || 200, // how many events allowed in one check interval
46
+ checkInterval: 1000, // duration of one check interval
47
+ };
48
+ // do not send too many state updates
49
+ }
50
+ start(onThresholdChanged) {
51
+ // detect event bursts
52
+ this.thresholdInterval = setInterval(() => {
53
+ if (!this.eventsThreshold.active) {
54
+ if (this.eventsThreshold.count > this.eventsThreshold.value) {
55
+ this.eventsThreshold.accidents++;
56
+ if (this.eventsThreshold.accidents >= this.eventsThreshold.repeatSeconds) {
57
+ this.#enableEventThreshold();
58
+ }
59
+ }
60
+ else {
61
+ this.eventsThreshold.accidents = 0;
62
+ }
63
+ this.eventsThreshold.count = 0;
64
+ }
65
+ else if (Date.now() - this.eventsThreshold.timeActivated > 60000) {
66
+ this.disableEventThreshold();
67
+ }
68
+ }, this.eventsThreshold.checkInterval);
69
+ this.onThresholdChanged = onThresholdChanged;
70
+ }
71
+ /**
72
+ * Read a file with ratings from server
73
+ *
74
+ * @param uuid Unique ioBroker system identification
75
+ * @param _isAutoUpdate not implemented
76
+ */
77
+ async updateRatings(uuid, _isAutoUpdate) {
78
+ let _uuid;
79
+ if (!uuid) {
80
+ const obj = await this.adapter.getForeignObjectAsync('system.meta.uuid');
81
+ _uuid = obj?.native?.uuid || '';
82
+ }
83
+ else {
84
+ _uuid = uuid;
85
+ }
86
+ try {
87
+ const response = await axios_1.default.get(`https://rating.iobroker.net/rating?uuid=${uuid}`, {
88
+ timeout: 15000,
89
+ validateStatus: status => status < 400,
90
+ });
91
+ this.context.ratings = response.data;
92
+ if (!this.context.ratings ||
93
+ typeof this.context.ratings !== 'object' ||
94
+ Array.isArray(this.context.ratings)) {
95
+ // @ts-expect-error exception
96
+ this.context.ratings = { uuid: _uuid };
97
+ }
98
+ else {
99
+ this.context.ratings.uuid = _uuid;
100
+ }
101
+ // auto update only in admin
102
+ if (this.adapter.name === 'admin') {
103
+ this.context.ratingTimeout && clearTimeout(this.context.ratingTimeout);
104
+ this.context.ratingTimeout = setTimeout(() => {
105
+ this.context.ratingTimeout = null;
106
+ void this.updateRatings(uuid).then(() => this.adapter.log.info('Adapter rating updated'));
107
+ }, 24 * 3600000);
108
+ }
109
+ return this.context.ratings;
110
+ }
111
+ catch (error) {
112
+ this.adapter.log.warn(`Cannot update rating: ${error.response ? error.response.data : error.message || error.code}`);
113
+ return null;
114
+ }
115
+ }
116
+ async #readInstanceConfig(id, user, isTab, configs) {
117
+ let obj;
118
+ try {
119
+ obj = await this.adapter.getForeignObjectAsync(`system.adapter.${id}`, {
120
+ user,
121
+ });
122
+ }
123
+ catch {
124
+ // ignore
125
+ }
126
+ if (obj?.common) {
127
+ const instance = id.split('.').pop();
128
+ const config = {
129
+ id,
130
+ title: obj.common.titleLang || obj.common.title,
131
+ desc: obj.common.desc,
132
+ color: obj.common.color,
133
+ url: '',
134
+ icon: obj.common.icon,
135
+ materialize: obj.common.materialize,
136
+ // @ts-expect-error it is deprecated
137
+ jsonConfig: obj.common.jsonConfig,
138
+ version: obj.common.version,
139
+ };
140
+ if (obj.common.adminUI?.config === 'materialize') {
141
+ config.materialize = true;
142
+ }
143
+ else if (obj.common.adminUI?.config === 'json') {
144
+ config.jsonConfig = true;
145
+ }
146
+ config.url = `/adapter/${obj.common.name}/${isTab ? 'tab' : 'index'}${!isTab && config.materialize ? '_m' : ''}.html${instance ? `?${instance}` : ''}`;
147
+ if (isTab) {
148
+ config.tab = true;
149
+ }
150
+ else {
151
+ config.config = true;
152
+ }
153
+ configs.push(config);
154
+ }
155
+ }
156
+ _sendToHost = (host, command, message, callback) => {
157
+ const hash = `${host}_${command}`;
158
+ if (!message && _a.ALLOW_CACHE.includes(command) && this.cache[hash]) {
159
+ if (Date.now() - this.cache[hash].ts < 500) {
160
+ if (typeof callback === 'function') {
161
+ setImmediate(data => callback(data), JSON.parse(this.cache[hash].res));
162
+ }
163
+ return;
164
+ }
165
+ delete this.cache[hash];
166
+ }
167
+ try {
168
+ this.adapter.sendToHost(host, command, message, res => {
169
+ if (!message && _a.ALLOW_CACHE.includes(command)) {
170
+ this.cache[hash] = { ts: Date.now(), res: JSON.stringify(res) };
171
+ this.cacheGB =
172
+ this.cacheGB ||
173
+ setInterval(() => {
174
+ const commands = Object.keys(this.cache);
175
+ commands.forEach(cmd => {
176
+ if (Date.now() - this.cache[cmd].ts > 500) {
177
+ delete this.cache[cmd];
178
+ }
179
+ });
180
+ if (!commands.length && this.cacheGB) {
181
+ clearInterval(this.cacheGB);
182
+ this.cacheGB = null;
183
+ }
184
+ }, 2000);
185
+ }
186
+ if (typeof callback === 'function') {
187
+ setImmediate(() => callback(res));
188
+ }
189
+ });
190
+ }
191
+ catch (error) {
192
+ this.adapter.log.error(`[sendToHost] ERROR: ${error.toString()}`);
193
+ typeof callback === 'function' && setImmediate(() => callback({ error }));
194
+ }
195
+ };
196
+ // remove this function when js.controller 4.x are mainstream
197
+ async #readLicenses(login, password) {
198
+ const config = {
199
+ headers: { Authorization: `Basic ${Buffer.from(`${login}:${password}`).toString('base64')}` },
200
+ timeout: 4000,
201
+ validateStatus: (status) => status < 400,
202
+ };
203
+ try {
204
+ const response = await axios_1.default.get(`https://iobroker.net:3001/api/v1/licenses`, config);
205
+ if (response?.data?.length) {
206
+ const now = Date.now();
207
+ response.data = response.data.filter((license) => !license.validTill ||
208
+ license.validTill === '0000-00-00 00:00:00' ||
209
+ new Date(license.validTill).getTime() > now);
210
+ }
211
+ return response.data;
212
+ }
213
+ catch (error) {
214
+ if (error.response) {
215
+ throw new Error((error.response.data && error.response.data.error) || error.response.data || error.response.status);
216
+ }
217
+ if (error.request) {
218
+ throw new Error('no response');
219
+ }
220
+ throw error;
221
+ }
222
+ }
223
+ // remove this function when js.controller 4.x is mainstream
224
+ async #updateLicenses(login, password, options) {
225
+ // if login and password provided in the message, just try to read without saving it in system.licenses
226
+ if (login && password) {
227
+ return this.#readLicenses(login, password);
228
+ }
229
+ // get actual object
230
+ const systemLicenses = await this.adapter.getForeignObjectAsync('system.licenses', options);
231
+ // If password and login exist
232
+ if (systemLicenses?.native?.password && systemLicenses.native.login) {
233
+ // get the secret to decode the password
234
+ if (!this.secret) {
235
+ const systemConfig = await this.adapter.getForeignObjectAsync('system.config', options);
236
+ if (systemConfig?.native?.secret) {
237
+ this.secret = systemConfig.native.secret;
238
+ }
239
+ }
240
+ // decode the password
241
+ let password = '';
242
+ try {
243
+ password = this.adapter.decrypt(this.secret, systemLicenses.native.password);
244
+ }
245
+ catch {
246
+ throw new Error('Cannot decode password');
247
+ }
248
+ try {
249
+ const licenses = await this.#readLicenses(systemLicenses.native.login, password);
250
+ // save licenses to system.licenses and remember the time
251
+ // merge the information together
252
+ const oldLicenses = systemLicenses.native.licenses || [];
253
+ systemLicenses.native.licenses = licenses;
254
+ oldLicenses.forEach(oldLicense => {
255
+ if (oldLicense.usedBy) {
256
+ const newLicense = licenses.find(item => item.json === oldLicense.json);
257
+ if (newLicense) {
258
+ newLicense.usedBy = oldLicense.usedBy;
259
+ }
260
+ }
261
+ });
262
+ systemLicenses.native.readTime = new Date().toISOString();
263
+ // save only if an object changed
264
+ await this.adapter.setForeignObjectAsync('system.licenses', systemLicenses, options);
265
+ return licenses;
266
+ }
267
+ catch (error) {
268
+ // if password is invalid
269
+ if (error.message.includes('Authentication required') ||
270
+ error.message.includes('Cannot decode password')) {
271
+ // clear existing licenses if exist
272
+ if (systemLicenses?.native?.licenses?.length) {
273
+ systemLicenses.native.licenses = [];
274
+ systemLicenses.native.readTime = new Date().toISOString();
275
+ return this.adapter
276
+ .setForeignObjectAsync('system.licenses', systemLicenses, options)
277
+ .then(() => {
278
+ throw error;
279
+ });
280
+ }
281
+ throw error;
282
+ }
283
+ else {
284
+ throw error;
285
+ }
286
+ }
287
+ }
288
+ else {
289
+ // if password or login are empty => clear existing licenses if exist
290
+ if (systemLicenses?.native?.licenses?.length) {
291
+ systemLicenses.native.licenses = [];
292
+ systemLicenses.native.readTime = new Date().toISOString();
293
+ return this.adapter.setForeignObjectAsync('system.licenses', systemLicenses, options).then(() => {
294
+ throw new Error('No password or login');
295
+ });
296
+ }
297
+ throw new Error('No password or login');
298
+ }
299
+ }
300
+ disableEventThreshold() {
301
+ if (this.eventsThreshold.active) {
302
+ this.eventsThreshold.accidents = 0;
303
+ this.eventsThreshold.count = 0;
304
+ this.eventsThreshold.active = false;
305
+ this.eventsThreshold.timeActivated = 0;
306
+ this.adapter.log.info('Subscribe to all states again');
307
+ setTimeout(async () => {
308
+ this.onThresholdChanged && this.onThresholdChanged(false);
309
+ try {
310
+ await this.adapter.unsubscribeForeignStatesAsync('system.adapter.*');
311
+ }
312
+ catch (e) {
313
+ this.adapter.log.error(`Cannot unsubscribe "system.adapter.*": ${e.message}`);
314
+ }
315
+ for (const pattern of Object.keys(this.subscribes.stateChange)) {
316
+ try {
317
+ await this.adapter.subscribeForeignStatesAsync(pattern);
318
+ }
319
+ catch (e) {
320
+ this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`);
321
+ }
322
+ }
323
+ }, 50);
324
+ }
325
+ }
326
+ #enableEventThreshold() {
327
+ if (!this.eventsThreshold.active) {
328
+ this.eventsThreshold.active = true;
329
+ setTimeout(async () => {
330
+ this.adapter.log.info(`Unsubscribe from all states, except system's, because over ${this.eventsThreshold.repeatSeconds} seconds the number of events is over ${this.eventsThreshold.value} (in last second ${this.eventsThreshold.count})`);
331
+ this.eventsThreshold.timeActivated = Date.now();
332
+ this.onThresholdChanged && this.onThresholdChanged(true);
333
+ for (const pattern of Object.keys(this.subscribes.stateChange)) {
334
+ try {
335
+ await this.adapter.unsubscribeForeignStatesAsync(pattern);
336
+ }
337
+ catch (e) {
338
+ this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`);
339
+ }
340
+ }
341
+ try {
342
+ await this.adapter.subscribeForeignStatesAsync('system.adapter.*');
343
+ }
344
+ catch (e) {
345
+ this.adapter.log.error(`Cannot subscribe "system.adapter.*": ${e.message}`);
346
+ }
347
+ }, 100);
348
+ }
349
+ }
350
+ #addUser(user, password, options, callback) {
351
+ if (typeof options === 'function') {
352
+ callback = options;
353
+ options = null;
354
+ }
355
+ if (!user.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&# ]+$/)) {
356
+ return socketCommands_1.SocketCommands._fixCallback(callback, 'Invalid characters in the name. Only following special characters are allowed: -@+$§=?!&# and letters');
357
+ }
358
+ try {
359
+ void this.adapter
360
+ .getForeignObjectAsync(`system.user.${user}`, options)
361
+ .then(async (obj) => {
362
+ if (obj) {
363
+ socketCommands_1.SocketCommands._fixCallback(callback, 'User yet exists');
364
+ }
365
+ else {
366
+ try {
367
+ await this.adapter.setForeignObject(`system.user.${user}`, {
368
+ type: 'user',
369
+ common: {
370
+ name: user,
371
+ enabled: true,
372
+ password: '',
373
+ },
374
+ native: {},
375
+ }, options);
376
+ try {
377
+ await this.adapter.setPassword(user, password, options || {}, callback);
378
+ }
379
+ catch (error) {
380
+ this.adapter.log.error(`[#addUser] cannot set password: ${error.toString()}`);
381
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
382
+ }
383
+ }
384
+ catch (error) {
385
+ this.adapter.log.error(`[#addUser] cannot save user: ${error.toString()}`);
386
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
387
+ }
388
+ }
389
+ });
390
+ }
391
+ catch (error) {
392
+ this.adapter.log.error(`[#addUser] cannot read user: ${error.toString()}`);
393
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
394
+ }
395
+ }
396
+ #delUser(user, options, callback) {
397
+ try {
398
+ void this.adapter.getForeignObject(`system.user.${user}`, options, (error, obj) => {
399
+ if (error || !obj) {
400
+ socketCommands_1.SocketCommands._fixCallback(callback, 'User does not exist');
401
+ }
402
+ else {
403
+ if (obj.common.dontDelete) {
404
+ socketCommands_1.SocketCommands._fixCallback(callback, 'Cannot delete user, while is system user');
405
+ }
406
+ else {
407
+ try {
408
+ this.adapter.delForeignObject(`system.user.${user}`, options || {}, error =>
409
+ // Remove this user from all groups in the web client
410
+ socketCommands_1.SocketCommands._fixCallback(callback, error));
411
+ }
412
+ catch (error) {
413
+ this.adapter.log.error(`[#delUser] cannot delete user: ${error.toString()}`);
414
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
415
+ }
416
+ }
417
+ }
418
+ });
419
+ }
420
+ catch (error) {
421
+ this.adapter.log.error(`[#delUser] cannot read user: ${error.toString()}`);
422
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
423
+ }
424
+ }
425
+ #addGroup(group, desc, acl, options, callback) {
426
+ let name = group;
427
+ if (name && name.substring(0, 1) !== name.substring(0, 1).toUpperCase()) {
428
+ name = name.substring(0, 1).toUpperCase() + name.substring(1);
429
+ }
430
+ group = group.substring(0, 1).toLowerCase() + group.substring(1);
431
+ if (!group.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&#_ ]+$/)) {
432
+ return socketCommands_1.SocketCommands._fixCallback(callback, 'Invalid characters in the group name. Only following special characters are allowed: -@+$§=?!&# and letters');
433
+ }
434
+ try {
435
+ void this.adapter.getForeignObject(`system.group.${group}`, options, (_error, obj) => {
436
+ if (obj) {
437
+ socketCommands_1.SocketCommands._fixCallback(callback, 'Group yet exists');
438
+ }
439
+ else {
440
+ obj = {
441
+ _id: `system.group.${group}`,
442
+ type: 'group',
443
+ common: {
444
+ name,
445
+ desc: desc || undefined,
446
+ members: [],
447
+ acl: acl || {
448
+ object: {
449
+ list: false,
450
+ read: false,
451
+ write: false,
452
+ create: false,
453
+ delete: false,
454
+ },
455
+ state: {
456
+ list: false,
457
+ read: false,
458
+ write: false,
459
+ create: false,
460
+ delete: false,
461
+ },
462
+ users: {
463
+ list: false,
464
+ read: false,
465
+ write: false,
466
+ create: false,
467
+ delete: false,
468
+ },
469
+ other: {
470
+ execute: false,
471
+ http: false,
472
+ sendto: false,
473
+ },
474
+ file: {
475
+ list: false,
476
+ read: false,
477
+ write: false,
478
+ create: false,
479
+ delete: false,
480
+ },
481
+ },
482
+ },
483
+ native: {},
484
+ };
485
+ try {
486
+ void this.adapter.setForeignObject(`system.group.${group}`, obj, options, error => socketCommands_1.SocketCommands._fixCallback(callback, error, obj));
487
+ }
488
+ catch (error) {
489
+ this.adapter.log.error(`[#addGroup] cannot write group: ${error.toString()}`);
490
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
491
+ }
492
+ }
493
+ });
494
+ }
495
+ catch (error) {
496
+ this.adapter.log.error(`[#addGroup] cannot read group: ${error.toString()}`);
497
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
498
+ }
499
+ }
500
+ #delGroup(group, options, callback) {
501
+ try {
502
+ void this.adapter.getForeignObject(`system.group.${group}`, options, (error, obj) => {
503
+ if (error || !obj) {
504
+ socketCommands_1.SocketCommands._fixCallback(callback, 'Group does not exist');
505
+ }
506
+ else {
507
+ if (obj.common.dontDelete) {
508
+ socketCommands_1.SocketCommands._fixCallback(callback, 'Cannot delete group, while is system group');
509
+ }
510
+ else {
511
+ try {
512
+ this.adapter.delForeignObject(`system.group.${group}`, options, error =>
513
+ // Remove this group from all users in the web client
514
+ socketCommands_1.SocketCommands._fixCallback(callback, error));
515
+ }
516
+ catch (error) {
517
+ this.adapter.log.error(`[#delGroup] cannot delete group: ${error.toString()}`);
518
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
519
+ }
520
+ }
521
+ }
522
+ });
523
+ }
524
+ catch (error) {
525
+ this.adapter.log.error(`[#delGroup] cannot read group: ${error.toString()}`);
526
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
527
+ }
528
+ }
529
+ static #checkObject(obj, options, flag) {
530
+ // read the rights of the object
531
+ if (!obj?.common || !obj.acl || flag === 'list') {
532
+ return true;
533
+ }
534
+ if (options.user !== 'system.user.admin' && !options.groups.includes('system.group.administrator')) {
535
+ if (obj.acl.owner !== options.user) {
536
+ // Check if the user is in the group
537
+ if (options.groups.includes(obj.acl.ownerGroup)) {
538
+ // Check group rights
539
+ if (!(obj.acl.object & (flag << 4))) {
540
+ return false;
541
+ }
542
+ }
543
+ else {
544
+ // everybody
545
+ if (!(obj.acl.object & flag)) {
546
+ return false;
547
+ }
548
+ }
549
+ }
550
+ else {
551
+ // Check group rights
552
+ if (!(obj.acl.object & (flag << 8))) {
553
+ return false;
554
+ }
555
+ }
556
+ }
557
+ return true;
558
+ }
559
+ #getAllObjects(socket, callback) {
560
+ if (typeof callback !== 'function') {
561
+ return this.adapter.log.warn('[#getAllObjects] Invalid callback');
562
+ }
563
+ if (this._checkPermissions(socket, 'getObjects', callback)) {
564
+ if (this.objects) {
565
+ if (socket._acl &&
566
+ socket._acl?.user !== 'system.user.admin' &&
567
+ !socket._acl.groups.includes('system.group.administrator')) {
568
+ const result = {};
569
+ for (const id in this.objects) {
570
+ if (Object.prototype.hasOwnProperty.call(this.objects, id) &&
571
+ _a.#checkObject(this.objects[id], socket._acl, ACL_READ /* 'read' */)) {
572
+ result[id] = this.objects[id];
573
+ }
574
+ }
575
+ callback(null, result);
576
+ }
577
+ else {
578
+ callback(null, this.objects);
579
+ }
580
+ }
581
+ else {
582
+ try {
583
+ this.adapter.getObjectList({ include_docs: true }, { user: socket._acl?.user }, (_error, res) => {
584
+ this.adapter.log.info('received all objects');
585
+ const rows = res?.rows || [];
586
+ const objects = {};
587
+ if (socket._acl &&
588
+ socket._acl?.user !== 'system.user.admin' &&
589
+ !socket._acl.groups.includes('system.group.administrator')) {
590
+ for (let i = 0; i < rows.length; i++) {
591
+ if (_a.#checkObject(rows[i].doc, socket._acl, ACL_READ)) {
592
+ objects[rows[i].doc._id] = rows[i].doc;
593
+ }
594
+ }
595
+ callback(null, objects);
596
+ }
597
+ else {
598
+ for (let j = 0; j < rows.length; j++) {
599
+ objects[rows[j].doc._id] = rows[j].doc;
600
+ }
601
+ callback(null, objects);
602
+ }
603
+ });
604
+ }
605
+ catch (error) {
606
+ this.adapter.log.error(`[#getAllObjects] ERROR: ${error.toString()}`);
607
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
608
+ }
609
+ }
610
+ }
611
+ }
612
+ _initCommandsUser() {
613
+ /**
614
+ * #DOCUMENTATION users
615
+ * Add a new user.
616
+ *
617
+ * @param socket - WebSocket client instance
618
+ * @param user - User name, e.g., `benjamin`
619
+ * @param pass - User password
620
+ * @param callback - Callback function `(error: string | null) => void`
621
+ */
622
+ this.commands.addUser = (socket, user, pass, callback) => {
623
+ if (this._checkPermissions(socket, 'addUser', callback, user)) {
624
+ this.#addUser(user, pass, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
625
+ }
626
+ };
627
+ /**
628
+ * #DOCUMENTATION users
629
+ * Delete an existing user. Admin cannot be deleted.
630
+ *
631
+ * @param socket - WebSocket client instance
632
+ * @param user - User name, e.g., `benjamin`
633
+ * @param callback - Callback function `(error: string | null) => void`
634
+ */
635
+ this.commands.delUser = (socket, user, callback) => {
636
+ if (this._checkPermissions(socket, 'delUser', callback, user)) {
637
+ this.#delUser(user, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
638
+ }
639
+ };
640
+ /**
641
+ * #DOCUMENTATION users
642
+ * Add a new group.
643
+ *
644
+ * @param socket - WebSocket client instance
645
+ * @param group - Group name, e.g., `users`
646
+ * @param desc - Optional description
647
+ * @param acl - Optional access control list object, e.g., `{"object":{"list":true,"read":true,"write":false,"delete":false},"state":{"list":true,"read":true,"write":true,"create":true,"delete":false},"users":{"list":true,"read":true,"write":false,"create":false,"delete":false},"other":{"execute":false,"http":true,"sendto":false},"file":{"list":true,"read":true,"write":false,"create":false,"delete":false}}`
648
+ * @param callback - Callback function `(error: string | null) => void`
649
+ */
650
+ this.commands.addGroup = (socket, group, desc, acl, callback) => {
651
+ if (this._checkPermissions(socket, 'addGroup', callback, group)) {
652
+ this.#addGroup(group, desc, acl, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
653
+ }
654
+ };
655
+ /**
656
+ * #DOCUMENTATION users
657
+ * Delete an existing group. Administrator group cannot be deleted.
658
+ *
659
+ * @param socket - WebSocket client instance
660
+ * @param group - Group name, e.g., `users`
661
+ * @param callback - Callback function `(error: string | null) => void`
662
+ */
663
+ this.commands.delGroup = (socket, group, callback) => {
664
+ if (this._checkPermissions(socket, 'delGroup', callback, group)) {
665
+ this.#delGroup(group, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
666
+ }
667
+ };
668
+ /**
669
+ * #DOCUMENTATION users
670
+ * Change user password.
671
+ *
672
+ * @param socket - WebSocket client instance
673
+ * @param user - User name, e.g., `benjamin`
674
+ * @param pass - New password
675
+ * @param callback - Callback function `(error: string | null) => void`
676
+ */
677
+ this.commands.changePassword = (socket, user, pass, callback) => {
678
+ if (user === socket._acl?.user || this._checkPermissions(socket, 'changePassword', callback, user)) {
679
+ try {
680
+ void this.adapter.setPassword(user, pass, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
681
+ }
682
+ catch (error) {
683
+ this.adapter.log.error(`[_changePassword] ERROR: ${error.toString()}`);
684
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
685
+ }
686
+ }
687
+ };
688
+ }
689
+ _initCommandsAdmin() {
690
+ /**
691
+ * #DOCUMENTATION admin
692
+ * Read the host object by IP address.
693
+ *
694
+ * @param socket - WebSocket client instance
695
+ * @param ip - IP address, e.g., `192.168.1.1`. IPv4 or IPv6
696
+ * @param callback - Callback function `(ip: string, obj: ioBroker.HostObject | null) => void`
697
+ */
698
+ this.commands.getHostByIp = (socket, ip, callback) => {
699
+ if (typeof callback !== 'function') {
700
+ return this.adapter.log.warn('[getHostByIp] Invalid callback');
701
+ }
702
+ if (this._checkPermissions(socket, 'getHostByIp', callback, ip)) {
703
+ try {
704
+ this.adapter.getObjectView('system', 'host', {}, { user: socket._acl?.user }, (error, data) => {
705
+ if (data?.rows?.length) {
706
+ for (let i = 0; i < data.rows.length; i++) {
707
+ const obj = data.rows[i].value;
708
+ // if we requested specific name
709
+ if (obj.common.hostname === ip) {
710
+ return callback(ip, obj);
711
+ }
712
+ if (obj.native.hardware?.networkInterfaces) {
713
+ // try to find this IP in the list
714
+ const net = obj.native.hardware.networkInterfaces;
715
+ for (const eth in net) {
716
+ if (!Object.prototype.hasOwnProperty.call(net, eth) || !net[eth]) {
717
+ continue;
718
+ }
719
+ for (let j = 0; j < net[eth].length; j++) {
720
+ if (net[eth][j].address === ip) {
721
+ return callback(ip, obj);
722
+ }
723
+ }
724
+ }
725
+ }
726
+ }
727
+ }
728
+ callback(ip, null);
729
+ });
730
+ }
731
+ catch (error) {
732
+ this.adapter.log.error(`[_changePassword] ERROR: ${error.toString()}`);
733
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
734
+ }
735
+ }
736
+ };
737
+ /**
738
+ * #DOCUMENTATION admin
739
+ * Activate or deactivate logging events. Events will be sent to the socket as `log` event. Adapter must have `common.logTransporter = true`.
740
+ *
741
+ * @param socket - WebSocket client instance
742
+ * @param isEnabled - Is logging enabled
743
+ * @param callback - Callback function `(error: string | null) => void`
744
+ */
745
+ this.commands.requireLog = (socket, isEnabled, callback) => {
746
+ if (this._checkPermissions(socket, 'setObject', callback)) {
747
+ if (isEnabled) {
748
+ this.subscribe(socket, 'log', 'dummy');
749
+ }
750
+ else {
751
+ this.unsubscribe(socket, 'log', 'dummy');
752
+ }
753
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'log');
754
+ typeof callback === 'function' && setImmediate(callback, null);
755
+ }
756
+ };
757
+ /**
758
+ * #DOCUMENTATION admin
759
+ * Get the log files from the given host.
760
+ *
761
+ * @param socket - WebSocket client instance
762
+ * @param host - Host ID, e.g., `system.host.raspberrypi`
763
+ * @param callback - Callback function `(error: string | null, list?: { fileName: string; size: number }[]) => void`
764
+ */
765
+ this.commands.readLogs = (socket, host, callback) => {
766
+ if (this._checkPermissions(socket, 'readLogs', callback)) {
767
+ let timeout = setTimeout(() => {
768
+ if (timeout) {
769
+ let result = { list: [] };
770
+ // deliver the file list
771
+ try {
772
+ const config = this.adapter.systemConfig;
773
+ // detect file log
774
+ if (config?.log?.transport) {
775
+ for (const transport in config.log.transport) {
776
+ if (Object.prototype.hasOwnProperty.call(config.log.transport, transport) &&
777
+ config.log.transport[transport].type === 'file') {
778
+ let fileName = config.log.transport[transport].filename || 'log/';
779
+ const parts = fileName.replace(/\\/g, '/').split('/');
780
+ parts.pop();
781
+ fileName = parts.join('/');
782
+ if (fileName[0] !== '/' && !fileName.match(/^\W:/)) {
783
+ const _filename = (0, node_path_1.normalize)(`${__dirname}/../../../`) + fileName;
784
+ if (!(0, node_fs_1.existsSync)(_filename)) {
785
+ fileName = (0, node_path_1.normalize)(`${__dirname}/../../`) + fileName;
786
+ }
787
+ else {
788
+ fileName = _filename;
789
+ }
790
+ }
791
+ if ((0, node_fs_1.existsSync)(fileName)) {
792
+ const files = (0, node_fs_1.readdirSync)(fileName);
793
+ for (let f = 0; f < files.length; f++) {
794
+ try {
795
+ if (!files[f].endsWith('-audit.json')) {
796
+ const stat = (0, node_fs_1.lstatSync)(`${fileName}/${files[f]}`);
797
+ if (!stat.isDirectory()) {
798
+ result.list?.push({
799
+ fileName: `log/${transport}/${files[f]}`,
800
+ size: stat.size,
801
+ });
802
+ }
803
+ }
804
+ }
805
+ catch {
806
+ // push unchecked
807
+ // result.list.push('log/' + transport + '/' + files[f]);
808
+ this.adapter.log.error(`Cannot check file: ${fileName}/${files[f]}`);
809
+ }
810
+ }
811
+ }
812
+ }
813
+ }
814
+ }
815
+ else {
816
+ result = { error: 'no file loggers' };
817
+ }
818
+ }
819
+ catch (error) {
820
+ this.adapter.log.error(`Cannot read logs: ${error}`);
821
+ result = { error };
822
+ }
823
+ socketCommands_1.SocketCommands._fixCallback(callback, result.error, result.list);
824
+ }
825
+ }, 500);
826
+ this._sendToHost(host, 'getLogFiles', null, (result) => {
827
+ if (timeout) {
828
+ clearTimeout(timeout);
829
+ timeout = null;
830
+ }
831
+ socketCommands_1.SocketCommands._fixCallback(callback, result.error, result.list);
832
+ });
833
+ }
834
+ };
835
+ /**
836
+ * #DOCUMENTATION states
837
+ * Delete a state. The corresponding object will be deleted too.
838
+ *
839
+ * @param socket - WebSocket client instance
840
+ * @param id - State ID
841
+ * @param callback - Callback function `(error: string | null) => void`
842
+ */
843
+ this.commands.delState = (socket, id, callback) => {
844
+ if (this._checkPermissions(socket, 'delState', callback, id)) {
845
+ // clear cache
846
+ if (this.states?.[id]) {
847
+ delete this.states[id];
848
+ }
849
+ try {
850
+ this.adapter.delForeignState(id, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
851
+ }
852
+ catch (error) {
853
+ this.adapter.log.error(`[delState] ERROR: ${error.toString()}`);
854
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
855
+ }
856
+ }
857
+ };
858
+ /**
859
+ * #DOCUMENTATION admin
860
+ * Execute the shell command on host/controller.
861
+ * Following response commands are expected: `cmdStdout`, `cmdStderr`, `cmdExit`.
862
+ *
863
+ * @param socket - WebSocket client instance
864
+ * @param host - Host name, e.g., `system.host.raspberrypi`
865
+ * @param id - Session ID, e.g., `Date.now()`. This session ID will come in events `cmdStdout`, `cmdStderr`, `cmdExit`
866
+ * @param cmd - Command to execute
867
+ * @param callback - Callback function `(error: string | null) => void`
868
+ */
869
+ this.commands.cmdExec = (socket, host, id, cmd, callback) => {
870
+ if (id === undefined) {
871
+ this.adapter.log.error(`cmdExec no session ID for "${cmd}"`);
872
+ socketCommands_1.SocketCommands._fixCallback(callback, 'no session ID');
873
+ }
874
+ else if (this._checkPermissions(socket, 'cmdExec', callback, cmd)) {
875
+ this.adapter.log.debug(`cmdExec on ${host}(${id}): ${cmd}`);
876
+ // remember socket for this ID.
877
+ this.cmdSessions[id] = { socket };
878
+ try {
879
+ this.adapter.sendToHost(host, 'cmdExec', { data: cmd, id });
880
+ socketCommands_1.SocketCommands._fixCallback(callback, null);
881
+ }
882
+ catch (error) {
883
+ this.adapter.log.error(`[cmdExec] ERROR: ${error.toString()}`);
884
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
885
+ }
886
+ }
887
+ };
888
+ /**
889
+ * #DOCUMENTATION admin
890
+ * Enable or disable the event threshold. Used only for admin to limit the number of events to the front-end.
891
+ *
892
+ * @param _socket - WebSocket client instance
893
+ * @param isActive - If true, then events will be limited
894
+ */
895
+ this.commands.eventsThreshold = (_socket, isActive) => {
896
+ if (!isActive) {
897
+ this.disableEventThreshold();
898
+ }
899
+ else {
900
+ this.#enableEventThreshold();
901
+ }
902
+ };
903
+ /**
904
+ * #DOCUMENTATION admin
905
+ * Get the ratings of adapters.
906
+ *
907
+ * @param _socket - WebSocket client instance
908
+ * @param update - If true, the ratings will be read from the central server, if false from the local cache
909
+ * @param callback - Callback function `(error: string | null, ratings?: Ratings) => void`
910
+ */
911
+ this.commands.getRatings = (_socket, update, callback) => {
912
+ if (typeof update === 'function') {
913
+ callback = update;
914
+ update = false;
915
+ }
916
+ if (update || !this.context.ratings) {
917
+ void this.updateRatings().then(() => socketCommands_1.SocketCommands._fixCallback(callback, null, this.context.ratings));
918
+ }
919
+ else {
920
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.context.ratings);
921
+ }
922
+ };
923
+ /**
924
+ * #DOCUMENTATION admin
925
+ * Get the current instance name, like "admin.0"
926
+ *
927
+ * @param _socket - WebSocket client instance
928
+ * @param callback - Callback function `(error: string | null, namespace?: string) => void`
929
+ */
930
+ this.commands.getCurrentInstance = (_socket, callback) => {
931
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.namespace);
932
+ };
933
+ /**
934
+ * #DOCUMENTATION admin
935
+ * Decrypts text with the system secret key.
936
+ *
937
+ * @param socket - WebSocket client instance
938
+ * @param encryptedText - Encrypted text
939
+ * @param callback - Callback function `(error: string | null, decryptedText?: string) => void`
940
+ */
941
+ this.commands.decrypt = (socket, encryptedText, callback) => {
942
+ if (this.secret) {
943
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.decrypt(this.secret, encryptedText));
944
+ }
945
+ else {
946
+ try {
947
+ void this.adapter.getForeignObject('system.config', { user: socket._acl?.user }, (error, obj) => {
948
+ if (obj && obj.native && obj.native.secret) {
949
+ this.secret = obj.native.secret;
950
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.decrypt(this.secret, encryptedText));
951
+ }
952
+ else {
953
+ this.adapter.log.error(`No system.config found: ${error}`);
954
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
955
+ }
956
+ });
957
+ }
958
+ catch (error) {
959
+ this.adapter.log.error(`Cannot decrypt: ${error}`);
960
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
961
+ }
962
+ }
963
+ };
964
+ /**
965
+ * #DOCUMENTATION admin
966
+ * Encrypts text with the system secret key.
967
+ *
968
+ * @param socket - WebSocket client instance
969
+ * @param plainText - Plain text to encrypt
970
+ * @param callback - Callback function `(error: string | null, encryptedText?: string) => void`
971
+ */
972
+ this.commands.encrypt = (socket, plainText, callback) => {
973
+ if (this.secret) {
974
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.encrypt(this.secret, plainText));
975
+ }
976
+ else {
977
+ void this.adapter.getForeignObject('system.config', { user: socket._acl?.user }, (error, obj) => {
978
+ if (obj && obj.native && obj.native.secret) {
979
+ this.secret = obj.native.secret;
980
+ try {
981
+ const encrypted = this.adapter.encrypt(this.secret, plainText);
982
+ socketCommands_1.SocketCommands._fixCallback(callback, null, encrypted);
983
+ }
984
+ catch (error) {
985
+ this.adapter.log.error(`Cannot encrypt: ${error}`);
986
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
987
+ }
988
+ }
989
+ else {
990
+ this.adapter.log.error(`No system.config found: ${error}`);
991
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
992
+ }
993
+ });
994
+ }
995
+ };
996
+ /**
997
+ * #DOCUMENTATION admin
998
+ * Get if the admin has easy mode enabled.
999
+ *
1000
+ * @param _socket - WebSocket client instance
1001
+ * @param callback - Callback function `(error: string | null, isEasyModeStrict?: boolean) => void`
1002
+ */
1003
+ this.commands.getIsEasyModeStrict = (_socket, callback) => {
1004
+ socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.config.accessLimit);
1005
+ };
1006
+ /**
1007
+ * #DOCUMENTATION admin
1008
+ * Get easy mode configuration.
1009
+ *
1010
+ * @param socket - WebSocket client instance
1011
+ * @param callback - Callback function `(error: string | null, easyModeConfig?: { strict: boolean; configs: InstanceConfig[] }) => void`
1012
+ */
1013
+ this.commands.getEasyMode = (socket, callback) => {
1014
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1015
+ let user;
1016
+ if (this.adapter.config.auth) {
1017
+ user = socket._acl?.user || '';
1018
+ }
1019
+ else {
1020
+ user = this.adapter.config.defaultUser || socket._acl?.user || '';
1021
+ }
1022
+ if (!user.startsWith('system.user.')) {
1023
+ user = `system.user.${user}`;
1024
+ }
1025
+ if (this.adapter.config.accessLimit) {
1026
+ const configs = [];
1027
+ const promises = [];
1028
+ this.adapter.config.accessAllowedConfigs?.forEach(id => promises.push(this.#readInstanceConfig(id, user, false, configs)));
1029
+ this.adapter.config.accessAllowedTabs?.forEach(id => promises.push(this.#readInstanceConfig(id, user, true, configs)));
1030
+ void Promise.all(promises).then(() => {
1031
+ socketCommands_1.SocketCommands._fixCallback(callback, null, {
1032
+ strict: true,
1033
+ configs,
1034
+ });
1035
+ });
1036
+ }
1037
+ else {
1038
+ this.adapter.getObjectView('system', 'instance', { startkey: 'system.adapter.', endkey: 'system.adapter.\u9999' }, { user }, (error, doc) => {
1039
+ const configs = [];
1040
+ const promises = [];
1041
+ if (!error && doc?.rows?.length) {
1042
+ for (let i = 0; i < doc.rows.length; i++) {
1043
+ const obj = doc.rows[i].value;
1044
+ if (obj.common.noConfig && !obj.common.adminTab) {
1045
+ continue;
1046
+ }
1047
+ if (!obj.common.enabled) {
1048
+ continue;
1049
+ }
1050
+ if (!obj.common.noConfig) {
1051
+ promises.push(this.#readInstanceConfig(obj._id.substring('system.adapter.'.length), user, false, configs));
1052
+ }
1053
+ }
1054
+ }
1055
+ void Promise.all(promises).then(() => socketCommands_1.SocketCommands._fixCallback(callback, null, {
1056
+ strict: false,
1057
+ configs,
1058
+ }));
1059
+ });
1060
+ }
1061
+ }
1062
+ };
1063
+ /**
1064
+ * #DOCUMENTATION admin
1065
+ * Get all adapter as objects.
1066
+ *
1067
+ * @param socket - WebSocket client instance
1068
+ * @param adapterName - Optional adapter name
1069
+ * @param callback - Callback function `(error: string | null, results?: ioBroker.Object[]) => void`
1070
+ */
1071
+ this.commands.getAdapters = (socket, adapterName, callback) => {
1072
+ if (typeof callback === 'function' && this._checkPermissions(socket, 'getObject', callback)) {
1073
+ this.adapter.getObjectView('system', 'adapter', {
1074
+ startkey: `system.adapter.${adapterName || ''}`,
1075
+ endkey: `system.adapter.${adapterName || '\u9999'}`,
1076
+ }, { user: socket._acl?.user }, (error, doc) => {
1077
+ if (error) {
1078
+ callback(error);
1079
+ }
1080
+ else {
1081
+ callback(null, doc?.rows
1082
+ .filter(obj => obj && (!adapterName || obj.value.common?.name === this.adapterName))
1083
+ .map(item => {
1084
+ const obj = item.value;
1085
+ if (obj.common) {
1086
+ delete obj.common.news;
1087
+ // @ts-expect-error to save the memory
1088
+ delete obj.native;
1089
+ }
1090
+ this.fixAdminUI(obj);
1091
+ return obj;
1092
+ }));
1093
+ }
1094
+ });
1095
+ }
1096
+ };
1097
+ /**
1098
+ * #DOCUMENTATION admin
1099
+ * Read software licenses (vis, knx, ...) from ioBroker.net cloud for given user
1100
+ *
1101
+ * @param socket - WebSocket client instance
1102
+ * @param login - Cloud login
1103
+ * @param password - Cloud password
1104
+ * @param callback - Callback function `(error: string | null, results?: License[]) => void`
1105
+ */
1106
+ this.commands.updateLicenses = (socket, login, password, callback) => {
1107
+ if (this._checkPermissions(socket, 'setObject', callback, login, password)) {
1108
+ if (this.adapter.supportsFeature('CONTROLLER_LICENSE_MANAGER')) {
1109
+ let timeout = setTimeout(() => {
1110
+ if (timeout) {
1111
+ timeout = null;
1112
+ socketCommands_1.SocketCommands._fixCallback(callback, 'updateLicenses timeout');
1113
+ }
1114
+ }, 7000);
1115
+ if (!this.adapter.common) {
1116
+ throw new Error('"common" is not defined in adapter!');
1117
+ }
1118
+ this._sendToHost(this.adapter.common.host, 'updateLicenses', { login, password }, result => {
1119
+ if (timeout) {
1120
+ clearTimeout(timeout);
1121
+ timeout = null;
1122
+ socketCommands_1.SocketCommands._fixCallback(callback, result.error, result?.result);
1123
+ }
1124
+ });
1125
+ }
1126
+ else {
1127
+ // remove this branch when js-controller 4.x is mainstream
1128
+ this.#updateLicenses(login, password, { user: socket._acl?.user || '' })
1129
+ .then(licenses => socketCommands_1.SocketCommands._fixCallback(callback, null, licenses))
1130
+ .catch(error => socketCommands_1.SocketCommands._fixCallback(callback, error));
1131
+ }
1132
+ }
1133
+ };
1134
+ /**
1135
+ * #DOCUMENTATION admin
1136
+ * Get all instances in a compact form to save bandwidth.
1137
+ *
1138
+ * @param socket - WebSocket client instance
1139
+ * @param callback - Callback function `(error: string | null, results?: Record<string, { adminTab: boolean; name: string; icon: string; enabled: boolean }>) => void`
1140
+ */
1141
+ this.commands.getCompactInstances = (socket, callback) => {
1142
+ if (typeof callback === 'function') {
1143
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1144
+ this.adapter.getObjectView('system', 'instance', { startkey: `system.adapter.`, endkey: `system.adapter.\u9999` }, { user: socket._acl?.user }, (error, doc) => {
1145
+ if (error) {
1146
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1147
+ }
1148
+ else {
1149
+ // calculate
1150
+ const result = {};
1151
+ doc?.rows.forEach(item => {
1152
+ const obj = item.value;
1153
+ result[item.id] = {
1154
+ adminTab: obj.common.adminTab,
1155
+ name: obj.common.name,
1156
+ icon: obj.common.icon,
1157
+ enabled: obj.common.enabled,
1158
+ version: obj.common.version,
1159
+ };
1160
+ });
1161
+ socketCommands_1.SocketCommands._fixCallback(callback, null, result);
1162
+ }
1163
+ });
1164
+ }
1165
+ }
1166
+ };
1167
+ /**
1168
+ * #DOCUMENTATION admin
1169
+ * Get all adapters in a compact form to save bandwidth.
1170
+ *
1171
+ * @param socket - WebSocket client instance
1172
+ * @param callback - Callback function `(error: string | null, results?: Record<string, { icon: string; v: string; iv: string }>) => void`
1173
+ */
1174
+ this.commands.getCompactAdapters = (socket, callback) => {
1175
+ if (typeof callback === 'function') {
1176
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1177
+ this.adapter.getObjectView('system', 'adapter', { startkey: `system.adapter.`, endkey: `system.adapter.\u9999` }, { user: socket._acl?.user }, (error, doc) => {
1178
+ if (error) {
1179
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1180
+ }
1181
+ else {
1182
+ // calculate
1183
+ const result = {};
1184
+ doc?.rows.forEach(item => {
1185
+ const obj = item.value;
1186
+ if (obj?.common?.name) {
1187
+ result[obj.common.name] = { icon: obj.common.icon, v: obj.common.version };
1188
+ if (obj.common.ignoreVersion) {
1189
+ result[obj.common.name].iv = obj.common.ignoreVersion;
1190
+ }
1191
+ }
1192
+ });
1193
+ socketCommands_1.SocketCommands._fixCallback(callback, null, result);
1194
+ }
1195
+ });
1196
+ }
1197
+ }
1198
+ };
1199
+ /**
1200
+ * #DOCUMENTATION admin
1201
+ * Get all installed adapters in a compact form to save bandwidth.
1202
+ *
1203
+ * @param socket - WebSocket client instance
1204
+ * @param host - Host name, e.g., `system.host.raspberrypi`
1205
+ * @param callback - Callback function `(error: string | null, results?: Record<string, { version: string }>) => void`
1206
+ */
1207
+ this.commands.getCompactInstalled = (socket, host, callback) => {
1208
+ if (typeof callback === 'function') {
1209
+ if (this._checkPermissions(socket, 'sendToHost', callback)) {
1210
+ this._sendToHost(host, 'getInstalled', null, (data) => {
1211
+ const castData = data;
1212
+ const result = {};
1213
+ Object.keys(castData).forEach(name => {
1214
+ if (name !== 'hosts') {
1215
+ result[name] = { version: castData[name].version };
1216
+ }
1217
+ });
1218
+ callback(result);
1219
+ });
1220
+ }
1221
+ }
1222
+ };
1223
+ /**
1224
+ * #DOCUMENTATION admin
1225
+ * Get the system configuration in a compact form to save bandwidth.
1226
+ *
1227
+ * @param socket - WebSocket client instance
1228
+ * @param callback - Callback function `(error: string | null, systemConfig?: { common: any; native?: { secret: string } }) => void`
1229
+ */
1230
+ this.commands.getCompactSystemConfig = (socket, callback) => {
1231
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1232
+ void this.adapter.getForeignObject('system.config', { user: socket._acl?.user }, (error, obj) => {
1233
+ obj ||= {};
1234
+ const secret = obj?.native?.secret;
1235
+ // @ts-expect-error to save the memory
1236
+ delete obj.native;
1237
+ if (secret) {
1238
+ obj.native = { secret };
1239
+ }
1240
+ socketCommands_1.SocketCommands._fixCallback(callback, error, obj);
1241
+ });
1242
+ }
1243
+ };
1244
+ /**
1245
+ * #DOCUMENTATION admin
1246
+ * Get system repositories in a compact form to save bandwidth.
1247
+ *
1248
+ * @param socket - WebSocket client instance
1249
+ * @param callback - Callback function `(error: string | null, systemRepositories?: { common: any; native?: { repositories: Record<string, { json: { _repoInfo: any } } } } }) => void`
1250
+ */
1251
+ this.commands.getCompactSystemRepositories = (socket, callback) => {
1252
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1253
+ void this.adapter.getForeignObject('system.repositories', { user: socket._acl?.user }, (error, obj) => {
1254
+ obj &&
1255
+ obj.native &&
1256
+ obj.native.repositories &&
1257
+ Object.keys(obj.native.repositories).forEach(name => {
1258
+ if (obj.native.repositories[name].json) {
1259
+ // limit information to _repoInfo
1260
+ obj.native.repositories[name].json = {
1261
+ _repoInfo: obj.native.repositories[name].json._repoInfo,
1262
+ };
1263
+ }
1264
+ });
1265
+ socketCommands_1.SocketCommands._fixCallback(callback, error, obj);
1266
+ });
1267
+ }
1268
+ };
1269
+ /**
1270
+ * #DOCUMENTATION admin
1271
+ * Get the repository in a compact form to save bandwidth.
1272
+ *
1273
+ * @param socket - WebSocket client instance
1274
+ * @param host - Host name, e.g., `system.host.raspberrypi`
1275
+ * @param callback - Callback function `(error: string | null, results?: Record<string, { version: string; icon?: string }>) => void`
1276
+ */
1277
+ this.commands.getCompactRepository = (socket, host, callback) => {
1278
+ if (this._checkPermissions(socket, 'sendToHost', callback)) {
1279
+ this._sendToHost(host, 'getRepository', null, (data) => {
1280
+ // Extract only the version and icon
1281
+ const castData = data;
1282
+ const result = {};
1283
+ if (castData) {
1284
+ Object.keys(castData).forEach(name => (result[name] = {
1285
+ version: castData[name].version,
1286
+ icon: castData[name].extIcon,
1287
+ }));
1288
+ }
1289
+ callback(result);
1290
+ });
1291
+ }
1292
+ };
1293
+ /**
1294
+ * #DOCUMENTATION admin
1295
+ * Get all hosts in a compact form to save bandwidth.
1296
+ *
1297
+ * @param socket - WebSocket client instance
1298
+ * @param callback - Callback function `(error: string | null, results?: Record<string, { common: { name: string; icon: string; color: string; installedVersion: string }; native: { hardware: { networkInterfaces: any[] } } }>) => void`
1299
+ */
1300
+ this.commands.getCompactHosts = (socket, callback) => {
1301
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1302
+ this.adapter.getObjectView('system', 'host', { startkey: 'system.host.', endkey: 'system.host.\u9999' }, { user: socket._acl?.user }, (error, doc) => {
1303
+ if (error) {
1304
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1305
+ }
1306
+ else {
1307
+ const result = [];
1308
+ doc?.rows.map(item => {
1309
+ const host = item.value;
1310
+ if (host) {
1311
+ host.common ||= {};
1312
+ result.push({
1313
+ _id: host._id,
1314
+ common: {
1315
+ name: host.common.name,
1316
+ icon: host.common.icon,
1317
+ color: host.common.color,
1318
+ installedVersion: host.common.installedVersion,
1319
+ },
1320
+ native: {
1321
+ hardware: {
1322
+ networkInterfaces: host.native?.hardware?.networkInterfaces || undefined,
1323
+ },
1324
+ },
1325
+ });
1326
+ }
1327
+ });
1328
+ socketCommands_1.SocketCommands._fixCallback(callback, null, result);
1329
+ }
1330
+ });
1331
+ }
1332
+ };
1333
+ }
1334
+ _initCommandsCommon() {
1335
+ super._initCommandsCommon();
1336
+ this._initCommandsAdmin();
1337
+ this._initCommandsUser();
1338
+ }
1339
+ _initCommandsFiles() {
1340
+ super._initCommandsFiles();
1341
+ /**
1342
+ * #DOCUMENTATION files
1343
+ * Write the file into ioBroker DB as base64 string.
1344
+ *
1345
+ * @param socket - WebSocket client instance
1346
+ * @param adapter - Instance name, e.g., `vis.0`
1347
+ * @param fileName - File name, e.g., `main/vis-views.json`
1348
+ * @param data64 - File content as base64 string
1349
+ * @param options - Optional settings, e.g., `{mode: 0x0644}`
1350
+ * @param callback - Callback function `(error: string | null) => void`
1351
+ */
1352
+ this.commands.writeFile = (socket, adapter, fileName, data64, options, callback) => {
1353
+ if (this._checkPermissions(socket, 'writeFile', callback, fileName)) {
1354
+ let _options;
1355
+ if (typeof options === 'function') {
1356
+ callback = options;
1357
+ _options = { user: socket._acl?.user };
1358
+ }
1359
+ else if (!options || options.mode === undefined) {
1360
+ _options = { user: socket._acl?.user };
1361
+ }
1362
+ else {
1363
+ _options = { user: socket._acl?.user, mode: options.mode };
1364
+ }
1365
+ try {
1366
+ const buffer = Buffer.from(data64, 'base64');
1367
+ this.adapter.writeFile(adapter, fileName, buffer, _options, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1368
+ }
1369
+ catch (error) {
1370
+ this.adapter.log.error(`[writeFile] Cannot convert data: ${error.toString()}`);
1371
+ if (callback) {
1372
+ callback(`Cannot convert data: ${error.toString()}`);
1373
+ }
1374
+ }
1375
+ }
1376
+ };
1377
+ }
1378
+ _initCommandsObjects() {
1379
+ super._initCommandsObjects();
1380
+ /**
1381
+ * #DOCUMENTATION objects
1382
+ * Read absolutely all objects.
1383
+ *
1384
+ * @param socket - WebSocket client instance
1385
+ * @param callback - Callback function `(error: string | null, objects?: Record<string, ioBroker.Object>) => void`
1386
+ */
1387
+ this.commands.getAllObjects = (socket, callback) => {
1388
+ return this.#getAllObjects(socket, callback);
1389
+ };
1390
+ /**
1391
+ * #DOCUMENTATION objects
1392
+ * Read absolutely all objects. Same as `getAllObjects`.
1393
+ *
1394
+ * @param socket - WebSocket client instance
1395
+ * @param list - optional list of IDs.
1396
+ * @param callback - Callback function `(error: string | null, objects?: Record<string, ioBroker.Object>) => void`
1397
+ */
1398
+ this.commands.getObjects = (socket, list, callback) => {
1399
+ if (typeof list === 'function') {
1400
+ callback = list;
1401
+ list = null;
1402
+ }
1403
+ if (typeof callback !== 'function') {
1404
+ this.adapter.log.warn('[getObjects] Invalid callback');
1405
+ }
1406
+ else if (list && !list.length) {
1407
+ socketCommands_1.SocketCommands._fixCallback(callback, null, {});
1408
+ }
1409
+ else if (list?.length) {
1410
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1411
+ try {
1412
+ this.adapter.getForeignObjects(list, { user: socket._acl?.user }, (error, objs) => socketCommands_1.SocketCommands._fixCallback(callback, error, objs));
1413
+ }
1414
+ catch (error) {
1415
+ this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
1416
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1417
+ }
1418
+ }
1419
+ }
1420
+ else {
1421
+ this.#getAllObjects(socket, callback);
1422
+ }
1423
+ };
1424
+ /**
1425
+ * #DOCUMENTATION objects
1426
+ * Extend the existing object.
1427
+ *
1428
+ * @param socket - WebSocket client instance
1429
+ * @param id - Object ID
1430
+ * @param obj - New parts of the object, e.g., `{common: {name: 'new name'}}`
1431
+ * @param callback - Callback function `(error: string | null) => void`
1432
+ */
1433
+ this.commands.extendObject = (socket, id, obj, callback) => {
1434
+ if (this._checkPermissions(socket, 'extendObject', callback, id)) {
1435
+ try {
1436
+ this.adapter.extendForeignObject(id, obj, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1437
+ }
1438
+ catch (error) {
1439
+ this.adapter.log.error(`[extendObject] ERROR: ${error}`);
1440
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1441
+ }
1442
+ }
1443
+ };
1444
+ /**
1445
+ * #DOCUMENTATION objects
1446
+ * Read objects by pattern.
1447
+ *
1448
+ * @param socket - WebSocket client instance
1449
+ * @param pattern - Pattern like `system.adapter.admin.0.*`
1450
+ * @param type - Type of objects to delete, like `state`, `channel`, `device`, `host`, `adapter`. Default - `state`
1451
+ * @param callback - Callback function `(error: string | null, objects?: Record<string, ioBroker.Object>) => void`
1452
+ */
1453
+ this.commands.getForeignObjects = (socket, pattern, type, callback) => {
1454
+ if (this._checkPermissions(socket, 'getObjects', callback)) {
1455
+ if (typeof type === 'function') {
1456
+ callback = type;
1457
+ type = undefined;
1458
+ }
1459
+ if (typeof callback === 'function') {
1460
+ if (type) {
1461
+ try {
1462
+ this.adapter.getForeignObjects(pattern, type, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1463
+ }
1464
+ catch (error) {
1465
+ this.adapter.log.error(`[extendObject] ERROR: ${error}`);
1466
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1467
+ }
1468
+ }
1469
+ else {
1470
+ try {
1471
+ this.adapter.getForeignObjects(pattern, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1472
+ }
1473
+ catch (error) {
1474
+ this.adapter.log.error(`[extendObject] ERROR: ${error}`);
1475
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1476
+ }
1477
+ }
1478
+ }
1479
+ else {
1480
+ this.adapter.log.warn('[getObjects] Invalid callback');
1481
+ }
1482
+ }
1483
+ };
1484
+ /**
1485
+ * #DOCUMENTATION objects
1486
+ * Delete an object or objects recursively.
1487
+ * Objects with `dontDelete` cannot be deleted.
1488
+ *
1489
+ * @param socket - WebSocket client instance
1490
+ * @param id - Object ID, like 'adapterName.0.channel'
1491
+ * @param options - Options for deletion.
1492
+ * @param options.recursive - Delete all sub objects in this branch
1493
+ * @param callback - Callback function `(error: string | null) => void`
1494
+ */
1495
+ this.commands.delObject = (socket, id, options, callback) => {
1496
+ if (this._checkPermissions(socket, 'delObject', callback, id)) {
1497
+ let _options;
1498
+ if (typeof options === 'function') {
1499
+ callback = options;
1500
+ _options = { user: socket._acl?.user };
1501
+ }
1502
+ else if (options?.recursive) {
1503
+ _options = { user: socket._acl?.user, recursive: true };
1504
+ }
1505
+ else {
1506
+ _options = { user: socket._acl?.user };
1507
+ }
1508
+ try {
1509
+ // options.recursive = true; // the only difference between delObject and delObjects is this line
1510
+ this.adapter.delForeignObject(id, _options, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1511
+ }
1512
+ catch (error) {
1513
+ this.adapter.log.error(`[delObject] ERROR: ${error}`);
1514
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1515
+ }
1516
+ }
1517
+ };
1518
+ /**
1519
+ * #DOCUMENTATION objects
1520
+ * Delete an object or objects recursively.
1521
+ * Objects with `dontDelete` cannot be deleted.
1522
+ * Same as `delObject` but with `recursive: true`.
1523
+ *
1524
+ * @param socket - WebSocket client instance
1525
+ * @param id - Object ID, like 'adapterName.0.channel'
1526
+ * @param options - Options for deletion.
1527
+ * @param options.recursive - Delete all sub objects in this branch
1528
+ * @param callback - Callback function `(error: string | null) => void`
1529
+ */
1530
+ this.commands.delObjects = (socket, id, options, callback) => {
1531
+ if (this._checkPermissions(socket, 'delObject', callback, id)) {
1532
+ let _options;
1533
+ if (typeof options === 'function') {
1534
+ callback = options;
1535
+ _options = { user: socket._acl?.user, recursive: true };
1536
+ }
1537
+ else if (options?.recursive) {
1538
+ _options = { user: socket._acl?.user, recursive: true };
1539
+ }
1540
+ else {
1541
+ _options = { user: socket._acl?.user, recursive: true };
1542
+ }
1543
+ try {
1544
+ this.adapter.delForeignObject(id, _options, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args));
1545
+ }
1546
+ catch (error) {
1547
+ this.adapter.log.error(`[delObjects] ERROR: ${error}`);
1548
+ socketCommands_1.SocketCommands._fixCallback(callback, error);
1549
+ }
1550
+ }
1551
+ };
1552
+ }
1553
+ stateChange(id, state) {
1554
+ if (this.states) {
1555
+ if (!state) {
1556
+ if (this.states[id]) {
1557
+ delete this.states[id];
1558
+ }
1559
+ }
1560
+ else {
1561
+ this.states[id] = state;
1562
+ }
1563
+ }
1564
+ if (!this.eventsThreshold.active) {
1565
+ this.eventsThreshold.count++;
1566
+ }
1567
+ }
1568
+ sendCommand(obj) {
1569
+ if (obj.message && this.cmdSessions[obj.message.id]) {
1570
+ if (obj.command === 'cmdExit') {
1571
+ delete this.cmdSessions[obj.message.id];
1572
+ }
1573
+ return true;
1574
+ }
1575
+ }
1576
+ destroy() {
1577
+ this.thresholdInterval && clearInterval(this.thresholdInterval);
1578
+ this.thresholdInterval = null;
1579
+ this.cacheGB && clearInterval(this.cacheGB);
1580
+ this.cacheGB = null;
1581
+ super.destroy();
1582
+ }
1583
+ }
1584
+ exports.SocketCommandsAdmin = SocketCommandsAdmin;
1585
+ _a = SocketCommandsAdmin;
1586
+ //# sourceMappingURL=socketCommandsAdmin.js.map