@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,2015 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SocketCommands = exports.COMMANDS_PERMISSIONS = void 0;
4
+ const adapter_core_1 = require("@iobroker/adapter-core"); // Get common adapter utils
5
+ exports.COMMANDS_PERMISSIONS = {
6
+ getObject: { type: 'object', operation: 'read' },
7
+ getObjects: { type: 'object', operation: 'list' },
8
+ getObjectView: { type: 'object', operation: 'list' },
9
+ setObject: { type: 'object', operation: 'write' },
10
+ requireLog: { type: 'object', operation: 'write' }, // just mapping to some command
11
+ delObject: { type: 'object', operation: 'delete' },
12
+ extendObject: { type: 'object', operation: 'write' },
13
+ getHostByIp: { type: 'object', operation: 'list' },
14
+ subscribeObjects: { type: 'object', operation: 'read' },
15
+ unsubscribeObjects: { type: 'object', operation: 'read' },
16
+ getStates: { type: 'state', operation: 'list' },
17
+ getState: { type: 'state', operation: 'read' },
18
+ setState: { type: 'state', operation: 'write' },
19
+ delState: { type: 'state', operation: 'delete' },
20
+ createState: { type: 'state', operation: 'create' },
21
+ subscribe: { type: 'state', operation: 'read' },
22
+ unsubscribe: { type: 'state', operation: 'read' },
23
+ getStateHistory: { type: 'state', operation: 'read' },
24
+ getVersion: { type: '', operation: '' },
25
+ getAdapterName: { type: '', operation: '' },
26
+ addUser: { type: 'users', operation: 'create' },
27
+ delUser: { type: 'users', operation: 'delete' },
28
+ addGroup: { type: 'users', operation: 'create' },
29
+ delGroup: { type: 'users', operation: 'delete' },
30
+ changePassword: { type: 'users', operation: 'write' },
31
+ httpGet: { type: 'other', operation: 'http' },
32
+ cmdExec: { type: 'other', operation: 'execute' },
33
+ sendTo: { type: 'other', operation: 'sendto' },
34
+ sendToHost: { type: 'other', operation: 'sendto' },
35
+ readLogs: { type: 'other', operation: 'execute' },
36
+ readDir: { type: 'file', operation: 'list' },
37
+ createFile: { type: 'file', operation: 'create' },
38
+ writeFile: { type: 'file', operation: 'write' },
39
+ readFile: { type: 'file', operation: 'read' },
40
+ fileExists: { type: 'file', operation: 'read' },
41
+ deleteFile: { type: 'file', operation: 'delete' },
42
+ readFile64: { type: 'file', operation: 'read' },
43
+ writeFile64: { type: 'file', operation: 'write' },
44
+ unlink: { type: 'file', operation: 'delete' },
45
+ rename: { type: 'file', operation: 'write' },
46
+ mkdir: { type: 'file', operation: 'write' },
47
+ chmodFile: { type: 'file', operation: 'write' },
48
+ chownFile: { type: 'file', operation: 'write' },
49
+ subscribeFiles: { type: 'file', operation: 'read' },
50
+ unsubscribeFiles: { type: 'file', operation: 'read' },
51
+ authEnabled: { type: '', operation: '' },
52
+ disconnect: { type: '', operation: '' },
53
+ listPermissions: { type: '', operation: '' },
54
+ getUserPermissions: { type: 'object', operation: 'read' },
55
+ };
56
+ const pattern2RegEx = adapter_core_1.commonTools.pattern2RegEx;
57
+ let axiosGet = null;
58
+ let zipFiles = null;
59
+ class SocketCommands {
60
+ static ERROR_PERMISSION = 'permissionError';
61
+ static COMMANDS_PERMISSIONS = exports.COMMANDS_PERMISSIONS;
62
+ adapter;
63
+ context;
64
+ commands = {};
65
+ subscribes = {};
66
+ #logEnabled = false;
67
+ #clientSubscribes = {};
68
+ #updateSession;
69
+ adapterName;
70
+ _sendToHost;
71
+ states;
72
+ constructor(adapter, updateSession, context) {
73
+ this.adapter = adapter;
74
+ this.#updateSession = updateSession;
75
+ this.context = context;
76
+ this.#updateSession ||= () => true;
77
+ this._sendToHost = null;
78
+ this.#initCommands();
79
+ }
80
+ /**
81
+ * Rename file or folder
82
+ *
83
+ * @param adapter Object ID
84
+ * @param oldName Old file name
85
+ * @param newName New file name
86
+ * @param options options { user?: string; }
87
+ */
88
+ async #rename(adapter, oldName, newName, options) {
89
+ // read if it is a file or folder
90
+ try {
91
+ if (oldName.endsWith('/')) {
92
+ oldName = oldName.substring(0, oldName.length - 1);
93
+ }
94
+ if (newName.endsWith('/')) {
95
+ newName = newName.substring(0, newName.length - 1);
96
+ }
97
+ const files = await this.adapter.readDirAsync(adapter, oldName, options);
98
+ if (files?.length) {
99
+ for (let f = 0; f < files.length; f++) {
100
+ await this.#rename(adapter, `${oldName}/${files[f].file}`, `${newName}/${files[f].file}`);
101
+ }
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (error.message !== 'Not exists') {
106
+ throw error;
107
+ }
108
+ // else ignore, because it is a file and not a folder
109
+ }
110
+ try {
111
+ await this.adapter.renameAsync(adapter, oldName, newName, options);
112
+ }
113
+ catch (error) {
114
+ if (error.message !== 'Not exists') {
115
+ throw error;
116
+ }
117
+ // else ignore, because the folder cannot be deleted
118
+ }
119
+ }
120
+ /**
121
+ * Delete file or folder
122
+ *
123
+ * @param adapter Object ID
124
+ * @param name File name
125
+ * @param options options { user?: string; }
126
+ */
127
+ async #unlink(adapter, name, options) {
128
+ // read if it is a file or folder
129
+ try {
130
+ // remove trailing '/'
131
+ if (name.endsWith('/')) {
132
+ name = name.substring(0, name.length - 1);
133
+ }
134
+ const files = await this.adapter.readDirAsync(adapter, name, options);
135
+ if (files && files.length) {
136
+ for (let f = 0; f < files.length; f++) {
137
+ await this.#unlink(adapter, `${name}/${files[f].file}`);
138
+ }
139
+ }
140
+ }
141
+ catch (error) {
142
+ // ignore, because it is a file and not a folder
143
+ if (error.message !== 'Not exists') {
144
+ throw error;
145
+ }
146
+ }
147
+ try {
148
+ await this.adapter.unlinkAsync(adapter, name, options);
149
+ }
150
+ catch (error) {
151
+ if (error.message !== 'Not exists') {
152
+ throw error;
153
+ }
154
+ // else ignore, because folder cannot be deleted
155
+ }
156
+ }
157
+ /**
158
+ * Convert errors into strings and then call cb
159
+ *
160
+ * @param callback Callback function
161
+ * @param error Error
162
+ * @param args Arguments passed to callback
163
+ */
164
+ static _fixCallback(callback, error, ...args) {
165
+ if (typeof callback !== 'function') {
166
+ return;
167
+ }
168
+ if (error instanceof Error) {
169
+ error = error.message;
170
+ }
171
+ callback(error, ...args);
172
+ }
173
+ _checkPermissions(socket, command, callback, ...args) {
174
+ const _command = command;
175
+ if (socket._acl?.user !== 'system.user.admin') {
176
+ // type: file, object, state, other
177
+ // operation: create, read, write, list, delete, sendto, execute, sendToHost, readLogs
178
+ if (SocketCommands.COMMANDS_PERMISSIONS[_command]) {
179
+ // If permission required
180
+ if (SocketCommands.COMMANDS_PERMISSIONS[_command].type) {
181
+ if (SocketCommands.COMMANDS_PERMISSIONS[_command].type === 'object' &&
182
+ socket._acl?.object &&
183
+ (socket._acl?.object)[SocketCommands.COMMANDS_PERMISSIONS[_command].operation]) {
184
+ return true;
185
+ }
186
+ else if (SocketCommands.COMMANDS_PERMISSIONS[_command].type === 'state' &&
187
+ socket._acl?.state &&
188
+ (socket._acl?.state)[SocketCommands.COMMANDS_PERMISSIONS[_command].operation]) {
189
+ return true;
190
+ }
191
+ this.adapter.log.warn(`No permission for "${socket._acl?.user}" to call ${_command}. Need "${SocketCommands.COMMANDS_PERMISSIONS[_command].type}"."${SocketCommands.COMMANDS_PERMISSIONS[_command].operation}"`);
192
+ }
193
+ else {
194
+ return true;
195
+ }
196
+ }
197
+ else {
198
+ this.adapter.log.warn(`No rule for command: ${_command}`);
199
+ }
200
+ if (typeof callback === 'function') {
201
+ callback(SocketCommands.ERROR_PERMISSION);
202
+ }
203
+ else {
204
+ if (SocketCommands.COMMANDS_PERMISSIONS[_command]) {
205
+ socket.emit(SocketCommands.ERROR_PERMISSION, {
206
+ command,
207
+ type: SocketCommands.COMMANDS_PERMISSIONS[_command].type,
208
+ operation: SocketCommands.COMMANDS_PERMISSIONS[_command].operation,
209
+ args,
210
+ });
211
+ }
212
+ else {
213
+ socket.emit(SocketCommands.ERROR_PERMISSION, { command: _command, args });
214
+ }
215
+ }
216
+ return false;
217
+ }
218
+ return true;
219
+ }
220
+ publish(socket, type, id, obj) {
221
+ if (socket?.subscribe?.[type] && this.#updateSession(socket)) {
222
+ return !!socket.subscribe[type].find(sub => {
223
+ if (sub.regex.test(id)) {
224
+ // replace language
225
+ if (this.context.language &&
226
+ id === 'system.config' &&
227
+ obj.common) {
228
+ obj.common.language = this.context.language;
229
+ }
230
+ socket.emit(type, id, obj);
231
+ return true;
232
+ }
233
+ });
234
+ }
235
+ return false;
236
+ }
237
+ publishFile(socket, id, fileName, size) {
238
+ if (socket?.subscribe?.fileChange && this.#updateSession(socket)) {
239
+ const key = `${id}####${fileName}`;
240
+ return !!socket.subscribe.fileChange.find(sub => {
241
+ if (sub.regex.test(key)) {
242
+ socket.emit('fileChange', id, fileName, size);
243
+ return true;
244
+ }
245
+ });
246
+ }
247
+ return false;
248
+ }
249
+ publishInstanceMessage(socket, sourceInstance, messageType, data) {
250
+ if (this.#clientSubscribes[socket.id]?.[sourceInstance]?.includes(messageType)) {
251
+ socket.emit('im', messageType, sourceInstance, data);
252
+ return true;
253
+ }
254
+ // inform instance about missing subscription
255
+ this.adapter.sendTo(sourceInstance, 'clientSubscribeError', {
256
+ type: messageType,
257
+ sid: socket.id,
258
+ reason: 'no one subscribed',
259
+ });
260
+ return false;
261
+ }
262
+ _showSubscribes(socket, type) {
263
+ if (socket?.subscribe) {
264
+ const s = socket.subscribe[type] || [];
265
+ const ids = [];
266
+ for (let i = 0; i < s.length; i++) {
267
+ ids.push(s[i].pattern);
268
+ }
269
+ this.adapter.log.debug(`Subscribes: ${ids.join(', ')}`);
270
+ }
271
+ else {
272
+ this.adapter.log.debug('Subscribes: no subscribes');
273
+ }
274
+ }
275
+ isLogEnabled() {
276
+ return this.#logEnabled;
277
+ }
278
+ subscribe(socket, type, pattern, patternFile) {
279
+ if (!pattern) {
280
+ this.adapter.log.warn('Empty pattern on subscribe!');
281
+ return;
282
+ }
283
+ this.subscribes[type] = this.subscribes[type] || {};
284
+ let p;
285
+ let key;
286
+ pattern = pattern.toString();
287
+ if (patternFile && type === 'fileChange') {
288
+ patternFile = patternFile.toString();
289
+ key = `${pattern}####${patternFile}`;
290
+ }
291
+ else {
292
+ key = pattern;
293
+ }
294
+ try {
295
+ p = pattern2RegEx(key);
296
+ }
297
+ catch (e) {
298
+ this.adapter.log.error(`Invalid pattern on subscribe: ${e.message}`);
299
+ return;
300
+ }
301
+ if (p === null) {
302
+ this.adapter.log.warn('Empty pattern on subscribe!');
303
+ return;
304
+ }
305
+ let s;
306
+ if (socket) {
307
+ socket.subscribe = socket.subscribe || {};
308
+ s = socket.subscribe[type] = socket.subscribe[type] || [];
309
+ if (s.find(item => item.pattern === key)) {
310
+ return;
311
+ }
312
+ s.push({ pattern: key, regex: new RegExp(p) });
313
+ }
314
+ const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
315
+ if (this.subscribes[type][key] === undefined) {
316
+ this.subscribes[type][key] = 1;
317
+ if (type === 'stateChange') {
318
+ this.adapter
319
+ .subscribeForeignStatesAsync(pattern, options)
320
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
321
+ }
322
+ else if (type === 'objectChange') {
323
+ this.adapter
324
+ .subscribeForeignObjectsAsync(pattern, options)
325
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
326
+ }
327
+ else if (type === 'log') {
328
+ if (!this.#logEnabled && this.adapter.requireLog) {
329
+ this.#logEnabled = true;
330
+ void this.adapter.requireLog(true, options);
331
+ }
332
+ }
333
+ else if (type === 'fileChange' && this.adapter.subscribeForeignFiles) {
334
+ void this.adapter
335
+ .subscribeForeignFiles(pattern, patternFile || '*', options)
336
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
337
+ }
338
+ }
339
+ else {
340
+ this.subscribes[type][key]++;
341
+ }
342
+ }
343
+ unsubscribe(socket, type, pattern, patternFile) {
344
+ if (!pattern) {
345
+ this.adapter.log.warn('Empty pattern on subscribe!');
346
+ return;
347
+ }
348
+ if (!this.subscribes[type]) {
349
+ return;
350
+ }
351
+ let key;
352
+ pattern = pattern.toString();
353
+ if (patternFile && type === 'fileChange') {
354
+ patternFile = patternFile.toString();
355
+ key = `${pattern}####${patternFile}`;
356
+ }
357
+ else {
358
+ key = pattern;
359
+ }
360
+ const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
361
+ if (socket && typeof socket === 'object') {
362
+ if (!socket.subscribe || !socket.subscribe[type]) {
363
+ return;
364
+ }
365
+ for (let i = socket.subscribe[type].length - 1; i >= 0; i--) {
366
+ if (socket.subscribe[type][i].pattern === key) {
367
+ // Remove a pattern from a global list
368
+ if (this.subscribes[type][key] !== undefined) {
369
+ this.subscribes[type][key]--;
370
+ if (this.subscribes[type][key] <= 0) {
371
+ if (type === 'stateChange') {
372
+ this.adapter
373
+ .unsubscribeForeignStatesAsync(pattern, options)
374
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
375
+ }
376
+ else if (type === 'objectChange') {
377
+ this.adapter
378
+ .unsubscribeForeignObjectsAsync(pattern, options)
379
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
380
+ }
381
+ else if (type === 'log') {
382
+ if (this.#logEnabled && this.adapter.requireLog) {
383
+ this.#logEnabled = false;
384
+ void this.adapter.requireLog(false, options);
385
+ }
386
+ }
387
+ else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
388
+ void this.adapter
389
+ .unsubscribeForeignFiles(pattern, patternFile || '*', options)
390
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
391
+ }
392
+ delete this.subscribes[type][pattern];
393
+ }
394
+ }
395
+ socket.subscribe[type].splice(i, 1);
396
+ return;
397
+ }
398
+ }
399
+ }
400
+ else if (key) {
401
+ // Remove a pattern from a global list
402
+ if (this.subscribes[type][key] !== undefined) {
403
+ this.subscribes[type][key]--;
404
+ if (this.subscribes[type][key] <= 0) {
405
+ if (type === 'stateChange') {
406
+ this.adapter
407
+ .unsubscribeForeignStatesAsync(pattern, options)
408
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
409
+ }
410
+ else if (type === 'objectChange') {
411
+ this.adapter
412
+ .unsubscribeForeignObjectsAsync(pattern, options)
413
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
414
+ }
415
+ else if (type === 'log') {
416
+ if (this.adapter.requireLog && this.#logEnabled) {
417
+ this.#logEnabled = false;
418
+ void this.adapter.requireLog(false, options);
419
+ }
420
+ }
421
+ else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
422
+ void this.adapter
423
+ .unsubscribeForeignFiles(pattern, patternFile || '*', options)
424
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
425
+ }
426
+ delete this.subscribes[type][key];
427
+ }
428
+ }
429
+ }
430
+ else {
431
+ for (const pattern of Object.keys(this.subscribes[type])) {
432
+ if (type === 'stateChange') {
433
+ this.adapter
434
+ .unsubscribeForeignStatesAsync(pattern, options)
435
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
436
+ }
437
+ else if (type === 'objectChange') {
438
+ this.adapter
439
+ .unsubscribeForeignObjectsAsync(pattern, options)
440
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
441
+ }
442
+ else if (type === 'log') {
443
+ // console.log((socket._name || socket.id) + ' requireLog false');
444
+ if (this.adapter.requireLog && this.#logEnabled) {
445
+ this.#logEnabled = false;
446
+ void this.adapter.requireLog(false, options);
447
+ }
448
+ }
449
+ else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
450
+ const [id, fileName] = pattern.split('####');
451
+ void this.adapter
452
+ .unsubscribeForeignFiles(id, fileName, options)
453
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
454
+ }
455
+ }
456
+ this.subscribes[type] = {};
457
+ }
458
+ }
459
+ subscribeSocket(socket, type) {
460
+ if (!socket || !socket.subscribe) {
461
+ return;
462
+ }
463
+ if (!type) {
464
+ // all
465
+ Object.keys(socket.subscribe).forEach(type => this.subscribeSocket(socket, type));
466
+ return;
467
+ }
468
+ if (!socket.subscribe[type]) {
469
+ return;
470
+ }
471
+ const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
472
+ for (let i = 0; i < socket.subscribe[type].length; i++) {
473
+ const pattern = socket.subscribe[type][i].pattern;
474
+ if (this.subscribes[type][pattern] === undefined) {
475
+ this.subscribes[type][pattern] = 1;
476
+ if (type === 'stateChange') {
477
+ this.adapter
478
+ .subscribeForeignStatesAsync(pattern, options)
479
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
480
+ }
481
+ else if (type === 'objectChange') {
482
+ this.adapter
483
+ .subscribeForeignObjectsAsync(pattern, options)
484
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
485
+ }
486
+ else if (type === 'log') {
487
+ if (this.adapter.requireLog && !this.#logEnabled) {
488
+ this.#logEnabled = true;
489
+ void this.adapter.requireLog(true, options);
490
+ }
491
+ }
492
+ else if (type === 'fileChange' && this.adapter.subscribeForeignFiles) {
493
+ const [id, fileName] = pattern.split('####');
494
+ void this.adapter
495
+ .subscribeForeignFiles(id, fileName, options)
496
+ .catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
497
+ }
498
+ }
499
+ else {
500
+ this.subscribes[type][pattern]++;
501
+ }
502
+ }
503
+ }
504
+ unsubscribeSocket(socket, type) {
505
+ if (!socket || !socket.subscribe) {
506
+ return;
507
+ }
508
+ // inform all instances about disconnected socket
509
+ this.#informAboutDisconnect(socket.id);
510
+ if (!type) {
511
+ // all
512
+ Object.keys(socket.subscribe).forEach(type => this.unsubscribeSocket(socket, type));
513
+ return;
514
+ }
515
+ if (!socket.subscribe[type]) {
516
+ return;
517
+ }
518
+ const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
519
+ for (let i = 0; i < socket.subscribe[type].length; i++) {
520
+ const pattern = socket.subscribe[type][i].pattern;
521
+ if (this.subscribes[type][pattern] !== undefined) {
522
+ this.subscribes[type][pattern]--;
523
+ if (this.subscribes[type][pattern] <= 0) {
524
+ if (type === 'stateChange') {
525
+ this.adapter
526
+ .unsubscribeForeignStatesAsync(pattern, options)
527
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
528
+ }
529
+ else if (type === 'objectChange') {
530
+ this.adapter
531
+ .unsubscribeForeignObjectsAsync(pattern, options)
532
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
533
+ }
534
+ else if (type === 'log') {
535
+ if (this.adapter.requireLog && !this.#logEnabled) {
536
+ this.#logEnabled = true;
537
+ void this.adapter.requireLog(true, options);
538
+ }
539
+ }
540
+ else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
541
+ const [id, fileName] = pattern.split('####');
542
+ void this.adapter
543
+ .unsubscribeForeignFiles(id, fileName, options)
544
+ .catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
545
+ }
546
+ delete this.subscribes[type][pattern];
547
+ }
548
+ }
549
+ }
550
+ }
551
+ #subscribeStates(socket, pattern, callback) {
552
+ if (this._checkPermissions(socket, 'subscribe', callback, pattern)) {
553
+ if (Array.isArray(pattern)) {
554
+ for (let p = 0; p < pattern.length; p++) {
555
+ this.subscribe(socket, 'stateChange', pattern[p]);
556
+ }
557
+ }
558
+ else {
559
+ this.subscribe(socket, 'stateChange', pattern);
560
+ }
561
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
562
+ typeof callback === 'function' && setImmediate(callback, null);
563
+ }
564
+ }
565
+ #unsubscribeStates(socket, pattern, callback) {
566
+ if (this._checkPermissions(socket, 'unsubscribe', callback, pattern)) {
567
+ if (Array.isArray(pattern)) {
568
+ for (let p = 0; p < pattern.length; p++) {
569
+ this.unsubscribe(socket, 'stateChange', pattern[p]);
570
+ }
571
+ }
572
+ else {
573
+ this.unsubscribe(socket, 'stateChange', pattern);
574
+ }
575
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
576
+ typeof callback === 'function' && setImmediate(callback, null);
577
+ }
578
+ }
579
+ #subscribeFiles(socket, id, pattern, callback) {
580
+ if (this._checkPermissions(socket, 'subscribeFiles', callback, pattern)) {
581
+ if (Array.isArray(pattern)) {
582
+ for (let p = 0; p < pattern.length; p++) {
583
+ this.subscribe(socket, 'fileChange', id, pattern[p]);
584
+ }
585
+ }
586
+ else {
587
+ this.subscribe(socket, 'fileChange', id, pattern);
588
+ }
589
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'fileChange');
590
+ typeof callback === 'function' && setImmediate(callback, null);
591
+ }
592
+ }
593
+ _unsubscribeFiles(socket, id, pattern, callback) {
594
+ if (this._checkPermissions(socket, 'unsubscribeFiles', callback, pattern)) {
595
+ if (Array.isArray(pattern)) {
596
+ for (let p = 0; p < pattern.length; p++) {
597
+ this.unsubscribe(socket, 'fileChange', id, pattern[p]);
598
+ }
599
+ }
600
+ else {
601
+ this.unsubscribe(socket, 'fileChange', id, pattern);
602
+ }
603
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'fileChange');
604
+ typeof callback === 'function' && setImmediate(callback, null);
605
+ }
606
+ }
607
+ addCommandHandler(command, handler) {
608
+ if (handler) {
609
+ this.commands[command] = handler;
610
+ }
611
+ else if (command in this.commands) {
612
+ delete this.commands[command];
613
+ }
614
+ }
615
+ getCommandHandler(command) {
616
+ return this.commands[command];
617
+ }
618
+ /**
619
+ * Converts old structures of config definitions into new one - `adminUI`
620
+ *
621
+ * @param obj Instance or adapter object to be converted
622
+ */
623
+ fixAdminUI(obj) {
624
+ if (obj?.common && !obj.common.adminUI) {
625
+ obj.common.adminUI = { config: 'none' };
626
+ if (obj.common.noConfig) {
627
+ obj.common.adminUI.config = 'none';
628
+ // @ts-expect-error this attribute is deprecated, but still used
629
+ }
630
+ else if (obj.common.jsonConfig) {
631
+ obj.common.adminUI.config = 'json';
632
+ }
633
+ else if (obj.common.materialize) {
634
+ obj.common.adminUI.config = 'materialize';
635
+ }
636
+ else {
637
+ obj.common.adminUI.config = 'html';
638
+ }
639
+ // @ts-expect-error this attribute is deprecated, but still used
640
+ if (obj.common.jsonCustom) {
641
+ obj.common.adminUI.custom = 'json';
642
+ }
643
+ else if (obj.common.supportCustoms) {
644
+ obj.common.adminUI.custom = 'json';
645
+ }
646
+ if (obj.common.materializeTab && obj.common.adminTab) {
647
+ obj.common.adminUI.tab = 'materialize';
648
+ }
649
+ else if (obj.common.adminTab) {
650
+ obj.common.adminUI.tab = 'html';
651
+ }
652
+ if (obj.common.adminUI) {
653
+ this.adapter.log.debug(`Please add to "${obj._id.replace(/\.\d+$/, '')}" common.adminUI=${JSON.stringify(obj.common.adminUI)}`);
654
+ }
655
+ }
656
+ }
657
+ #httpGet(url, callback) {
658
+ this.adapter.log.debug(`httpGet: ${url}`);
659
+ if (axiosGet) {
660
+ try {
661
+ axiosGet(url, {
662
+ responseType: 'arraybuffer',
663
+ timeout: 15000,
664
+ validateStatus: (status) => status < 400,
665
+ })
666
+ .then((result) => callback(null, { status: result.status, statusText: result.statusText }, result.data))
667
+ .catch((error) => callback(error));
668
+ }
669
+ catch (error) {
670
+ callback(error);
671
+ }
672
+ }
673
+ else {
674
+ callback(new Error('axios is not initialized'));
675
+ }
676
+ }
677
+ // Init common commands that not belong to stats, objects or files
678
+ _initCommandsCommon() {
679
+ /**
680
+ * #DOCUMENTATION commands
681
+ * Wait till the user is authenticated.
682
+ * As the user authenticates himself, the callback will be called
683
+ *
684
+ * @param socket Socket instance
685
+ * @param callback Callback `(isUserAuthenticated: boolean, isAuthenticationUsed: boolean) => void`
686
+ */
687
+ this.commands.authenticate = (socket, callback) => {
688
+ if (socket._acl?.user !== null) {
689
+ this.adapter.log.debug(`${new Date().toISOString()} Request authenticate [${socket._acl?.user}]`);
690
+ if (typeof callback === 'function') {
691
+ callback(true, socket._secure);
692
+ }
693
+ }
694
+ else {
695
+ socket._authPending = callback;
696
+ }
697
+ };
698
+ /**
699
+ * #DOCUMENTATION commands
700
+ * Write error into ioBroker log
701
+ *
702
+ * @param _socket Socket instance (not used)
703
+ * @param error Error object or error text
704
+ */
705
+ this.commands.error = (_socket, error) => {
706
+ this.adapter.log.error(`Socket error: ${error.toString()}`);
707
+ };
708
+ /**
709
+ * #DOCUMENTATION commands
710
+ * Write log entry into ioBroker log
711
+ *
712
+ * @param _socket Socket instance (not used)
713
+ * @param text log text
714
+ * @param level one of `['silly', 'debug', 'info', 'warn', 'error']`. Default is 'debug'.
715
+ */
716
+ this.commands.log = (_socket, text, level) => {
717
+ if (level === 'error') {
718
+ this.adapter.log.error(text);
719
+ }
720
+ else if (level === 'warn') {
721
+ this.adapter.log.warn(text);
722
+ }
723
+ else if (level === 'info') {
724
+ this.adapter.log.info(text);
725
+ }
726
+ else {
727
+ this.adapter.log.debug(text);
728
+ }
729
+ };
730
+ /**
731
+ * #DOCUMENTATION commands
732
+ * Check if the same feature is supported by the current js-controller
733
+ *
734
+ * @param _socket Socket instance (not used)
735
+ * @param feature feature name like `CONTROLLER_LICENSE_MANAGER`
736
+ * @param callback callback `(error: string | Error | null | undefined, isSupported: boolean) => void`
737
+ */
738
+ this.commands.checkFeatureSupported = (_socket, feature, callback) => {
739
+ if (feature === 'INSTANCE_MESSAGES') {
740
+ SocketCommands._fixCallback(callback, null, true);
741
+ }
742
+ else if (feature === 'PARTIAL_OBJECT_TREE') {
743
+ SocketCommands._fixCallback(callback, null, true);
744
+ }
745
+ else {
746
+ SocketCommands._fixCallback(callback, null, this.adapter.supportsFeature(feature));
747
+ }
748
+ };
749
+ /**
750
+ * #DOCUMENTATION commands
751
+ * Get history data from specific instance
752
+ *
753
+ * @param socket Socket instance
754
+ * @param id object ID
755
+ * @param options History options
756
+ * @param callback callback `(error: string | Error | null | undefined, result: ioBroker.GetHistoryResult) => void`
757
+ */
758
+ this.commands.getHistory = (socket, id, options, callback) => {
759
+ if (this._checkPermissions(socket, 'getStateHistory', callback, id)) {
760
+ if (typeof options === 'string') {
761
+ options = {
762
+ instance: options,
763
+ };
764
+ }
765
+ options = options || {};
766
+ // @ts-expect-error fixed in js-controller
767
+ options.user = socket._acl?.user;
768
+ options.aggregate = options.aggregate || 'none';
769
+ try {
770
+ this.adapter.getHistory(id, options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
771
+ }
772
+ catch (error) {
773
+ this.adapter.log.error(`[getHistory] ERROR: ${error.toString()}`);
774
+ SocketCommands._fixCallback(callback, error);
775
+ }
776
+ }
777
+ };
778
+ /**
779
+ * #DOCUMENTATION commands
780
+ * Read content of HTTP(s) page server-side (without CORS and stuff)
781
+ *
782
+ * @param socket Socket instance
783
+ * @param url Page URL
784
+ * @param callback callback `(error: Error | null, result?: { status: number; statusText: string }, data?: string) => void`
785
+ */
786
+ this.commands.httpGet = (socket, url, callback) => {
787
+ if (this._checkPermissions(socket, 'httpGet', callback, url)) {
788
+ if (axiosGet) {
789
+ this.#httpGet(url, callback);
790
+ }
791
+ else {
792
+ void import('axios').then(({ default: axios }) => {
793
+ axiosGet = axiosGet || axios.get;
794
+ this.#httpGet(url, callback);
795
+ });
796
+ }
797
+ }
798
+ };
799
+ /**
800
+ * #DOCUMENTATION commands
801
+ * Send the message to specific instance
802
+ *
803
+ * @param socket Socket instance
804
+ * @param adapterInstance instance name, e.g. `history.0`
805
+ * @param command command name
806
+ * @param message the message is instance-dependent
807
+ * @param callback callback `(result: any) => void`
808
+ */
809
+ this.commands.sendTo = (socket, adapterInstance, command, message, callback) => {
810
+ if (this._checkPermissions(socket, 'sendTo', callback, command)) {
811
+ try {
812
+ this.adapter.sendTo(adapterInstance, command, message, res => typeof callback === 'function' && setImmediate(() => callback(res)));
813
+ }
814
+ catch (error) {
815
+ typeof callback === 'function' && setImmediate(() => callback({ error }));
816
+ }
817
+ }
818
+ };
819
+ // following commands are protected and require the extra permissions
820
+ const protectedCommands = [
821
+ 'cmdExec',
822
+ 'getLocationOnDisk',
823
+ 'getDiagData',
824
+ 'getDevList',
825
+ 'delLogs',
826
+ 'writeDirAsZip',
827
+ 'writeObjectsAsZip',
828
+ 'readObjectsAsZip',
829
+ 'checkLogging',
830
+ 'updateMultihost',
831
+ 'rebuildAdapter',
832
+ ];
833
+ /**
834
+ * #DOCUMENTATION commands
835
+ * Send a message to the specific host.
836
+ * Host can answer to the following commands: `cmdExec, getRepository, getInstalled, getInstalledAdapter, getVersion, getDiagData, getLocationOnDisk, getDevList, getLogs, getHostInfo, delLogs, readDirAsZip, writeDirAsZip, readObjectsAsZip, writeObjectsAsZip, checkLogging, updateMultihost`.
837
+ *
838
+ * @param socket Socket instance
839
+ * @param host Host name. With or without 'system.host.' prefix
840
+ * @param command Host command
841
+ * @param message the message is command-specific
842
+ * @param callback callback `(result: { error?: string; result?: any }) => void`
843
+ */
844
+ this.commands.sendToHost = (socket, host, command, message, callback) => {
845
+ if (this._checkPermissions(socket, protectedCommands.includes(command) ? 'cmdExec' : 'sendToHost', (error) => callback({ error: error || SocketCommands.ERROR_PERMISSION }), command)) {
846
+ // Try to decode this file locally as redis has a limitation for files bigger than 20MB
847
+ if (command === 'writeDirAsZip' && message && message.data.length > 1024 * 1024) {
848
+ let buffer;
849
+ try {
850
+ buffer = Buffer.from(message.data, 'base64');
851
+ }
852
+ catch (error) {
853
+ this.adapter.log.error(`Cannot convert data: ${error.toString()}`);
854
+ return callback && callback({ error: `Cannot convert data: ${error.toString()}` });
855
+ }
856
+ zipFiles = zipFiles || adapter_core_1.commonTools.zipFiles;
857
+ zipFiles
858
+ .writeDirAsZip(this.adapter, // normally we have to pass here the internal "objects" object, but as
859
+ // only writeFile is used, and it has the same name, we can pass here the
860
+ // adapter, which has the function with the same name and arguments
861
+ message.id, message.name, buffer, message.options, (error) => callback({ error: error?.toString() }))
862
+ .then(() => callback({}))
863
+ .catch((error) => {
864
+ this.adapter.log.error(`Cannot write zip file as folder: ${error.toString()}`);
865
+ if (callback) {
866
+ callback({ error: error?.toString() });
867
+ }
868
+ });
869
+ }
870
+ else if (this._sendToHost) {
871
+ this._sendToHost(host, command, message, callback);
872
+ }
873
+ else {
874
+ try {
875
+ this.adapter.sendToHost(host, command, message, callback);
876
+ }
877
+ catch (error) {
878
+ if (callback) {
879
+ callback({ error });
880
+ }
881
+ }
882
+ }
883
+ }
884
+ };
885
+ /**
886
+ * #DOCUMENTATION commands
887
+ * Ask server is authentication enabled, and if the user authenticated
888
+ *
889
+ * @param socket Socket instance
890
+ * @param callback callback `(isUserAuthenticated: boolean | Error | string, isAuthenticationUsed: boolean) => void`
891
+ */
892
+ this.commands.authEnabled = (socket, callback) => {
893
+ if (this._checkPermissions(socket, 'authEnabled', callback)) {
894
+ if (typeof callback === 'function') {
895
+ // @ts-expect-error auth could exist in adapter settings
896
+ callback(this.adapter.config.auth, (socket._acl?.user || '').replace(/^system\.user\./, ''));
897
+ }
898
+ else {
899
+ this.adapter.log.warn('[authEnabled] Invalid callback');
900
+ }
901
+ }
902
+ };
903
+ /**
904
+ * #DOCUMENTATION commands
905
+ * Logout user
906
+ *
907
+ * @param socket Socket instance
908
+ * @param callback callback `(error?: Error) => void`
909
+ */
910
+ this.commands.logout = (socket, callback) => {
911
+ if (socket.id) {
912
+ void this.adapter.destroySession(socket.id, callback);
913
+ }
914
+ else if (callback) {
915
+ callback(new Error('No session'));
916
+ }
917
+ };
918
+ /**
919
+ * #DOCUMENTATION commands
920
+ * List commands and permissions
921
+ *
922
+ * @param _socket Socket instance (not used)
923
+ * @param callback callback `(permissions: Record<string, { type: 'object' | 'state' | 'users' | 'other' | 'file' | ''; operation: SocketOperation }>) => void`
924
+ */
925
+ this.commands.listPermissions = (_socket, callback) => {
926
+ if (typeof callback === 'function') {
927
+ callback(SocketCommands.COMMANDS_PERMISSIONS);
928
+ }
929
+ else {
930
+ this.adapter.log.warn('[listPermissions] Invalid callback');
931
+ }
932
+ };
933
+ /**
934
+ * #DOCUMENTATION commands
935
+ * Get user permissions
936
+ *
937
+ * @param socket Socket instance
938
+ * @param callback callback `(error: string | null | undefined, userPermissions?: SocketACL | null) => void`
939
+ */
940
+ this.commands.getUserPermissions = (socket, callback) => {
941
+ if (this._checkPermissions(socket, 'getUserPermissions', callback)) {
942
+ if (typeof callback === 'function') {
943
+ callback(null, socket._acl);
944
+ }
945
+ else {
946
+ this.adapter.log.warn('[getUserPermissions] Invalid callback');
947
+ }
948
+ }
949
+ };
950
+ /**
951
+ * #DOCUMENTATION commands
952
+ * Get the adapter version. Not the socket-classes version!
953
+ *
954
+ * @param socket Socket instance
955
+ * @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
956
+ */
957
+ this.commands.getVersion = (socket, callback) => {
958
+ if (this._checkPermissions(socket, 'getVersion', callback)) {
959
+ if (typeof callback === 'function') {
960
+ callback(null, this.adapter.version, this.adapter.name);
961
+ }
962
+ else {
963
+ this.adapter.log.warn('[getVersion] Invalid callback');
964
+ }
965
+ }
966
+ };
967
+ /**
968
+ * #DOCUMENTATION commands
969
+ * Get adapter name: "iobroker.ws", "iobroker.socketio", "iobroker.web", "iobroker.admin"
970
+ *
971
+ * @param socket Socket instance
972
+ * @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
973
+ */
974
+ this.commands.getAdapterName = (socket, callback) => {
975
+ if (this._checkPermissions(socket, 'getAdapterName', callback)) {
976
+ if (typeof callback === 'function') {
977
+ callback(null, this.adapter.name || 'unknown');
978
+ }
979
+ else {
980
+ this.adapter.log.warn('[getAdapterName] Invalid callback');
981
+ }
982
+ }
983
+ };
984
+ }
985
+ /** Init commands for files */
986
+ _initCommandsFiles() {
987
+ /**
988
+ * #DOCUMENTATION files
989
+ * Read a file from ioBroker DB
990
+ *
991
+ * @param socket Socket instance
992
+ * @param adapter instance name, e.g. `vis.0`
993
+ * @param fileName file name, e.g. `main/vis-views.json`
994
+ * @param callback Callback `(error: null | undefined | Error | string, data: Buffer | string, mimeType: string) => void`
995
+ */
996
+ this.commands.readFile = (socket, adapter, fileName, callback) => {
997
+ if (this._checkPermissions(socket, 'readFile', callback, fileName)) {
998
+ try {
999
+ this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1000
+ }
1001
+ catch (error) {
1002
+ this.adapter.log.error(`[readFile] ERROR: ${error.toString()}`);
1003
+ SocketCommands._fixCallback(callback, error);
1004
+ }
1005
+ }
1006
+ };
1007
+ /**
1008
+ * #DOCUMENTATION files
1009
+ * Read a file from ioBroker DB as base64 string
1010
+ *
1011
+ * @param socket Socket instance
1012
+ * @param adapter instance name, e.g. `vis.0`
1013
+ * @param fileName file name, e.g. `main/vis-views.json`
1014
+ * @param callback Callback `(error: null | undefined | Error | string, base64: string, mimeType: string) => void`
1015
+ */
1016
+ this.commands.readFile64 = (socket, adapter, fileName, callback) => {
1017
+ if (this._checkPermissions(socket, 'readFile64', callback, fileName)) {
1018
+ try {
1019
+ this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, buffer, type) => {
1020
+ let data64;
1021
+ if (buffer) {
1022
+ try {
1023
+ if (type === 'application/json' ||
1024
+ type === 'application/json5' ||
1025
+ fileName.toLowerCase().endsWith('.json5')) {
1026
+ data64 = Buffer.from(encodeURIComponent(buffer)).toString('base64');
1027
+ }
1028
+ else {
1029
+ if (typeof buffer === 'string') {
1030
+ data64 = Buffer.from(buffer).toString('base64');
1031
+ }
1032
+ else {
1033
+ data64 = buffer.toString('base64');
1034
+ }
1035
+ }
1036
+ }
1037
+ catch (error) {
1038
+ this.adapter.log.error(`[readFile64] Cannot convert data: ${error.toString()}`);
1039
+ }
1040
+ }
1041
+ // Convert buffer to base 64
1042
+ if (typeof callback === 'function') {
1043
+ callback(error, data64 || '', type);
1044
+ }
1045
+ else {
1046
+ this.adapter.log.warn('[readFile64] Invalid callback');
1047
+ }
1048
+ });
1049
+ }
1050
+ catch (error) {
1051
+ this.adapter.log.error(`[readFile64] ERROR: ${error.toString()}`);
1052
+ SocketCommands._fixCallback(callback, error);
1053
+ }
1054
+ }
1055
+ };
1056
+ /**
1057
+ * #DOCUMENTATION files
1058
+ * Write a file into ioBroker DB as base64 string
1059
+ *
1060
+ * @param socket Socket instance
1061
+ * @param adapter instance name, e.g. `vis.0`
1062
+ * @param fileName file name, e.g. `main/vis-views.json`
1063
+ * @param data64 file content as base64 string
1064
+ * @param options optional `{mode: 0x0644}`
1065
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1066
+ */
1067
+ this.commands.writeFile64 = (socket, adapter, fileName, data64, options, callback) => {
1068
+ let _options;
1069
+ if (typeof options === 'function') {
1070
+ callback = options;
1071
+ _options = { user: socket._acl?.user };
1072
+ }
1073
+ else if (!options || options.mode === undefined) {
1074
+ _options = { user: socket._acl?.user };
1075
+ }
1076
+ else {
1077
+ _options = { user: socket._acl?.user, mode: options.mode };
1078
+ }
1079
+ if (this._checkPermissions(socket, 'writeFile64', callback, fileName)) {
1080
+ if (!data64) {
1081
+ return SocketCommands._fixCallback(callback, 'No data provided');
1082
+ }
1083
+ // Convert base 64 to buffer
1084
+ try {
1085
+ const buffer = Buffer.from(data64, 'base64');
1086
+ this.adapter.writeFile(adapter, fileName, buffer, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1087
+ }
1088
+ catch (error) {
1089
+ this.adapter.log.error(`[writeFile64] Cannot convert data: ${error.toString()}`);
1090
+ SocketCommands._fixCallback(callback, `Cannot convert data: ${error.toString()}`);
1091
+ }
1092
+ }
1093
+ };
1094
+ /**
1095
+ * #DOCUMENTATION files
1096
+ * Write a file into ioBroker DB as text
1097
+ *
1098
+ * This function is overloaded in admin (because admin accepts only base64)
1099
+ *
1100
+ * @deprecated
1101
+ * @param socket Socket instance
1102
+ * @param adapter instance name, e.g. `vis.0`
1103
+ * @param fileName file name, e.g. `main/vis-views.json`
1104
+ * @param data file content as text
1105
+ * @param options optional `{mode: 0x0644}`
1106
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1107
+ */
1108
+ this.commands.writeFile = (socket, adapter, fileName, data, options, callback) => {
1109
+ if (this._checkPermissions(socket, 'writeFile', callback, fileName)) {
1110
+ let _options;
1111
+ if (typeof options === 'function') {
1112
+ callback = options;
1113
+ _options = { user: socket._acl?.user };
1114
+ }
1115
+ else if (!options || options.mode === undefined) {
1116
+ _options = { user: socket._acl?.user };
1117
+ }
1118
+ else {
1119
+ _options = { user: socket._acl?.user, mode: options.mode };
1120
+ }
1121
+ this.adapter.log.debug('writeFile deprecated. Please use writeFile64');
1122
+ try {
1123
+ this.adapter.writeFile(adapter, fileName, data, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1124
+ }
1125
+ catch (error) {
1126
+ this.adapter.log.error(`[writeFile] ERROR: ${error.toString()}`);
1127
+ SocketCommands._fixCallback(callback, error);
1128
+ }
1129
+ }
1130
+ };
1131
+ /**
1132
+ * #DOCUMENTATION files
1133
+ * Delete file in ioBroker DB
1134
+ *
1135
+ * @param socket Socket instance
1136
+ * @param adapter instance name, e.g. `vis.0`
1137
+ * @param name file name, e.g. `main/vis-views.json`
1138
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1139
+ */
1140
+ this.commands.unlink = (socket, adapter, name, callback) => {
1141
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1142
+ try {
1143
+ this.#unlink(adapter, name, { user: socket._acl?.user })
1144
+ .then(() => SocketCommands._fixCallback(callback, undefined))
1145
+ .catch(error => SocketCommands._fixCallback(callback, error));
1146
+ }
1147
+ catch (error) {
1148
+ this.adapter.log.error(`[unlink] ERROR: ${error.toString()}`);
1149
+ SocketCommands._fixCallback(callback, error);
1150
+ }
1151
+ }
1152
+ };
1153
+ /**
1154
+ * #DOCUMENTATION files
1155
+ * Delete a file in ioBroker DB (same as "unlink", but only for files)
1156
+ *
1157
+ * @param socket Socket instance
1158
+ * @param adapter instance name, e.g. `vis.0`
1159
+ * @param name file name, e.g. `main/vis-views.json`
1160
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1161
+ */
1162
+ this.commands.deleteFile = (socket, adapter, name, callback) => {
1163
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1164
+ try {
1165
+ this.adapter.unlink(adapter, name, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1166
+ }
1167
+ catch (error) {
1168
+ this.adapter.log.error(`[deleteFile] ERROR: ${error.toString()}`);
1169
+ SocketCommands._fixCallback(callback, error);
1170
+ }
1171
+ }
1172
+ };
1173
+ /**
1174
+ * #DOCUMENTATION files
1175
+ * Delete folder in ioBroker DB (same as `unlink`, but only for folders)
1176
+ *
1177
+ * @param socket Socket instance
1178
+ * @param adapter instance name, e.g. `vis.0`
1179
+ * @param name folder name, e.g. `main`
1180
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1181
+ */
1182
+ this.commands.deleteFolder = (socket, adapter, name, callback) => {
1183
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1184
+ try {
1185
+ this.#unlink(adapter, name, { user: socket._acl?.user })
1186
+ .then(() => SocketCommands._fixCallback(callback, null))
1187
+ .catch(error => SocketCommands._fixCallback(callback, error));
1188
+ }
1189
+ catch (error) {
1190
+ this.adapter.log.error(`[deleteFolder] ERROR: ${error.toString()}`);
1191
+ SocketCommands._fixCallback(callback, error);
1192
+ }
1193
+ }
1194
+ };
1195
+ /**
1196
+ * #DOCUMENTATION files
1197
+ * Rename a file in ioBroker DB
1198
+ *
1199
+ * @param socket Socket instance
1200
+ * @param adapter instance name, e.g. `vis.0`
1201
+ * @param oldName current file name, e.g. `main/vis-views.json`
1202
+ * @param newName new file name, e.g. `main/vis-views-new.json`
1203
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1204
+ */
1205
+ this.commands.renameFile = (socket, adapter, oldName, newName, callback) => {
1206
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
1207
+ try {
1208
+ this.adapter.rename(adapter, oldName, newName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1209
+ }
1210
+ catch (error) {
1211
+ this.adapter.log.error(`[renameFile] ERROR: ${error.toString()}`);
1212
+ SocketCommands._fixCallback(callback, error);
1213
+ }
1214
+ }
1215
+ };
1216
+ /**
1217
+ * #DOCUMENTATION files
1218
+ * Rename file or folder in ioBroker DB
1219
+ *
1220
+ * @param socket Socket instance
1221
+ * @param adapter instance name, e.g. `vis.0`
1222
+ * @param oldName current file name, e.g. `main/vis-views.json`
1223
+ * @param newName new file name, e.g. `main/vis-views-new.json`
1224
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1225
+ */
1226
+ this.commands.rename = (socket, adapter, oldName, newName, callback) => {
1227
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
1228
+ try {
1229
+ this.#rename(adapter, oldName, newName, { user: socket._acl?.user })
1230
+ .then(() => SocketCommands._fixCallback(callback, undefined))
1231
+ .catch(error => SocketCommands._fixCallback(callback, error));
1232
+ }
1233
+ catch (error) {
1234
+ this.adapter.log.error(`[rename] ERROR: ${error.toString()}`);
1235
+ SocketCommands._fixCallback(callback, error);
1236
+ }
1237
+ }
1238
+ };
1239
+ /**
1240
+ * #DOCUMENTATION files
1241
+ * Create a folder in ioBroker DB
1242
+ *
1243
+ * @param socket Socket instance
1244
+ * @param adapter instance name, e.g. `vis.0`
1245
+ * @param dirName desired folder name, e.g. `main`
1246
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1247
+ */
1248
+ this.commands.mkdir = (socket, adapter, dirName, callback) => {
1249
+ if (this._checkPermissions(socket, 'mkdir', callback, dirName)) {
1250
+ try {
1251
+ this.adapter.mkdir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1252
+ }
1253
+ catch (error) {
1254
+ this.adapter.log.error(`[mkdir] ERROR: ${error.toString()}`);
1255
+ SocketCommands._fixCallback(callback, error);
1256
+ }
1257
+ }
1258
+ };
1259
+ /**
1260
+ * #DOCUMENTATION files
1261
+ * Read content of folder in ioBroker DB
1262
+ *
1263
+ * @param socket Socket instance
1264
+ * @param adapter instance name, e.g. `vis.0`
1265
+ * @param dirName folder name, e.g. `main`
1266
+ * @param options for future use
1267
+ * @param callback Callback `(error: null | undefined | Error | string, files: Array<{file: string, isDir: boolean, stats: {size: number}, modifiedAt: number, acl: {owner: string, ownerGroup: string, permissions: number, read: boolean, write: boolean}}>) => void`
1268
+ */
1269
+ this.commands.readDir = (socket, adapter, dirName, options, callback) => {
1270
+ if (typeof options === 'function') {
1271
+ callback = options;
1272
+ }
1273
+ if (this._checkPermissions(socket, 'readDir', callback, dirName)) {
1274
+ try {
1275
+ this.adapter.readDir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1276
+ }
1277
+ catch (error) {
1278
+ this.adapter.log.error(`[readDir] ERROR: ${error.toString()}`);
1279
+ SocketCommands._fixCallback(callback, error);
1280
+ }
1281
+ }
1282
+ };
1283
+ /**
1284
+ * #DOCUMENTATION files
1285
+ * Change a file mode in ioBroker DB
1286
+ *
1287
+ * @param socket Socket instance
1288
+ * @param adapter instance name, e.g. `vis.0`
1289
+ * @param fileName file name, e.g. `main/vis-views.json`
1290
+ * @param options options `{mode: 0x644}`
1291
+ * @param options.mode File mode in linux format 0x644. The first digit is user, second group, third others. Bit 1 is `execute`, bit 2 is `write`, bit 3 is `read`
1292
+ * @param callback Callback `(error: string | Error | null | undefined) => void`
1293
+ */
1294
+ this.commands.chmodFile = (socket, adapter, fileName, options, callback) => {
1295
+ let _options;
1296
+ if (options?.mode !== undefined) {
1297
+ _options = { user: socket._acl?.user, mode: options.mode };
1298
+ }
1299
+ else {
1300
+ this.adapter.log.error(`[chownFile] ERROR: no options`);
1301
+ SocketCommands._fixCallback(callback, 'no options');
1302
+ return;
1303
+ }
1304
+ if (this._checkPermissions(socket, 'chmodFile', callback, fileName)) {
1305
+ try {
1306
+ this.adapter.chmodFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1307
+ }
1308
+ catch (error) {
1309
+ this.adapter.log.error(`[chmodFile] ERROR: ${error.toString()}`);
1310
+ SocketCommands._fixCallback(callback, error);
1311
+ }
1312
+ }
1313
+ };
1314
+ /**
1315
+ * #DOCUMENTATION files
1316
+ * Change file owner in ioBroker DB
1317
+ *
1318
+ * @param socket Socket instance
1319
+ * @param adapter instance name, e.g. `vis.0`
1320
+ * @param fileName file name, e.g. `main/vis-views.json`
1321
+ * @param options options `{owner: 'system.user.user', ownerGroup: 'system.group.administrator'}` or `system.user.user`. If ownerGroup is not defined, it will be taken from owner.
1322
+ * @param options.owner New owner, like 'system.user.user'
1323
+ * @param options.ownerGroup New owner group, like 'system.group.administrator' If ownerGroup is not defined, it will be taken from owner.
1324
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1325
+ */
1326
+ this.commands.chownFile = (socket, adapter, fileName, options, callback) => {
1327
+ let _options;
1328
+ if (options) {
1329
+ _options = { user: socket._acl?.user, owner: options.owner, ownerGroup: options.ownerGroup };
1330
+ }
1331
+ else {
1332
+ this.adapter.log.error(`[chownFile] ERROR: no options`);
1333
+ SocketCommands._fixCallback(callback, 'no options');
1334
+ return;
1335
+ }
1336
+ if (this._checkPermissions(socket, 'chownFile', callback, fileName)) {
1337
+ try {
1338
+ this.adapter.chownFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1339
+ }
1340
+ catch (error) {
1341
+ this.adapter.log.error(`[chownFile] ERROR: ${error.toString()}`);
1342
+ SocketCommands._fixCallback(callback, error);
1343
+ }
1344
+ }
1345
+ };
1346
+ /**
1347
+ * #DOCUMENTATION files
1348
+ * Check if the file or folder exists in ioBroker DB
1349
+ *
1350
+ * @param socket Socket instance
1351
+ * @param adapter instance name, e.g. `vis.0`
1352
+ * @param fileName file name, e.g. `main/vis-views.json`
1353
+ * @param callback Callback `(error: null | undefined | Error | string, exists?: boolean) => void`
1354
+ */
1355
+ this.commands.fileExists = (socket, adapter, fileName, callback) => {
1356
+ if (this._checkPermissions(socket, 'fileExists', callback, fileName)) {
1357
+ try {
1358
+ this.adapter.fileExists(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1359
+ }
1360
+ catch (error) {
1361
+ this.adapter.log.error(`[fileExists] ERROR: ${error.toString()}`);
1362
+ SocketCommands._fixCallback(callback, error);
1363
+ }
1364
+ }
1365
+ };
1366
+ /**
1367
+ * #DOCUMENTATION files
1368
+ * Subscribe to file changes in ioBroker DB
1369
+ *
1370
+ * @param socket Socket instance
1371
+ * @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
1372
+ * @param pattern file name pattern, e.g. `main/*.json` or array of names
1373
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1374
+ */
1375
+ this.commands.subscribeFiles = (socket, id, pattern, callback) => {
1376
+ return this.#subscribeFiles(socket, id, pattern, callback);
1377
+ };
1378
+ /**
1379
+ * #DOCUMENTATION files
1380
+ * Unsubscribe from file changes in ioBroker DB
1381
+ *
1382
+ * @param socket Socket instance
1383
+ * @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
1384
+ * @param pattern file name pattern, e.g. `main/*.json` or array of names
1385
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1386
+ */
1387
+ this.commands.unsubscribeFiles = (socket, id, pattern, callback) => {
1388
+ return this._unsubscribeFiles(socket, id, pattern, callback);
1389
+ };
1390
+ /**
1391
+ * #DOCUMENTATION commands
1392
+ * Read all instances of the given adapter, or all instances of all adapters if adapterName is not defined
1393
+ *
1394
+ * @param socket Socket instance
1395
+ * @param adapterName adapter name, e.g. `history`. To get all instances of all adapters just place here "".
1396
+ * @param callback callback `(error: null | undefined | Error | string, instanceList?: ioBroker.InstanceObject[]) => void`
1397
+ */
1398
+ this.commands.getAdapterInstances = (socket, adapterName, callback) => {
1399
+ if (typeof callback === 'function') {
1400
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1401
+ let _adapterName = adapterName !== undefined && adapterName !== null ? adapterName : this.adapterName || '';
1402
+ if (_adapterName) {
1403
+ _adapterName += '.';
1404
+ }
1405
+ try {
1406
+ this.adapter.getObjectView('system', 'instance', {
1407
+ startkey: `system.adapter.${_adapterName}`,
1408
+ endkey: `system.adapter.${_adapterName}\u9999`,
1409
+ }, { user: socket._acl?.user }, (error, doc) => {
1410
+ if (error) {
1411
+ callback(error);
1412
+ }
1413
+ else {
1414
+ callback(null, doc?.rows
1415
+ .map(item => {
1416
+ const obj = item.value;
1417
+ if (obj.common) {
1418
+ delete obj.common.news;
1419
+ }
1420
+ this.fixAdminUI(obj);
1421
+ return obj;
1422
+ })
1423
+ .filter(obj => obj && (!adapterName || obj.common?.name === adapterName)));
1424
+ }
1425
+ });
1426
+ }
1427
+ catch (error) {
1428
+ this.adapter.log.error(`[getAdapterInstances] ERROR: ${error.toString()}`);
1429
+ SocketCommands._fixCallback(callback, error);
1430
+ }
1431
+ }
1432
+ }
1433
+ };
1434
+ }
1435
+ /** Init commands for states */
1436
+ _initCommandsStates() {
1437
+ /**
1438
+ * #DOCUMENTATION states
1439
+ * Get states by pattern of current adapter
1440
+ *
1441
+ * @param socket Socket instance
1442
+ * @param pattern optional pattern, like `system.adapter.*` or array of state IDs. If the pattern is omitted, you will get ALL states of current adapter
1443
+ * @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
1444
+ */
1445
+ this.commands.getStates = (socket, pattern, callback) => {
1446
+ if (this._checkPermissions(socket, 'getStates', callback, pattern)) {
1447
+ if (typeof pattern === 'function') {
1448
+ callback = pattern;
1449
+ pattern = undefined;
1450
+ }
1451
+ if (typeof callback === 'function') {
1452
+ try {
1453
+ this.adapter.getForeignStates(pattern || '*', { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1454
+ }
1455
+ catch (error) {
1456
+ this.adapter.log.error(`[getStates] ERROR: ${error.toString()}`);
1457
+ SocketCommands._fixCallback(callback, error);
1458
+ }
1459
+ }
1460
+ else {
1461
+ this.adapter.log.warn('[getStates] Invalid callback');
1462
+ }
1463
+ }
1464
+ };
1465
+ /**
1466
+ * #DOCUMENTATION states
1467
+ * Same as getStates
1468
+ *
1469
+ * @deprecated
1470
+ * @param socket Socket instance
1471
+ * @param pattern pattern like `system.adapter.*` or array of state IDs
1472
+ * @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
1473
+ */
1474
+ this.commands.getForeignStates = (socket, pattern, callback) => {
1475
+ this.adapter.log.info('Use getStates');
1476
+ this.commands.getStates(socket, pattern, callback);
1477
+ };
1478
+ /**
1479
+ * #DOCUMENTATION states
1480
+ * Get a state by ID
1481
+ *
1482
+ * @param socket Socket instance
1483
+ * @param id State ID, e.g. `system.adapter.admin.0.memRss`
1484
+ * @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
1485
+ */
1486
+ this.commands.getState = (socket, id, callback) => {
1487
+ if (this._checkPermissions(socket, 'getState', callback, id)) {
1488
+ if (typeof callback === 'function') {
1489
+ if (this.states && this.states[id]) {
1490
+ callback(null, this.states[id]);
1491
+ }
1492
+ else {
1493
+ try {
1494
+ void this.adapter
1495
+ .getForeignStateAsync(id, { user: socket._acl?.user })
1496
+ .then(state => SocketCommands._fixCallback(callback, null, [state]))
1497
+ .catch(error => {
1498
+ this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
1499
+ SocketCommands._fixCallback(callback, error);
1500
+ });
1501
+ }
1502
+ catch (error) {
1503
+ this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
1504
+ SocketCommands._fixCallback(callback, error);
1505
+ }
1506
+ }
1507
+ }
1508
+ else {
1509
+ this.adapter.log.warn('[getState] Invalid callback');
1510
+ }
1511
+ }
1512
+ };
1513
+ /**
1514
+ * #DOCUMENTATION states
1515
+ * Set a state by ID
1516
+ *
1517
+ * @param socket Socket instance
1518
+ * @param id State ID, e.g. `system.adapter.admin.0.memRss`
1519
+ * @param state State value or object, e.g. `{val: 123, ack: true}`
1520
+ * @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
1521
+ */
1522
+ this.commands.setState = (socket, id, state, callback) => {
1523
+ if (this._checkPermissions(socket, 'setState', callback, id)) {
1524
+ if (typeof state !== 'object') {
1525
+ state = { val: state };
1526
+ }
1527
+ // clear cache
1528
+ if (this.states?.[id]) {
1529
+ delete this.states[id];
1530
+ }
1531
+ try {
1532
+ this.adapter.setForeignState(id, state, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1533
+ }
1534
+ catch (error) {
1535
+ this.adapter.log.error(`[setState] ERROR: ${error.toString()}`);
1536
+ SocketCommands._fixCallback(callback, error);
1537
+ }
1538
+ }
1539
+ };
1540
+ /**
1541
+ * #DOCUMENTATION states
1542
+ * Get a binary state by ID
1543
+ *
1544
+ * @deprecated
1545
+ * @param _socket Socket instance (not used)
1546
+ * @param id State ID, e.g. `javascript.0.binary`
1547
+ * @param callback Callback `(error: null | undefined | Error | string, base64?: string) => void`
1548
+ */
1549
+ this.commands.getBinaryState = (_socket, id, callback) => {
1550
+ if (typeof callback === 'function') {
1551
+ this.adapter.log.warn(`getBinaryState is deprecated, but called for ${id}`);
1552
+ callback('This function is deprecated');
1553
+ }
1554
+ };
1555
+ /**
1556
+ * #DOCUMENTATION states
1557
+ * Set a binary state by ID
1558
+ *
1559
+ * @deprecated
1560
+ * @param _socket Socket instance
1561
+ * @param id State ID, e.g. `javascript.0.binary`
1562
+ * @param _base64 State value as base64 string. Binary states have no acknowledged flag.
1563
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1564
+ */
1565
+ this.commands.setBinaryState = (_socket, id, _base64, callback) => {
1566
+ if (typeof callback === 'function') {
1567
+ this.adapter.log.warn(`setBinaryState is deprecated, but called for ${id}`);
1568
+ callback('This function is deprecated');
1569
+ }
1570
+ };
1571
+ /**
1572
+ * #DOCUMENTATION states
1573
+ * Subscribe to state changes by pattern.
1574
+ * The events will come as 'stateChange' events to the socket.
1575
+ *
1576
+ * @param socket Socket instance
1577
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1578
+ * @param callback Callback `(error: string | null) => void`
1579
+ */
1580
+ this.commands.subscribe = (socket, pattern, callback) => {
1581
+ this.#subscribeStates(socket, pattern, callback);
1582
+ };
1583
+ /**
1584
+ * #DOCUMENTATION states
1585
+ * Subscribe to state changes by pattern. Same as `subscribe`.
1586
+ * The events will come as 'stateChange' events to the socket.
1587
+ *
1588
+ * @param socket Socket instance
1589
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1590
+ * @param callback Callback `(error: string | null) => void`
1591
+ */
1592
+ this.commands.subscribeStates = (socket, pattern, callback) => {
1593
+ this.#subscribeStates(socket, pattern, callback);
1594
+ };
1595
+ /**
1596
+ * #DOCUMENTATION states
1597
+ * Unsubscribe from state changes by pattern.
1598
+ *
1599
+ * @param socket Socket instance
1600
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1601
+ * @param callback Callback `(error: string | null) => void`
1602
+ */
1603
+ this.commands.unsubscribe = (socket, pattern, callback) => {
1604
+ this.#unsubscribeStates(socket, pattern, callback);
1605
+ };
1606
+ /**
1607
+ * #DOCUMENTATION states
1608
+ * Unsubscribe from state changes by pattern. Same as `unsubscribe`.
1609
+ * The events will come as 'stateChange' events to the socket.
1610
+ *
1611
+ * @param socket Socket instance
1612
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1613
+ * @param callback Callback `(error: string | null) => void`
1614
+ */
1615
+ this.commands.unsubscribeStates = (socket, pattern, callback) => {
1616
+ this.#unsubscribeStates(socket, pattern, callback);
1617
+ };
1618
+ }
1619
+ /** Init commands for objects */
1620
+ _initCommandsObjects() {
1621
+ /**
1622
+ * #DOCUMENTATION objects
1623
+ * Get one object.
1624
+ *
1625
+ * @param socket Socket instance
1626
+ * @param id Object ID
1627
+ * @param callback Callback `(error: string | null, obj?: ioBroker.Object) => void`
1628
+ */
1629
+ this.commands.getObject = (socket, id, callback) => {
1630
+ if (this._checkPermissions(socket, 'getObject', callback, id)) {
1631
+ try {
1632
+ void this.adapter.getForeignObject(id, { user: socket._acl?.user }, (error, obj) => {
1633
+ // overload language from current instance
1634
+ if (this.context.language && id === 'system.config' && obj?.common) {
1635
+ obj.common.language = this.context.language;
1636
+ }
1637
+ SocketCommands._fixCallback(callback, error, obj);
1638
+ });
1639
+ }
1640
+ catch (error) {
1641
+ this.adapter.log.error(`[getObject] ERROR: ${error.toString()}`);
1642
+ SocketCommands._fixCallback(callback, error);
1643
+ }
1644
+ }
1645
+ };
1646
+ /**
1647
+ * #DOCUMENTATION objects
1648
+ * Get all objects that are relevant for web: all states and enums with rooms.
1649
+ * This is non-admin version of "all objects" and will be overloaded in admin
1650
+ *
1651
+ * @param socket Socket instance
1652
+ * @param list Optional list of IDs
1653
+ * @param callback Callback `(error: string | null, objs?: Record<string, ioBroker.Object>) => void`
1654
+ */
1655
+ this.commands.getObjects = (socket, list, callback) => {
1656
+ if (typeof list === 'function') {
1657
+ callback = list;
1658
+ list = null;
1659
+ }
1660
+ if (list?.length) {
1661
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1662
+ if (typeof callback === 'function') {
1663
+ try {
1664
+ this.adapter.getForeignObjects(list, { user: socket._acl?.user }, (error, objs) => SocketCommands._fixCallback(callback, error, objs));
1665
+ }
1666
+ catch (error) {
1667
+ this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
1668
+ SocketCommands._fixCallback(callback, error);
1669
+ }
1670
+ }
1671
+ else {
1672
+ this.adapter.log.warn('[getObjects] Invalid callback');
1673
+ }
1674
+ }
1675
+ }
1676
+ else if (this._checkPermissions(socket, 'getObjects', callback)) {
1677
+ try {
1678
+ if (typeof callback === 'function') {
1679
+ this.adapter.getForeignObjects('*', 'state', 'rooms', { user: socket._acl?.user }, async (error, states) => {
1680
+ const result = {};
1681
+ try {
1682
+ const channels = await this.adapter.getForeignObjectsAsync('*', 'channel', null, {
1683
+ user: socket._acl?.user,
1684
+ });
1685
+ const devices = await this.adapter.getForeignObjectsAsync('*', 'device', null, {
1686
+ user: socket._acl?.user,
1687
+ });
1688
+ const enums = await this.adapter.getForeignObjectsAsync('*', 'enum', null, {
1689
+ user: socket._acl?.user,
1690
+ });
1691
+ const config = await this.adapter.getForeignObjectAsync('system.config', {
1692
+ user: socket._acl?.user,
1693
+ });
1694
+ Object.assign(result, states, channels, devices, enums);
1695
+ if (config) {
1696
+ result[config._id] = config;
1697
+ }
1698
+ }
1699
+ catch (e) {
1700
+ this.adapter.log.error(`[getObjects] ERROR: ${e.toString()}`);
1701
+ }
1702
+ // overload language
1703
+ if (this.context.language && result['system.config']?.common) {
1704
+ result['system.config'].common.language = this.context.language;
1705
+ }
1706
+ SocketCommands._fixCallback(callback, error, result);
1707
+ });
1708
+ }
1709
+ else {
1710
+ this.adapter.log.warn('[getObjects] Invalid callback');
1711
+ }
1712
+ }
1713
+ catch (error) {
1714
+ this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
1715
+ SocketCommands._fixCallback(callback, error);
1716
+ }
1717
+ }
1718
+ };
1719
+ /**
1720
+ * #DOCUMENTATION objects
1721
+ * Subscribe to object changes by pattern. The events will come as 'objectChange' events to the socket.
1722
+ *
1723
+ * @param socket Socket instance
1724
+ * @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1725
+ * @param callback Callback `(error: string | null) => void`
1726
+ */
1727
+ this.commands.subscribeObjects = (socket, pattern, callback) => {
1728
+ if (this._checkPermissions(socket, 'subscribeObjects', callback, pattern)) {
1729
+ try {
1730
+ if (Array.isArray(pattern)) {
1731
+ for (let p = 0; p < pattern.length; p++) {
1732
+ this.subscribe(socket, 'objectChange', pattern[p]);
1733
+ }
1734
+ }
1735
+ else {
1736
+ this.subscribe(socket, 'objectChange', pattern);
1737
+ }
1738
+ if (typeof callback === 'function') {
1739
+ setImmediate(callback, null);
1740
+ }
1741
+ }
1742
+ catch (error) {
1743
+ if (typeof callback === 'function') {
1744
+ setImmediate(callback, error);
1745
+ }
1746
+ }
1747
+ }
1748
+ };
1749
+ /**
1750
+ * #DOCUMENTATION objects
1751
+ * Unsubscribe from object changes by pattern.
1752
+ *
1753
+ * @param socket Socket instance
1754
+ * @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1755
+ * @param callback Callback `(error: string | null) => void`
1756
+ */
1757
+ this.commands.unsubscribeObjects = (socket, pattern, callback) => {
1758
+ if (this._checkPermissions(socket, 'unsubscribeObjects', callback, pattern)) {
1759
+ try {
1760
+ if (Array.isArray(pattern)) {
1761
+ for (let p = 0; p < pattern.length; p++) {
1762
+ this.unsubscribe(socket, 'objectChange', pattern[p]);
1763
+ }
1764
+ }
1765
+ else {
1766
+ this.unsubscribe(socket, 'objectChange', pattern);
1767
+ }
1768
+ if (typeof callback === 'function') {
1769
+ setImmediate(callback, null);
1770
+ }
1771
+ }
1772
+ catch (error) {
1773
+ if (typeof callback === 'function') {
1774
+ setImmediate(callback, error);
1775
+ }
1776
+ }
1777
+ }
1778
+ };
1779
+ /**
1780
+ * #DOCUMENTATION objects
1781
+ * Get a view of objects. Make a query to the object database.
1782
+ *
1783
+ * @param socket Socket instance
1784
+ * @param design Design name, e.g., 'system' or other designs like `custom`, but it must exist object `_design/custom`. To 99,9% use `system`.
1785
+ * @param search Search name, object type, like `state`, `instance`, `adapter`, `host`, ...
1786
+ * @param params Parameters for the query, e.g., `{startkey: 'system.adapter.', endkey: 'system.adapter.\u9999', depth?: number}`
1787
+ * @param params.startkey Start key
1788
+ * @param params.endkey End key. If not provided the `startkey + '\u9999'` will be taken
1789
+ * @param params.depth If the depth is provided, only first level of objects will be returned for smaller size
1790
+ * @param callback Callback `(error: string | null, result?: { rows: Array<GetObjectViewItem>) => void`
1791
+ */
1792
+ this.commands.getObjectView = (socket, design, search, params, callback) => {
1793
+ if (typeof callback === 'function') {
1794
+ if (this._checkPermissions(socket, 'getObjectView', callback, search)) {
1795
+ try {
1796
+ if (params?.depth) {
1797
+ // To save the bandwidth, the request can define root and depth. Default is depth 1.
1798
+ this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, (err, result) => {
1799
+ if (result?.rows?.length && result.rows[0].value?._id) {
1800
+ const rows = [];
1801
+ // filter rows
1802
+ const depth = params.depth || 1;
1803
+ let root = params.startkey || '';
1804
+ let rootWithoutDot;
1805
+ if (root) {
1806
+ if (!root.endsWith('.')) {
1807
+ root += '.';
1808
+ rootWithoutDot = root;
1809
+ }
1810
+ else {
1811
+ rootWithoutDot = root.substring(0, root.length - 1);
1812
+ }
1813
+ }
1814
+ else {
1815
+ rootWithoutDot = '';
1816
+ }
1817
+ const rootDepth = root.split('.').length;
1818
+ const virtualObjects = {};
1819
+ for (let r = 0; r < result.rows.length; r++) {
1820
+ const _id = result.rows[r].value._id;
1821
+ if (!root || _id.startsWith(root) || _id === rootWithoutDot) {
1822
+ const parts = _id.split('.');
1823
+ if (parts.length - rootDepth <= depth) {
1824
+ rows.push(result.rows[r]);
1825
+ }
1826
+ else {
1827
+ // create virtual objects to show that there are more objects
1828
+ for (let d = depth; d < parts.length - rootDepth; d++) {
1829
+ const id = parts.slice(0, rootDepth + d).join('.');
1830
+ if (!virtualObjects[id]) {
1831
+ virtualObjects[id] = {
1832
+ id,
1833
+ value: {
1834
+ _id: id,
1835
+ common: {},
1836
+ native: {},
1837
+ type: 'folder',
1838
+ virtual: true,
1839
+ hasChildren: 1,
1840
+ },
1841
+ };
1842
+ rows.push(virtualObjects[id]);
1843
+ }
1844
+ else {
1845
+ virtualObjects[id].value.hasChildren++;
1846
+ }
1847
+ }
1848
+ }
1849
+ }
1850
+ }
1851
+ result.rows = rows;
1852
+ }
1853
+ callback(err, result);
1854
+ });
1855
+ }
1856
+ else {
1857
+ this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, callback);
1858
+ }
1859
+ }
1860
+ catch (error) {
1861
+ this.adapter.log.error(`[getObjectView] ERROR: ${error.toString()}`);
1862
+ SocketCommands._fixCallback(callback, error);
1863
+ }
1864
+ }
1865
+ }
1866
+ else {
1867
+ this.adapter.log.error('Callback is not a function');
1868
+ }
1869
+ };
1870
+ /**
1871
+ * #DOCUMENTATION objects
1872
+ * Set an object.
1873
+ *
1874
+ * @param socket Socket instance
1875
+ * @param id Object ID
1876
+ * @param obj Object to set
1877
+ * @param callback Callback `(error: string | null) => void`
1878
+ */
1879
+ this.commands.setObject = (socket, id, obj, callback) => {
1880
+ if (this._checkPermissions(socket, 'setObject', callback, id)) {
1881
+ try {
1882
+ void this.adapter.setForeignObject(id, obj, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1883
+ }
1884
+ catch (error) {
1885
+ this.adapter.log.error(`[setObject] ERROR: ${error.toString()}`);
1886
+ SocketCommands._fixCallback(callback, error);
1887
+ }
1888
+ }
1889
+ };
1890
+ // this function is overloaded in admin
1891
+ /**
1892
+ * #DOCUMENTATION objects
1893
+ * Delete an object. Only deletion of flot and fullcalendar objects is allowed
1894
+ *
1895
+ * @param socket Socket instance
1896
+ * @param id Object ID, like 'flot.0.myChart'
1897
+ * @param _options Options for deletion. Ignored
1898
+ * @param callback Callback `(error: string | null) => void`
1899
+ */
1900
+ this.commands.delObject = (socket, id, _options, callback) => {
1901
+ if (id.startsWith('flot.') || id.startsWith('fullcalendar.')) {
1902
+ if (this._checkPermissions(socket, 'delObject', callback, id)) {
1903
+ try {
1904
+ this.adapter.delForeignObject(id, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1905
+ }
1906
+ catch (error) {
1907
+ this.adapter.log.error(`[delObject] ERROR: ${error.toString()}`);
1908
+ SocketCommands._fixCallback(callback, error);
1909
+ }
1910
+ }
1911
+ }
1912
+ else {
1913
+ SocketCommands._fixCallback(callback, SocketCommands.ERROR_PERMISSION);
1914
+ }
1915
+ };
1916
+ /**
1917
+ * #DOCUMENTATION commands
1918
+ * Client subscribes to specific instance's messages.
1919
+ * Client informs specific instance about subscription on its messages.
1920
+ * After subscription, the socket will receive "im" messages from desired instance
1921
+ * The target instance MUST acknowledge the subscription and return result
1922
+ *
1923
+ * @param socket Socket instance
1924
+ * @param targetInstance Instance name, e.g., 'cameras.0'
1925
+ * @param messageType Message type, e.g., 'startRecording/cam1'
1926
+ * @param data Optional data object, e.g., {width: 640, height: 480}
1927
+ * @param callback Callback `(error: string | null, result?:{ accepted: boolean; heartbeat?: number; error?: string; }) => void`
1928
+ */
1929
+ this.commands.clientSubscribe = (socket, targetInstance, messageType, data, callback) => {
1930
+ if (typeof data === 'function') {
1931
+ callback = data;
1932
+ data = null;
1933
+ }
1934
+ if (!targetInstance.startsWith('system.adapter.')) {
1935
+ targetInstance = `system.adapter.${targetInstance}`;
1936
+ }
1937
+ const sid = socket.id;
1938
+ // GUI subscribes for messages from targetInstance
1939
+ this.#clientSubscribes[sid] = this.#clientSubscribes[sid] || {};
1940
+ this.#clientSubscribes[sid][targetInstance] = this.#clientSubscribes[sid][targetInstance] || [];
1941
+ if (!this.#clientSubscribes[sid][targetInstance].includes(messageType)) {
1942
+ this.#clientSubscribes[sid][targetInstance].push(messageType);
1943
+ }
1944
+ // inform instance about new subscription
1945
+ this.adapter.sendTo(targetInstance, 'clientSubscribe', { type: messageType, sid, data }, result => SocketCommands._fixCallback(callback, null, result));
1946
+ };
1947
+ /**
1948
+ * #DOCUMENTATION commands
1949
+ * Client unsubscribes from specific instance's messages.
1950
+ * The target instance MUST NOT acknowledge the un-subscription
1951
+ *
1952
+ * @param socket Socket instance
1953
+ * @param targetInstance Instance name, e.g., 'cameras.0'
1954
+ * @param messageType Message type, e.g., 'startRecording/cam1'
1955
+ * @param callback Callback `(error: string | null) => void`
1956
+ */
1957
+ this.commands.clientUnsubscribe = (socket, targetInstance, messageType, callback) => {
1958
+ const sid = socket.id;
1959
+ if (!targetInstance.startsWith('system.adapter.')) {
1960
+ targetInstance = `system.adapter.${targetInstance}`;
1961
+ }
1962
+ // GUI unsubscribes for messages from targetInstance
1963
+ if (this.#clientSubscribes[sid] && this.#clientSubscribes[sid][targetInstance]) {
1964
+ const pos = this.#clientSubscribes[sid][targetInstance].indexOf(messageType);
1965
+ if (pos !== -1) {
1966
+ this.#clientSubscribes[sid][targetInstance].splice(pos, 1);
1967
+ // inform instance about unsubscription
1968
+ this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
1969
+ type: [messageType],
1970
+ sid,
1971
+ reason: 'client',
1972
+ });
1973
+ SocketCommands._fixCallback(callback, null, true);
1974
+ return;
1975
+ }
1976
+ }
1977
+ SocketCommands._fixCallback(callback, null, false);
1978
+ };
1979
+ }
1980
+ /** Init all commands: common, objects, states, files */
1981
+ #initCommands() {
1982
+ this._initCommandsCommon();
1983
+ this._initCommandsObjects();
1984
+ this._initCommandsStates();
1985
+ this._initCommandsFiles();
1986
+ }
1987
+ #informAboutDisconnect(socketId) {
1988
+ // say to all instances, that this socket was disconnected
1989
+ if (this.#clientSubscribes[socketId]) {
1990
+ Object.keys(this.#clientSubscribes[socketId]).forEach(targetInstance => {
1991
+ this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
1992
+ type: this.#clientSubscribes[socketId][targetInstance],
1993
+ sid: socketId,
1994
+ reason: 'disconnect',
1995
+ });
1996
+ });
1997
+ delete this.#clientSubscribes[socketId];
1998
+ }
1999
+ }
2000
+ applyCommands(socket) {
2001
+ Object.keys(this.commands).forEach(command => socket.on(command, (...args) => {
2002
+ if (this.#updateSession(socket)) {
2003
+ this.commands[command](socket, ...args);
2004
+ }
2005
+ }));
2006
+ }
2007
+ disableEventThreshold() {
2008
+ // could be overloaded
2009
+ }
2010
+ destroy() {
2011
+ // could be overloaded
2012
+ }
2013
+ }
2014
+ exports.SocketCommands = SocketCommands;
2015
+ //# sourceMappingURL=socketCommands.js.map