@iobroker/js-controller-common 4.0.0-alpha.9-20211115-5dac659e → 4.0.5

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.
@@ -8,9 +8,11 @@ const forge = require('node-forge');
8
8
  const deepClone = require('deep-clone');
9
9
  const cpPromise = require('promisify-child-process');
10
10
  const jwt = require('jsonwebtoken');
11
- const {createInterface} = require('readline');
12
- const {PassThrough} = require('stream');
13
- const {detectPackageManager} = require('@alcalzone/pak');
11
+ const { createInterface } = require('readline');
12
+ const { PassThrough } = require('stream');
13
+ const { detectPackageManager } = require('@alcalzone/pak');
14
+ const EXIT_CODES = require('./exitCodes');
15
+ const zlib = require('zlib');
14
16
 
15
17
  // @ts-ignore
16
18
  require('events').EventEmitter.prototype._maxListeners = 100;
@@ -21,7 +23,7 @@ let password;
21
23
  let npmVersion;
22
24
  let crypto;
23
25
  let diskusage;
24
- const randomID = Math.round(Math.random() * 10000000000000); // Used for creation of User-Agent
26
+ const randomID = Math.round(Math.random() * 10000000000000); // Used for creation of User-Agent
25
27
  const VENDOR_FILE = '/etc/iob-vendor.json';
26
28
 
27
29
  let lastCalculationOfIps;
@@ -54,7 +56,12 @@ const FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~\p{Ll}\p{Lu}\p{Nd}]+/gu;
54
56
  */
