@iobroker/socket-classes 1.6.1 → 2.0.0

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