@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,2024 @@
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 = null;
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
+ // Logout user
912
+ // @param {function} callback - function (error)
913
+ if (socket.id) {
914
+ void this.adapter.destroySession(socket.id, callback);
915
+ }
916
+ else if (callback) {
917
+ callback(new Error('No session'));
918
+ }
919
+ };
920
+ /**
921
+ * #DOCUMENTATION commands
922
+ * List commands and permissions
923
+ *
924
+ * @param _socket Socket instance (not used)
925
+ * @param callback callback `(permissions: Record<string, { type: 'object' | 'state' | 'users' | 'other' | 'file' | ''; operation: SocketOperation }>) => void`
926
+ */
927
+ this.commands.listPermissions = (_socket, callback) => {
928
+ if (typeof callback === 'function') {
929
+ callback(SocketCommands.COMMANDS_PERMISSIONS);
930
+ }
931
+ else {
932
+ this.adapter.log.warn('[listPermissions] Invalid callback');
933
+ }
934
+ };
935
+ /**
936
+ * #DOCUMENTATION commands
937
+ * Get user permissions
938
+ *
939
+ * @param socket Socket instance
940
+ * @param callback callback `(error: string | null | undefined, userPermissions?: SocketACL | null) => void`
941
+ */
942
+ this.commands.getUserPermissions = (socket, callback) => {
943
+ // Get user permissions
944
+ // @param {function} callback - `function (error, permissions)`
945
+ if (this._checkPermissions(socket, 'getUserPermissions', callback)) {
946
+ if (typeof callback === 'function') {
947
+ callback(null, socket._acl);
948
+ }
949
+ else {
950
+ this.adapter.log.warn('[getUserPermissions] Invalid callback');
951
+ }
952
+ }
953
+ };
954
+ /**
955
+ * #DOCUMENTATION commands
956
+ * Get the adapter version. Not the socket-classes version!
957
+ *
958
+ * @param socket Socket instance
959
+ * @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
960
+ */
961
+ this.commands.getVersion = (socket, callback) => {
962
+ // Get the adapter version. Not the socket-classes version!
963
+ // @param {function} callback - `function (error, adapterVersion, adapterName)`
964
+ if (this._checkPermissions(socket, 'getVersion', callback)) {
965
+ if (typeof callback === 'function') {
966
+ callback(null, this.adapter.version, this.adapter.name);
967
+ }
968
+ else {
969
+ this.adapter.log.warn('[getVersion] Invalid callback');
970
+ }
971
+ }
972
+ };
973
+ /**
974
+ * #DOCUMENTATION commands
975
+ * Get adapter name: "iobroker.ws", "iobroker.socketio", "iobroker.web", "iobroker.admin"
976
+ *
977
+ * @param socket Socket instance
978
+ * @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
979
+ */
980
+ this.commands.getAdapterName = (socket, callback) => {
981
+ // Get adapter name. Not the socket-classes version!
982
+ // @param {function} callback - `function (error, adapterVersion)`
983
+ if (this._checkPermissions(socket, 'getAdapterName', callback)) {
984
+ if (typeof callback === 'function') {
985
+ callback(null, this.adapter.name || 'unknown');
986
+ }
987
+ else {
988
+ this.adapter.log.warn('[getAdapterName] Invalid callback');
989
+ }
990
+ }
991
+ };
992
+ }
993
+ /** Init commands for files */
994
+ _initCommandsFiles() {
995
+ /**
996
+ * #DOCUMENTATION files
997
+ * Read a file from ioBroker DB
998
+ *
999
+ * @param socket Socket instance
1000
+ * @param adapter instance name, e.g. `vis.0`
1001
+ * @param fileName file name, e.g. `main/vis-views.json`
1002
+ * @param callback Callback `(error: null | undefined | Error | string, data: Buffer | string, mimeType: string) => void`
1003
+ */
1004
+ this.commands.readFile = (socket, adapter, fileName, callback) => {
1005
+ if (this._checkPermissions(socket, 'readFile', callback, fileName)) {
1006
+ try {
1007
+ this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1008
+ }
1009
+ catch (error) {
1010
+ this.adapter.log.error(`[readFile] ERROR: ${error.toString()}`);
1011
+ SocketCommands._fixCallback(callback, error);
1012
+ }
1013
+ }
1014
+ };
1015
+ /**
1016
+ * #DOCUMENTATION files
1017
+ * Read a file from ioBroker DB as base64 string
1018
+ *
1019
+ * @param socket Socket instance
1020
+ * @param adapter instance name, e.g. `vis.0`
1021
+ * @param fileName file name, e.g. `main/vis-views.json`
1022
+ * @param callback Callback `(error: null | undefined | Error | string, base64: string, mimeType: string) => void`
1023
+ */
1024
+ this.commands.readFile64 = (socket, adapter, fileName, callback) => {
1025
+ if (this._checkPermissions(socket, 'readFile64', callback, fileName)) {
1026
+ try {
1027
+ this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, buffer, type) => {
1028
+ let data64;
1029
+ if (buffer) {
1030
+ try {
1031
+ if (type === 'application/json' ||
1032
+ type === 'application/json5' ||
1033
+ fileName.toLowerCase().endsWith('.json5')) {
1034
+ data64 = Buffer.from(encodeURIComponent(buffer)).toString('base64');
1035
+ }
1036
+ else {
1037
+ if (typeof buffer === 'string') {
1038
+ data64 = Buffer.from(buffer).toString('base64');
1039
+ }
1040
+ else {
1041
+ data64 = buffer.toString('base64');
1042
+ }
1043
+ }
1044
+ }
1045
+ catch (error) {
1046
+ this.adapter.log.error(`[readFile64] Cannot convert data: ${error.toString()}`);
1047
+ }
1048
+ }
1049
+ // Convert buffer to base 64
1050
+ if (typeof callback === 'function') {
1051
+ callback(error, data64 || '', type);
1052
+ }
1053
+ else {
1054
+ this.adapter.log.warn('[readFile64] Invalid callback');
1055
+ }
1056
+ });
1057
+ }
1058
+ catch (error) {
1059
+ this.adapter.log.error(`[readFile64] ERROR: ${error.toString()}`);
1060
+ SocketCommands._fixCallback(callback, error);
1061
+ }
1062
+ }
1063
+ };
1064
+ /**
1065
+ * #DOCUMENTATION files
1066
+ * Write a file into ioBroker DB as base64 string
1067
+ *
1068
+ * @param socket Socket instance
1069
+ * @param adapter instance name, e.g. `vis.0`
1070
+ * @param fileName file name, e.g. `main/vis-views.json`
1071
+ * @param data64 file content as base64 string
1072
+ * @param options optional `{mode: 0x0644}`
1073
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1074
+ */
1075
+ this.commands.writeFile64 = (socket, adapter, fileName, data64, options, callback) => {
1076
+ let _options;
1077
+ if (typeof options === 'function') {
1078
+ callback = options;
1079
+ _options = { user: socket._acl?.user };
1080
+ }
1081
+ else if (!options || options.mode === undefined) {
1082
+ _options = { user: socket._acl?.user };
1083
+ }
1084
+ else {
1085
+ _options = { user: socket._acl?.user, mode: options.mode };
1086
+ }
1087
+ if (this._checkPermissions(socket, 'writeFile64', callback, fileName)) {
1088
+ if (!data64) {
1089
+ return SocketCommands._fixCallback(callback, 'No data provided');
1090
+ }
1091
+ // Convert base 64 to buffer
1092
+ try {
1093
+ const buffer = Buffer.from(data64, 'base64');
1094
+ this.adapter.writeFile(adapter, fileName, buffer, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1095
+ }
1096
+ catch (error) {
1097
+ this.adapter.log.error(`[writeFile64] Cannot convert data: ${error.toString()}`);
1098
+ SocketCommands._fixCallback(callback, `Cannot convert data: ${error.toString()}`);
1099
+ }
1100
+ }
1101
+ };
1102
+ /**
1103
+ * #DOCUMENTATION files
1104
+ * Write a file into ioBroker DB as text
1105
+ *
1106
+ * This function is overloaded in admin (because admin accepts only base64)
1107
+ *
1108
+ * @deprecated
1109
+ * @param socket Socket instance
1110
+ * @param adapter instance name, e.g. `vis.0`
1111
+ * @param fileName file name, e.g. `main/vis-views.json`
1112
+ * @param data file content as text
1113
+ * @param options optional `{mode: 0x0644}`
1114
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1115
+ */
1116
+ this.commands.writeFile = (socket, adapter, fileName, data, options, callback) => {
1117
+ if (this._checkPermissions(socket, 'writeFile', callback, fileName)) {
1118
+ let _options;
1119
+ if (typeof options === 'function') {
1120
+ callback = options;
1121
+ _options = { user: socket._acl?.user };
1122
+ }
1123
+ else if (!options || options.mode === undefined) {
1124
+ _options = { user: socket._acl?.user };
1125
+ }
1126
+ else {
1127
+ _options = { user: socket._acl?.user, mode: options.mode };
1128
+ }
1129
+ this.adapter.log.debug('writeFile deprecated. Please use writeFile64');
1130
+ try {
1131
+ this.adapter.writeFile(adapter, fileName, data, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1132
+ }
1133
+ catch (error) {
1134
+ this.adapter.log.error(`[writeFile] ERROR: ${error.toString()}`);
1135
+ SocketCommands._fixCallback(callback, error);
1136
+ }
1137
+ }
1138
+ };
1139
+ /**
1140
+ * #DOCUMENTATION files
1141
+ * Delete file in ioBroker DB
1142
+ *
1143
+ * @param socket Socket instance
1144
+ * @param adapter instance name, e.g. `vis.0`
1145
+ * @param name file name, e.g. `main/vis-views.json`
1146
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1147
+ */
1148
+ this.commands.unlink = (socket, adapter, name, callback) => {
1149
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1150
+ try {
1151
+ this.#unlink(adapter, name, { user: socket._acl?.user })
1152
+ .then(() => SocketCommands._fixCallback(callback, undefined))
1153
+ .catch(error => SocketCommands._fixCallback(callback, error));
1154
+ }
1155
+ catch (error) {
1156
+ this.adapter.log.error(`[unlink] ERROR: ${error.toString()}`);
1157
+ SocketCommands._fixCallback(callback, error);
1158
+ }
1159
+ }
1160
+ };
1161
+ /**
1162
+ * #DOCUMENTATION files
1163
+ * Delete a file in ioBroker DB (same as "unlink", but only for files)
1164
+ *
1165
+ * @param socket Socket instance
1166
+ * @param adapter instance name, e.g. `vis.0`
1167
+ * @param name file name, e.g. `main/vis-views.json`
1168
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1169
+ */
1170
+ this.commands.deleteFile = (socket, adapter, name, callback) => {
1171
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1172
+ try {
1173
+ this.adapter.unlink(adapter, name, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1174
+ }
1175
+ catch (error) {
1176
+ this.adapter.log.error(`[deleteFile] ERROR: ${error.toString()}`);
1177
+ SocketCommands._fixCallback(callback, error);
1178
+ }
1179
+ }
1180
+ };
1181
+ /**
1182
+ * #DOCUMENTATION files
1183
+ * Delete folder in ioBroker DB (same as `unlink`, but only for folders)
1184
+ *
1185
+ * @param socket Socket instance
1186
+ * @param adapter instance name, e.g. `vis.0`
1187
+ * @param name folder name, e.g. `main`
1188
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1189
+ */
1190
+ this.commands.deleteFolder = (socket, adapter, name, callback) => {
1191
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
1192
+ try {
1193
+ this.#unlink(adapter, name, { user: socket._acl?.user })
1194
+ .then(() => SocketCommands._fixCallback(callback, null))
1195
+ .catch(error => SocketCommands._fixCallback(callback, error));
1196
+ }
1197
+ catch (error) {
1198
+ this.adapter.log.error(`[deleteFolder] ERROR: ${error.toString()}`);
1199
+ SocketCommands._fixCallback(callback, error);
1200
+ }
1201
+ }
1202
+ };
1203
+ /**
1204
+ * #DOCUMENTATION files
1205
+ * Rename a file in ioBroker DB
1206
+ *
1207
+ * @param socket Socket instance
1208
+ * @param adapter instance name, e.g. `vis.0`
1209
+ * @param oldName current file name, e.g. `main/vis-views.json`
1210
+ * @param newName new file name, e.g. `main/vis-views-new.json`
1211
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1212
+ */
1213
+ this.commands.renameFile = (socket, adapter, oldName, newName, callback) => {
1214
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
1215
+ try {
1216
+ this.adapter.rename(adapter, oldName, newName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1217
+ }
1218
+ catch (error) {
1219
+ this.adapter.log.error(`[renameFile] ERROR: ${error.toString()}`);
1220
+ SocketCommands._fixCallback(callback, error);
1221
+ }
1222
+ }
1223
+ };
1224
+ /**
1225
+ * #DOCUMENTATION files
1226
+ * Rename file or folder in ioBroker DB
1227
+ *
1228
+ * @param socket Socket instance
1229
+ * @param adapter instance name, e.g. `vis.0`
1230
+ * @param oldName current file name, e.g. `main/vis-views.json`
1231
+ * @param newName new file name, e.g. `main/vis-views-new.json`
1232
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1233
+ */
1234
+ this.commands.rename = (socket, adapter, oldName, newName, callback) => {
1235
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
1236
+ try {
1237
+ this.#rename(adapter, oldName, newName, { user: socket._acl?.user })
1238
+ .then(() => SocketCommands._fixCallback(callback, undefined))
1239
+ .catch(error => SocketCommands._fixCallback(callback, error));
1240
+ }
1241
+ catch (error) {
1242
+ this.adapter.log.error(`[rename] ERROR: ${error.toString()}`);
1243
+ SocketCommands._fixCallback(callback, error);
1244
+ }
1245
+ }
1246
+ };
1247
+ /**
1248
+ * #DOCUMENTATION files
1249
+ * Create a folder in ioBroker DB
1250
+ *
1251
+ * @param socket Socket instance
1252
+ * @param adapter instance name, e.g. `vis.0`
1253
+ * @param dirName desired folder name, e.g. `main`
1254
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1255
+ */
1256
+ this.commands.mkdir = (socket, adapter, dirName, callback) => {
1257
+ if (this._checkPermissions(socket, 'mkdir', callback, dirName)) {
1258
+ try {
1259
+ this.adapter.mkdir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1260
+ }
1261
+ catch (error) {
1262
+ this.adapter.log.error(`[mkdir] ERROR: ${error.toString()}`);
1263
+ SocketCommands._fixCallback(callback, error);
1264
+ }
1265
+ }
1266
+ };
1267
+ /**
1268
+ * #DOCUMENTATION files
1269
+ * Read content of folder in ioBroker DB
1270
+ *
1271
+ * @param socket Socket instance
1272
+ * @param adapter instance name, e.g. `vis.0`
1273
+ * @param dirName folder name, e.g. `main`
1274
+ * @param options for future use
1275
+ * @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`
1276
+ */
1277
+ this.commands.readDir = (socket, adapter, dirName, options, callback) => {
1278
+ if (typeof options === 'function') {
1279
+ callback = options;
1280
+ }
1281
+ if (this._checkPermissions(socket, 'readDir', callback, dirName)) {
1282
+ try {
1283
+ this.adapter.readDir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1284
+ }
1285
+ catch (error) {
1286
+ this.adapter.log.error(`[readDir] ERROR: ${error.toString()}`);
1287
+ SocketCommands._fixCallback(callback, error);
1288
+ }
1289
+ }
1290
+ };
1291
+ /**
1292
+ * #DOCUMENTATION files
1293
+ * Change a file mode in ioBroker DB
1294
+ *
1295
+ * @param socket Socket instance
1296
+ * @param adapter instance name, e.g. `vis.0`
1297
+ * @param fileName file name, e.g. `main/vis-views.json`
1298
+ * @param options options `{mode: 0x644}`
1299
+ * @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`
1300
+ * @param callback Callback `(error: string | Error | null | undefined) => void`
1301
+ */
1302
+ this.commands.chmodFile = (socket, adapter, fileName, options, callback) => {
1303
+ let _options;
1304
+ if (options?.mode !== undefined) {
1305
+ _options = { user: socket._acl?.user, mode: options.mode };
1306
+ }
1307
+ else {
1308
+ this.adapter.log.error(`[chownFile] ERROR: no options`);
1309
+ SocketCommands._fixCallback(callback, 'no options');
1310
+ return;
1311
+ }
1312
+ if (this._checkPermissions(socket, 'chmodFile', callback, fileName)) {
1313
+ try {
1314
+ this.adapter.chmodFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1315
+ }
1316
+ catch (error) {
1317
+ this.adapter.log.error(`[chmodFile] ERROR: ${error.toString()}`);
1318
+ SocketCommands._fixCallback(callback, error);
1319
+ }
1320
+ }
1321
+ };
1322
+ /**
1323
+ * #DOCUMENTATION files
1324
+ * Change file owner in ioBroker DB
1325
+ *
1326
+ * @param socket Socket instance
1327
+ * @param adapter instance name, e.g. `vis.0`
1328
+ * @param fileName file name, e.g. `main/vis-views.json`
1329
+ * @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.
1330
+ * @param options.owner New owner, like 'system.user.user'
1331
+ * @param options.ownerGroup New owner group, like 'system.group.administrator' If ownerGroup is not defined, it will be taken from owner.
1332
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1333
+ */
1334
+ this.commands.chownFile = (socket, adapter, fileName, options, callback) => {
1335
+ let _options;
1336
+ if (options) {
1337
+ _options = { user: socket._acl?.user, owner: options.owner, ownerGroup: options.ownerGroup };
1338
+ }
1339
+ else {
1340
+ this.adapter.log.error(`[chownFile] ERROR: no options`);
1341
+ SocketCommands._fixCallback(callback, 'no options');
1342
+ return;
1343
+ }
1344
+ if (this._checkPermissions(socket, 'chownFile', callback, fileName)) {
1345
+ try {
1346
+ this.adapter.chownFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1347
+ }
1348
+ catch (error) {
1349
+ this.adapter.log.error(`[chownFile] ERROR: ${error.toString()}`);
1350
+ SocketCommands._fixCallback(callback, error);
1351
+ }
1352
+ }
1353
+ };
1354
+ /**
1355
+ * #DOCUMENTATION files
1356
+ * Check if the file or folder exists in ioBroker DB
1357
+ *
1358
+ * @param socket Socket instance
1359
+ * @param adapter instance name, e.g. `vis.0`
1360
+ * @param fileName file name, e.g. `main/vis-views.json`
1361
+ * @param callback Callback `(error: null | undefined | Error | string, exists?: boolean) => void`
1362
+ */
1363
+ this.commands.fileExists = (socket, adapter, fileName, callback) => {
1364
+ if (this._checkPermissions(socket, 'fileExists', callback, fileName)) {
1365
+ try {
1366
+ this.adapter.fileExists(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1367
+ }
1368
+ catch (error) {
1369
+ this.adapter.log.error(`[fileExists] ERROR: ${error.toString()}`);
1370
+ SocketCommands._fixCallback(callback, error);
1371
+ }
1372
+ }
1373
+ };
1374
+ /**
1375
+ * #DOCUMENTATION files
1376
+ * Subscribe to file changes in ioBroker DB
1377
+ *
1378
+ * @param socket Socket instance
1379
+ * @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
1380
+ * @param pattern file name pattern, e.g. `main/*.json` or array of names
1381
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1382
+ */
1383
+ this.commands.subscribeFiles = (socket, id, pattern, callback) => {
1384
+ return this.#subscribeFiles(socket, id, pattern, callback);
1385
+ };
1386
+ /**
1387
+ * #DOCUMENTATION files
1388
+ * Unsubscribe from file changes in ioBroker DB
1389
+ *
1390
+ * @param socket Socket instance
1391
+ * @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
1392
+ * @param pattern file name pattern, e.g. `main/*.json` or array of names
1393
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1394
+ */
1395
+ this.commands.unsubscribeFiles = (socket, id, pattern, callback) => {
1396
+ return this._unsubscribeFiles(socket, id, pattern, callback);
1397
+ };
1398
+ /**
1399
+ * #DOCUMENTATION commands
1400
+ * Read all instances of the given adapter, or all instances of all adapters if adapterName is not defined
1401
+ *
1402
+ * @param socket Socket instance
1403
+ * @param adapterName adapter name, e.g. `history`. To get all instances of all adapters just place here "".
1404
+ * @param callback callback `(error: null | undefined | Error | string, instanceList?: ioBroker.InstanceObject[]) => void`
1405
+ */
1406
+ this.commands.getAdapterInstances = (socket, adapterName, callback) => {
1407
+ if (typeof callback === 'function') {
1408
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1409
+ let _adapterName = adapterName !== undefined && adapterName !== null ? adapterName : this.adapterName || '';
1410
+ if (_adapterName) {
1411
+ _adapterName += '.';
1412
+ }
1413
+ try {
1414
+ this.adapter.getObjectView('system', 'instance', {
1415
+ startkey: `system.adapter.${_adapterName}`,
1416
+ endkey: `system.adapter.${_adapterName}\u9999`,
1417
+ }, { user: socket._acl?.user }, (error, doc) => {
1418
+ if (error) {
1419
+ callback(error);
1420
+ }
1421
+ else {
1422
+ callback(null, doc?.rows
1423
+ .map(item => {
1424
+ const obj = item.value;
1425
+ if (obj.common) {
1426
+ delete obj.common.news;
1427
+ }
1428
+ this.fixAdminUI(obj);
1429
+ return obj;
1430
+ })
1431
+ .filter(obj => obj && (!adapterName || obj.common?.name === adapterName)));
1432
+ }
1433
+ });
1434
+ }
1435
+ catch (error) {
1436
+ this.adapter.log.error(`[getAdapterInstances] ERROR: ${error.toString()}`);
1437
+ SocketCommands._fixCallback(callback, error);
1438
+ }
1439
+ }
1440
+ }
1441
+ };
1442
+ }
1443
+ /** Init commands for states */
1444
+ _initCommandsStates() {
1445
+ /**
1446
+ * #DOCUMENTATION states
1447
+ * Get states by pattern of current adapter
1448
+ *
1449
+ * @param socket Socket instance
1450
+ * @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
1451
+ * @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
1452
+ */
1453
+ this.commands.getStates = (socket, pattern, callback) => {
1454
+ if (this._checkPermissions(socket, 'getStates', callback, pattern)) {
1455
+ if (typeof pattern === 'function') {
1456
+ callback = pattern;
1457
+ pattern = undefined;
1458
+ }
1459
+ if (typeof callback === 'function') {
1460
+ try {
1461
+ this.adapter.getForeignStates(pattern || '*', { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1462
+ }
1463
+ catch (error) {
1464
+ this.adapter.log.error(`[getStates] ERROR: ${error.toString()}`);
1465
+ SocketCommands._fixCallback(callback, error);
1466
+ }
1467
+ }
1468
+ else {
1469
+ this.adapter.log.warn('[getStates] Invalid callback');
1470
+ }
1471
+ }
1472
+ };
1473
+ /**
1474
+ * #DOCUMENTATION states
1475
+ * Same as getStates
1476
+ *
1477
+ * @deprecated
1478
+ * @param socket Socket instance
1479
+ * @param pattern pattern like `system.adapter.*` or array of state IDs
1480
+ * @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
1481
+ */
1482
+ this.commands.getForeignStates = (socket, pattern, callback) => {
1483
+ this.adapter.log.info('Use getStates');
1484
+ this.commands.getStates(socket, pattern, callback);
1485
+ };
1486
+ /**
1487
+ * #DOCUMENTATION states
1488
+ * Get a state by ID
1489
+ *
1490
+ * @param socket Socket instance
1491
+ * @param id State ID, e.g. `system.adapter.admin.0.memRss`
1492
+ * @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
1493
+ */
1494
+ this.commands.getState = (socket, id, callback) => {
1495
+ if (this._checkPermissions(socket, 'getState', callback, id)) {
1496
+ if (typeof callback === 'function') {
1497
+ if (this.states && this.states[id]) {
1498
+ callback(null, this.states[id]);
1499
+ }
1500
+ else {
1501
+ try {
1502
+ void this.adapter
1503
+ .getForeignStateAsync(id, { user: socket._acl?.user })
1504
+ .then(state => SocketCommands._fixCallback(callback, null, [state]))
1505
+ .catch(error => {
1506
+ this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
1507
+ SocketCommands._fixCallback(callback, error);
1508
+ });
1509
+ }
1510
+ catch (error) {
1511
+ this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
1512
+ SocketCommands._fixCallback(callback, error);
1513
+ }
1514
+ }
1515
+ }
1516
+ else {
1517
+ this.adapter.log.warn('[getState] Invalid callback');
1518
+ }
1519
+ }
1520
+ };
1521
+ /**
1522
+ * #DOCUMENTATION states
1523
+ * Set a state by ID
1524
+ *
1525
+ * @param socket Socket instance
1526
+ * @param id State ID, e.g. `system.adapter.admin.0.memRss`
1527
+ * @param state State value or object, e.g. `{val: 123, ack: true}`
1528
+ * @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
1529
+ */
1530
+ this.commands.setState = (socket, id, state, callback) => {
1531
+ if (this._checkPermissions(socket, 'setState', callback, id)) {
1532
+ if (typeof state !== 'object') {
1533
+ state = { val: state };
1534
+ }
1535
+ // clear cache
1536
+ if (this.states?.[id]) {
1537
+ delete this.states[id];
1538
+ }
1539
+ try {
1540
+ this.adapter.setForeignState(id, state, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1541
+ }
1542
+ catch (error) {
1543
+ this.adapter.log.error(`[setState] ERROR: ${error.toString()}`);
1544
+ SocketCommands._fixCallback(callback, error);
1545
+ }
1546
+ }
1547
+ };
1548
+ /**
1549
+ * #DOCUMENTATION states
1550
+ * Get a binary state by ID
1551
+ *
1552
+ * @deprecated
1553
+ * @param _socket Socket instance (not used)
1554
+ * @param id State ID, e.g. `javascript.0.binary`
1555
+ * @param callback Callback `(error: null | undefined | Error | string, base64?: string) => void`
1556
+ */
1557
+ this.commands.getBinaryState = (_socket, id, callback) => {
1558
+ if (typeof callback === 'function') {
1559
+ this.adapter.log.warn(`getBinaryState is deprecated, but called for ${id}`);
1560
+ callback('This function is deprecated');
1561
+ }
1562
+ };
1563
+ /**
1564
+ * #DOCUMENTATION states
1565
+ * Set a binary state by ID
1566
+ *
1567
+ * @deprecated
1568
+ * @param _socket Socket instance
1569
+ * @param id State ID, e.g. `javascript.0.binary`
1570
+ * @param _base64 State value as base64 string. Binary states have no acknowledged flag.
1571
+ * @param callback Callback `(error: null | undefined | Error | string) => void`
1572
+ */
1573
+ this.commands.setBinaryState = (_socket, id, _base64, callback) => {
1574
+ if (typeof callback === 'function') {
1575
+ this.adapter.log.warn(`setBinaryState is deprecated, but called for ${id}`);
1576
+ callback('This function is deprecated');
1577
+ }
1578
+ };
1579
+ /**
1580
+ * #DOCUMENTATION states
1581
+ * Subscribe to state changes by pattern.
1582
+ * The events will come as 'stateChange' events to the socket.
1583
+ *
1584
+ * @param socket Socket instance
1585
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1586
+ * @param callback Callback `(error: string | null) => void`
1587
+ */
1588
+ this.commands.subscribe = (socket, pattern, callback) => {
1589
+ this.#subscribeStates(socket, pattern, callback);
1590
+ };
1591
+ /**
1592
+ * #DOCUMENTATION states
1593
+ * Subscribe to state changes by pattern. Same as `subscribe`.
1594
+ * The events will come as 'stateChange' events to the socket.
1595
+ *
1596
+ * @param socket Socket instance
1597
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1598
+ * @param callback Callback `(error: string | null) => void`
1599
+ */
1600
+ this.commands.subscribeStates = (socket, pattern, callback) => {
1601
+ this.#subscribeStates(socket, pattern, callback);
1602
+ };
1603
+ /**
1604
+ * #DOCUMENTATION states
1605
+ * Unsubscribe from state changes by pattern.
1606
+ *
1607
+ * @param socket Socket instance
1608
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1609
+ * @param callback Callback `(error: string | null) => void`
1610
+ */
1611
+ this.commands.unsubscribe = (socket, pattern, callback) => {
1612
+ this.#unsubscribeStates(socket, pattern, callback);
1613
+ };
1614
+ /**
1615
+ * #DOCUMENTATION states
1616
+ * Unsubscribe from state changes by pattern. Same as `unsubscribe`.
1617
+ * The events will come as 'stateChange' events to the socket.
1618
+ *
1619
+ * @param socket Socket instance
1620
+ * @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1621
+ * @param callback Callback `(error: string | null) => void`
1622
+ */
1623
+ this.commands.unsubscribeStates = (socket, pattern, callback) => {
1624
+ this.#unsubscribeStates(socket, pattern, callback);
1625
+ };
1626
+ }
1627
+ /** Init commands for objects */
1628
+ _initCommandsObjects() {
1629
+ /**
1630
+ * #DOCUMENTATION objects
1631
+ * Get one object.
1632
+ *
1633
+ * @param socket Socket instance
1634
+ * @param id Object ID
1635
+ * @param callback Callback `(error: string | null, obj?: ioBroker.Object) => void`
1636
+ */
1637
+ this.commands.getObject = (socket, id, callback) => {
1638
+ if (this._checkPermissions(socket, 'getObject', callback, id)) {
1639
+ try {
1640
+ void this.adapter.getForeignObject(id, { user: socket._acl?.user }, (error, obj) => {
1641
+ // overload language from current instance
1642
+ if (this.context.language && id === 'system.config' && obj?.common) {
1643
+ obj.common.language = this.context.language;
1644
+ }
1645
+ SocketCommands._fixCallback(callback, error, obj);
1646
+ });
1647
+ }
1648
+ catch (error) {
1649
+ this.adapter.log.error(`[getObject] ERROR: ${error.toString()}`);
1650
+ SocketCommands._fixCallback(callback, error);
1651
+ }
1652
+ }
1653
+ };
1654
+ /**
1655
+ * #DOCUMENTATION objects
1656
+ * Get all objects that are relevant for web: all states and enums with rooms.
1657
+ * This is non-admin version of "all objects" and will be overloaded in admin
1658
+ *
1659
+ * @param socket Socket instance
1660
+ * @param list Optional list of IDs
1661
+ * @param callback Callback `(error: string | null, objs?: Record<string, ioBroker.Object>) => void`
1662
+ */
1663
+ this.commands.getObjects = (socket, list, callback) => {
1664
+ if (typeof list === 'function') {
1665
+ callback = list;
1666
+ list = null;
1667
+ }
1668
+ if (list?.length) {
1669
+ if (this._checkPermissions(socket, 'getObject', callback)) {
1670
+ if (typeof callback === 'function') {
1671
+ try {
1672
+ this.adapter.getForeignObjects(list, { user: socket._acl?.user }, (error, objs) => SocketCommands._fixCallback(callback, error, objs));
1673
+ }
1674
+ catch (error) {
1675
+ this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
1676
+ SocketCommands._fixCallback(callback, error);
1677
+ }
1678
+ }
1679
+ else {
1680
+ this.adapter.log.warn('[getObjects] Invalid callback');
1681
+ }
1682
+ }
1683
+ }
1684
+ else if (this._checkPermissions(socket, 'getObjects', callback)) {
1685
+ try {
1686
+ if (typeof callback === 'function') {
1687
+ this.adapter.getForeignObjects('*', 'state', 'rooms', { user: socket._acl?.user }, async (error, states) => {
1688
+ const result = {};
1689
+ try {
1690
+ const channels = await this.adapter.getForeignObjectsAsync('*', 'channel', null, {
1691
+ user: socket._acl?.user,
1692
+ });
1693
+ const devices = await this.adapter.getForeignObjectsAsync('*', 'device', null, {
1694
+ user: socket._acl?.user,
1695
+ });
1696
+ const enums = await this.adapter.getForeignObjectsAsync('*', 'enum', null, {
1697
+ user: socket._acl?.user,
1698
+ });
1699
+ const config = await this.adapter.getForeignObjectAsync('system.config', {
1700
+ user: socket._acl?.user,
1701
+ });
1702
+ Object.assign(result, states, channels, devices, enums);
1703
+ if (config) {
1704
+ result[config._id] = config;
1705
+ }
1706
+ }
1707
+ catch (e) {
1708
+ this.adapter.log.error(`[getObjects] ERROR: ${e.toString()}`);
1709
+ }
1710
+ // overload language
1711
+ if (this.context.language && result['system.config']?.common) {
1712
+ result['system.config'].common.language = this.context.language;
1713
+ }
1714
+ SocketCommands._fixCallback(callback, error, result);
1715
+ });
1716
+ }
1717
+ else {
1718
+ this.adapter.log.warn('[getObjects] Invalid callback');
1719
+ }
1720
+ }
1721
+ catch (error) {
1722
+ this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
1723
+ SocketCommands._fixCallback(callback, error);
1724
+ }
1725
+ }
1726
+ };
1727
+ /**
1728
+ * #DOCUMENTATION objects
1729
+ * Subscribe to object changes by pattern. The events will come as 'objectChange' events to the socket.
1730
+ *
1731
+ * @param socket Socket instance
1732
+ * @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1733
+ * @param callback Callback `(error: string | null) => void`
1734
+ */
1735
+ this.commands.subscribeObjects = (socket, pattern, callback) => {
1736
+ if (this._checkPermissions(socket, 'subscribeObjects', callback, pattern)) {
1737
+ try {
1738
+ if (Array.isArray(pattern)) {
1739
+ for (let p = 0; p < pattern.length; p++) {
1740
+ this.subscribe(socket, 'objectChange', pattern[p]);
1741
+ }
1742
+ }
1743
+ else {
1744
+ this.subscribe(socket, 'objectChange', pattern);
1745
+ }
1746
+ if (typeof callback === 'function') {
1747
+ setImmediate(callback, null);
1748
+ }
1749
+ }
1750
+ catch (error) {
1751
+ if (typeof callback === 'function') {
1752
+ setImmediate(callback, error);
1753
+ }
1754
+ }
1755
+ }
1756
+ };
1757
+ /**
1758
+ * #DOCUMENTATION objects
1759
+ * Unsubscribe from object changes by pattern.
1760
+ *
1761
+ * @param socket Socket instance
1762
+ * @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
1763
+ * @param callback Callback `(error: string | null) => void`
1764
+ */
1765
+ this.commands.unsubscribeObjects = (socket, pattern, callback) => {
1766
+ if (this._checkPermissions(socket, 'unsubscribeObjects', callback, pattern)) {
1767
+ try {
1768
+ if (Array.isArray(pattern)) {
1769
+ for (let p = 0; p < pattern.length; p++) {
1770
+ this.unsubscribe(socket, 'objectChange', pattern[p]);
1771
+ }
1772
+ }
1773
+ else {
1774
+ this.unsubscribe(socket, 'objectChange', pattern);
1775
+ }
1776
+ if (typeof callback === 'function') {
1777
+ setImmediate(callback, null);
1778
+ }
1779
+ }
1780
+ catch (error) {
1781
+ if (typeof callback === 'function') {
1782
+ setImmediate(callback, error);
1783
+ }
1784
+ }
1785
+ }
1786
+ };
1787
+ /**
1788
+ * #DOCUMENTATION objects
1789
+ * Get a view of objects. Make a query to the object database.
1790
+ *
1791
+ * @param socket Socket instance
1792
+ * @param design Design name, e.g., 'system' or other designs like `custom`, but it must exist object `_design/custom`. To 99,9% use `system`.
1793
+ * @param search Search name, object type, like `state`, `instance`, `adapter`, `host`, ...
1794
+ * @param params Parameters for the query, e.g., `{startkey: 'system.adapter.', endkey: 'system.adapter.\u9999', depth?: number}`
1795
+ * @param params.startkey Start key
1796
+ * @param params.endkey End key. If not provided the `startkey + '\u9999'` will be taken
1797
+ * @param params.depth If the depth is provided, only first level of objects will be returned for smaller size
1798
+ * @param callback Callback `(error: string | null, result?: { rows: Array<GetObjectViewItem>) => void`
1799
+ */
1800
+ this.commands.getObjectView = (socket, design, search, params, callback) => {
1801
+ if (typeof callback === 'function') {
1802
+ if (this._checkPermissions(socket, 'getObjectView', callback, search)) {
1803
+ try {
1804
+ if (params?.depth) {
1805
+ // To save the bandwidth, the request can define root and depth. Default is depth 1.
1806
+ this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, (err, result) => {
1807
+ if (result?.rows?.length && result.rows[0].value?._id) {
1808
+ const rows = [];
1809
+ // filter rows
1810
+ const depth = params.depth || 1;
1811
+ let root = params.startkey || '';
1812
+ let rootWithoutDot;
1813
+ if (root) {
1814
+ if (!root.endsWith('.')) {
1815
+ root += '.';
1816
+ rootWithoutDot = root;
1817
+ }
1818
+ else {
1819
+ rootWithoutDot = root.substring(0, root.length - 1);
1820
+ }
1821
+ }
1822
+ else {
1823
+ rootWithoutDot = '';
1824
+ }
1825
+ const rootDepth = root.split('.').length;
1826
+ const virtualObjects = {};
1827
+ for (let r = 0; r < result.rows.length; r++) {
1828
+ const _id = result.rows[r].value._id;
1829
+ if (!root || _id.startsWith(root) || _id === rootWithoutDot) {
1830
+ const parts = _id.split('.');
1831
+ if (parts.length - rootDepth <= depth) {
1832
+ rows.push(result.rows[r]);
1833
+ }
1834
+ else {
1835
+ // create virtual objects to show that there are more objects
1836
+ for (let d = depth; d < parts.length - rootDepth; d++) {
1837
+ const id = parts.slice(0, rootDepth + d).join('.');
1838
+ if (!virtualObjects[id]) {
1839
+ virtualObjects[id] = {
1840
+ id,
1841
+ value: {
1842
+ _id: id,
1843
+ common: {},
1844
+ native: {},
1845
+ type: 'folder',
1846
+ virtual: true,
1847
+ hasChildren: 1,
1848
+ },
1849
+ };
1850
+ rows.push(virtualObjects[id]);
1851
+ }
1852
+ else {
1853
+ virtualObjects[id].value.hasChildren++;
1854
+ }
1855
+ }
1856
+ }
1857
+ }
1858
+ }
1859
+ result.rows = rows;
1860
+ }
1861
+ callback(err, result);
1862
+ });
1863
+ }
1864
+ else {
1865
+ this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, callback);
1866
+ }
1867
+ }
1868
+ catch (error) {
1869
+ this.adapter.log.error(`[getObjectView] ERROR: ${error.toString()}`);
1870
+ SocketCommands._fixCallback(callback, error);
1871
+ }
1872
+ }
1873
+ }
1874
+ else {
1875
+ this.adapter.log.error('Callback is not a function');
1876
+ }
1877
+ };
1878
+ /**
1879
+ * #DOCUMENTATION objects
1880
+ * Set an object.
1881
+ *
1882
+ * @param socket Socket instance
1883
+ * @param id Object ID
1884
+ * @param obj Object to set
1885
+ * @param callback Callback `(error: string | null) => void`
1886
+ */
1887
+ this.commands.setObject = (socket, id, obj, callback) => {
1888
+ if (this._checkPermissions(socket, 'setObject', callback, id)) {
1889
+ try {
1890
+ void this.adapter.setForeignObject(id, obj, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1891
+ }
1892
+ catch (error) {
1893
+ this.adapter.log.error(`[setObject] ERROR: ${error.toString()}`);
1894
+ SocketCommands._fixCallback(callback, error);
1895
+ }
1896
+ }
1897
+ };
1898
+ // this function is overloaded in admin
1899
+ /**
1900
+ * #DOCUMENTATION objects
1901
+ * Delete an object. Only deletion of flot and fullcalendar objects is allowed
1902
+ *
1903
+ * @param socket Socket instance
1904
+ * @param id Object ID, like 'flot.0.myChart'
1905
+ * @param _options Options for deletion. Ignored
1906
+ * @param callback Callback `(error: string | null) => void`
1907
+ */
1908
+ this.commands.delObject = (socket, id, _options, callback) => {
1909
+ if (id.startsWith('flot.') || id.startsWith('fullcalendar.')) {
1910
+ if (this._checkPermissions(socket, 'delObject', callback, id)) {
1911
+ try {
1912
+ this.adapter.delForeignObject(id, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
1913
+ }
1914
+ catch (error) {
1915
+ this.adapter.log.error(`[delObject] ERROR: ${error.toString()}`);
1916
+ SocketCommands._fixCallback(callback, error);
1917
+ }
1918
+ }
1919
+ }
1920
+ else {
1921
+ SocketCommands._fixCallback(callback, SocketCommands.ERROR_PERMISSION);
1922
+ }
1923
+ };
1924
+ /**
1925
+ * #DOCUMENTATION commands
1926
+ * Client subscribes to specific instance's messages.
1927
+ * Client informs specific instance about subscription on its messages.
1928
+ * After subscription, the socket will receive "im" messages from desired instance
1929
+ * The target instance MUST acknowledge the subscription and return result
1930
+ *
1931
+ * @param socket Socket instance
1932
+ * @param targetInstance Instance name, e.g., 'cameras.0'
1933
+ * @param messageType Message type, e.g., 'startRecording/cam1'
1934
+ * @param data Optional data object, e.g., {width: 640, height: 480}
1935
+ * @param callback Callback `(error: string | null, result?:{ accepted: boolean; heartbeat?: number; error?: string; }) => void`
1936
+ */
1937
+ this.commands.clientSubscribe = (socket, targetInstance, messageType, data, callback) => {
1938
+ if (typeof data === 'function') {
1939
+ callback = data;
1940
+ data = null;
1941
+ }
1942
+ if (!targetInstance.startsWith('system.adapter.')) {
1943
+ targetInstance = `system.adapter.${targetInstance}`;
1944
+ }
1945
+ const sid = socket.id;
1946
+ // GUI subscribes for messages from targetInstance
1947
+ this.#clientSubscribes[sid] = this.#clientSubscribes[sid] || {};
1948
+ this.#clientSubscribes[sid][targetInstance] = this.#clientSubscribes[sid][targetInstance] || [];
1949
+ if (!this.#clientSubscribes[sid][targetInstance].includes(messageType)) {
1950
+ this.#clientSubscribes[sid][targetInstance].push(messageType);
1951
+ }
1952
+ // inform instance about new subscription
1953
+ this.adapter.sendTo(targetInstance, 'clientSubscribe', { type: messageType, sid, data }, result => SocketCommands._fixCallback(callback, null, result));
1954
+ };
1955
+ /**
1956
+ * #DOCUMENTATION commands
1957
+ * Client unsubscribes from specific instance's messages.
1958
+ * The target instance MUST NOT acknowledge the un-subscription
1959
+ *
1960
+ * @param socket Socket instance
1961
+ * @param targetInstance Instance name, e.g., 'cameras.0'
1962
+ * @param messageType Message type, e.g., 'startRecording/cam1'
1963
+ * @param callback Callback `(error: string | null) => void`
1964
+ */
1965
+ this.commands.clientUnsubscribe = (socket, targetInstance, messageType, callback) => {
1966
+ // @param {function} callback - `function (error, wasSubscribed)`, target instance MUST NOT acknowledge the un-subscription
1967
+ const sid = socket.id;
1968
+ if (!targetInstance.startsWith('system.adapter.')) {
1969
+ targetInstance = `system.adapter.${targetInstance}`;
1970
+ }
1971
+ // GUI unsubscribes for messages from targetInstance
1972
+ if (this.#clientSubscribes[sid] && this.#clientSubscribes[sid][targetInstance]) {
1973
+ const pos = this.#clientSubscribes[sid][targetInstance].indexOf(messageType);
1974
+ if (pos !== -1) {
1975
+ this.#clientSubscribes[sid][targetInstance].splice(pos, 1);
1976
+ // inform instance about unsubscription
1977
+ this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
1978
+ type: [messageType],
1979
+ sid,
1980
+ reason: 'client',
1981
+ });
1982
+ SocketCommands._fixCallback(callback, null, true);
1983
+ return;
1984
+ }
1985
+ }
1986
+ SocketCommands._fixCallback(callback, null, false);
1987
+ };
1988
+ }
1989
+ /** Init all commands: common, objects, states, files */
1990
+ #initCommands() {
1991
+ this._initCommandsCommon();
1992
+ this._initCommandsObjects();
1993
+ this._initCommandsStates();
1994
+ this._initCommandsFiles();
1995
+ }
1996
+ #informAboutDisconnect(socketId) {
1997
+ // say to all instances, that this socket was disconnected
1998
+ if (this.#clientSubscribes[socketId]) {
1999
+ Object.keys(this.#clientSubscribes[socketId]).forEach(targetInstance => {
2000
+ this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
2001
+ type: this.#clientSubscribes[socketId][targetInstance],
2002
+ sid: socketId,
2003
+ reason: 'disconnect',
2004
+ });
2005
+ });
2006
+ delete this.#clientSubscribes[socketId];
2007
+ }
2008
+ }
2009
+ applyCommands(socket) {
2010
+ Object.keys(this.commands).forEach(command => socket.on(command, (...args) => {
2011
+ if (this.#updateSession(socket)) {
2012
+ this.commands[command](socket, ...args);
2013
+ }
2014
+ }));
2015
+ }
2016
+ disableEventThreshold() {
2017
+ // could be overloaded
2018
+ }
2019
+ destroy() {
2020
+ // could be overloaded
2021
+ }
2022
+ }
2023
+ exports.SocketCommands = SocketCommands;
2024
+ //# sourceMappingURL=socketCommands.js.map