55
57
  function copyAttributes(oldObj, newObj, originalObj, isNonEdit) {
56
58
  for (const attr of Object.keys(oldObj)) {
57
- if (typeof oldObj[attr] !== 'object' || oldObj[attr] instanceof Array) {
59
+ if (
60
+ oldObj[attr] === undefined ||
61
+ oldObj[attr] === null ||
62
+ typeof oldObj[attr] !== 'object' ||
63
+ oldObj[attr] instanceof Array
64
+ ) {
58
65
  if (oldObj[attr] === '__no_change__' && originalObj && !isNonEdit) {
59
66
  if (originalObj[attr] !== undefined) {
60
67
  newObj[attr] = deepClone(originalObj[attr]);
@@ -70,7 +77,12 @@ function copyAttributes(oldObj, newObj, originalObj, isNonEdit) {
70
77
  }
71
78
  } else {
72
79
  newObj[attr] = newObj[attr] || {};
73
- copyAttributes(oldObj[attr], newObj[attr], originalObj && originalObj[attr], isNonEdit || attr === 'nonEdit');
80
+ copyAttributes(
81
+ oldObj[attr],
82
+ newObj[attr],
83
+ originalObj && originalObj[attr],
84
+ isNonEdit || attr === 'nonEdit'
85
+ );
74
86
  }
75
87
  }
76
88
  }
@@ -121,7 +133,6 @@ function checkNonEditable(oldObject, newObject) {
121
133
  } else {
122
134
  newObject.nonEdit = oldObject.nonEdit;
123
135
  }
124
-
125
136
  } else if (newObject.nonEdit) {
126
137
  oldObject.nonEdit = deepClone(newObject.nonEdit);
127
138
  if (newObject.nonEdit.password) {
@@ -149,6 +160,7 @@ function checkNonEditable(oldObject, newObject) {
149
160
  /**
150
161
  * @param {string} repoVersion
151
162
  * @param {string} installedVersion
163
+ * @throws {Error} if version is invalid
152
164
  */
153
165
  function upToDate(repoVersion, installedVersion) {
154
166
  // Check if the installed version is at least the repo version
@@ -178,22 +190,49 @@ function decryptPhrase(password, data, callback) {
178
190
  decipher.write(data, 'hex');
179
191
  decipher.end();
180
192
  } catch (e) {
181
- console.error('Cannot decode secret: ' + e.message);
193
+ console.error(`Cannot decode secret: ${e.message}`);
182
194
  callback(null);
183
195
  }
184
196
  }
185
197
 
186
- function getAppName() {
187
- const parts = __dirname.replace(/\\/g, '/').split('/');
198
+ /**
199
+ * Checks if multiple host objects exists, without using object views
200
+ *
201
+ * @param {object} objects the objects db
202
+ * @return {Promise<boolean>} true if only one host object exists
203
+ */
204
+ async function isSingleHost(objects) {
205
+ const res = await objects.getObjectList({ startkey: 'system.host.', endkey: 'system.host.\u9999' });
206
+ const hostObjs = res.rows.filter(obj => obj.value && obj.value.type === 'host');
207
+ return hostObjs.length <= 1; // on setup no host object is there yet
208
+ }
188
209
 
189
- if (fs.existsSync(__dirname + '/../../../../packages/controller')) {
190
- // dev install
191
- return parts[parts.length - 5].split('.')[0];
210
+ /**
211
+ * Checks if at least one host is running in a MH environment
212
+ *
213
+ * @param {object} objects the objects db
214
+ * @param {object} states the states db
215
+ * @return Promise<boolean> true if one or more hosts running else false
216
+ */
217
+ async function isHostRunning(objects, states) {
218
+ const res = await objects.getObjectViewAsync('system', 'host', { startkey: '', endkey: '\u9999' });
219
+
220
+ for (const hostObj of res.rows) {
221
+ const state = await states.getState(`${hostObj.id}.alive`);
222
+ if (state.val) {
223
+ return true;
224
+ }
192
225
  }
226
+ return false;
227
+ }
193
228
 
194
- // find @orga folder
195
- return parts[parts.length - 4].replace('@', '');
229
+ function getAppName() {
230
+ if (fs.existsSync(__dirname + '/../../../../packages/controller')) {
231
+ // dev install - GitHub folder is uppercase
232
+ return 'ioBroker';
233
+ }
196
234
 
235
+ return 'iobroker';
197
236
  }
198
237
 
199
238
  function rmdirRecursiveSync(path) {
@@ -224,9 +263,12 @@ function findIPs() {
224
263
  try {
225
264
  const ifaces = require('os').networkInterfaces();
226
265
  Object.keys(ifaces).forEach(dev =>
227
- ifaces[dev].forEach(details =>
228
- // noinspection JSUnresolvedVariable
229
- !details.internal && ownIpArr.push(details.address)));
266
+ ifaces[dev].forEach(
267
+ details =>
268
+ // noinspection JSUnresolvedVariable
269
+ !details.internal && ownIpArr.push(details.address)
270
+ )
271
+ );
230
272
  } catch (e) {
231
273
  console.error(`Can not find local IPs: ${e.message}`);
232
274
  }
@@ -239,12 +281,10 @@ function findPath(path, url) {
239
281
  if (!url) {
240
282
  return '';
241
283
  }
242
- if (url.substring(0, 'http://'.length) === 'http://' ||
243
- url.substring(0, 'https://'.length) === 'https://') {
284
+ if (url.substring(0, 'http://'.length) === 'http://' || url.substring(0, 'https://'.length) === 'https://') {
244
285
  return url;
245
286
  } else {
246
- if (path.substring(0, 'http://'.length) === 'http://' ||
247
- path.substring(0, 'https://'.length) === 'https://') {
287
+ if (path.substring(0, 'http://'.length) === 'http://' || path.substring(0, 'https://'.length) === 'https://') {
248
288
  return (path + url).replace(/\/\//g, '/').replace('http:/', 'http://').replace('https:/', 'https://');
249
289
  } else {
250
290
  if (url[0] === '/') {
@@ -257,11 +297,11 @@ function findPath(path, url) {
257
297
  }
258
298
 
259
299
  function getMac(callback) {
260
- const macRegex = /(?:[a-z0-9]{2}[:-]){5}[a-z0-9]{2}/ig;
300
+ const macRegex = /(?:[a-z0-9]{2}[:-]){5}[a-z0-9]{2}/gi;
261
301
  const zeroRegex = /(?:[0]{2}[:-]){5}[0]{2}/;
262
- const command = (process.platform.indexOf('win') === 0) ? 'getmac' : 'ifconfig || ip link';
302
+ const command = process.platform.indexOf('win') === 0 ? 'getmac' : 'ifconfig || ip link';
263
303
 
264
- require('child_process').exec(command, {windowsHide: true}, (err, stdout, _stderr) => {
304
+ require('child_process').exec(command, { windowsHide: true }, (err, stdout, _stderr) => {
265
305
  if (err) {
266
306
  callback(err);
267
307
  } else {
@@ -323,7 +363,7 @@ function uuid(givenMac, callback) {
323
363
  return callback('55travis-pipe-line-cior-githubaction');
324
364
  }
325
365
 
326
- let mac = givenMac !== null ? (givenMac || '') : null;
366
+ let mac = givenMac !== null ? givenMac || '' : null;
327
367
  let u;
328
368
 
329
369
  if (!_isDocker && mac === '') {
@@ -352,7 +392,16 @@ function uuid(givenMac, callback) {
352
392
  const md5sum = require('crypto').createHash('md5');
353
393
  md5sum.update(mac);
354
394
  mac = md5sum.digest('hex');
355
- u = mac.substring(0, 8) + '-' + mac.substring(8, 12) + '-' + mac.substring(12, 16) + '-' + mac.substring(16, 20) + '-' + mac.substring(20);
395
+ u =
396
+ mac.substring(0, 8) +
397
+ '-' +
398
+ mac.substring(8, 12) +
399
+ '-' +
400
+ mac.substring(12, 16) +
401
+ '-' +
402
+ mac.substring(16, 20) +
403
+ '-' +
404
+ mac.substring(20);
356
405
  } else {
357
406
  // Returns a RFC4122 compliant v4 UUID https://gist.github.com/LeverOne/1308368 (DO WTF YOU WANT TO PUBLIC LICENSE)
358
407
  /** @type {any} */
@@ -360,7 +409,7 @@ function uuid(givenMac, callback) {
360
409
  let b;
361
410
  b = a = '';
362
411
  while (a++ < 36) {
363
- b += ((a * 51) & 52) ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) : '-';
412
+ b += (a * 51) & 52 ? (a ^ 15 ? 8 ^ (Math.random() * (a ^ 20 ? 16 : 4)) : 4).toString(16) : '-';
364
413
  }
365
414
  u = b;
366
415
  }
@@ -383,62 +432,75 @@ function updateUuid(newUuid, _objects, callback) {
383
432
  }
384
433
  }
385
434
 
386
- _objects.setObject('system.meta.uuid', {
387
- type: 'meta',
388
- common: {
389
- name: 'uuid',
390
- type: 'uuid'
435
+ _objects.setObject(
436
+ 'system.meta.uuid',
437
+ {
438
+ type: 'meta',
439
+ common: {
440
+ name: 'uuid',
441
+ type: 'uuid'
442
+ },
443
+ ts: new Date().getTime(),
444
+ from: 'system.host.' + getHostName() + '.tools',
445
+ native: {
446
+ uuid: _uuid
447
+ }
391
448
  },
392
- ts: new Date().getTime(),
393
- from: 'system.host.' + getHostName() + '.tools',
394
- native: {
395
- uuid: _uuid
396
- }
397
- }, err => {
398
- if (err) {
399
- console.error('object system.meta.uuid cannot be updated: ' + err);
400
- callback();
401
- } else {
402
- _objects.getObject('system.meta.uuid', (err, obj) => {
403
- if (obj.native.uuid !== _uuid) {
404
- console.error('object system.meta.uuid cannot be updated: write protected');
405
- } else {
406
- console.log('object system.meta.uuid created: ' + _uuid);
407
- }
408
-
409
- callback(_uuid);
410
- });
449
+ err => {
450
+ if (err) {
451
+ console.error('object system.meta.uuid cannot be updated: ' + err);
452
+ callback();
453
+ } else {
454
+ _objects.getObject('system.meta.uuid', (err, obj) => {
455
+ if (obj.native.uuid !== _uuid) {
456
+ console.error('object system.meta.uuid cannot be updated: write protected');
457
+ } else {
458
+ console.log('object system.meta.uuid created: ' + _uuid);
459
+ }
460
+ callback(_uuid);
461
+ });
462
+ }
411
463
  }
412
- });
464
+ );
413
465
  });
414
466
  }
415
467
 
416
- function createUuid(_objects, callback) {
468
+ /**
469
+ * Generates a new uuid if non existing
470
+ *
471
+ * @param {object} objects - objects DB
472
+ * @return {Promise<void|string>} uuid if successfully created/updated
473
+ */
474
+ async function createUuid(objects) {
417
475
  const promiseCheckPassword = new Promise(resolve =>
418
- _objects.getObject('system.user.admin', (err, obj) => {
476
+ objects.getObject('system.user.admin', (err, obj) => {
419
477
  if (err || !obj) {
420
478
  password = password || require('./password');
421
479
 
422
480
  // Default Password for user 'admin' is application name in lower case
423
- password(getAppName()).hash(null, null, (err, res) => {
481
+ password(module.exports.appName).hash(null, null, (err, res) => {
424
482
  err && console.error(err);
425
483
 
426
484
  // Create user here and not in io-package.js because of hash password
427
- _objects.setObject('system.user.admin', {
428
- type: 'user',
429
- common: {
430
- name: 'admin',
431
- password: res,
432
- dontDelete: true,
433
- enabled: true
485
+ objects.setObject(
486
+ 'system.user.admin',
487
+ {
488
+ type: 'user',
489
+ common: {
490
+ name: 'admin',
491
+ password: res,
492
+ dontDelete: true,
493
+ enabled: true
494
+ },
495
+ ts: new Date().getTime(),
496
+ from: `system.host.${getHostName()}.tools`,
497
+ native: {}
434
498
  },
435
- ts: new Date().getTime(),
436
- from: 'system.host.' + getHostName() + '.tools',
437
- native: {}
438
- }, () => {
439
- console.log('object system.user.admin created');
440
- resolve();
441
- });
499
+ () => {
500
+ console.log('object system.user.admin created');
501
+ resolve();
502
+ }
503
+ );
442
504
  });
443
505
  } else {
444
506
  resolve();
@@ -446,7 +508,7 @@ function createUuid(_objects, callback) {
446
508
  })
447
509
  );
448
510
  const promiseCheckUuid = new Promise(resolve =>
449
- _objects.getObject('system.meta.uuid', (err, obj) => {
511
+ objects.getObject('system.meta.uuid', (err, obj) => {
450
512
  if (!err && obj && obj.native && obj.native.uuid) {
451
513
  const PROBLEM_UUIDS = [
452
514
  'ab265f4a-67f9-a46a-c0b2-61e4b95cefe5',
@@ -460,10 +522,10 @@ function createUuid(_objects, callback) {
460
522
  // if COMMON invalid docker uuid
461
523
  if (PROBLEM_UUIDS.includes(obj.native.uuid)) {
462
524
  // Read vis license
463
- _objects.getObject('system.adapter.vis.0', (err, licObj) => {
525
+ objects.getObject('system.adapter.vis.0', (err, licObj) => {
464
526
  if (!licObj || !licObj.native || !licObj.native.license) {
465
527
  // generate new UUID
466
- updateUuid('', _objects, _uuid => resolve(_uuid));
528
+ updateUuid('', objects, _uuid => resolve(_uuid));
467
529
  } else {
468
530
  // decode obj.native.license
469
531
  let data;
@@ -475,14 +537,18 @@ function createUuid(_objects, callback) {
475
537
 
476
538
  if (!data || !data.uuid) {
477
539
  // generate new UUID
478
- updateUuid('', _objects, __uuid => resolve(__uuid));
540
+ updateUuid('', objects, __uuid => resolve(__uuid));
479
541
  } else {
480
542
  if (data.uuid !== obj.native.uuid) {
481
- updateUuid(data.correct ? data.uuid : '', _objects, _uuid => resolve(_uuid));
543
+ updateUuid(data.correct ? data.uuid : '', objects, _uuid => resolve(_uuid));
482
544
  } else {
483
545
  // Show error
484
- console.warn(`Your iobroker.vis license must be updated. Please contact info@iobroker.net to get a new license!`);
485
- console.warn(`Provide following information in email: ${data.email}, invoice: ${data.invoice}`);
546
+ console.warn(
547
+ `Your iobroker.vis license must be updated. Please contact info@iobroker.net to get a new license!`
548
+ );
549
+ console.warn(
550
+ `Provide following information in email: ${data.email}, invoice: ${data.invoice}`
551
+ );
486
552
  resolve();
487
553
  }
488
554
  }
@@ -493,13 +559,13 @@ function createUuid(_objects, callback) {
493
559
  }
494
560
  } else {
495
561
  // generate new UUID
496
- updateUuid('', _objects, _uuid => resolve(_uuid));
562
+ updateUuid('', objects, _uuid => resolve(_uuid));
497
563
  }
498
564
  })
499
565
  );
500
566
 
501
- return Promise.all([promiseCheckPassword, promiseCheckUuid])
502
- .then(result => callback && callback(result[1]));
567
+ const result = await Promise.all([promiseCheckPassword, promiseCheckUuid]);
568
+ return result[1];
503
569
  }
504
570
 
505
571
  // Download file to tmp or return file name directly
@@ -507,41 +573,41 @@ function getFile(urlOrPath, fileName, callback) {
507
573
  request = request || require('request');
508
574
 
509
575
  // If object was read
510
- if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
511
- urlOrPath.substring(0, 'https://'.length) === 'https://') {
512
- const tmpFile = `${__dirname}/../tmp/${fileName || Math.floor(Math.random() * 0xFFFFFFE) + '.zip'}`;
576
+ if (
577
+ urlOrPath.substring(0, 'http://'.length) === 'http://' ||
578
+ urlOrPath.substring(0, 'https://'.length) === 'https://'
579
+ ) {
580
+ const tmpFile = `${__dirname}/../tmp/${fileName || Math.floor(Math.random() * 0xffffffe) + '.zip'}`;
513
581
  // Add some information to user-agent, like chrome, IE and Firefox do
514
582
  request({
515
583
  url: urlOrPath,
516
584
  gzip: true,
517
- headers: {'User-Agent': `${module.exports.appName}, RND: ${randomID}, N: ${process.version}`}
518
- }).on('error', error => {
519
- console.log(`Cannot download "${tmpFile}": ${error}`);
520
- if (callback) {
521
- callback(tmpFile);
522
- }
523
- }).pipe(fs.createWriteStream(tmpFile)).on('close', () => {
524
- console.log('downloaded ' + tmpFile);
525
- if (callback) {
526
- callback(tmpFile);
527
- }
528
- });
585
+ headers: { 'User-Agent': `${module.exports.appName}, RND: ${randomID}, N: ${process.version}` }
586
+ })
587
+ .on('error', error => {
588
+ console.log(`Cannot download "${tmpFile}": ${error}`);
589
+ callback && callback(tmpFile);
590
+ })
591
+ .pipe(fs.createWriteStream(tmpFile))
592
+ .on('close', () => {
593
+ console.log('downloaded ' + tmpFile);
594
+ callback && callback(tmpFile);
595
+ });
529
596
  } else {
530
- if (fs.existsSync(urlOrPath)) {
531
- if (callback) {
532
- callback(urlOrPath);
533
- }
534
- } else if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
535
- if (callback) {
536
- callback(__dirname + '/../' + urlOrPath);
537
- }
538
- } else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) {
539
- if (callback) {
540
- callback(__dirname + '/../tmp/' + urlOrPath);
597
+ try {
598
+ if (fs.existsSync(urlOrPath)) {
599
+ callback && callback(urlOrPath);
600
+ } else if (fs.existsSync(`${__dirname}/../${urlOrPath}`)) {
601
+ callback && callback(`${__dirname}/../${urlOrPath}`);
602
+ } else if (fs.existsSync(`${__dirname}/../tmp/${urlOrPath}`)) {
603
+ callback && callback(`${__dirname}/../tmp/${urlOrPath}`);
604
+ } else {
605
+ console.log('File not found: ' + urlOrPath);
606
+ process.exit(EXIT_CODES.FILE_NOT_FOUND);
541
607
  }
542
- } else {
543
- console.log('File not found: ' + urlOrPath);
544
- process.exit(1);
608
+ } catch (err) {
609
+ console.log(`File "${urlOrPath}" could no be read: ${err.message}`);
610
+ process.exit(EXIT_CODES.FILE_NOT_FOUND);
545
611
  }
546
612
  }
547
613
  }
@@ -567,35 +633,40 @@ function getJson(urlOrPath, agent, callback) {
567
633
  callback(null);
568
634
  }
569
635
  } else {
570
- if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
571
- urlOrPath.substring(0, 'https://'.length) === 'https://') {
572
- request({
573
- url: urlOrPath,
574
- timeout: 10000,
575
- gzip: true,
576
- headers: {'User-Agent': agent}
577
- }, (error, response, body) => {
578
- if (error || !body || response.statusCode !== 200) {
579
- console.warn('Cannot download json from ' + urlOrPath + '. Error: ' + (error || body));
580
- if (callback) {
581
- callback(null, urlOrPath);
636
+ if (
637
+ urlOrPath.substring(0, 'http://'.length) === 'http://' ||
638
+ urlOrPath.substring(0, 'https://'.length) === 'https://'
639
+ ) {
640
+ request(
641
+ {
642
+ url: urlOrPath,
643
+ timeout: 10000,
644
+ gzip: true,
645
+ headers: { 'User-Agent': agent }
646
+ },
647
+ (error, response, body) => {
648
+ if (error || !body || response.statusCode !== 200) {
649
+ console.warn(`Cannot download json from ${urlOrPath}. Error: ${error || body}`);
650
+ if (callback) {
651
+ callback(null, urlOrPath);
652
+ }
653
+ return;
582
654
  }
583
- return;
584
- }
585
- try {
586
- sources = JSON.parse(body);
587
- } catch {
588
- console.error('Json file is invalid on ' + urlOrPath);
589
- if (callback) {
590
- callback(null, urlOrPath);
655
+ try {
656
+ sources = JSON.parse(body);
657
+ } catch {
658
+ console.error('Json file is invalid on ' + urlOrPath);
659
+ if (callback) {
660
+ callback(null, urlOrPath);
661
+ }
662
+ return;
591
663
  }
592
- return;
593
- }
594
664
 
595
- if (callback) {
596
- callback(sources, urlOrPath);
665
+ if (callback) {
666
+ callback(sources, urlOrPath);
667
+ }
597
668
  }
598
- }).on('error', _error => {
669
+ ).on('error', _error => {
599
670
  //console.log('Cannot download json from ' + urlOrPath + '. Error: ' + error);
600
671
  //if (callback) callback(null, urlOrPath);
601
672
  });
@@ -617,7 +688,9 @@ function getJson(urlOrPath, agent, callback) {
617
688
  try {
618
689
  sources = fs.readJSONSync(__dirname + '/../' + urlOrPath);
619
690
  } catch (e) {
620
- console.log('Cannot parse json file from ' + __dirname + '/../' + urlOrPath + '. Error: ' + e.message);
691
+ console.log(
692
+ 'Cannot parse json file from ' + __dirname + '/../' + urlOrPath + '. Error: ' + e.message
693
+ );
621
694
  if (callback) {
622
695
  callback(null, urlOrPath);
623
696
  }
@@ -630,7 +703,9 @@ function getJson(urlOrPath, agent, callback) {
630
703
  try {
631
704
  sources = fs.readJSONSync(__dirname + '/../tmp/' + urlOrPath);
632
705
  } catch (e) {
633
- console.log('Cannot parse json file from ' + __dirname + '/../tmp/' + urlOrPath + '. Error: ' + e.message);
706
+ console.log(
707
+ 'Cannot parse json file from ' + __dirname + '/../tmp/' + urlOrPath + '. Error: ' + e.message
708
+ );
634
709
  if (callback) {
635
710
  callback(null, urlOrPath);
636
711
  }
@@ -649,6 +724,69 @@ function getJson(urlOrPath, agent, callback) {
649
724
  }
650
725
  }
651
726
 
727
+ /**
728
+ * Return content of the json file. Download it or read directly
729
+ * @param {string|object} urlOrPath URL where the json file could be found
730
+ * @param {string} agent optional agent identifier like "Windows Chrome 12.56"
731
+ * @returns {object} json object
732
+ */
733
+ async function getJsonAsync(urlOrPath, agent) {
734
+ agent = agent || '';
735
+
736
+ axios = axios || require('axios');
737
+ let sources = {};
738
+ // If object was read
739
+ if (urlOrPath && typeof urlOrPath === 'object') {
740
+ return urlOrPath;
741
+ } else if (!urlOrPath) {
742
+ console.log('Empty url!');
743
+ return null;
744
+ } else {
745
+ if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://')) {
746
+ try {
747
+ const result = await axios(urlOrPath, {
748
+ timeout: 10000,
749
+ headers: { 'User-Agent': agent },
750
+ validateStatus: status => status !== 200
751
+ });
752
+ return result.data;
753
+ } catch (error) {
754
+ console.warn(`Cannot download json from ${urlOrPath}. Error: ${error}`);
755
+ return null;
756
+ }
757
+ } else {
758
+ if (fs.existsSync(urlOrPath)) {
759
+ try {
760
+ sources = fs.readJSONSync(urlOrPath);
761
+ } catch (e) {
762
+ console.warn(`Cannot parse json file from ${urlOrPath}. Error: ${e.message}`);
763
+ return null;
764
+ }
765
+ return sources;
766
+ } else if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
767
+ try {
768
+ sources = fs.readJSONSync(`${__dirname}/../${urlOrPath}`);
769
+ } catch (e) {
770
+ console.warn(`Cannot parse json file from ${__dirname}/../${urlOrPath}. Error: ${e.message}`);
771
+ return null;
772
+ }
773
+ return sources;
774
+ } else if (fs.existsSync(`${__dirname}/../tmp/${urlOrPath}`)) {
775
+ try {
776
+ sources = fs.readJSONSync(`${__dirname}/../tmp/${urlOrPath}`);
777
+ } catch (e) {
778
+ console.log(`Cannot parse json file from ${__dirname}/../tmp/${urlOrPath}. Error: ${e.message}`);
779
+ return null;
780
+ }
781
+ return sources;
782
+ } else {
783
+ //if (urlOrPath.indexOf('/example/') === -1) console.log('Json file not found: ' + urlOrPath);
784
+ return null;
785
+ }
786
+ }
787
+ }
788
+ }
789
+
652
790
  function scanDirectory(dirName, list, regExp) {
653
791
  if (fs.existsSync(dirName)) {
654
792
  let dirs;
@@ -665,10 +803,10 @@ function scanDirectory(dirName, list, regExp) {
665
803
  const fileName = path.join(fullPath, 'package.json');
666
804
  if (regExp.test(dirs[i]) && fs.existsSync(fileIoName)) {
667
805
  const ioPackage = fs.readJSONSync(fileIoName);
668
- const package_ = fs.existsSync(fileName)
669
- ? fs.readJSONSync(fileName)
670
- : {};
671
- const localIcon = (ioPackage.common.icon ? `/adapter/${dirs[i].substring(module.exports.appName.length + 1)}/${ioPackage.common.icon}` : '');
806
+ const package_ = fs.existsSync(fileName) ? fs.readJSONSync(fileName) : {};
807
+ const localIcon = ioPackage.common.icon
808
+ ? `/adapter/${dirs[i].substring(module.exports.appName.length + 1)}/${ioPackage.common.icon}`
809
+ : '';
672
810
  //noinspection JSUnresolvedVariable
673
811
  list[ioPackage.common.name] = {
674
812
  controller: ioPackage.common.controller || false,
@@ -682,12 +820,18 @@ function scanDirectory(dirName, list, regExp) {
682
820
  keywords: ioPackage.common.keywords,
683
821
  readme: ioPackage.common.readme,
684
822
  type: ioPackage.common.type,
685
- license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''),
686
- licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : ''
823
+ license: ioPackage.common.license
824
+ ? ioPackage.common.license
825
+ : package_.licenses && package_.licenses.length
826
+ ? package_.licenses[0].type
827
+ : '',
828
+ licenseUrl: package_.licenses && package_.licenses.length ? package_.licenses[0].url : ''
687
829
  };
688
830
  }
689
831
  } catch (e) {
690
- console.log(`Cannot read or parse ${__dirname}/../node_modules/${dirs[i]}/io-package.json: ${e.message}`);
832
+ console.log(
833
+ `Cannot read or parse ${__dirname}/../node_modules/${dirs[i]}/io-package.json: ${e.message}`
834
+ );
691
835
  }
692
836
  }
693
837
  }
@@ -709,7 +853,9 @@ function getInstalledInfo(hostRunningVersion) {
709
853
  } catch (e) {
710
854
  console.error(`Cannot get installed host information: ${e.message}`);
711
855
  }
712
- const package_ = fs.existsSync(path.join(fullPath, 'package.json')) ? fs.readJSONSync(path.join(fullPath, 'package.json')) : {};
856
+ const package_ = fs.existsSync(path.join(fullPath, 'package.json'))
857
+ ? fs.readJSONSync(path.join(fullPath, 'package.json'))
858
+ : {};
713
859
  const regExp = new RegExp(`^${module.exports.appName}\\.`, 'i');
714
860
 
715
861
  if (ioPackage) {
@@ -724,20 +870,19 @@ function getInstalledInfo(hostRunningVersion) {
724
870
  keywords: ioPackage.common.keywords,
725
871
  readme: ioPackage.common.readme,
726
872
  runningVersion: hostRunningVersion,
727
- license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''),
728
- licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : ''
873
+ license: ioPackage.common.license
874
+ ? ioPackage.common.license
875
+ : package_.licenses && package_.licenses.length
876
+ ? package_.licenses[0].type
877
+ : '',
878
+ licenseUrl: package_.licenses && package_.licenses.length ? package_.licenses[0].url : ''
729
879
  };
730
880
  }
731
881
 
732
- scanDirectory(path.join(__dirname, '../node_modules'), result, regExp);
733
- scanDirectory(path.join(__dirname, '../../node_modules'), result, regExp);
882
+ // we scan the sub node modules of controller and same hierarchy as controller
883
+ scanDirectory(path.join(fullPath, 'node_modules'), result, regExp);
884
+ scanDirectory(path.join(fullPath, '..'), result, regExp);
734
885
 
735
- if (
736
- fs.existsSync(path.join(__dirname, `../../../node_modules/${module.exports.appName.toLowerCase()}.js-controller`)) ||
737
- fs.existsSync(path.join(__dirname, `../../../node_modules/${module.exports.appName}.js-controller`))
738
- ) {
739
- scanDirectory(path.join(__dirname, '../..'), result, regExp);
740
- }
741
886
  return result;
742
887
  }
743
888
 
@@ -753,7 +898,7 @@ function getNpmVersion(adapter, callback) {
753
898
  const cliCommand = `npm view ${adapter}@latest version`;
754
899
 
755
900
  const exec = require('child_process').exec;
756
- exec(cliCommand, {timeout: 2000, windowsHide: true}, (error, stdout, _stderr) => {
901
+ exec(cliCommand, { timeout: 2000, windowsHide: true }, (error, stdout, _stderr) => {
757
902
  let version;
758
903
  if (error) {
759
904
  // command failed
@@ -832,8 +977,10 @@ function getIoPack(sources, name, callback) {
832
977
  callback(sources, name);
833
978
  }
834
979
  } else {
835
- if (sources[name].meta.substring(0, 'http://'.length) === 'http://' ||
836
- sources[name].meta.substring(0, 'https://'.length) === 'https://') {
980
+ if (
981
+ sources[name].meta.substring(0, 'http://'.length) === 'http://' ||
982
+ sources[name].meta.substring(0, 'https://'.length) === 'https://'
983
+ ) {
837
984
  //installed from npm
838
985
  getNpmVersion(name, (_err, version) => {
839
986
  if (version) {
@@ -867,7 +1014,7 @@ function _getRepositoryFile(sources, path, callback) {
867
1014
  }
868
1015
  count++;
869
1016
  }
870
- sources._helper = {failCounter: []};
1017
+ sources._helper = { failCounter: [] };
871
1018
 
872
1019
  sources._helper.timeout = setTimeout(() => {
873
1020
  if (sources._helper) {
@@ -918,8 +1065,7 @@ function _getRepositoryFile(sources, path, callback) {
918
1065
  callback = null;
919
1066
  } else {
920
1067
  // process next
921
- setImmediate(() =>
922
- _getRepositoryFile(sources, path, callback));
1068
+ setImmediate(() => _getRepositoryFile(sources, path, callback));
923
1069
  }
924
1070
  }
925
1071
  });
@@ -954,7 +1100,7 @@ function _checkRepositoryFileHash(urlOrPath, additionalInfo, callback) {
954
1100
  if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://')) {
955
1101
  urlOrPath = urlOrPath.replace(/\.json$/, '-hash.json');
956
1102
  let json = null;
957
- request({url: urlOrPath, timeout: 10000, gzip: true}, (error, response, body) => {
1103
+ request({ url: urlOrPath, timeout: 10000, gzip: true }, (error, response, body) => {
958
1104
  if (error || !body || response.statusCode !== 200) {
959
1105
  console.warn(`Cannot download json from ${urlOrPath}. Error: ${error || body}`);
960
1106
  } else {
@@ -1055,7 +1201,9 @@ function getRepositoryFile(urlOrPath, additionalInfo, callback) {
1055
1201
  let agent = '';
1056
1202
  if (additionalInfo) {
1057
1203
  // Add some information to user-agent, like chrome, IE and Firefox do
1058
- agent = `${additionalInfo.name}, RND: ${additionalInfo.randomID || randomID}, Node:${additionalInfo.node}, V:${additionalInfo.controller}`;
1204
+ agent = `${additionalInfo.name}, RND: ${additionalInfo.randomID || randomID}, Node:${
1205
+ additionalInfo.node
1206
+ }, V:${additionalInfo.controller}`;
1059
1207
  }
1060
1208
 
1061
1209
  // load hash of file first to not load the whole 1MB of sources
@@ -1075,11 +1223,21 @@ function getRepositoryFile(urlOrPath, additionalInfo, callback) {
1075
1223
  _getRepositoryFile(sources, path, err => {
1076
1224
  err && console.error(`[${new Date()}] ${err}`);
1077
1225
  typeof callback === 'function' && callback(err, sources, actualSourcesHash);
1078
- }));
1226
+ })
1227
+ );
1079
1228
  } else {
1080
1229
  // return cached sources, because no sources found
1081
- console.log(`failed to download new sources, ${additionalInfo.sources ? 'use cached sources' : 'no cached sources available'}`);
1082
- return maybeCallbackWithError(callback, `Cannot read "${urlOrPath}"`, additionalInfo.sources, '');
1230
+ console.log(
1231
+ `failed to download new sources, ${
1232
+ additionalInfo.sources ? 'use cached sources' : 'no cached sources available'
1233
+ }`
1234
+ );
1235
+ return maybeCallbackWithError(
1236
+ callback,
1237
+ `Cannot read "${urlOrPath}"`,
1238
+ additionalInfo.sources,
1239
+ ''
1240
+ );
1083
1241
  }
1084
1242
  });
1085
1243
  }
@@ -1102,7 +1260,7 @@ async function getRepositoryFileAsync(url, hash, force, _actualRepo) {
1102
1260
  let _hash;
1103
1261
  if (_actualRepo && !force && hash && (url.startsWith('http://') || url.startsWith('https://'))) {
1104
1262
  axios = axios || require('axios');
1105
- _hash = await axios({url: url.replace(/\.json$/, '-hash.json'), timeout: 10000});
1263
+ _hash = await axios({ url: url.replace(/\.json$/, '-hash.json'), timeout: 10000 });
1106
1264
  if (_hash && _hash.data && hash === _hash.data.hash) {
1107
1265
  return _actualRepo;
1108
1266
  }
@@ -1113,13 +1271,15 @@ async function getRepositoryFileAsync(url, hash, force, _actualRepo) {
1113
1271
  if (url.startsWith('http://') || url.startsWith('https://')) {
1114
1272
  axios = axios || require('axios');
1115
1273
  if (!_hash) {
1116
- _hash = await axios({url: url.replace(/\.json$/, '-hash.json'), timeout: 10000});
1274
+ _hash = await axios({ url: url.replace(/\.json$/, '-hash.json'), timeout: 10000 });
1117
1275
  }
1118
1276
 
1119
1277
  if (_actualRepo && hash && _hash && _hash.data && _hash.data.hash === hash) {
1120
1278
  data = _actualRepo;
1121
1279
  } else {
1122
- const agent = `${module.exports.appName}, RND: ${randomID}, Node:${process.version}, V:${require('../../package.json').version}`;
1280
+ const agent = `${module.exports.appName}, RND: ${randomID}, Node:${process.version}, V:${
1281
+ require('../../package.json').version
1282
+ }`;
1123
1283
  data = await axios({
1124
1284
  url,
1125
1285
  timeout: 10000,
@@ -1139,27 +1299,35 @@ async function getRepositoryFileAsync(url, hash, force, _actualRepo) {
1139
1299
  }
1140
1300
  }
1141
1301
 
1142
- return {json: data, changed: _hash && _hash.data ? hash !== _hash.data.hash : true, hash: _hash && _hash.data ? _hash.data.hash : ''};
1302
+ return {
1303
+ json: data,
1304
+ changed: _hash && _hash.data ? hash !== _hash.data.hash : true,
1305
+ hash: _hash && _hash.data ? _hash.data.hash : ''
1306
+ };
1143
1307
  }
1144
1308
 
1145
- function sendDiagInfo(obj, callback) {
1146
- request = request || require('request');
1147
-
1148
- console.log(`Send diag info: ${JSON.stringify(obj)}`);
1309
+ /**
1310
+ * Sends the given object to the diagnosis server
1311
+ *
1312
+ * @param {object} obj - diagnosis object
1313
+ * @return {Promise<void>}
1314
+ */
1315
+ async function sendDiagInfo(obj) {
1316
+ const objStr = JSON.stringify(obj);
1317
+ console.log(`Send diag info: ${objStr}`);
1149
1318
  axios = axios || require('axios');
1150
1319
  const params = new URLSearchParams();
1151
- params.append('data', JSON.stringify(obj));
1320
+ params.append('data', objStr);
1152
1321
  const config = {
1153
- headers: {'Content-Type': 'application/x-www-form-urlencoded'},
1322
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1154
1323
  timeout: 4000
1155
1324
  };
1156
1325
 
1157
- return axios.post(`http://download.${module.exports.appName}.net/diag.php`, params, config)
1158
- .then(() => typeof callback === 'function' && callback())
1159
- .catch(error => {
1160
- console.log(`Cannot send diag info: ${error.message}`);
1161
- typeof callback === 'function' && callback(error);
1162
- });
1326
+ try {
1327
+ await axios.post(`http://download.${module.exports.appName}.net/diag.php`, params, config);
1328
+ } catch (e) {
1329
+ console.log(`Cannot send diag info: ${e.message}`);
1330
+ }
1163
1331
  }
1164
1332
 
1165
1333
  /**
@@ -1182,10 +1350,7 @@ function getAdapterDir(adapter) {
1182
1350
  adapter = adapter.substr(0, adapter.lastIndexOf('.'));
1183
1351
  }
1184
1352
 
1185
- const possibilities = [
1186
- `${appName.toLowerCase()}.${adapter}/package.json`,
1187
- `${appName}.${adapter}/package.json`
1188
- ];
1353
+ const possibilities = [`${appName.toLowerCase()}.${adapter}/package.json`, `${appName}.${adapter}/package.json`];
1189
1354
 
1190
1355
  /** @type {string} */
1191
1356
  let adapterPath;
@@ -1267,7 +1432,8 @@ function getSystemNpmVersion(callback) {
1267
1432
  }
1268
1433
  }, 10000);
1269
1434
 
1270
- exec('npm -v', {encoding: 'utf8', env: newEnv, windowsHide: true}, (error, stdout) => {//, stderr) {
1435
+ exec('npm -v', { encoding: 'utf8', env: newEnv, windowsHide: true }, (error, stdout) => {
1436
+ //, stderr) {
1271
1437
  if (timeout) {
1272
1438
  clearTimeout(timeout);
1273
1439
  timeout = null;
@@ -1301,19 +1467,19 @@ const getSystemNpmVersionAsync = promisify(getSystemNpmVersion);
1301
1467
  * Installs a node module using npm or a similar package manager
1302
1468
  * @param {string} npmUrl Which node module to install
1303
1469
  * @param {InstallNodeModuleOptions} options Options for the installation
1304
- * @returns {Promise<import("@alcalzone/pak").CommandResult>}
1470
+ * @returns {Promise<import('@alcalzone/pak').CommandResult>}
1305
1471
  */
1306
1472
  async function installNodeModule(npmUrl, options = {}) {
1307
1473
  // Figure out which package manager is in charge (probably npm at this point)
1308
1474
  const pak = await detectPackageManager(
1309
1475
  typeof options.cwd === 'string'
1310
- // If a cwd was provided, use it
1311
- ? {cwd: options.cwd}
1312
- // Otherwise find the ioBroker root dir
1313
- : {
1314
- cwd: __dirname,
1315
- setCwdToPackageRoot: true
1316
- }
1476
+ ? // If a cwd was provided, use it
1477
+ { cwd: options.cwd }
1478
+ : // Otherwise find the ioBroker root dir
1479
+ {
1480
+ cwd: __dirname,
1481
+ setCwdToPackageRoot: true
1482
+ }
1317
1483
  );
1318
1484
  // By default, don't print all the stuff the package manager spits out
1319
1485
  if (!options.debug) {
@@ -1332,7 +1498,7 @@ async function installNodeModule(npmUrl, options = {}) {
1332
1498
  }
1333
1499
 
1334
1500
  // And install the module
1335
- /** @type {import("@alcalzone/pak").InstallOptions} */
1501
+ /** @type {import('@alcalzone/pak').InstallOptions} */
1336
1502
  const installOpts = {};
1337
1503
  if (options.unsafePerm) {
1338
1504
  installOpts.additionalArgs = ['--unsafe-perm'];
@@ -1350,19 +1516,19 @@ async function installNodeModule(npmUrl, options = {}) {
1350
1516
  * Uninstalls a node module using npm or a similar package manager
1351
1517
  * @param {string} packageName Which node module to uninstall
1352
1518
  * @param {UninstallNodeModuleOptions} options Options for the installation
1353
- * @returns {Promise<import("@alcalzone/pak").CommandResult>}
1519
+ * @returns {Promise<import('@alcalzone/pak').CommandResult>}
1354
1520
  */
1355
1521
  async function uninstallNodeModule(packageName, options = {}) {
1356
1522
  // Figure out which package manager is in charge (probably npm at this point)
1357
1523
  const pak = await detectPackageManager(
1358
1524
  typeof options.cwd === 'string'
1359
- // If a cwd was provided, use it
1360
- ? {cwd: options.cwd}
1361
- // Otherwise find the ioBroker root dir
1362
- : {
1363
- cwd: __dirname,
1364
- setCwdToPackageRoot: true
1365
- }
1525
+ ? // If a cwd was provided, use it
1526
+ { cwd: options.cwd }
1527
+ : // Otherwise find the ioBroker root dir
1528
+ {
1529
+ cwd: __dirname,
1530
+ setCwdToPackageRoot: true
1531
+ }
1366
1532
  );
1367
1533
  // By default, don't print all the stuff the package manager spits out
1368
1534
  if (!options.debug) {
@@ -1393,19 +1559,19 @@ async function uninstallNodeModule(packageName, options = {}) {
1393
1559
  * Rebuilds all native node_modules that are dependencies of the project in the current working directory / project root.
1394
1560
  * If `options.cwd` is given, the directory must contain a lockfile.
1395
1561
  * @param {RebuildNodeModulesOptions} options Options for the rebuild
1396
- * @returns {Promise<import("@alcalzone/pak").CommandResult>}
1562
+ * @returns {Promise<import('@alcalzone/pak').CommandResult>}
1397
1563
  */
1398
1564
  async function rebuildNodeModules(options = {}) {
1399
1565
  // Figure out which package manager is in charge (probably npm at this point)
1400
1566
  const pak = await detectPackageManager(
1401
1567
  typeof options.cwd === 'string'
1402
- // If a cwd was provided, use it
1403
- ? {cwd: options.cwd}
1404
- // Otherwise find the ioBroker root dir
1405
- : {
1406
- cwd: __dirname,
1407
- setCwdToPackageRoot: true
1408
- }
1568
+ ? // If a cwd was provided, use it
1569
+ { cwd: options.cwd }
1570
+ : // Otherwise find the ioBroker root dir
1571
+ {
1572
+ cwd: __dirname,
1573
+ setCwdToPackageRoot: true
1574
+ }
1409
1575
  );
1410
1576
  // By default, don't print all the stuff the package manager spits out
1411
1577
  if (!options.debug) {
@@ -1445,7 +1611,7 @@ function getDiskInfo(platform, callback) {
1445
1611
  try {
1446
1612
  const path = platform === 'win32' ? __dirname.substring(0, 2) : '/';
1447
1613
  const info = diskusage.checkSync(path);
1448
- return callback && callback(null, {'Disk size': info.total, 'Disk free': info.free});
1614
+ return callback && callback(null, { 'Disk size': info.total, 'Disk free': info.free });
1449
1615
  } catch (err) {
1450
1616
  console.log(err);
1451
1617
  }
@@ -1461,37 +1627,49 @@ function getDiskInfo(platform, callback) {
1461
1627
  // Z: 116649795584 148368257024
1462
1628
  const disk = __dirname.substring(0, 2).toUpperCase();
1463
1629
 
1464
- exec('wmic logicaldisk get size,freespace,caption', {
1465
- encoding: 'utf8',
1466
- windowsHide: true
1467
- }, (error, stdout) => {//, stderr) {
1468
- if (stdout) {
1469
- const lines = stdout.split('\n');
1470
- const line = lines.find(line => {
1471
- const parts = line.split(/\s+/);
1472
- return parts[0].toUpperCase() === disk;
1473
- });
1474
- if (line) {
1475
- const parts = line.split(/\s+/);
1476
- return callback && callback(error, {
1477
- 'Disk size': parseInt(parts[2]),
1478
- 'Disk free': parseInt(parts[1])
1630
+ exec(
1631
+ 'wmic logicaldisk get size,freespace,caption',
1632
+ {
1633
+ encoding: 'utf8',
1634
+ windowsHide: true
1635
+ },
1636
+ (error, stdout) => {
1637
+ //, stderr) {
1638
+ if (stdout) {
1639
+ const lines = stdout.split('\n');
1640
+ const line = lines.find(line => {
1641
+ const parts = line.split(/\s+/);
1642
+ return parts[0].toUpperCase() === disk;
1479
1643
  });
1644
+ if (line) {
1645
+ const parts = line.split(/\s+/);
1646
+ return (
1647
+ callback &&
1648
+ callback(error, {
1649
+ 'Disk size': parseInt(parts[2]),
1650
+ 'Disk free': parseInt(parts[1])
1651
+ })
1652
+ );
1653
+ }
1480
1654
  }
1655
+ callback && callback(error, null);
1481
1656
  }
1482
- callback && callback(error, null);
1483
- });
1657
+ );
1484
1658
  } else {
1485
- exec('df -k /', {encoding: 'utf8', windowsHide: true}, (error, stdout) => {//, stderr) {
1659
+ exec('df -k /', { encoding: 'utf8', windowsHide: true }, (error, stdout) => {
1660
+ //, stderr) {
1486
1661
  // Filesystem 1K-blocks Used Available Use% Mounted on
1487
1662
  // /dev/mapper/vg00-lv01 162544556 9966192 145767152 7% /
1488
1663
  try {
1489
1664
  if (stdout) {
1490
1665
  const parts = stdout.split('\n')[1].split(/\s+/);
1491
- return callback && callback(error, {
1492
- 'Disk size': parseInt(parts[1]) * 1024,
1493
- 'Disk free': parseInt(parts[3]) * 1024
1494
- });
1666
+ return (
1667
+ callback &&
1668
+ callback(error, {
1669
+ 'Disk size': parseInt(parts[1]) * 1024,
1670
+ 'Disk free': parseInt(parts[3]) * 1024
1671
+ })
1672
+ );
1495
1673
  }
1496
1674
  } catch {
1497
1675
  // continue regardless of error
@@ -1596,7 +1774,7 @@ function generateDefaultCertificates() {
1596
1774
  // https://github.com/digitalbazaar/forge
1597
1775
  forge.options.usePureJavaScript = false;
1598
1776
  const pki = forge.pki;
1599
- const keys = pki.rsa.generateKeyPair({bits: 2048, e: 0x10001});
1777
+ const keys = pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 });
1600
1778
  const cert = pki.createCertificate();
1601
1779
 
1602
1780
  cert.publicKey = keys.publicKey;
@@ -1606,15 +1784,15 @@ function generateDefaultCertificates() {
1606
1784
  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
1607
1785
 
1608
1786
  const subAttrs = [
1609
- {name: 'commonName', value: getHostName()},
1610
- {name: 'organizationName', value: 'ioBroker GmbH'},
1611
- {shortName: 'OU', value: 'iobroker'}
1787
+ { name: 'commonName', value: getHostName() },
1788
+ { name: 'organizationName', value: 'ioBroker GmbH' },
1789
+ { shortName: 'OU', value: 'iobroker' }
1612
1790
  ];
1613
1791
 
1614
1792
  const issAttrs = [
1615
- {name: 'commonName', value: 'iobroker'},
1616
- {name: 'organizationName', value: 'ioBroker GmbH'},
1617
- {shortName: 'OU', value: 'iobroker'}
1793
+ { name: 'commonName', value: 'iobroker' },
1794
+ { name: 'organizationName', value: 'ioBroker GmbH' },
1795
+ { shortName: 'OU', value: 'iobroker' }
1618
1796
  ];
1619
1797
 
1620
1798
  cert.setSubject(subAttrs);
@@ -1641,10 +1819,12 @@ function generateDefaultCertificates() {
1641
1819
  },
1642
1820
  {
1643
1821
  name: 'subjectAltName',
1644
- altNames: [{
1645
- type: 2,
1646
- value: os.hostname()
1647
- }]
1822
+ altNames: [
1823
+ {
1824
+ type: 2,
1825
+ value: os.hostname()
1826
+ }
1827
+ ]
1648
1828
  },
1649
1829
  {
1650
1830
  name: 'subjectKeyIdentifier'
@@ -1715,8 +1895,10 @@ async function getHostInfo(objects, callback) {
1715
1895
  }
1716
1896
 
1717
1897
  const cpus = os.cpus();
1898
+ const dateObj = new Date();
1899
+
1718
1900
  const data = {
1719
- Platform: os.platform(),
1901
+ Platform: isDocker() ? 'docker' : os.platform(),
1720
1902
  os: process.platform,
1721
1903
  Architecture: os.arch(),
1722
1904
  CPUs: cpus && Array.isArray(cpus) ? cpus.length : null,
@@ -1724,7 +1906,9 @@ async function getHostInfo(objects, callback) {
1724
1906
  Model: cpus && Array.isArray(cpus) ? cpus[0].model : null,
1725
1907
  RAM: os.totalmem(),
1726
1908
  'System uptime': Math.round(os.uptime()),
1727
- 'Node.js': process.version
1909
+ 'Node.js': process.version,
1910
+ time: dateObj.getTime(), // give infos to compare the local times
1911
+ timeOffset: dateObj.getTimezoneOffset()
1728
1912
  };
1729
1913
 
1730
1914
  if (data.Platform === 'win32') {
@@ -1739,7 +1923,9 @@ async function getHostInfo(objects, callback) {
1739
1923
  // Check if repositories exists
1740
1924
  const allRepos = {};
1741
1925
  if (systemRepos && systemRepos.native && systemRepos.native.repositories) {
1742
- const repos = Array.isArray(systemConfig.common.activeRepo) ? systemConfig.common.activeRepo : [systemConfig.common.activeRepo];
1926
+ const repos = Array.isArray(systemConfig.common.activeRepo)
1927
+ ? systemConfig.common.activeRepo
1928
+ : [systemConfig.common.activeRepo];
1743
1929
  repos
1744
1930
  .filter(repo => systemRepos.native.repositories[repo] && systemRepos.native.repositories[repo].json)
1745
1931
  .forEach(repo => Object.assign(allRepos, systemRepos.native.repositories[repo].json));
@@ -1860,8 +2046,10 @@ function getConfigFileName() {
1860
2046
  }
1861
2047
 
1862
2048
  // if debugging with npm5 -> node_modules on e.g. /opt/node_modules
1863
- if (fs.existsSync(`${__dirname}/../../../../../../../node_modules/${appName.toLowerCase()}.js-controller`) ||
1864
- fs.existsSync(`${__dirname}/../../../../../../../node_modules/${appName}.js-controller`)) {
2049
+ if (
2050
+ fs.existsSync(`${__dirname}/../../../../../../../node_modules/${appName.toLowerCase()}.js-controller`) ||
2051
+ fs.existsSync(`${__dirname}/../../../../../../../node_modules/${appName}.js-controller`)
2052
+ ) {
1865
2053
  // remove /node_modules/' + appName + '.js-controller/lib
1866
2054
  configDir.splice(configDir.length - 7, 7);
1867
2055
  configDir = configDir.join('/');
@@ -1905,37 +2093,41 @@ function promisify(fn, context, returnArgNames) {
1905
2093
  // @ts-ignore we cannot know the type of `this`
1906
2094
  context = context || this;
1907
2095
  return new Promise((resolve, reject) => {
1908
- fn.apply(context, args.concat([
1909
- function (error, result) {
1910
- if (error) {
1911
- return reject(error instanceof Error ? error : new Error(error));
1912
- } else {
1913
- // decide on how we want to return the callback arguments
1914
- switch (arguments.length) {
1915
- case 1: // only an error was given
1916
- return resolve(); // Promise<void>
1917
- case 2: // a single value (result) was returned
1918
- return resolve(result);
1919
- default: {// multiple values should be returned
1920
- /** @type {{} | any[]} */
1921
- let ret;
1922
- const extraArgs = sliceArgs(arguments, 1);
1923
- if (returnArgNames && returnArgNames.length === extraArgs.length) {
1924
- // we can build an object
1925
- ret = {};
1926
- for (let i = 0; i < returnArgNames.length; i++) {
1927
- ret[returnArgNames[i]] = extraArgs[i];
2096
+ fn.apply(
2097
+ context,
2098
+ args.concat([
2099
+ function (error, result) {
2100
+ if (error) {
2101
+ return reject(error instanceof Error ? error : new Error(error));
2102
+ } else {
2103
+ // decide on how we want to return the callback arguments
2104
+ switch (arguments.length) {
2105
+ case 1: // only an error was given
2106
+ return resolve(); // Promise<void>
2107
+ case 2: // a single value (result) was returned
2108
+ return resolve(result);
2109
+ default: {
2110
+ // multiple values should be returned
2111
+ /** @type {{} | any[]} */
2112
+ let ret;
2113
+ const extraArgs = sliceArgs(arguments, 1);
2114
+ if (returnArgNames && returnArgNames.length === extraArgs.length) {
2115
+ // we can build an object
2116
+ ret = {};
2117
+ for (let i = 0; i < returnArgNames.length; i++) {
2118
+ ret[returnArgNames[i]] = extraArgs[i];
2119
+ }
2120
+ } else {
2121
+ // we return the raw array
2122
+ ret = extraArgs;
1928
2123
  }
1929
- } else {
1930
- // we return the raw array
1931
- ret = extraArgs;
2124
+ return resolve(ret);
1932
2125
  }
1933
- return resolve(ret);
1934
2126
  }
1935
2127
  }
1936
2128
  }
1937
- }
1938
- ]));
2129
+ ])
2130
+ );
1939
2131
  });
1940
2132
  };
1941
2133
  }
@@ -1955,33 +2147,37 @@ function promisifyNoError(fn, context, returnArgNames) {
1955
2147
  // @ts-ignore we cannot know the type of `this`
1956
2148
  context = context || this;
1957
2149
  return new Promise((resolve, _reject) => {
1958
- fn.apply(context, args.concat([
1959
- function (result) {
1960
- // decide on how we want to return the callback arguments
1961
- switch (arguments.length) {
1962
- case 0: // no arguments were given
1963
- return resolve(); // Promise<void>
1964
- case 1: // a single value (result) was returned
1965
- return resolve(result);
1966
- default: {// multiple values should be returned
1967
- /** @type {{} | any[]} */
1968
- let ret;
1969
- const extraArgs = sliceArgs(arguments, 0);
1970
- if (returnArgNames && returnArgNames.length === extraArgs.length) {
1971
- // we can build an object
1972
- ret = {};
1973
- for (let i = 0; i < returnArgNames.length; i++) {
1974
- ret[returnArgNames[i]] = extraArgs[i];
2150
+ fn.apply(
2151
+ context,
2152
+ args.concat([
2153
+ function (result) {
2154
+ // decide on how we want to return the callback arguments
2155
+ switch (arguments.length) {
2156
+ case 0: // no arguments were given
2157
+ return resolve(); // Promise<void>
2158
+ case 1: // a single value (result) was returned
2159
+ return resolve(result);
2160
+ default: {
2161
+ // multiple values should be returned
2162
+ /** @type {{} | any[]} */
2163
+ let ret;
2164
+ const extraArgs = sliceArgs(arguments, 0);
2165
+ if (returnArgNames && returnArgNames.length === extraArgs.length) {
2166
+ // we can build an object
2167
+ ret = {};
2168
+ for (let i = 0; i < returnArgNames.length; i++) {
2169
+ ret[returnArgNames[i]] = extraArgs[i];
2170
+ }
2171
+ } else {
2172
+ // we return the raw array
2173
+ ret = extraArgs;
1975
2174
  }
1976
- } else {
1977
- // we return the raw array
1978
- ret = extraArgs;
2175
+ return resolve(ret);
1979
2176
  }
1980
- return resolve(ret);
1981
2177
  }
1982
2178
  }
1983
- }
1984
- ]));
2179
+ ])
2180
+ );
1985
2181
  });
1986
2182
  };
1987
2183
  }
@@ -2000,43 +2196,52 @@ function _setQualityForStates(states, keys, quality, cb) {
2000
2196
  if (!keys || !states || !keys.length) {
2001
2197
  cb();
2002
2198
  } else {
2003
- states.setState(keys.shift(), {
2004
- ack: null,
2005
- q: quality
2006
- }, () => setImmediate(_setQualityForStates, states, keys, quality, cb));
2199
+ states.setState(
2200
+ keys.shift(),
2201
+ {
2202
+ ack: null,
2203
+ q: quality
2204
+ },
2205
+ () => setImmediate(_setQualityForStates, states, keys, quality, cb)
2206
+ );
2007
2207
  }
2008
2208
  }
2009
2209
 
2010
2210
  function setQualityForInstance(objects, states, namespace, q) {
2011
2211
  return new Promise((resolve, reject) => {
2012
- objects.getObjectView('system', 'state', {
2013
- startkey: namespace + '.',
2014
- endkey: namespace + '.\u9999',
2015
- include_docs: false
2016
- }, (err, _states) => {
2017
- if (err) {
2018
- reject(err);
2019
- } else {
2020
- let keys = [];
2021
- if (_states && _states.rows) {
2022
- for (let s = 0; s < _states.rows.length; s++) {
2023
- const id = _states.rows[s].id;
2024
- // if instance still active, but device is offline
2025
- if (!(q & 0x10) && id.match(/\.info\.connection$/)) {
2026
- continue;
2212
+ objects.getObjectView(
2213
+ 'system',
2214
+ 'state',
2215
+ {
2216
+ startkey: namespace + '.',
2217
+ endkey: namespace + '.\u9999',
2218
+ include_docs: false
2219
+ },
2220
+ (err, _states) => {
2221
+ if (err) {
2222
+ reject(err);
2223
+ } else {
2224
+ let keys = [];
2225
+ if (_states && _states.rows) {
2226
+ for (let s = 0; s < _states.rows.length; s++) {
2227
+ const id = _states.rows[s].id;
2228
+ // if instance still active, but device is offline
2229
+ if (!(q & 0x10) && id.match(/\.info\.connection$/)) {
2230
+ continue;
2231
+ }
2232
+ keys.push(id);
2027
2233
  }
2028
- keys.push(id);
2029
2234
  }
2235
+ // read all values for IDs
2236
+ states.getStates(keys, (_err, values) => {
2237
+ // Get only states, that have ack = true
2238
+ keys = keys.filter((_id, i) => values[i] && values[i].ack);
2239
+ // update quality code of the states to new one
2240
+ _setQualityForStates(states, keys, q, err => (err ? reject(err) : resolve()));
2241
+ });
2030
2242
  }
2031
- // read all values for IDs
2032
- states.getStates(keys, (_err, values) => {
2033
- // Get only states, that have ack = true
2034
- keys = keys.filter((_id, i) => values[i] && values[i].ack);
2035
- // update quality code of the states to new one
2036
- _setQualityForStates(states, keys, q, err => err ? reject(err) : resolve());
2037
- });
2038
2243
  }
2039
- });
2244
+ );
2040
2245
  });
2041
2246
  }
2042
2247
 
@@ -2069,17 +2274,16 @@ function captureStackTrace(wrapperName) {
2069
2274
  const ret = new Error();
2070
2275
  if (ret.stack) {
2071
2276
  let foundSelf = false;
2072
- const lines = ret.stack.split('\n')
2073
- .filter(line => {
2074
- // keep all lines after this function's
2075
- if (foundSelf) {
2076
- return true;
2077
- }
2078
- if (line.indexOf(wrapperName) > -1) {
2079
- foundSelf = true;
2080
- }
2081
- return false;
2082
- });
2277
+ const lines = ret.stack.split('\n').filter(line => {
2278
+ // keep all lines after this function's
2279
+ if (foundSelf) {
2280
+ return true;
2281
+ }
2282
+ if (line.includes(wrapperName)) {
2283
+ foundSelf = true;
2284
+ }
2285
+ return false;
2286
+ });
2083
2287
  return lines.join('\n');
2084
2288
  }
2085
2289
  return '';
@@ -2232,7 +2436,7 @@ function measureEventLoopLag(ms, cb) {
2232
2436
 
2233
2437
  function hrtime() {
2234
2438
  const t = process.hrtime();
2235
- return (t[0] * 1e3) + (t[1] / 1e6);
2439
+ return t[0] * 1e3 + t[1] / 1e6;
2236
2440
  }
2237
2441
  }
2238
2442
 
@@ -2259,10 +2463,29 @@ function formatAliasValue(sourceObj, targetObj, state, logger, logNamespace) {
2259
2463
  if (targetObj && targetObj.alias && targetObj.alias.read) {
2260
2464
  try {
2261
2465
  // process the value here
2262
- const func = new Function('val', 'type', 'min', 'max', 'sType', 'sMin', 'sMax', 'return ' + targetObj.alias.read);
2263
- state.val = func(state.val, targetObj.type, targetObj.min, targetObj.max, sourceObj.type, sourceObj.min, sourceObj.max);
2466
+ const func = new Function(
2467
+ 'val',
2468
+ 'type',
2469
+ 'min',
2470
+ 'max',
2471
+ 'sType',
2472
+ 'sMin',
2473
+ 'sMax',
2474
+ 'return ' + targetObj.alias.read
2475
+ );
2476
+ state.val = func(
2477
+ state.val,
2478
+ targetObj.type,
2479
+ targetObj.min,
2480
+ targetObj.max,
2481
+ sourceObj.type,
2482
+ sourceObj.min,
2483
+ sourceObj.max
2484
+ );
2264
2485
  } catch (e) {
2265
- logger.error(`${logNamespace}Invalid read function for ${targetObj._id}: ${targetObj.alias.read} => ${e.message}`);
2486
+ logger.error(
2487
+ `${logNamespace} Invalid read function for ${targetObj._id}: ${targetObj.alias.read} => ${e.message}`
2488
+ );
2266
2489
  return null;
2267
2490
  }
2268
2491
  }
@@ -2270,10 +2493,29 @@ function formatAliasValue(sourceObj, targetObj, state, logger, logNamespace) {
2270
2493
  if (sourceObj && sourceObj.alias && sourceObj.alias.write) {
2271
2494
  try {
2272
2495
  // process the value here
2273
- const func = new Function('val', 'type', 'min', 'max', 'tType', 'tMin', 'tMax', 'return ' + sourceObj.alias.write);
2274
- state.val = func(state.val, sourceObj.type, sourceObj.min, sourceObj.max, targetObj.type, targetObj.min, targetObj.max);
2496
+ const func = new Function(
2497
+ 'val',
2498
+ 'type',
2499
+ 'min',
2500
+ 'max',
2501
+ 'tType',
2502
+ 'tMin',
2503
+ 'tMax',
2504
+ 'return ' + sourceObj.alias.write
2505
+ );
2506
+ state.val = func(
2507
+ state.val,
2508
+ sourceObj.type,
2509
+ sourceObj.min,
2510
+ sourceObj.max,
2511
+ targetObj.type,
2512
+ targetObj.min,
2513
+ targetObj.max
2514
+ );
2275
2515
  } catch (e) {
2276
- logger.error(`${logNamespace}Invalid write function for ${sourceObj._id}: ${sourceObj.alias.write} => ${e.message}`);
2516
+ logger.error(
2517
+ `${logNamespace} Invalid write function for ${sourceObj._id}: ${sourceObj.alias.write} => ${e.message}`
2518
+ );
2277
2519
  return null;
2278
2520
  }
2279
2521
  }
@@ -2295,15 +2537,35 @@ function formatAliasValue(sourceObj, targetObj, state, logger, logNamespace) {
2295
2537
  }
2296
2538
 
2297
2539
  // auto-scaling, only if val not null and unit for target (x)or source is %
2298
- if (((targetObj && targetObj.alias && !targetObj.alias.read) || (sourceObj && sourceObj.alias && !sourceObj.alias.write)) && state.val !== null) {
2299
- if (targetObj && targetObj.type === 'number' && targetObj.unit === '%' && sourceObj &&
2300
- sourceObj.type === 'number' && sourceObj.unit !== '%' && sourceObj.min !== undefined && sourceObj.max !== undefined) {
2540
+ if (
2541
+ ((targetObj && targetObj.alias && !targetObj.alias.read) ||
2542
+ (sourceObj && sourceObj.alias && !sourceObj.alias.write)) &&
2543
+ state.val !== null
2544
+ ) {
2545
+ if (
2546
+ targetObj &&
2547
+ targetObj.type === 'number' &&
2548
+ targetObj.unit === '%' &&
2549
+ sourceObj &&
2550
+ sourceObj.type === 'number' &&
2551
+ sourceObj.unit !== '%' &&
2552
+ sourceObj.min !== undefined &&
2553
+ sourceObj.max !== undefined
2554
+ ) {
2301
2555
  // scale target between 0 and 100 % based on sources min/max
2302
- state.val = (state.val - sourceObj.min) / (sourceObj.max - sourceObj.min) * 100;
2303
- } else if (sourceObj && sourceObj.type === 'number' && sourceObj.unit === '%' && targetObj &&
2304
- targetObj.unit !== '%' && targetObj.type === 'number' && targetObj.min !== undefined && targetObj.max !== undefined) {
2556
+ state.val = ((state.val - sourceObj.min) / (sourceObj.max - sourceObj.min)) * 100;
2557
+ } else if (
2558
+ sourceObj &&
2559
+ sourceObj.type === 'number' &&
2560
+ sourceObj.unit === '%' &&
2561
+ targetObj &&
2562
+ targetObj.unit !== '%' &&
2563
+ targetObj.type === 'number' &&
2564
+ targetObj.min !== undefined &&
2565
+ targetObj.max !== undefined
2566
+ ) {
2305
2567
  // scale target based on its min/max by its source (assuming source is meant to be 0 - 100 %)
2306
- state.val = ((targetObj.max - targetObj.min) * state.val / 100) + targetObj.min;
2568
+ state.val = ((targetObj.max - targetObj.min) * state.val) / 100 + targetObj.min;
2307
2569
  }
2308
2570
  }
2309
2571
 
@@ -2317,32 +2579,35 @@ function formatAliasValue(sourceObj, targetObj, state, logger, logNamespace) {
2317
2579
  * @memberof tools
2318
2580
  * @param {object} objects object to access objects db
2319
2581
  * @param {string} id the object id which will be deleted from enums
2320
- * @returns {Promise}
2582
+ * @param {object} [allEnums] objects with all enums to use - if not provided all enums will be queried
2583
+ * @returns {Promise} All objects are tried to be updated - reject will happen as soon as one fails with the error of the first fail
2321
2584
  *
2322
2585
  */
2323
- function removeIdFromAllEnums(objects, id) {
2324
- return new Promise((resolve, reject) => {
2325
- objects.getObjectView('system', 'enum', {startkey: '', endkey: '\u9999'}, (err, res) => {
2326
- if (err) {
2327
- reject(err);
2328
- } else {
2329
- const promises = [];
2330
- for (const obj of res.rows) {
2331
- const idx = obj.value && obj.value.common && obj.value.common.members ? obj.value.common.members.indexOf(id) : -1;
2332
- if (idx !== -1) {
2333
- // the id is in the enum now we have to remove it
2334
- obj.value.common.members.splice(idx, 1);
2335
- promises.push(new Promise(resolve => {
2336
- objects.setObject(obj.value._id, obj.value, err => {
2337
- err ? reject(err) : resolve();
2338
- });
2339
- }));
2340
- } // endIf
2341
- } // endFor
2342
- Promise.all(promises).then(resolve);
2343
- } // endElse
2344
- });
2345
- });
2586
+ async function removeIdFromAllEnums(objects, id, allEnums) {
2587
+ if (!allEnums) {
2588
+ allEnums = await this.getAllEnums(objects);
2589
+ }
2590
+
2591
+ let error = null;
2592
+ for (const [enumId, enumObj] of Object.entries(allEnums)) {
2593
+ const idx = enumObj.common.members ? enumObj.common.members.indexOf(id) : -1;
2594
+ if (idx !== -1) {
2595
+ // the id is in the enum now we have to remove it
2596
+ enumObj.common.members.splice(idx, 1);
2597
+ try {
2598
+ await objects.setObjectAsync(enumId, enumObj);
2599
+ // update cache directly to prevent race conditions when sending many delete in a short time
2600
+ allEnums[enumId] = enumObj;
2601
+ } catch (err) {
2602
+ if (!error) {
2603
+ error = err;
2604
+ }
2605
+ }
2606
+ }
2607
+ }
2608
+ if (error) {
2609
+ throw error;
2610
+ }
2346
2611
  }
2347
2612
 
2348
2613
  /**
@@ -2350,8 +2615,8 @@ function removeIdFromAllEnums(objects, id) {
2350
2615
  *
2351
2616
  * @alias parseDependencies
2352
2617
  * @memberof tools
2353
- * @param {string[]|object[]|string} dependencies dependencies array or single dependency
2354
- * @returns {object} parsed dependencies
2618
+ * @param {string[]|Record<string, string>[]|string|Record<string, string>} dependencies dependencies array or single dependency
2619
+ * @returns {Record<string, string>} parsed dependencies
2355
2620
  */
2356
2621
  function parseDependencies(dependencies) {
2357
2622
  let adapters = {};
@@ -2362,7 +2627,9 @@ function parseDependencies(dependencies) {
2362
2627
  adapters[rule] = '*';
2363
2628
  } else if (isObject(rule)) {
2364
2629
  // can be object containing single adapter or multiple
2365
- Object.keys(rule).filter(adapter => !adapters[adapter]).forEach(adapter => adapters[adapter] = rule[adapter]);
2630
+ Object.keys(rule)
2631
+ .filter(adapter => !adapters[adapter])
2632
+ .forEach(adapter => (adapters[adapter] = rule[adapter]));
2366
2633
  }
2367
2634
  });
2368
2635
  } else if (typeof dependencies === 'string') {
@@ -2394,12 +2661,29 @@ function validateGeneralObjectProperties(obj, extend) {
2394
2661
  }
2395
2662
 
2396
2663
  if (obj.type !== undefined && typeof obj.type !== 'string') {
2397
- throw new Error(`obj.type has an invalid type! Expected "string", received "${typeof obj.type}"`);
2398
- }
2399
-
2400
- const allowedObjectTypes = ['state', 'channel', 'device', 'enum', 'host', 'adapter', 'instance', 'meta', 'config', 'script', 'user', 'group', 'chart', 'folder'];
2664
+ throw new Error(`obj.type has an invalid type! Expected "string", received "${typeof obj.type}"`);
2665
+ }
2666
+
2667
+ const allowedObjectTypes = [
2668
+ 'state',
2669
+ 'channel',
2670
+ 'device',
2671
+ 'enum',
2672
+ 'host',
2673
+ 'adapter',
2674
+ 'instance',
2675
+ 'meta',
2676
+ 'config',
2677
+ 'script',
2678
+ 'user',
2679
+ 'group',
2680
+ 'chart',
2681
+ 'folder'
2682
+ ];
2401
2683
  if (obj.type !== undefined && !allowedObjectTypes.includes(obj.type)) {
2402
- throw new Error(`obj.type has an invalid value (${obj.type}) but has to be one of ${allowedObjectTypes.join(', ')}`);
2684
+ throw new Error(
2685
+ `obj.type has an invalid value (${obj.type}) but has to be one of ${allowedObjectTypes.join(', ')}`
2686
+ );
2403
2687
  }
2404
2688
 
2405
2689
  // obj.common is optional in general check
@@ -2408,39 +2692,152 @@ function validateGeneralObjectProperties(obj, extend) {
2408
2692
  }
2409
2693
 
2410
2694
  if (obj.common.name !== undefined && typeof obj.common.name !== 'string' && typeof obj.common.name !== 'object') {
2411
- throw new Error(`obj.common.name has an invalid type! Expected "string" or "object", received "${typeof obj.common.name}"`);
2695
+ throw new Error(
2696
+ `obj.common.name has an invalid type! Expected "string" or "object", received "${typeof obj.common.name}"`
2697
+ );
2698
+ } else if (['adapter'].includes(obj.type) && typeof obj.common.name !== 'string') {
2699
+ // TODO: we need this for group/user too, but have to solve problems described in #1266
2700
+ // for some types, name needs to be a unique string
2701
+ throw new Error(`obj.common.name has an invalid type! Expected "string", received "${typeof obj.common.name}"`);
2412
2702
  }
2413
2703
 
2414
2704
  if (obj.common.type !== undefined) {
2415
2705
  if (typeof obj.common.type !== 'string') {
2416
- throw new Error(`obj.common.type has an invalid type! Expected "string", received "${typeof obj.common.type}"`);
2417
- }
2706
+ throw new Error(
2707
+ `obj.common.type has an invalid type! Expected "string", received "${typeof obj.common.type}"`
2708
+ );
2709
+ }
2710
+
2711
+ if (obj.type === 'state') {
2712
+ // if object type indicates a state, check that common.type matches
2713
+ const allowedStateTypes = ['number', 'string', 'boolean', 'array', 'object', 'mixed', 'file', 'json'];
2714
+ if (!allowedStateTypes.includes(obj.common.type)) {
2715
+ throw new Error(
2716
+ `obj.common.type has an invalid value (${
2717
+ obj.common.type
2718
+ }) but has to be one of ${allowedStateTypes.join(', ')}`
2719
+ );
2720
+ }
2721
+
2722
+ // ensure that min max only exists for common.type number and is number itself
2723
+ if (obj.common.min !== undefined) {
2724
+ if (typeof obj.common.min !== 'number') {
2725
+ throw new Error(
2726
+ `obj.common.min has an invalid type! Expected "number", received "${typeof obj.common.min}"`
2727
+ );
2728
+ }
2729
+
2730
+ if (obj.common.type !== 'number' && obj.common.type !== 'mixed') {
2731
+ throw new Error(
2732
+ `obj.common.min is only allowed on obj.common.type "number" or "mixed", received "${obj.common.type}"`
2733
+ );
2734
+ }
2735
+ }
2736
+
2737
+ if (obj.common.max !== undefined) {
2738
+ if (typeof obj.common.max !== 'number') {
2739
+ throw new Error(
2740
+ `obj.common.max has an invalid type! Expected "number", received "${typeof obj.common.max}"`
2741
+ );
2742
+ }
2743
+
2744
+ if (obj.common.type !== 'number' && obj.common.type !== 'mixed') {
2745
+ throw new Error(
2746
+ `obj.common.max is only allowed on obj.common.type "number" or "mixed", received "${obj.common.type}"`
2747
+ );
2748
+ }
2418
2749
 
2419
- // if object type indicates a state, check that common.type matches
2420
- const allowedStateTypes = ['number', 'string', 'boolean', 'array', 'object', 'mixed', 'file', 'json'];
2421
- if (obj.type === 'state' && !allowedStateTypes.includes(obj.common.type)) {
2422
- throw new Error(`obj.common.type has an invalid value (${obj.common.type}) but has to be one of ${allowedStateTypes.join(', ')}`);
2750
+ if (obj.common.min !== undefined && obj.common.min > obj.common.max) {
2751
+ throw new Error(
2752
+ `obj.common.min (${obj.common.min}) needs to be less than or equal to obj.common.max (${obj.common.max})`
2753
+ );
2754
+ }
2755
+ }
2756
+
2757
+ // ensure, that default value has correct type
2758
+ if (obj.common.def !== undefined && obj.common.def !== null) {
2759
+ if (obj.common.type === 'file') {
2760
+ // defaults are set via setState but would need setBinaryState
2761
+ throw new Error('Default value is not supported for type "file"');
2762
+ }
2763
+
2764
+ // else do what strictObjectChecks does for val
2765
+ if (
2766
+ !(
2767
+ (obj.common.type === 'mixed' && typeof obj.common.def !== 'object') ||
2768
+ (obj.common.type !== 'object' && obj.common.type === typeof obj.common.def) ||
2769
+ (obj.common.type === 'array' && typeof obj.common.def === 'string') ||
2770
+ (obj.common.type === 'json' && typeof obj.common.def === 'string') ||
2771
+ (obj.common.type === 'file' && typeof obj.common.def === 'string') ||
2772
+ (obj.common.type === 'object' && typeof obj.common.def === 'string')
2773
+ )
2774
+ ) {
2775
+ // types can be 'number', 'string', 'boolean', 'array', 'object', 'mixed', 'file', 'json'
2776
+ // array, object, json need to be string
2777
+ if (['object', 'json', 'file', 'array'].includes(obj.common.type)) {
2778
+ throw new Error(
2779
+ `Default value has to be stringified but received type "${typeof obj.common.def}"`
2780
+ );
2781
+ } else {
2782
+ throw new Error(
2783
+ `Default value has to be ${
2784
+ obj.common.type === 'mixed'
2785
+ ? `one of type "string", "number", "boolean"`
2786
+ : `type "${obj.common.type}"`
2787
+ } but received type "${typeof obj.common.def}"`
2788
+ );
2789
+ }
2790
+ }
2791
+ }
2423
2792
  }
2424
2793
  }
2425
2794
 
2426
2795
  if (obj.common.read !== undefined && typeof obj.common.read !== 'boolean') {
2427
- throw new Error(`obj.common.read has an invalid type! Expected "boolean", received "${typeof obj.common.read}"`);
2796
+ throw new Error(
2797
+ `obj.common.read has an invalid type! Expected "boolean", received "${typeof obj.common.read}"`
2798
+ );
2428
2799
  }
2429
2800
 
2430
2801
  if (obj.common.write !== undefined && typeof obj.common.write !== 'boolean') {
2431
- throw new Error(`obj.common.write has an invalid type! Expected "boolean", received "${typeof obj.common.write}"`);
2802
+ throw new Error(
2803
+ `obj.common.write has an invalid type! Expected "boolean", received "${typeof obj.common.write}"`
2804
+ );
2432
2805
  }
2433
2806
 
2434
2807
  if (obj.common.role !== undefined && typeof obj.common.role !== 'string') {
2435
- throw new Error(`obj.common.role has an invalid type! Expected "string", received "${typeof obj.common.role}"`);
2808
+ throw new Error(`obj.common.role has an invalid type! Expected "string", received "${typeof obj.common.role}"`);
2436
2809
  }
2437
2810
 
2438
2811
  if (obj.common.desc !== undefined && typeof obj.common.desc !== 'string' && typeof obj.common.desc !== 'object') {
2439
- throw new Error(`obj.common.desc has an invalid type! Expected "string" or "object", received "${typeof obj.common.desc}"`);
2812
+ throw new Error(
2813
+ `obj.common.desc has an invalid type! Expected "string" or "object", received "${typeof obj.common.desc}"`
2814
+ );
2440
2815
  }
2441
2816
 
2442
- if (obj.type === 'state' && obj.common.custom !== undefined && obj.common.custom !== null && !isObject(obj.common.custom)) {
2443
- throw new Error(`obj.common.custom has an invalid type! Expected "object", received "${typeof obj.common.custom}"`);
2817
+ if (
2818
+ obj.type === 'state' &&
2819
+ obj.common.custom !== undefined &&
2820
+ obj.common.custom !== null &&
2821
+ !isObject(obj.common.custom)
2822
+ ) {
2823
+ throw new Error(
2824
+ `obj.common.custom has an invalid type! Expected "object", received "${typeof obj.common.custom}"`
2825
+ );
2826
+ }
2827
+
2828
+ // common.states needs to be a real object or an array
2829
+ if (
2830
+ obj.common.states !== null &&
2831
+ obj.common.states !== undefined &&
2832
+ !isObject(obj.common.states) &&
2833
+ !Array.isArray(obj.common.states)
2834
+ ) {
2835
+ throw new Error(
2836
+ `obj.common.states has an invalid type! Expected "object", received "${typeof obj.common.states}"`
2837
+ );
2838
+ } else if (obj.common.states === null && !extend) {
2839
+ // extend only allowed on extend
2840
+ throw new Error(`obj.common.states has an invalid type! Expected non-null "object", received "null"`);
2444
2841
  }
2445
2842
  }
2446
2843
 
@@ -2450,81 +2847,63 @@ function validateGeneralObjectProperties(obj, extend) {
2450
2847
  * @alias getAllInstances
2451
2848
  * @memberof tools
2452
2849
  * @param {string[]} adapters list of adapter names to get instances for
2453
- * @param {function} callback callback to be executed
2850
+ * @param {object} objects class redis objects
2851
+ * @returns {Promise<string[]>} - array of IDs
2454
2852
  */
2455
- function getAllInstances(adapters, objects, callback) {
2853
+ async function getAllInstances(adapters, objects) {
2456
2854
  const instances = [];
2457
- let count = 0;
2458
- for (let k = 0; k < adapters.length; k++) {
2459
- if (!adapters[k]) {
2460
- continue;
2461
- }
2462
- if (adapters[k].indexOf('.') === -1) {
2463
- count++;
2464
- }
2465
- }
2855
+
2466
2856
  for (let i = 0; i < adapters.length; i++) {
2467
2857
  if (!adapters[i]) {
2468
2858
  continue;
2469
2859
  }
2470
- if (adapters[i].indexOf('.') === -1) {
2471
- getInstances(adapters[i], objects, false, (err, inst) => {
2472
- for (let j = 0; j < inst.length; j++) {
2473
- if (!instances.includes(inst[j])) {
2474
- instances.push(inst[j]);
2475
- }
2476
- }
2477
- if (!--count && callback) {
2478
- callback(null, instances);
2479
- callback = null;
2860
+ if (!adapters[i].includes('.')) {
2861
+ const inst = await getInstances(adapters[i], objects, false);
2862
+ for (let j = 0; j < inst.length; j++) {
2863
+ if (!instances.includes(inst[j])) {
2864
+ instances.push(inst[j]);
2480
2865
  }
2481
- });
2866
+ }
2482
2867
  } else {
2483
- if (instances.indexOf(adapters[i]) === -1) {
2868
+ if (!instances.includes(adapters[i])) {
2484
2869
  instances.push(adapters[i]);
2485
2870
  }
2486
2871
  }
2487
2872
  }
2488
- if (!count && callback) {
2489
- callback(null, instances);
2490
- callback = null;
2491
- }
2492
- }
2493
2873
 
2494
- /**
2495
- * Promise-version of getAllInstances
2496
- */
2497
- const getAllInstancesAsync = promisify(getAllInstances);
2874
+ return instances;
2875
+ }
2498
2876
 
2499
2877
  /**
2500
- * get all instances of one adapter
2878
+ * Get all existing enums
2501
2879
  *
2502
- * @alias getInstances
2503
- * @param {string }adapter name of the adapter
2504
- * @param {object }objects objects DB
2505
- * @param {boolean} withObjects return objects instead of only ids
2506
- * @param {function} callback callback to be executed
2880
+ * @param {object} objects - objects db
2881
+ * @returns {Promise<{}>}
2507
2882
  */
2508
- function getInstances(adapter, objects, withObjects, callback) {
2509
- if (typeof withObjects === 'function') {
2510
- callback = withObjects;
2511
- withObjects = false;
2883
+ async function getAllEnums(objects) {
2884
+ const allEnums = {};
2885
+ const res = await objects.getObjectViewAsync('system', 'enum', {
2886
+ startkey: 'enum.',
2887
+ endkey: 'enum.\u9999'
2888
+ });
2889
+ if (res && res.rows) {
2890
+ for (const row of res.rows) {
2891
+ allEnums[row.id] = row.value;
2892
+ }
2512
2893
  }
2513
2894
 
2514
- return getInstancesAsync(adapter, objects, withObjects)
2515
- .then(instances => callback(null, instances))
2516
- .catch(error => callback(error));
2895
+ return allEnums;
2517
2896
  }
2518
2897
 
2519
2898
  /**
2520
2899
  * get async all instances of one adapter
2521
2900
  *
2522
- * @alias getInstancesAsync
2523
- * @param {string }adapter name of the adapter
2524
- * @param {object }objects objects DB
2901
+ * @alias getInstances
2902
+ * @param {string} adapter name of the adapter
2903
+ * @param {object}objects objects DB
2525
2904
  * @param {boolean} withObjects return objects instead of only ids
2526
2905
  */
2527
- async function getInstancesAsync(adapter, objects, withObjects) {
2906
+ async function getInstances(adapter, objects, withObjects) {
2528
2907
  const arr = await objects.getObjectListAsync({
2529
2908
  startkey: 'system.adapter.' + adapter + '.',
2530
2909
  endkey: 'system.adapter.' + adapter + '.\u9999'
@@ -2550,7 +2929,7 @@ async function getInstancesAsync(adapter, objects, withObjects) {
2550
2929
  * Checks if the given callback is a function and if so calls it with the given parameter immediately, else a resolved Promise is returned
2551
2930
  *
2552
2931
  * @param {(...args: any[]) => void | null | undefined} callback - callback function to be executed
2553
- * @param {any[]} args - as many arguments as needed, which will be returned by the callback function or by the Promise
2932
+ * @param {...any} args - as many arguments as needed, which will be returned by the callback function or by the Promise
2554
2933
  * @returns {Promise<any>} - if Promise is resolved with multiple arguments, an array is returned
2555
2934
  */
2556
2935
  function maybeCallback(callback, ...args) {
@@ -2568,7 +2947,7 @@ function maybeCallback(callback, ...args) {
2568
2947
  * @param {((error: Error | null | undefined, ...args: any[]) => void) | null | undefined} callback - callback function to be executed
2569
2948
  * @param {Error | string | null | undefined} error - error which will be used by the callback function. If callback is not a function and
2570
2949
  * error is given, a rejected Promise is returned. If error is given but it is not an instance of Error, it is converted into one.
2571
- * @param {any[]} args - as many arguments as needed, which will be returned by the callback function or by the Promise
2950
+ * @param {...any} args - as many arguments as needed, which will be returned by the callback function or by the Promise
2572
2951
  * @returns {Promise<any>} - if Promise is resolved with multiple arguments, an array is returned
2573
2952
  */
2574
2953
  function maybeCallbackWithError(callback, error, ...args) {
@@ -2591,8 +2970,8 @@ function maybeCallbackWithError(callback, error, ...args) {
2591
2970
  * Executes a command asynchronously. On success, the promise resolves with stdout and stderr.
2592
2971
  * On error, the promise rejects with the exit code or signal, as well as stdout and stderr.
2593
2972
  * @param {string} command The command to execute
2594
- * @param {import("child_process").ExecOptions} [execOptions] The options for child_process.exec
2595
- * @returns {import("child_process").ChildProcess & Promise<{stdout?: string; stderr?: string}>}
2973
+ * @param {import('child_process').ExecOptions} [execOptions] The options for child_process.exec
2974
+ * @returns {import('child_process').ChildProcess & Promise<{stdout?: string; stderr?: string}>}
2596
2975
  */
2597
2976
  function execAsync(command, execOptions) {
2598
2977
  const defaultOptions = {
@@ -2602,7 +2981,7 @@ function execAsync(command, execOptions) {
2602
2981
  encoding: 'utf8'
2603
2982
  };
2604
2983
  // @ts-ignore We set the encoding, so stdout/stdrr must be a string
2605
- return cpPromise.exec(command, {...defaultOptions, ...execOptions});
2984
+ return cpPromise.exec(command, { ...defaultOptions, ...execOptions });
2606
2985
  }
2607
2986
 
2608
2987
  /**
@@ -2616,6 +2995,7 @@ function pipeLinewise(input, output) {
2616
2995
  crlfDelay: Infinity
2617
2996
  });
2618
2997
  rl.on('line', line => output.write(line + os.EOL));
2998
+ rl.on('error', () => {}); // Ignore errors
2619
2999
  }
2620
3000
 
2621
3001
  /**
@@ -2631,19 +3011,11 @@ async function resolveAdapterMainFile(adapter) {
2631
3011
  throw new Error(`Could not find adapter dir of ${adapter}`);
2632
3012
  }
2633
3013
 
2634
- const possibleMainFiles = [
2635
- 'main.js',
2636
- `${adapter}.js`
2637
- ];
3014
+ const possibleMainFiles = ['main.js', `${adapter}.js`];
2638
3015
 
2639
3016
  // Add package.json -> main as the 2nd choice
2640
3017
  try {
2641
- const pack = JSON.parse(
2642
- await fs.readFile(
2643
- path.join(adapterDir, 'package.json'),
2644
- 'utf8'
2645
- )
2646
- );
3018
+ const pack = JSON.parse(await fs.readFile(path.join(adapterDir, 'package.json'), 'utf8'));
2647
3019
  if (pack && typeof pack.main === 'string') {
2648
3020
  possibleMainFiles.unshift(pack.main);
2649
3021
  }
@@ -2653,12 +3025,7 @@ async function resolveAdapterMainFile(adapter) {
2653
3025
 
2654
3026
  // Add io-package.json -> common.main as the preferred choice
2655
3027
  try {
2656
- const ioPack = JSON.parse(
2657
- await fs.readFile(
2658
- path.join(adapterDir, 'io-package.json'),
2659
- 'utf8'
2660
- )
2661
- );
3028
+ const ioPack = JSON.parse(await fs.readFile(path.join(adapterDir, 'io-package.json'), 'utf8'));
2662
3029
  if (ioPack && ioPack.common && typeof ioPack.common.main === 'string') {
2663
3030
  possibleMainFiles.unshift(ioPack.common.main);
2664
3031
  }
@@ -2718,7 +3085,8 @@ function parseShortGithubUrl(url) {
2718
3085
  }
2719
3086
 
2720
3087
  /** This is used to parse the pathname of a github URL */
2721
- const githubPathnameRegex = /^\/(?<user>[^/]+)\/(?<repo>[^/]*?)(?:\.git)?(?:\/(?:tree|tarball|archive)\/(?<commit>.*?)(?:\.(?:zip|gz|tar\.gz))?)?$/;
3088
+ const githubPathnameRegex =
3089
+ /^\/(?<user>[^/]+)\/(?<repo>[^/]*?)(?:\.git)?(?:\/(?:tree|tarball|archive)\/(?<commit>.*?)(?:\.(?:zip|gz|tar\.gz))?)?$/;
2722
3090
 
2723
3091
  /**
2724
3092
  * Tests if the given pathname matches the format /<githubname>/<githubrepo>[.git][/<tarball|tree|archive>/<commit-ish>[.zip|.gz]]
@@ -2756,7 +3124,7 @@ function removePreservedProperties(preserve, oldObj, newObj) {
2756
3124
  if (isObject(preserve[prop]) && isObject(newObj[prop])) {
2757
3125
  // we have to go one step deeper
2758
3126
  removePreservedProperties(preserve[prop], oldObj[prop], newObj[prop]);
2759
- } else if (newObj && newObj[prop] !== undefined && (oldObj && oldObj[prop] !== undefined)) {
3127
+ } else if (newObj && newObj[prop] !== undefined && oldObj && oldObj[prop] !== undefined) {
2760
3128
  // we only need to remove something if its in the old object and in the new one
2761
3129
  if (typeof preserve[prop] === 'boolean') {
2762
3130
  delete newObj[prop];
@@ -2831,7 +3199,8 @@ function getInstanceIndicatorObjects(namespace, createWakeup) {
2831
3199
  unit: '% of one core'
2832
3200
  },
2833
3201
  native: {}
2834
- }, {
3202
+ },
3203
+ {
2835
3204
  _id: `${id}.cputime`,
2836
3205
  type: 'state',
2837
3206
  common: {
@@ -2902,7 +3271,7 @@ function getInstanceIndicatorObjects(namespace, createWakeup) {
2902
3271
  type: 'state',
2903
3272
  common: {
2904
3273
  name: `${namespace} events input counter`,
2905
- desc: 'State\'s inputs in 15 seconds',
3274
+ desc: "State's inputs in 15 seconds",
2906
3275
  type: 'number',
2907
3276
  read: true,
2908
3277
  write: false,
@@ -2916,7 +3285,7 @@ function getInstanceIndicatorObjects(namespace, createWakeup) {
2916
3285
  type: 'state',
2917
3286
  common: {
2918
3287
  name: `${namespace} events output counter`,
2919
- desc: 'State\'s outputs in 15 seconds',
3288
+ desc: "State's outputs in 15 seconds",
2920
3289
  type: 'number',
2921
3290
  read: true,
2922
3291
  write: false,
@@ -2988,11 +3357,14 @@ function getInstanceIndicatorObjects(namespace, createWakeup) {
2988
3357
  function getLogger(log) {
2989
3358
  if (!log) {
2990
3359
  log = {
2991
- silly: function (_msg) {/*console.log(msg);*/
3360
+ silly: function (_msg) {
3361
+ /*console.log(msg);*/
2992
3362
  },
2993
- debug: function (_msg) {/*console.log(msg);*/
3363
+ debug: function (_msg) {
3364
+ /*console.log(msg);*/
2994
3365
  },
2995
- info: function (_msg) {/*console.log(msg);*/
3366
+ info: function (_msg) {
3367
+ /*console.log(msg);*/
2996
3368
  },
2997
3369
  warn: function (msg) {
2998
3370
  console.log(msg);
@@ -3016,7 +3388,7 @@ function getLogger(log) {
3016
3388
  * @return {Promise<object[]>}
3017
3389
  */
3018
3390
  async function getInstancesOrderedByStartPrio(objects, logger, logPrefix = '') {
3019
- const instances = {'1': [], '2': [], '3': [], 'admin': []};
3391
+ const instances = { 1: [], 2: [], 3: [], admin: [] };
3020
3392
  const allowedTiers = [1, 2, 3];
3021
3393
 
3022
3394
  if (logPrefix) {
@@ -3026,17 +3398,20 @@ async function getInstancesOrderedByStartPrio(objects, logger, logPrefix = '') {
3026
3398
 
3027
3399
  let doc = {};
3028
3400
  try {
3029
- doc = await objects.getObjectViewAsync('system', 'instance');
3401
+ doc = await objects.getObjectViewAsync('system', 'instance', {
3402
+ startkey: 'system.adapter.',
3403
+ endkey: 'system.adapter.\u9999'
3404
+ });
3030
3405
  } catch (e) {
3031
- if (e.message.startsWith('Cannot find ')) {
3032
- logger.error(`${logPrefix}_design/system missing - call node ${getAppName()}.js setup`);
3406
+ if (e.message && e.message.startsWith('Cannot find ')) {
3407
+ logger.error(`${logPrefix} _design/system missing - call node ${module.exports.appName}.js setup`);
3033
3408
  } else {
3034
- logger.error(`${logPrefix}Can not get instances: ${e.message}`);
3409
+ logger.error(`${logPrefix} Can not get instances: ${e.message}`);
3035
3410
  }
3036
3411
  }
3037
3412
 
3038
3413
  if (!doc.rows || doc.rows.length === 0) {
3039
- logger.info(`${logPrefix}no instances found`);
3414
+ logger.info(`${logPrefix} no instances found`);
3040
3415
  } else {
3041
3416
  for (const row of doc.rows) {
3042
3417
  if (row && row.value) {
@@ -3065,36 +3440,230 @@ async function getInstancesOrderedByStartPrio(objects, logger, logPrefix = '') {
3065
3440
  * @returns {Promise<void>}
3066
3441
  */
3067
3442
  async function setExecutableCapabilities(execPath, capabilities, modeEffective, modePermitted, modeInherited) {
3443
+ if (!Array.isArray(capabilities) || !capabilities.length) {
3444
+ throw new Error('No capabilities array provided');
3445
+ }
3446
+
3068
3447
  // if not linux do nothing and silently exit
3069
- if (os.platform() === 'linux') {
3070
- if (Array.isArray(capabilities) && capabilities.length) {
3071
- let modes = '';
3072
- const capabilitiesStr = capabilities.join(',');
3448
+ if (os.platform() !== 'linux') {
3449
+ return;
3450
+ }
3073
3451
 
3074
- if (modeEffective) {
3075
- modes += 'e';
3452
+ // if Docker and Admin Capabilities should be set check if we are allowed to do that
3453
+ if (isDocker() && capabilities.includes('cap_net_admin')) {
3454
+ try {
3455
+ const systemCaps = fs.readFileSync(`/proc/${process.pid}/status`, 'utf-8');
3456
+ const capBnd = systemCaps.match(/^CapBnd:\s(.+)$/m);
3457
+ // We found a value in CapBnd line
3458
+ if (capBnd && capBnd[1]) {
3459
+ const { stdout } = await execAsync(`capsh --decode=${capBnd[1]}`);
3460
+ // Stdout looks like "0x00000000a80425fb=cap_chown,cap_dac_override,..."
3461
+ if (stdout && stdout.startsWith(`0x${capBnd[1]}=`)) {
3462
+ const capBndArr = stdout.substring(capBnd[1].length + 3).split(',');
3463
+ // if Admin Capability is not included in System Capabilities we remove it from array
3464
+ if (!capBndArr.includes('cap_net_admin')) {
3465
+ capabilities = capabilities.filter(c => c !== 'cap_net_admin');
3466
+ }
3467
+ }
3076
3468
  }
3469
+ } catch {
3470
+ // Ok we could not find it out, so update Caps but better without Admin Capability
3471
+ capabilities = capabilities.filter(c => c !== 'cap_net_admin');
3472
+ }
3473
+ }
3077
3474
 
3078
- if (modePermitted) {
3079
- modes += 'p';
3080
- }
3475
+ if (capabilities.length) {
3476
+ let modes = '';
3477
+ const capabilitiesStr = capabilities.join(',');
3081
3478
 
3082
- if (modeInherited) {
3083
- modes += 'i';
3084
- }
3479
+ if (modeEffective) {
3480
+ modes += 'e';
3481
+ }
3085
3482
 
3086
- if (modes.length) {
3087
- modes = `+${modes}`;
3088
- }
3483
+ if (modePermitted) {
3484
+ modes += 'p';
3485
+ }
3486
+
3487
+ if (modeInherited) {
3488
+ modes += 'i';
3489
+ }
3490
+
3491
+ if (modes.length) {
3492
+ modes = `+${modes}`;
3493
+ }
3494
+
3495
+ // if this throws it needs to be caught outside
3496
+ await cpPromise.exec(`sudo setcap ${capabilitiesStr}${modes} ${execPath}`);
3497
+ }
3498
+ }
3499
+
3500
+ /**
3501
+ * Requests the licenses from ioBroker.net
3502
+ * @param {string} login Login for ioBroker.net
3503
+ * @param {string} password Decoded password for ioBroker.net
3504
+ * @returns {Promise<object[]>} array of all licenses stored on iobroker.net
3505
+ */
3506
+ async function _readLicenses(login, password) {
3507
+ axios = axios || require('axios');
3508
+ const config = {
3509
+ headers: { Authorization: `Basic ${Buffer.from(login + ':' + password).toString('base64')}` },
3510
+ timeout: 4000
3511
+ };
3089
3512
 
3090
- // if this throws it needs to be caught outside
3091
- await cpPromise.exec(`sudo setcap ${capabilitiesStr}${modes} ${execPath}`);
3513
+ try {
3514
+ const response = await axios.get(`https://iobroker.net:3001/api/v1/licenses`, config);
3515
+ if (response.data && response.data.length) {
3516
+ const now = Date.now();
3517
+ response.data = response.data.filter(
3518
+ license =>
3519
+ !license.validTill ||
3520
+ license.validTill === '0000-00-00 00:00:00' ||
3521
+ new Date(license.validTill).getTime() > now
3522
+ );
3523
+ }
3524
+
3525
+ return response.data;
3526
+ } catch (err) {
3527
+ if (err.response) {
3528
+ throw new Error((err.response.data && err.response.data.error) || err.response.data || err.response.status);
3529
+ } else if (err.request) {
3530
+ throw new Error('no response');
3092
3531
  } else {
3093
- throw new Error('No capabilities array provided');
3532
+ throw err;
3094
3533
  }
3095
3534
  }
3096
3535
  }
3097
3536
 
3537
+ /**
3538
+ * Reads the licenses from iobroker.net
3539
+ * Reads the licenses from iobroker.net and if no login/password provided stores it in system.licenses
3540
+ * @param {object} objects Object store instance
3541
+ * @param {string} login Login for ioBroker.net
3542
+ * @param {string} password Decoded password for ioBroker.net
3543
+ * @returns {Promise<object[]>} array of all licenses stored on iobroker.net
3544
+ */
3545
+ async function updateLicenses(objects, login, password) {
3546
+ // if login and password provided in the message, just try to read without saving it in system.licenses
3547
+ if (login && password) {
3548
+ return _readLicenses(login, password);
3549
+ } else {
3550
+ // get actual object
3551
+ const systemLicenses = await objects.getObjectAsync('system.licenses');
3552
+ // If password and login exist
3553
+ if (systemLicenses && systemLicenses.native && systemLicenses.native.password && systemLicenses.native.login) {
3554
+ try {
3555
+ // get the secret to decode the password
3556
+ const systemConfig = await objects.getObjectAsync('system.config');
3557
+
3558
+ // decode the password
3559
+ let password;
3560
+ try {
3561
+ password = decrypt(systemConfig.native.secret, systemLicenses.native.password);
3562
+ } catch (err) {
3563
+ throw new Error(`Cannot decode password: ${err.message}`);
3564
+ }
3565
+
3566
+ // read licenses from iobroker.net
3567
+ const licenses = await _readLicenses(systemLicenses.native.login, password);
3568
+ // save licenses to system.licenses and remember the time
3569
+ // merge the information together
3570
+ const oldLicenses = systemLicenses.native.licenses || [];
3571
+ systemLicenses.native.licenses = licenses;
3572
+ oldLicenses.forEach(oldLicense => {
3573
+ if (oldLicense.usedBy) {
3574
+ const newLicense = licenses.find(item => item.json === oldLicense.json);
3575
+ if (newLicense) {
3576
+ newLicense.usedBy = oldLicense.usedBy;
3577
+ }
3578
+ }
3579
+ });
3580
+
3581
+ systemLicenses.native.readTime = new Date().toISOString();
3582
+
3583
+ // update read time
3584
+ await objects.setObjectAsync('system.licenses', systemLicenses);
3585
+ return licenses;
3586
+ } catch (err) {
3587
+ // if password is invalid
3588
+ if (
3589
+ err.message.includes('Authentication required') ||
3590
+ err.message.includes('Cannot decode password:')
3591
+ ) {
3592
+ // clear existing licenses if exist
3593
+ if (
3594
+ systemLicenses &&
3595
+ systemLicenses.native &&
3596
+ systemLicenses.native.licenses &&
3597
+ systemLicenses.native.licenses.length
3598
+ ) {
3599
+ systemLicenses.native.licenses = [];
3600
+ systemLicenses.native.readTime = new Date().toISOString();
3601
+ await objects.setObjectAsync('system.licenses', systemLicenses);
3602
+ }
3603
+ }
3604
+
3605
+ throw err;
3606
+ }
3607
+ } else {
3608
+ // if password or login are empty => clear existing licenses if exist
3609
+ if (
3610
+ systemLicenses &&
3611
+ systemLicenses.native &&
3612
+ systemLicenses.native.licenses &&
3613
+ systemLicenses.native.licenses.length
3614
+ ) {
3615
+ systemLicenses.native.licenses = [];
3616
+ systemLicenses.native.readTime = new Date().toISOString();
3617
+ await objects.setObjectAsync('system.licenses', systemLicenses);
3618
+ }
3619
+ throw new Error('No password or login');
3620
+ }
3621
+ }
3622
+ }
3623
+
3624
+ /**
3625
+ * @typedef {object} GZipFileOptions
3626
+ * @property {boolean} [deleteInput] Delete the input file after compression. Default: false.
3627
+ */
3628
+
3629
+ /**
3630
+ * Compresses an input file using GZip and writes it somewhere else
3631
+ * @param {string} inputFilename The filename of the input file that should be gzipped
3632
+ * @param {string} outputFilename The filename of the output file where the gzipped content should be written to
3633
+ * @param {GZipFileOptions} [options] Options for the compression
3634
+ * @returns {Promise<void>}
3635
+ */
3636
+ function compressFileGZip(inputFilename, outputFilename, options = {}) {
3637
+ const { deleteInput = false } = options;
3638
+
3639
+ return new Promise((resolve, reject) => {
3640
+ const input = fs.createReadStream(inputFilename);
3641
+ const output = fs.createWriteStream(outputFilename);
3642
+ const compress = zlib.createGzip();
3643
+ input.on('error', err => {
3644
+ reject(err);
3645
+ });
3646
+ output.on('error', err => {
3647
+ reject(err);
3648
+ });
3649
+ compress.on('error', err => {
3650
+ reject(err);
3651
+ });
3652
+ output.on('close', () => {
3653
+ if (deleteInput) {
3654
+ try {
3655
+ fs.unlinkSync(inputFilename);
3656
+ } catch {
3657
+ // Ignore
3658
+ }
3659
+ }
3660
+ resolve();
3661
+ });
3662
+
3663
+ input.pipe(compress).pipe(output);
3664
+ });
3665
+ }
3666
+
3098
3667
  const ERROR_NOT_FOUND = 'Not exists';
3099
3668
  const ERROR_EMPTY_OBJECT = 'null object';
3100
3669
  const ERROR_NO_OBJECT = 'no object';
@@ -3109,9 +3678,7 @@ module.exports = {
3109
3678
  generateDefaultCertificates,
3110
3679
  getAdapterDir,
3111
3680
  getInstances,
3112
- getInstancesAsync,
3113
3681
  getAllInstances,
3114
- getAllInstancesAsync,
3115
3682
  getCertificateInfo,
3116
3683
  getConfigFileName,
3117
3684
  getDefaultDataDir,
@@ -3123,6 +3690,7 @@ module.exports = {
3123
3690
  getInstanceIndicatorObjects,
3124
3691
  getIoPack,
3125
3692
  getJson,
3693
+ getJsonAsync,
3126
3694
  getInstancesOrderedByStartPrio,
3127
3695
  getRepositoryFile,
3128
3696
  getRepositoryFileAsync,
@@ -3160,11 +3728,17 @@ module.exports = {
3160
3728
  parseShortGithubUrl,
3161
3729
  setExecutableCapabilities,
3162
3730
  isGithubPathname,
3731
+ isSingleHost,
3732
+ isHostRunning,
3163
3733
  parseGithubPathname,
3164
3734
  removePreservedProperties,
3165
3735
  FORBIDDEN_CHARS,
3166
3736
  getControllerDir,
3167
3737
  getLogger,
3738
+ getAllEnums,
3739
+ updateLicenses,
3740
+ compressFileGZip,
3741
+ isDocker,
3168
3742
  ERRORS: {
3169
3743
  ERROR_NOT_FOUND: ERROR_NOT_FOUND,
3170
3744
  ERROR_EMPTY_OBJECT: ERROR_EMPTY_OBJECT,