@iobroker/socket-classes 0.1.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,975 @@
1
+ let axios = null;
2
+
3
+ // taken from here: https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/src/lib/common/tools.ts#L2386
4
+ /**
5
+ * Converts ioB pattern into regex.
6
+ * @param pattern - Regex string to use it in new RegExp(pattern)
7
+ */
8
+ function pattern2RegEx(pattern) {
9
+ pattern = (pattern || '').toString();
10
+
11
+ const startsWithWildcard = pattern[0] === '*';
12
+ const endsWithWildcard = pattern[pattern.length - 1] === '*';
13
+
14
+ pattern = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
15
+
16
+ return (startsWithWildcard ? '' : '^') + pattern + (endsWithWildcard ? '' : '$');
17
+ }
18
+
19
+ class SocketCommands {
20
+ static ERROR_PERMISSION = 'permissionError';
21
+ static COMMANDS_PERMISSIONS = {
22
+ getObject: {type: 'object', operation: 'read'},
23
+ getObjects: {type: 'object', operation: 'list'},
24
+ getObjectView: {type: 'object', operation: 'list'},
25
+ setObject: {type: 'object', operation: 'write'},
26
+ requireLog: {type: 'object', operation: 'write'}, // just mapping to some command
27
+ delObject: {type: 'object', operation: 'delete'},
28
+ extendObject: {type: 'object', operation: 'write'},
29
+ getHostByIp: {type: 'object', operation: 'list'},
30
+ subscribeObjects: {type: 'object', operation: 'read'},
31
+ unsubscribeObjects: {type: 'object', operation: 'read'},
32
+
33
+ getStates: {type: 'state', operation: 'list'},
34
+ getState: {type: 'state', operation: 'read'},
35
+ setState: {type: 'state', operation: 'write'},
36
+ delState: {type: 'state', operation: 'delete'},
37
+ createState: {type: 'state', operation: 'create'},
38
+ subscribe: {type: 'state', operation: 'read'},
39
+ unsubscribe: {type: 'state', operation: 'read'},
40
+ getStateHistory: {type: 'state', operation: 'read'},
41
+ getVersion: {type: '', operation: ''},
42
+ getAdapterName: {type: '', operation: ''},
43
+
44
+ addUser: {type: 'users', operation: 'create'},
45
+ delUser: {type: 'users', operation: 'delete'},
46
+ addGroup: {type: 'users', operation: 'create'},
47
+ delGroup: {type: 'users', operation: 'delete'},
48
+ changePassword: {type: 'users', operation: 'write'},
49
+
50
+ httpGet: {type: 'other', operation: 'http'},
51
+ cmdExec: {type: 'other', operation: 'execute'},
52
+ sendTo: {type: 'other', operation: 'sendto'},
53
+ sendToHost: {type: 'other', operation: 'sendto'},
54
+ readLogs: {type: 'other', operation: 'execute'},
55
+
56
+ readDir: {type: 'file', operation: 'list'},
57
+ createFile: {type: 'file', operation: 'create'},
58
+ writeFile: {type: 'file', operation: 'write'},
59
+ readFile: {type: 'file', operation: 'read'},
60
+ fileExists: {type: 'file', operation: 'read'},
61
+ deleteFile: {type: 'file', operation: 'delete'},
62
+ readFile64: {type: 'file', operation: 'read'},
63
+ writeFile64: {type: 'file', operation: 'write'},
64
+ unlink: {type: 'file', operation: 'delete'},
65
+ rename: {type: 'file', operation: 'write'},
66
+ mkdir: {type: 'file', operation: 'write'},
67
+ chmodFile: {type: 'file', operation: 'write'},
68
+ chownFile: {type: 'file', operation: 'write'},
69
+
70
+ authEnabled: {type: '', operation: ''},
71
+ disconnect: {type: '', operation: ''},
72
+ listPermissions: {type: '', operation: ''},
73
+ getUserPermissions: {type: 'object', operation: 'read'}
74
+ };
75
+
76
+ constructor(adapter, updateSession) {
77
+ this.adapter = adapter;
78
+ this.commands = {};
79
+ this.subscribes = {};
80
+
81
+ this._updateSession = updateSession;
82
+
83
+ if (!this._updateSession) {
84
+ this._updateSession = () => true;
85
+ }
86
+
87
+ this._initCommands();
88
+ }
89
+
90
+ async _rename(_adapter, oldName, newName, options) {
91
+ // read if it is a file or folder
92
+ try {
93
+ if (oldName.endsWith('/')) {
94
+ oldName = oldName.substring(0, oldName.length - 1);
95
+ }
96
+
97
+ if (newName.endsWith('/')) {
98
+ newName = newName.substring(0, newName.length - 1);
99
+ }
100
+
101
+ const files = await this.adapter.readDirAsync(_adapter, oldName, options);
102
+ if (files && files.length) {
103
+ for (let f = 0; f < files.length; f++) {
104
+ await this._rename(_adapter, `${oldName}/${files[f].file}`, `${newName}/${files[f].file}`);
105
+ }
106
+ }
107
+ } catch (error) {
108
+ if (error.message !== 'Not exists') {
109
+ throw error;
110
+ }
111
+ // else ignore, because it is a file and not a folder
112
+ }
113
+
114
+ try {
115
+ await this.adapter.renameAsync(_adapter, oldName, newName, options);
116
+ } catch (error) {
117
+ if (error.message !== 'Not exists') {
118
+ throw error;
119
+ }
120
+ // else ignore, because folder cannot be deleted
121
+ }
122
+ }
123
+
124
+ async _unlink(_adapter, name, options) {
125
+ // read if it is a file or folder
126
+ try {
127
+ // remove trailing '/'
128
+ if (name.endsWith('/')) {
129
+ name = name.substring(0, name.length - 1);
130
+ }
131
+ const files = await this.adapter.readDirAsync(_adapter, name, options);
132
+ if (files && files.length) {
133
+ for (let f = 0; f < files.length; f++) {
134
+ await this._unlink(_adapter, name + '/' + files[f].file);
135
+ }
136
+ }
137
+ } catch (error) {
138
+ // ignore, because it is a file and not a folder
139
+ if (error.message !== 'Not exists') {
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ try {
145
+ await this.adapter.unlinkAsync(_adapter, name, options);
146
+ } catch (error) {
147
+ if (error.message !== 'Not exists') {
148
+ throw error;
149
+ }
150
+ // else ignore, because folder cannot be deleted
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Convert errors into strings and then call cb
156
+ * @param {function} cb - callback
157
+ * @param {string|Error|null} err - error argument
158
+ * @param {any[]} args - args passed to cb
159
+ */
160
+ static _fixCallback(cb, err, ...args) {
161
+ if (typeof cb !== 'function') {
162
+ return;
163
+ }
164
+
165
+ if (err instanceof Error) {
166
+ err = err.message;
167
+ }
168
+
169
+ cb(err, ...args);
170
+ }
171
+
172
+ _checkPermissions(socket, command, callback, arg) {
173
+ if (socket._acl.user !== 'system.user.admin') {
174
+ // type: file, object, state, other
175
+ // operation: create, read, write, list, delete, sendto, execute, sendToHost, readLogs
176
+ if (SocketCommands.COMMANDS_PERMISSIONS[command]) {
177
+ // If permission required
178
+ if (SocketCommands.COMMANDS_PERMISSIONS[command].type) {
179
+ if (socket._acl[SocketCommands.COMMANDS_PERMISSIONS[command].type] &&
180
+ socket._acl[SocketCommands.COMMANDS_PERMISSIONS[command].type][SocketCommands.COMMANDS_PERMISSIONS[command].operation]) {
181
+ return true;
182
+ } else {
183
+ this.adapter.log.warn(`No permission for "${socket._acl.user}" to call ${command}. Need "${SocketCommands.COMMANDS_PERMISSIONS[command].type}"."${SocketCommands.COMMANDS_PERMISSIONS[command].operation}"`);
184
+ }
185
+ } else {
186
+ return true;
187
+ }
188
+ } else {
189
+ this.adapter.log.warn('No rule for command: ' + command);
190
+ }
191
+
192
+ if (typeof callback === 'function') {
193
+ callback(SocketCommands.ERROR_PERMISSION);
194
+ } else {
195
+ if (SocketCommands.COMMANDS_PERMISSIONS[command]) {
196
+ socket.emit(SocketCommands.ERROR_PERMISSION, {
197
+ command,
198
+ type: SocketCommands.COMMANDS_PERMISSIONS[command].type,
199
+ operation: SocketCommands.COMMANDS_PERMISSIONS[command].operation,
200
+ arg
201
+ });
202
+ } else {
203
+ socket.emit(SocketCommands.ERROR_PERMISSION, {command, arg});
204
+ }
205
+ }
206
+ return false;
207
+ } else {
208
+ return true;
209
+ }
210
+ }
211
+
212
+ _subscribeStates(socket, pattern, callback) {
213
+ if (this._checkPermissions(socket, 'subscribe', callback, pattern)) {
214
+ if (pattern && typeof pattern === 'object' && pattern instanceof Array) {
215
+ for (let p = 0; p < pattern.length; p++) {
216
+ this.subscribe(socket, 'stateChange', pattern[p]);
217
+ }
218
+ } else {
219
+ this.subscribe(socket, 'stateChange', pattern);
220
+ }
221
+
222
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
223
+
224
+ typeof callback === 'function' && setImmediate(callback, null);
225
+ }
226
+ }
227
+
228
+ publish(socket, type, id, obj) {
229
+ if (socket && socket.subscribe && socket.subscribe[type]) {
230
+ return !!socket.subscribe[type].find(sub => {
231
+ if (sub.regex.test(id)) {
232
+ socket.emit(type, id, obj);
233
+ return true;
234
+ }
235
+ });
236
+ }
237
+ return false;
238
+ }
239
+
240
+ subscribe(socket, type, pattern) {
241
+ this.subscribes[type] = this.subscribes[type] || {};
242
+
243
+ pattern = pattern.toString();
244
+
245
+ const p = pattern2RegEx(pattern);
246
+ if (p === null) {
247
+ return this.adapter.log.warn('Empty pattern on subscribe!');
248
+ }
249
+
250
+ let s;
251
+ if (socket) {
252
+ socket.subscribe = socket.subscribe || {};
253
+ s = socket.subscribe[type] = socket.subscribe[type] || [];
254
+
255
+ if (s.find(item => item.pattern === pattern)) {
256
+ return;
257
+ }
258
+ s.push({pattern, regex: new RegExp(p)});
259
+ }
260
+
261
+ if (this.subscribes[type][pattern] === undefined) {
262
+ this.subscribes[type][pattern] = 1;
263
+ if (type === 'stateChange') {
264
+ this.adapter.subscribeForeignStates(pattern);
265
+ } else if (type === 'objectChange') {
266
+ this.adapter.subscribeForeignObjects && this.adapter.subscribeForeignObjects(pattern);
267
+ } else if (type === 'log') {
268
+ this.adapter.requireLog && this.adapter.requireLog(true);
269
+ }
270
+ } else {
271
+ this.subscribes[type][pattern]++;
272
+ }
273
+ };
274
+
275
+ _showSubscribes(socket, type) {
276
+ if (socket && socket.subscribe) {
277
+ const s = socket.subscribe[type] || [];
278
+ const ids = [];
279
+ for (let i = 0; i < s.length; i++) {
280
+ ids.push(s[i].pattern);
281
+ }
282
+ this.adapter.log.debug('Subscribes: ' + ids.join(', '));
283
+ } else {
284
+ this.adapter.log.debug('Subscribes: no subscribes');
285
+ }
286
+ }
287
+
288
+ unsubscribe(socket, type, pattern) {
289
+ //console.log((socket._name || socket.id) + ' unsubscribe ' + pattern);
290
+ this.subscribes[type] = this.subscribes[type] || {};
291
+
292
+ pattern = pattern.toString();
293
+
294
+ if (socket && typeof socket === 'object') {
295
+ if (!socket.subscribe || !socket.subscribe[type]) {
296
+ return;
297
+ }
298
+
299
+ for (let i = socket.subscribe[type].length - 1; i >= 0; i--) {
300
+ if (socket.subscribe[type][i].pattern === pattern) {
301
+
302
+ // Remove pattern from global list
303
+ if (this.subscribes[type][pattern] !== undefined) {
304
+ this.subscribes[type][pattern]--;
305
+ if (this.subscribes[type][pattern] <= 0) {
306
+ if (type === 'stateChange') {
307
+ //console.log((socket._name || socket.id) + ' unsubscribeForeignStates ' + pattern);
308
+ this.adapter.unsubscribeForeignStates(pattern);
309
+ } else if (type === 'objectChange') {
310
+ //console.log((socket._name || socket.id) + ' unsubscribeForeignObjects ' + pattern);
311
+ this.adapter.unsubscribeForeignObjects && this.adapter.unsubscribeForeignObjects(pattern);
312
+ } else if (type === 'log') {
313
+ //console.log((socket._name || socket.id) + ' requireLog false');
314
+ this.adapter.requireLog && this.adapter.requireLog(false);
315
+ }
316
+ delete this.subscribes[type][pattern];
317
+ }
318
+ }
319
+
320
+ delete socket.subscribe[type][i];
321
+ socket.subscribe[type].splice(i, 1);
322
+ return;
323
+ }
324
+ }
325
+ } else if (pattern) {
326
+ // Remove pattern from global list
327
+ if (this.subscribes[type][pattern] !== undefined) {
328
+ this.subscribes[type][pattern]--;
329
+ if (this.subscribes[type][pattern] <= 0) {
330
+ if (type === 'stateChange') {
331
+ this.adapter.unsubscribeForeignStates(pattern);
332
+ } else if (type === 'objectChange') {
333
+ this.adapter.unsubscribeForeignObjects && this.adapter.unsubscribeForeignObjects(pattern);
334
+ } else if (type === 'log') {
335
+ this.adapter.requireLog && this.adapter.requireLog(false);
336
+ }
337
+ delete this.subscribes[type][pattern];
338
+ }
339
+ }
340
+ } else {
341
+ for (pattern in this.subscribes[type]) {
342
+ if (!Object.prototype.hasOwnProperty.call(this.subscribes[type], pattern)) {
343
+ continue;
344
+ }
345
+ if (type === 'stateChange') {
346
+ //console.log((socket._name || socket.id) + ' unsubscribeForeignStates ' + pattern);
347
+ this.adapter.unsubscribeForeignStates(pattern);
348
+ } else if (type === 'objectChange') {
349
+ //console.log((socket._name || socket.id) + ' unsubscribeForeignObjects ' + pattern);
350
+ this.adapter.unsubscribeForeignObjects && this.adapter.unsubscribeForeignObjects(pattern);
351
+ } else if (type === 'log') {
352
+ //console.log((socket._name || socket.id) + ' requireLog false');
353
+ this.adapter.requireLog && this.adapter.requireLog(false);
354
+ }
355
+ delete this.subscribes[type][pattern];
356
+ }
357
+ }
358
+ };
359
+
360
+ unsubscribeSocket(socket, type) {
361
+ if (!socket || !socket.subscribe) {
362
+ return;
363
+ }
364
+
365
+ if (!type) {
366
+ // all
367
+ return Object.keys(socket.subscribe).forEach(type => this.unsubscribeSocket(socket, type));
368
+ }
369
+
370
+ if (!socket.subscribe[type]) {
371
+ return;
372
+ }
373
+
374
+ for (let i = 0; i < socket.subscribe[type].length; i++) {
375
+ const pattern = socket.subscribe[type][i].pattern;
376
+ if (this.subscribes[type][pattern] !== undefined) {
377
+ this.subscribes[type][pattern]--;
378
+ if (this.subscribes[type][pattern] <= 0) {
379
+ if (type === 'stateChange') {
380
+ this.adapter.unsubscribeForeignStates(pattern);
381
+ } else if (type === 'objectChange') {
382
+ this.adapter.unsubscribeForeignObjects && this.adapter.unsubscribeForeignObjects(pattern);
383
+ } else if (type === 'log') {
384
+ this.adapter.requireLog && this.adapter.requireLog(false);
385
+ }
386
+ delete this.subscribes[type][pattern];
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ subscribeSocket(socket, type) {
393
+ if (!socket || !socket.subscribe) {
394
+ return;
395
+ }
396
+
397
+ if (!type) {
398
+ // all
399
+ return Object.keys(socket.subscribe).forEach(type => this.subscribeSocket(socket, type));
400
+ }
401
+
402
+ if (!socket.subscribe[type]) {
403
+ return;
404
+ }
405
+
406
+ for (let i = 0; i < socket.subscribe[type].length; i++) {
407
+ const pattern = socket.subscribe[type][i].pattern;
408
+ if (this.subscribes[type][pattern] === undefined) {
409
+ this.subscribes[type][pattern] = 1;
410
+ if (type === 'stateChange') {
411
+ this.adapter.subscribeForeignStates(pattern);
412
+ } else if (type === 'objectChange') {
413
+ this.adapter.subscribeForeignObjects && this.adapter.subscribeForeignObjects(pattern);
414
+ } else if (type === 'log') {
415
+ this.adapter.requireLog && this.adapter.requireLog(true);
416
+ }
417
+ } else {
418
+ this.subscribes[type][pattern]++;
419
+ }
420
+ }
421
+ }
422
+
423
+ _unsubscribeStates(socket, pattern, callback) {
424
+ if (this._checkPermissions(socket, 'unsubscribe', callback, pattern)) {
425
+ if (pattern && typeof pattern === 'object' && pattern instanceof Array) {
426
+ for (let p = 0; p < pattern.length; p++) {
427
+ this.unsubscribe(socket, 'stateChange', pattern[p]);
428
+ }
429
+ } else {
430
+ this.unsubscribe(socket, 'stateChange', pattern);
431
+ }
432
+
433
+ this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
434
+
435
+ typeof callback === 'function' && setImmediate(callback, null);
436
+ }
437
+ }
438
+
439
+ addCommandHandler(command, handler) {
440
+ if (handler) {
441
+ this.commands[command] = handler;
442
+ } else if (this.commands.hasOwnProperty(command)) {
443
+ delete this.commands[command];
444
+ }
445
+ }
446
+
447
+ getCommandHandler(command) {
448
+ return this.commands[command];
449
+ }
450
+
451
+ __initCommandsCommon() {
452
+ this.commands['authenticate'] = (socket, user, pass, callback) => {
453
+ if (socket && socket.___socket) {
454
+ socket = socket.___socket;
455
+ }
456
+
457
+ this.adapter.log.debug(`${new Date().toISOString()} Request authenticate [${socket._acl.user}]`);
458
+ if (typeof user === 'function') {
459
+ callback = user;
460
+ // user = undefined;
461
+ }
462
+ if (socket._acl.user !== null) {
463
+ if (typeof callback === 'function') {
464
+ callback(true, socket._secure);
465
+ }
466
+ } else {
467
+ this.adapter.log.debug(`${new Date().toISOString()} Request authenticate [${socket._acl.user}]`);
468
+ socket._authPending = callback;
469
+ }
470
+ };
471
+
472
+ this.commands['error'] = (socket, err) => {
473
+ this.adapter.log.error('Socket error: ' + err);
474
+ };
475
+
476
+ this.commands['log'] = (socket, text, level) => {
477
+ if (level === 'error') {
478
+ this.adapter.log.error(text);
479
+ } else if (level === 'warn') {
480
+ this.adapter.log.warn(text);
481
+ } else if (level === 'info') {
482
+ this.adapter.log.info(text);
483
+ } else {
484
+ this.adapter.log.debug(text);
485
+ }
486
+ };
487
+
488
+ // new History
489
+ this.commands['getHistory'] = (socket, id, options, callback) => {
490
+ if (this._checkPermissions(socket, 'getStateHistory', callback, id)) {
491
+ if (typeof options === 'string') {
492
+ options = {
493
+ instance: options
494
+ };
495
+ }
496
+ options = options || {};
497
+ options.user = socket._acl.user;
498
+ options.aggregate = options.aggregate || 'none';
499
+ this.adapter.getHistory(id, options, (err, ...args) =>
500
+ SocketCommands._fixCallback(callback, err, ...args));
501
+ }
502
+ };
503
+
504
+ // HTTP
505
+ this.commands['httpGet'] = (socket, url, callback) => {
506
+ if (this._checkPermissions(socket, 'httpGet', callback, url)) {
507
+ axios = axios || require('axios');
508
+ this.adapter.log.debug('httpGet: ' + url);
509
+ try {
510
+ axios(url, {
511
+ responseType: 'arraybuffer',
512
+ timeout: 15000,
513
+ validateStatus: status => status < 400
514
+ })
515
+ .then(result => callback(null, {status: result.status, statusText: result.statusText}, result.data))
516
+ .catch(error => callback(error));
517
+ } catch (err) {
518
+ callback(err);
519
+ }
520
+ }
521
+ };
522
+
523
+ // commands
524
+ this.commands['sendTo'] = (socket, adapterInstance, command, message, callback) => {
525
+ if (this._checkPermissions(socket, 'sendTo', callback, command)) {
526
+ this.adapter.sendTo(adapterInstance, command, message, res =>
527
+ typeof callback === 'function' && setImmediate(() =>
528
+ callback(res)));
529
+ }
530
+ };
531
+
532
+ // following commands are protected and require the extra permissions
533
+ const protectedCommands = ['cmdExec', 'getLocationOnDisk', 'getDiagData', 'getDevList', 'delLogs', 'writeDirAsZip', 'writeObjectsAsZip', 'readObjectsAsZip', 'checkLogging', 'updateMultihost', 'rebuildAdapter'];
534
+
535
+ this.commands['sendToHost'] = (socket, host, command, message, callback) => {
536
+ // host can answer following commands: cmdExec, getRepository, getInstalled, getInstalledAdapter, getVersion, getDiagData, getLocationOnDisk, getDevList, getLogs, getHostInfo,
537
+ // delLogs, readDirAsZip, writeDirAsZip, readObjectsAsZip, writeObjectsAsZip, checkLogging, updateMultihost
538
+ if (this._checkPermissions(socket, protectedCommands.includes(command) ? 'cmdExec' : 'sendToHost', callback, command)) {
539
+ if (this.sendToHost) {
540
+ this.sendToHost(host, command, message, callback);
541
+ } else {
542
+ this.adapter.sendToHost(host, command, message, callback);
543
+ }
544
+ }
545
+ };
546
+
547
+ this.commands['authEnabled'] = (socket, callback) => {
548
+ if (this._checkPermissions(socket, 'authEnabled', callback)) {
549
+ if (typeof callback === 'function') {
550
+ callback(this.adapter.config.auth, (socket._acl.user || '').replace(/^system\.user\./, ''));
551
+ } else {
552
+ this.adapter.log.warn('[authEnabled] Invalid callback');
553
+ }
554
+ }
555
+ };
556
+
557
+ this.commands['logout'] = (socket, callback) => {
558
+ this.adapter.destroySession(socket._sessionID, callback);
559
+ };
560
+
561
+ this.commands['listPermissions'] = (socket, callback) => {
562
+ if (typeof callback === 'function') {
563
+ callback(SocketCommands.COMMANDS_PERMISSIONS);
564
+ } else {
565
+ this.adapter.log.warn('[listPermissions] Invalid callback');
566
+ }
567
+ };
568
+
569
+ this.commands['getUserPermissions'] = (socket, callback) => {
570
+ if (this._checkPermissions(socket, 'getUserPermissions', callback)) {
571
+ if (typeof callback === 'function') {
572
+ callback(null, socket._acl);
573
+ } else {
574
+ this.adapter.log.warn('[getUserPermissions] Invalid callback');
575
+ }
576
+ }
577
+ };
578
+
579
+ this.commands['getVersion'] = (socket, callback) => {
580
+ if (this._checkPermissions(socket, 'getVersion', callback)) {
581
+ if (typeof callback === 'function') {
582
+ callback(null, this.adapter.version, this.adapter.name);
583
+ } else {
584
+ this.adapter.log.warn('[getVersion] Invalid callback');
585
+ }
586
+ }
587
+ };
588
+
589
+ this.commands['getAdapterName'] = (socket, callback) => {
590
+ if (this._checkPermissions(socket, 'getAdapterName', callback)) {
591
+ if (typeof callback === 'function') {
592
+ callback(null, this.adapter.name || 'unknown');
593
+ } else {
594
+ this.adapter.log.warn('[getAdapterName] Invalid callback');
595
+ }
596
+ }
597
+ };
598
+
599
+ }
600
+
601
+ __initCommandsFiles() {
602
+ // file operations
603
+ this.commands['readFile'] = (socket, _adapter, fileName, callback) => {
604
+ if (this._checkPermissions(socket, 'readFile', callback, fileName)) {
605
+ this.adapter.readFile(_adapter, fileName, {user: socket._acl.user}, (err, ...args) =>
606
+ SocketCommands._fixCallback(callback, err, ...args));
607
+ }
608
+ };
609
+
610
+ this.commands['readFile64'] = (socket, _adapter, fileName, callback) => {
611
+ if (this._checkPermissions(socket, 'readFile64', callback, fileName)) {
612
+ this.adapter.readFile(_adapter, fileName, {user: socket._acl.user}, (err, buffer, type) => {
613
+ let data64;
614
+ if (buffer) {
615
+ if (type === 'application/json') {
616
+ data64 = Buffer.from(encodeURIComponent(buffer)).toString('base64');
617
+ } else {
618
+ if (typeof buffer === 'string') {
619
+ data64 = Buffer.from(buffer).toString('base64');
620
+ } else {
621
+ data64 = buffer.toString('base64');
622
+ }
623
+ }
624
+ }
625
+
626
+ //Convert buffer to base 64
627
+ if (typeof callback === 'function') {
628
+ callback(err, data64 || '', type);
629
+ } else {
630
+ this.adapter.log.warn('[readFile64] Invalid callback');
631
+ }
632
+ });
633
+ }
634
+ };
635
+
636
+ this.commands['writeFile64'] = (socket, _adapter, fileName, data64, options, callback) => {
637
+ if (typeof options === 'function') {
638
+ callback = options;
639
+ options = {user: socket._acl.user};
640
+ }
641
+
642
+ options = options || {};
643
+ options.user = socket._acl.user;
644
+
645
+ if (this._checkPermissions(socket, 'writeFile64', callback, fileName)) {
646
+ if (!data64) {
647
+ return typeof callback === 'function' && callback('No data provided');
648
+ }
649
+ // Convert base 64 to buffer
650
+ const buffer = Buffer.from(data64, 'base64');
651
+ this.adapter.writeFile(_adapter, fileName, buffer, options, (err, ...args) =>
652
+ SocketCommands._fixCallback(callback, err, ...args));
653
+ }
654
+ };
655
+
656
+ // this function is overloaded in admin (because admin accepts only base64)
657
+ this.commands['writeFile'] = (socket, _adapter, fileName, data, options, callback) => {
658
+ if (this._checkPermissions(socket, 'writeFile', callback, fileName)) {
659
+ if (typeof options === 'function') {
660
+ callback = options;
661
+ options = {user: socket._acl.user};
662
+ }
663
+ options = options || {};
664
+ options.user = socket._acl.user;
665
+ this.adapter.log.warn('writeFile deprecated. Please use writeFile64');
666
+ // const buffer = Buffer.from(data64, 'base64');
667
+ this.adapter.writeFile(_adapter, fileName, data, {user: socket._acl.user}, (err, ...args) =>
668
+ SocketCommands._fixCallback(callback, err, ...args));
669
+ }
670
+ };
671
+
672
+ this.commands['unlink'] = (socket, _adapter, name, callback) => {
673
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
674
+ this._unlink(_adapter, name, {user: socket._acl.user})
675
+ .then(() => callback && callback())
676
+ .catch(error => callback && callback(error));
677
+ }
678
+ };
679
+
680
+ this.commands['deleteFile'] = (socket, _adapter, name, callback) => {
681
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
682
+ this.adapter._unlink(_adapter, name, {user: socket._acl.user}, (err, ...args) =>
683
+ SocketCommands._fixCallback(callback, err, ...args));
684
+ }
685
+ };
686
+
687
+ this.commands['deleteFolder'] = (socket, _adapter, name, callback) => {
688
+ if (this._checkPermissions(socket, 'unlink', callback, name)) {
689
+ this._unlink(_adapter, name, {user: socket._acl.user})
690
+ .then(() => SocketCommands._fixCallback(callback, null))
691
+ .catch(err => SocketCommands._fixCallback(callback, err));
692
+ }
693
+ };
694
+
695
+ this.commands['renameFile'] = (socket, _adapter, oldName, newName, callback) => {
696
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
697
+ this.adapter._rename(_adapter, oldName, newName, {user: socket._acl.user}, (err, ...args) =>
698
+ SocketCommands._fixCallback(callback, err, ...args));
699
+ }
700
+ };
701
+
702
+ this.commands['rename'] = (socket, _adapter, oldName, newName, callback) => {
703
+ if (this._checkPermissions(socket, 'rename', callback, oldName)) {
704
+ this._rename(_adapter, oldName, newName, {user: socket._acl.user})
705
+ .then(() => callback && callback())
706
+ .catch(error => callback && callback(error));
707
+ }
708
+ };
709
+
710
+ this.commands['mkdir'] = (socket, _adapter, dirName, callback) => {
711
+ if (this._checkPermissions(socket, 'mkdir', callback, dirName)) {
712
+ this.adapter.mkdir(_adapter, dirName, {user: socket._acl.user}, (err, ...args) =>
713
+ SocketCommands._fixCallback(callback, err, ...args));
714
+ }
715
+ };
716
+
717
+ this.commands['readDir'] = (socket, _adapter, dirName, options, callback) => {
718
+ if (typeof options === 'function') {
719
+ callback = options;
720
+ options = {};
721
+ }
722
+ options = options || {};
723
+ options.user = socket._acl.user;
724
+
725
+ if (options.filter === undefined) {
726
+ options.filter = true;
727
+ }
728
+
729
+ if (this._checkPermissions(socket, 'readDir', callback, dirName)) {
730
+ this.adapter.readDir(_adapter, dirName, {user: socket._acl.user}, (err, ...args) =>
731
+ SocketCommands._fixCallback(callback, err, ...args));
732
+ }
733
+ };
734
+
735
+ this.commands['chmodFile'] = (socket, _adapter, fileName, options, callback) => {
736
+ if (typeof options === 'function') {
737
+ callback = options;
738
+ options = {};
739
+ }
740
+ options = options || {};
741
+ options.user = socket._acl.user;
742
+
743
+ if (options.filter === undefined) {
744
+ options.filter = true;
745
+ }
746
+
747
+ if (this._checkPermissions(socket, 'chmodFile', callback, fileName)) {
748
+ this.adapter.chmodFile(_adapter, fileName, options, (err, ...args) =>
749
+ SocketCommands._fixCallback(callback, err, ...args));
750
+ }
751
+ };
752
+
753
+ this.commands['chownFile'] = (socket, _adapter, filename, options, callback) => {
754
+ if (this._checkPermissions(socket, 'chownFile', callback, filename)) {
755
+ options = options || {};
756
+ options.user = socket._acl.user;
757
+ this.adapter.chownFile(_adapter, filename, options, (err, ...args) =>
758
+ SocketCommands._fixCallback(callback, err, ...args));
759
+ }
760
+ };
761
+
762
+ this.commands['fileExists'] = (socket, _adapter, filename, callback) => {
763
+ if (this._checkPermissions(socket, 'fileExists', callback, filename)) {
764
+ this.adapter.fileExists(_adapter, filename, {user: socket._acl.user}, (err, ...args) =>
765
+ SocketCommands._fixCallback(callback, err, ...args));
766
+ }
767
+ };
768
+ }
769
+
770
+ __initCommandsStates() {
771
+ this.commands['getStates'] = (socket, pattern, callback) => {
772
+ if (this._checkPermissions(socket, 'getStates', callback, pattern)) {
773
+ if (typeof pattern === 'function') {
774
+ callback = pattern;
775
+ pattern = null;
776
+ }
777
+ if (typeof callback === 'function') {
778
+ this.adapter.getForeignStates(pattern || '*', {user: socket._acl.user}, (err, ...args) =>
779
+ SocketCommands._fixCallback(callback, err, ...args));
780
+ } else {
781
+ this.adapter.log.warn('[getStates] Invalid callback')
782
+ }
783
+ }
784
+ };
785
+
786
+ // this function is overloaded in admin
787
+ this.commands['delObject'] = (socket, id, options, callback) => {
788
+ // only flot allowed
789
+ if (id.startsWith('flot.')) {
790
+ if (this._checkPermissions(socket, 'delObject', callback, id)) {
791
+ this.adapter.delForeignObject(id, {user: socket._acl.user}, callback);
792
+ }
793
+ } else {
794
+ if (typeof callback === 'function') {
795
+ callback(SocketCommands.ERROR_PERMISSION);
796
+ }
797
+ }
798
+ };
799
+
800
+ this.commands['getState'] = (socket, id, callback) => {
801
+ if (this._checkPermissions(socket, 'getState', callback, id)) {
802
+ if (typeof callback === 'function') {
803
+ if (this.states && this.states[id]) {
804
+ callback(null, this.states[id]);
805
+ } else {
806
+ this.adapter.getForeignState(id, {user: socket._acl.user}, (err, ...args) =>
807
+ SocketCommands._fixCallback(callback, err, ...args));
808
+ }
809
+ } else {
810
+ this.adapter.log.warn('[getState] Invalid callback');
811
+ }
812
+ }
813
+ };
814
+
815
+ this.commands['setState'] = (socket, id, state, callback) => {
816
+ if (this._checkPermissions(socket, 'setState', callback, id)) {
817
+ if (typeof state !== 'object') {
818
+ state = {val: state};
819
+ }
820
+
821
+ // clear cache
822
+ if (this.states && this.states[id]) {
823
+ delete this.states[id];
824
+ }
825
+
826
+ this.adapter.setForeignState(id, state, {user: socket._acl.user}, (err, ...args) =>
827
+ SocketCommands._fixCallback(callback, err, ...args));
828
+ }
829
+ };
830
+
831
+ this.commands['getBinaryState'] = (socket, id, callback) => {
832
+ if (this._checkPermissions(socket, 'getState', callback, id)) {
833
+ if (typeof callback === 'function') {
834
+ if (this.adapter.getForeignBinaryState) {
835
+ this.adapter.getForeignBinaryState(id, (err, data) => {
836
+ if (data) {
837
+ data = Buffer.from(data).toString('base64');
838
+ }
839
+ SocketCommands._fixCallback(callback, err, data);
840
+ });
841
+ } else {
842
+ this.adapter.getBinaryState(id, (err, data) => {
843
+ if (data) {
844
+ data = Buffer.from(data).toString('base64');
845
+ }
846
+ SocketCommands._fixCallback(callback, err, data);
847
+ });
848
+ }
849
+ } else {
850
+ this.adapter.log.warn('[getBinaryState] Invalid callback')
851
+ }
852
+ }
853
+ };
854
+
855
+ this.commands['setBinaryState'] = (socket, id, base64, callback) => {
856
+ if (this._checkPermissions(socket, 'setState', callback, id)) {
857
+ if (typeof callback === 'function') {
858
+ let data = null;
859
+ try {
860
+ data = Buffer.from(base64, 'base64')
861
+ } catch (e) {
862
+ this.adapter.log.warn('[setBinaryState] Cannot convert base64 data: ' + e);
863
+ }
864
+
865
+ if (this.adapter.setForeignBinaryState) {
866
+ this.adapter.setForeignBinaryState(id, data, (err, ...args) =>
867
+ SocketCommands._fixCallback(callback, err, ...args));
868
+ } else {
869
+ this.adapter.setBinaryState(id, data, (err, ...args) =>
870
+ SocketCommands._fixCallback(callback, err, ...args));
871
+ }
872
+ } else {
873
+ this.adapter.log.warn('[setBinaryState] Invalid callback');
874
+ }
875
+ }
876
+ };
877
+
878
+ this.commands['subscribe'] = (socket, pattern, callback) => this._subscribeStates(socket, pattern, callback);
879
+ this.commands['subscribeStates'] = (socket, pattern, callback) => this._subscribeStates(socket, pattern, callback);
880
+
881
+ this.commands['unsubscribe'] = (socket, pattern, callback) => this._unsubscribeStates(socket, pattern, callback);
882
+ this.commands['unsubscribeStates'] = (socket, pattern, callback) => this._unsubscribeStates(socket, pattern, callback);
883
+ }
884
+
885
+ __initCommandsObjects() {
886
+ this.commands['getObject'] = (socket, id, callback) => {
887
+ if (this._checkPermissions(socket, 'getObject', callback, id)) {
888
+ this.adapter.getForeignObject(id, {user: socket._acl.user}, (err, ...args) =>
889
+ SocketCommands._fixCallback(callback, err, ...args));
890
+ }
891
+ };
892
+
893
+ // not admin version of "all objects"
894
+ this.commands['getObjects'] = (socket, callback) => {
895
+ if (this._checkPermissions(socket, 'getObjects', callback)) {
896
+ this.adapter.getForeignObjects('*', 'state', 'rooms', {user: socket._acl.user}, (err, objs) => {
897
+ if (typeof callback === 'function') {
898
+ callback(err, objs);
899
+ } else {
900
+ this.adapter.log.warn('[getObjects] Invalid callback');
901
+ }
902
+ });
903
+ }
904
+ };
905
+
906
+ this.commands['subscribeObjects'] = (socket, pattern, callback) => {
907
+ if (this._checkPermissions(socket, 'subscribeObjects', callback, pattern)) {
908
+ if (pattern && typeof pattern === 'object' && pattern instanceof Array) {
909
+ for (let p = 0; p < pattern.length; p++) {
910
+ this.subscribe(socket, 'objectChange', pattern[p]);
911
+ }
912
+ } else {
913
+ this.subscribe(socket, 'objectChange', pattern);
914
+ }
915
+ if (typeof callback === 'function') {
916
+ setImmediate(callback, null);
917
+ }
918
+ }
919
+ };
920
+
921
+ this.commands['unsubscribeObjects'] = (socket, pattern, callback) => {
922
+ if (this._checkPermissions(socket, 'unsubscribeObjects', callback, pattern)) {
923
+ if (pattern && typeof pattern === 'object' && pattern instanceof Array) {
924
+ for (let p = 0; p < pattern.length; p++) {
925
+ this.unsubscribe(socket, 'objectChange', pattern[p]);
926
+ }
927
+ } else {
928
+ this.unsubscribe(socket, 'objectChange', pattern);
929
+ }
930
+ if (typeof callback === 'function') {
931
+ setImmediate(callback, null);
932
+ }
933
+ }
934
+ };
935
+
936
+ this.commands['getObjectView'] = (socket, design, search, params, callback) => {
937
+ if (typeof callback === 'function') {
938
+ if (this._checkPermissions(socket, 'getObjectView', callback, search)) {
939
+ this.adapter.getObjectView(design, search, params, {user: socket._acl.user}, callback);
940
+ }
941
+ } else {
942
+ this.adapter.log.error('Callback is not a function');
943
+ }
944
+ };
945
+
946
+ this.commands['setObject'] = (socket, id, obj, callback) => {
947
+ if (this._checkPermissions(socket, 'setObject', callback, id)) {
948
+ this.adapter.setForeignObject(id, obj, {user: socket._acl.user}, (err, ...args) =>
949
+ SocketCommands._fixCallback(callback, err, ...args));
950
+ }
951
+ };
952
+ }
953
+
954
+ _initCommands() {
955
+ this.__initCommandsCommon();
956
+ this.__initCommandsObjects();
957
+ this.__initCommandsStates();
958
+ this.__initCommandsFiles();
959
+ }
960
+
961
+ applyCommands(socket) {
962
+ Object.keys(this.commands)
963
+ .forEach(command => socket.on(command, (...args) => {
964
+ if (this._updateSession(socket)) {
965
+ this.commands[command](socket, ...args);
966
+ }
967
+ }));
968
+ }
969
+
970
+ destroy() {
971
+ // could be overloaded
972
+ }
973
+ }
974
+
975
+ module.exports = SocketCommands;