matterbridge 2.1.6-dev.3 → 2.1.6-dev.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.
@@ -1,13 +1,11 @@
1
- import { fileURLToPath } from 'url';
2
- import { promises as fs } from 'fs';
3
- import { exec, spawn } from 'child_process';
4
- import EventEmitter from 'events';
5
- import os from 'os';
6
- import path from 'path';
7
- import { randomBytes } from 'crypto';
8
- import { NodeStorageManager } from './storage/export.js';
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { promises as fs } from 'node:fs';
4
+ import EventEmitter from 'node:events';
9
5
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
10
- import { logInterfaces, copyDirectory, getParameter, getIntParameter, hasParameter, getNpmPackageVersion } from './utils/utils.js';
6
+ import { NodeStorageManager } from './storage/export.js';
7
+ import { getParameter, getIntParameter, hasParameter } from './utils/export.js';
8
+ import { logInterfaces, copyDirectory, getNpmPackageVersion, getGlobalNodeModules } from './utils/utils.js';
11
9
  import { PluginManager } from './pluginManager.js';
12
10
  import { DeviceManager } from './deviceManager.js';
13
11
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
@@ -35,7 +33,8 @@ export class Matterbridge extends EventEmitter {
35
33
  totalMemory: '',
36
34
  freeMemory: '',
37
35
  systemUptime: '',
38
- cpuUsed: '',
36
+ processUptime: '',
37
+ cpuUsage: '',
39
38
  rss: '',
40
39
  heapTotal: '',
41
40
  heapUsed: '',
@@ -85,6 +84,7 @@ export class Matterbridge extends EventEmitter {
85
84
  bridgeMode = '';
86
85
  restartMode = '';
87
86
  profile = getParameter('profile');
87
+ shutdown = false;
88
88
  edge = true;
89
89
  log;
90
90
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
@@ -126,6 +126,12 @@ export class Matterbridge extends EventEmitter {
126
126
  constructor() {
127
127
  super();
128
128
  }
129
+ emit(eventName, ...args) {
130
+ return super.emit(eventName, ...args);
131
+ }
132
+ on(eventName, listener) {
133
+ return super.on(eventName, listener);
134
+ }
129
135
  getDevices() {
130
136
  return this.devices.array();
131
137
  }
@@ -167,6 +173,7 @@ export class Matterbridge extends EventEmitter {
167
173
  return Matterbridge.instance;
168
174
  }
169
175
  async destroyInstance() {
176
+ this.log.info(`Destroy instance...`);
170
177
  const servers = [];
171
178
  if (this.bridgeMode === 'bridge') {
172
179
  if (this.serverNode)
@@ -179,6 +186,7 @@ export class Matterbridge extends EventEmitter {
179
186
  }
180
187
  }
181
188
  await this.cleanup('destroying instance...', false);
189
+ this.log.info(`Dispose ${servers.length} MdnsService...`);
182
190
  for (const server of servers) {
183
191
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
184
192
  this.log.info(`Closed ${server.id} MdnsService`);
@@ -436,7 +444,7 @@ export class Matterbridge extends EventEmitter {
436
444
  - disable [plugin name]: disable the globally installed plugin with the given name
437
445
  - reset [plugin path]: remove the commissioning for the plugin from the given absolute or relative path (childbridge mode). Shutdown Matterbridge before using it!
438
446
  - reset [plugin name]: remove the commissioning for the globally installed plugin (childbridge mode). Shutdown Matterbridge before using it!${rs}`);
439
- this.emit('shutdown');
447
+ this.shutdown = true;
440
448
  return;
441
449
  }
442
450
  if (hasParameter('list')) {
@@ -465,7 +473,7 @@ export class Matterbridge extends EventEmitter {
465
473
  this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
466
474
  }
467
475
  });
468
- this.emit('shutdown');
476
+ this.shutdown = true;
469
477
  return;
470
478
  }
471
479
  if (hasParameter('logstorage')) {
@@ -475,41 +483,42 @@ export class Matterbridge extends EventEmitter {
475
483
  this.log.info(`${plg}${plugin.name}${nf} storage log`);
476
484
  await plugin.nodeContext?.logStorage();
477
485
  }
478
- this.emit('shutdown');
486
+ this.shutdown = true;
479
487
  return;
480
488
  }
481
489
  if (hasParameter('loginterfaces')) {
482
490
  this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
483
491
  logInterfaces();
484
- this.emit('shutdown');
492
+ this.shutdown = true;
485
493
  return;
486
494
  }
487
495
  if (getParameter('add')) {
488
496
  this.log.debug(`Adding plugin ${getParameter('add')}`);
489
497
  await this.plugins.add(getParameter('add'));
490
- this.emit('shutdown');
498
+ this.shutdown = true;
491
499
  return;
492
500
  }
493
501
  if (getParameter('remove')) {
494
502
  this.log.debug(`Removing plugin ${getParameter('remove')}`);
495
503
  await this.plugins.remove(getParameter('remove'));
496
- this.emit('shutdown');
504
+ this.shutdown = true;
497
505
  return;
498
506
  }
499
507
  if (getParameter('enable')) {
500
508
  this.log.debug(`Enabling plugin ${getParameter('enable')}`);
501
509
  await this.plugins.enable(getParameter('enable'));
502
- this.emit('shutdown');
510
+ this.shutdown = true;
503
511
  return;
504
512
  }
505
513
  if (getParameter('disable')) {
506
514
  this.log.debug(`Disabling plugin ${getParameter('disable')}`);
507
515
  await this.plugins.disable(getParameter('disable'));
508
- this.emit('shutdown');
516
+ this.shutdown = true;
509
517
  return;
510
518
  }
511
519
  if (hasParameter('factoryreset')) {
512
520
  await this.shutdownProcessAndFactoryReset();
521
+ this.shutdown = true;
513
522
  return;
514
523
  }
515
524
  try {
@@ -521,6 +530,7 @@ export class Matterbridge extends EventEmitter {
521
530
  }
522
531
  if (hasParameter('reset') && getParameter('reset') === undefined) {
523
532
  await this.shutdownProcessAndReset();
533
+ this.shutdown = true;
524
534
  return;
525
535
  }
526
536
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
@@ -528,20 +538,23 @@ export class Matterbridge extends EventEmitter {
528
538
  const plugin = this.plugins.get(getParameter('reset'));
529
539
  if (plugin) {
530
540
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
531
- if (!matterStorageManager)
541
+ if (!matterStorageManager) {
532
542
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
533
- await matterStorageManager?.createContext('events')?.clearAll();
534
- await matterStorageManager?.createContext('fabrics')?.clearAll();
535
- await matterStorageManager?.createContext('root')?.clearAll();
536
- await matterStorageManager?.createContext('sessions')?.clearAll();
537
- await matterStorageManager?.createContext('persist')?.clearAll();
538
- this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
543
+ }
544
+ else {
545
+ await matterStorageManager?.createContext('events')?.clearAll();
546
+ await matterStorageManager?.createContext('fabrics')?.clearAll();
547
+ await matterStorageManager?.createContext('root')?.clearAll();
548
+ await matterStorageManager?.createContext('sessions')?.clearAll();
549
+ await matterStorageManager?.createContext('persist')?.clearAll();
550
+ this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
551
+ }
539
552
  }
540
553
  else {
541
554
  this.log.warn(`Plugin ${plg}${getParameter('reset')}${wr} not registerd in matterbridge`);
542
555
  }
543
556
  await this.stopMatterStorage();
544
- this.emit('shutdown');
557
+ this.shutdown = true;
545
558
  return;
546
559
  }
547
560
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
@@ -713,6 +726,7 @@ export class Matterbridge extends EventEmitter {
713
726
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
714
727
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
715
728
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
729
+ const { fileURLToPath } = await import('node:url');
716
730
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
717
731
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
718
732
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
@@ -721,7 +735,9 @@ export class Matterbridge extends EventEmitter {
721
735
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
722
736
  if (this.globalModulesDirectory === '') {
723
737
  try {
724
- this.globalModulesDirectory = await this.getGlobalNodeModules();
738
+ this.execRunningCount++;
739
+ this.globalModulesDirectory = await getGlobalNodeModules();
740
+ this.execRunningCount--;
725
741
  this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
726
742
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
727
743
  await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
@@ -790,20 +806,6 @@ export class Matterbridge extends EventEmitter {
790
806
  const cmdArgs = process.argv.slice(2).join(' ');
791
807
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
792
808
  }
793
- async getGlobalNodeModules() {
794
- return new Promise((resolve, reject) => {
795
- this.execRunningCount++;
796
- exec('npm root -g', (error, stdout) => {
797
- this.execRunningCount--;
798
- if (error) {
799
- reject(error);
800
- }
801
- else {
802
- resolve(stdout.trim());
803
- }
804
- });
805
- });
806
- }
807
809
  async getMatterbridgeLatestVersion() {
808
810
  getNpmPackageVersion('matterbridge')
809
811
  .then(async (version) => {
@@ -1195,11 +1197,7 @@ export class Matterbridge extends EventEmitter {
1195
1197
  if (!plugin.enabled)
1196
1198
  continue;
1197
1199
  if (plugin.type === 'DynamicPlatform') {
1198
- plugin.locked = true;
1199
- plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
1200
- plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1201
- plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1202
- await plugin.serverNode.add(plugin.aggregatorNode);
1200
+ await this.createDynamicPlugin(plugin);
1203
1201
  }
1204
1202
  }
1205
1203
  await this.startPlugins();
@@ -1252,7 +1250,7 @@ export class Matterbridge extends EventEmitter {
1252
1250
  for (const plugin of this.plugins) {
1253
1251
  if (!plugin.enabled || plugin.error)
1254
1252
  continue;
1255
- if (!plugin.addedDevices || plugin.addedDevices === 0) {
1253
+ if (plugin.type !== 'DynamicPlatform' && (!plugin.addedDevices || plugin.addedDevices === 0)) {
1256
1254
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
1257
1255
  continue;
1258
1256
  }
@@ -1308,6 +1306,7 @@ export class Matterbridge extends EventEmitter {
1308
1306
  this.log.info('Matter node storage closed');
1309
1307
  }
1310
1308
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1309
+ const { randomBytes } = await import('node:crypto');
1311
1310
  if (!this.matterStorageService)
1312
1311
  throw new Error('No storage service initialized');
1313
1312
  this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
@@ -1550,13 +1549,18 @@ export class Matterbridge extends EventEmitter {
1550
1549
  }
1551
1550
  }
1552
1551
  async advertiseServerNode(matterServerNode) {
1553
- if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
1552
+ if (matterServerNode) {
1554
1553
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
1555
1554
  const { qrPairingCode, manualPairingCode } = matterServerNode.state.commissioning.pairingCodes;
1556
1555
  this.log.notice(`Advertising for ${matterServerNode.id} is now started with the following pairing codes: qrPairingCode ${qrPairingCode}, manualPairingCode ${manualPairingCode}`);
1557
1556
  return { qrPairingCode, manualPairingCode };
1558
1557
  }
1559
- return undefined;
1558
+ }
1559
+ async stopAdvertiseServerNode(matterServerNode) {
1560
+ if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1561
+ await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1562
+ this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1563
+ }
1560
1564
  }
1561
1565
  async createAggregatorNode(storageContext) {
1562
1566
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
@@ -1628,15 +1632,6 @@ export class Matterbridge extends EventEmitter {
1628
1632
  plugin.registeredDevices--;
1629
1633
  if (plugin.addedDevices !== undefined)
1630
1634
  plugin.addedDevices--;
1631
- if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1632
- if (plugin.serverNode) {
1633
- await this.stopServerNode(plugin.serverNode);
1634
- plugin.locked = false;
1635
- plugin.aggregatorNode = undefined;
1636
- plugin.serverNode = undefined;
1637
- this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
1638
- }
1639
- }
1640
1635
  }
1641
1636
  this.devices.remove(device);
1642
1637
  }
@@ -1695,7 +1690,7 @@ export class Matterbridge extends EventEmitter {
1695
1690
  getVendorIdName = (vendorId) => {
1696
1691
  if (!vendorId)
1697
1692
  return '';
1698
- let vendorName = '';
1693
+ let vendorName = '(Unknown vendorId)';
1699
1694
  switch (vendorId) {
1700
1695
  case 4937:
1701
1696
  vendorName = '(AppleHome)';
@@ -1725,15 +1720,13 @@ export class Matterbridge extends EventEmitter {
1725
1720
  vendorName = '(eWeLink)';
1726
1721
  break;
1727
1722
  case 65521:
1728
- vendorName = '(PythonMatterServer)';
1729
- break;
1730
- default:
1731
- vendorName = '(unknown)';
1723
+ vendorName = '(MatterServer)';
1732
1724
  break;
1733
1725
  }
1734
1726
  return vendorName;
1735
1727
  };
1736
1728
  async spawnCommand(command, args = []) {
1729
+ const { spawn } = await import('node:child_process');
1737
1730
  const cmdLine = command + ' ' + args.join(' ');
1738
1731
  if (process.platform === 'win32' && command === 'npm') {
1739
1732
  const argstring = 'npm ' + args.join(' ');
@@ -1,6 +1,6 @@
1
1
  import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } from './logger/export.js';
2
2
  import { bridgedNode } from './matterbridgeDeviceTypes.js';
3
- import { isValidNumber, isValidObject } from './utils/utils.js';
3
+ import { isValidNumber, isValidObject } from './utils/export.js';
4
4
  import { MatterbridgeBehavior, MatterbridgeBehaviorDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, } from './matterbridgeBehaviors.js';
5
5
  import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, } from './matterbridgeEndpointHelpers.js';
6
6
  import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, VendorId } from '@matter/main';
@@ -1,5 +1,6 @@
1
- import { createHash } from 'crypto';
1
+ import { createHash } from 'node:crypto';
2
2
  import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from './logger/export.js';
3
+ import { deepCopy, deepEqual, isValidArray } from './utils/export.js';
3
4
  import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, } from './matterbridgeBehaviors.js';
4
5
  import { Lifecycle } from '@matter/main';
5
6
  import { getClusterNameById } from '@matter/main/types';
@@ -73,7 +74,6 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/main/behaviors/pm25-
73
74
  import { Pm10ConcentrationMeasurementServer } from '@matter/main/behaviors/pm10-concentration-measurement';
74
75
  import { RadonConcentrationMeasurementServer } from '@matter/main/behaviors/radon-concentration-measurement';
75
76
  import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement';
76
- import { deepCopy, deepEqual, isValidArray } from './utils/utils.js';
77
77
  export function capitalizeFirstLetter(name) {
78
78
  if (!name)
79
79
  return name;
@@ -1,8 +1,8 @@
1
1
  import { checkNotLatinCharacters } from './matterbridgeEndpointHelpers.js';
2
- import { isValidArray, isValidObject, isValidString } from './utils/utils.js';
2
+ import { isValidArray, isValidObject, isValidString } from './utils/export.js';
3
3
  import { CYAN, db, er, nf, wr } from './logger/export.js';
4
4
  import { NodeStorageManager } from './storage/export.js';
5
- import path from 'path';
5
+ import path from 'node:path';
6
6
  export class MatterbridgePlatform {
7
7
  matterbridge;
8
8
  log;
@@ -1,10 +1,5 @@
1
- import { AnsiLogger, BLUE, db, er, nf, nt, rs, UNDERLINE, UNDERLINEOFF, wr } from './logger/export.js';
2
- import path from 'path';
3
- import { promises as fs } from 'fs';
4
- import { pathToFileURL } from 'url';
5
- import { exec } from 'child_process';
1
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from './logger/export.js';
6
2
  import { plg, typ } from './matterbridgeTypes.js';
7
- import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
8
3
  export class PluginManager {
9
4
  _plugins = new Map();
10
5
  nodeContext;
@@ -84,12 +79,14 @@ export class PluginManager {
84
79
  return plugins.length;
85
80
  }
86
81
  async resolve(pluginPath) {
82
+ const { default: path } = await import('node:path');
83
+ const { promises } = await import('node:fs');
87
84
  if (!pluginPath.endsWith('package.json'))
88
85
  pluginPath = path.join(pluginPath, 'package.json');
89
86
  let packageJsonPath = path.resolve(pluginPath);
90
87
  this.log.debug(`Resolving plugin path ${plg}${packageJsonPath}${db}`);
91
88
  try {
92
- await fs.access(packageJsonPath);
89
+ await promises.access(packageJsonPath);
93
90
  }
94
91
  catch {
95
92
  this.log.debug(`Package.json not found at ${plg}${packageJsonPath}${db}`);
@@ -97,7 +94,7 @@ export class PluginManager {
97
94
  this.log.debug(`Trying at ${plg}${packageJsonPath}${db}`);
98
95
  }
99
96
  try {
100
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
97
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
101
98
  if (!packageJson.name) {
102
99
  this.log.error(`Package.json name not found at ${packageJsonPath}`);
103
100
  return null;
@@ -161,9 +158,10 @@ export class PluginManager {
161
158
  }
162
159
  }
163
160
  async parse(plugin) {
164
- this.log.debug(`Parsing package.json of plugin ${plg}${plugin.name}${db}`);
161
+ const { promises } = await import('node:fs');
165
162
  try {
166
- const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8'));
163
+ this.log.debug(`Parsing package.json of plugin ${plg}${plugin.name}${db}`);
164
+ const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
167
165
  if (!packageJson.name)
168
166
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no name in package.json`);
169
167
  if (!packageJson.version)
@@ -235,6 +233,7 @@ export class PluginManager {
235
233
  }
236
234
  }
237
235
  async enable(nameOrPath) {
236
+ const { promises } = await import('node:fs');
238
237
  if (!nameOrPath || nameOrPath === '')
239
238
  return null;
240
239
  if (this._plugins.has(nameOrPath)) {
@@ -250,7 +249,7 @@ export class PluginManager {
250
249
  return null;
251
250
  }
252
251
  try {
253
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
252
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
254
253
  const plugin = this._plugins.get(packageJson.name);
255
254
  if (!plugin) {
256
255
  this.log.error(`Failed to enable plugin ${plg}${nameOrPath}${er}: plugin not registered`);
@@ -267,6 +266,7 @@ export class PluginManager {
267
266
  }
268
267
  }
269
268
  async disable(nameOrPath) {
269
+ const { promises } = await import('node:fs');
270
270
  if (!nameOrPath || nameOrPath === '')
271
271
  return null;
272
272
  if (this._plugins.has(nameOrPath)) {
@@ -282,7 +282,7 @@ export class PluginManager {
282
282
  return null;
283
283
  }
284
284
  try {
285
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
285
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
286
286
  const plugin = this._plugins.get(packageJson.name);
287
287
  if (!plugin) {
288
288
  this.log.error(`Failed to disable plugin ${plg}${nameOrPath}${er}: plugin not registered`);
@@ -299,6 +299,7 @@ export class PluginManager {
299
299
  }
300
300
  }
301
301
  async remove(nameOrPath) {
302
+ const { promises } = await import('node:fs');
302
303
  if (!nameOrPath || nameOrPath === '')
303
304
  return null;
304
305
  if (this._plugins.has(nameOrPath)) {
@@ -314,7 +315,7 @@ export class PluginManager {
314
315
  return null;
315
316
  }
316
317
  try {
317
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
318
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
318
319
  const plugin = this._plugins.get(packageJson.name);
319
320
  if (!plugin) {
320
321
  this.log.error(`Failed to remove plugin ${plg}${nameOrPath}${er}: plugin not registered`);
@@ -331,6 +332,7 @@ export class PluginManager {
331
332
  }
332
333
  }
333
334
  async add(nameOrPath) {
335
+ const { promises } = await import('node:fs');
334
336
  if (!nameOrPath || nameOrPath === '')
335
337
  return null;
336
338
  const packageJsonPath = await this.resolve(nameOrPath);
@@ -339,7 +341,7 @@ export class PluginManager {
339
341
  return null;
340
342
  }
341
343
  try {
342
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
344
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
343
345
  if (this._plugins.get(packageJson.name)) {
344
346
  this.log.info(`Plugin ${plg}${nameOrPath}${nf} already registered`);
345
347
  return null;
@@ -351,15 +353,16 @@ export class PluginManager {
351
353
  return plugin || null;
352
354
  }
353
355
  catch (err) {
354
- this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err}`);
356
+ this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message : err}`);
355
357
  return null;
356
358
  }
357
359
  }
358
360
  async install(name) {
361
+ const { exec } = await import('node:child_process');
359
362
  await this.uninstall(name);
360
363
  this.log.info(`Installing plugin ${plg}${name}${nf}`);
361
- return new Promise((resolve, reject) => {
362
- exec(`npm install -g ${name} --omit=dev --force`, (error, stdout, stderr) => {
364
+ return new Promise((resolve) => {
365
+ exec(`npm install -g ${name} --omit=dev`, (error, stdout, stderr) => {
363
366
  if (error) {
364
367
  this.log.error(`Failed to install plugin ${plg}${name}${er}: ${error}`);
365
368
  this.log.debug(`Failed to install plugin ${plg}${name}${db}: ${stderr}`);
@@ -389,9 +392,10 @@ export class PluginManager {
389
392
  });
390
393
  }
391
394
  async uninstall(name) {
395
+ const { exec } = await import('node:child_process');
392
396
  this.log.info(`Uninstalling plugin ${plg}${name}${nf}`);
393
- return new Promise((resolve, reject) => {
394
- exec(`npm uninstall -g ${name} --force`, (error, stdout, stderr) => {
397
+ return new Promise((resolve) => {
398
+ exec(`npm uninstall -g ${name}`, (error, stdout, stderr) => {
395
399
  if (error) {
396
400
  this.log.error(`Failed to uninstall plugin ${plg}${name}${er}: ${error}`);
397
401
  this.log.debug(`Failed to uninstall plugin ${plg}${name}${db}: ${stderr}`);
@@ -406,6 +410,8 @@ export class PluginManager {
406
410
  });
407
411
  }
408
412
  async load(plugin, start = false, message = '', configure = false) {
413
+ const { promises } = await import('node:fs');
414
+ const { default: path } = await import('node:path');
409
415
  if (!plugin.enabled) {
410
416
  this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
411
417
  return undefined;
@@ -416,8 +422,9 @@ export class PluginManager {
416
422
  }
417
423
  this.log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
418
424
  try {
419
- const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8'));
425
+ const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
420
426
  const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
427
+ const { pathToFileURL } = await import('node:url');
421
428
  const pluginUrl = pathToFileURL(pluginEntry);
422
429
  this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
423
430
  const pluginInstance = await import(pluginUrl.href);
@@ -570,10 +577,13 @@ export class PluginManager {
570
577
  return undefined;
571
578
  }
572
579
  async loadConfig(plugin) {
580
+ const { default: path } = await import('node:path');
581
+ const { promises } = await import('node:fs');
582
+ const { shelly_config, somfytahoma_config, zigbee2mqtt_config } = await import('./defaultConfigSchema.js');
573
583
  const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
574
584
  try {
575
- await fs.access(configFile);
576
- const data = await fs.readFile(configFile, 'utf8');
585
+ await promises.access(configFile);
586
+ const data = await promises.readFile(configFile, 'utf8');
577
587
  const config = JSON.parse(data);
578
588
  this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
579
589
  config.name = plugin.name;
@@ -597,7 +607,7 @@ export class PluginManager {
597
607
  else
598
608
  config = { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
599
609
  try {
600
- await fs.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
610
+ await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
601
611
  this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
602
612
  return config;
603
613
  }
@@ -613,13 +623,15 @@ export class PluginManager {
613
623
  }
614
624
  }
615
625
  async saveConfigFromPlugin(plugin) {
626
+ const { default: path } = await import('node:path');
627
+ const { promises } = await import('node:fs');
616
628
  if (!plugin.platform?.config) {
617
629
  this.log.error(`Error saving config file for plugin ${plg}${plugin.name}${er}: config not found`);
618
630
  return Promise.reject(new Error(`Error saving config file for plugin ${plg}${plugin.name}${er}: config not found`));
619
631
  }
620
632
  const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
621
633
  try {
622
- await fs.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2), 'utf8');
634
+ await promises.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2), 'utf8');
623
635
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
624
636
  return Promise.resolve();
625
637
  }
@@ -629,13 +641,15 @@ export class PluginManager {
629
641
  }
630
642
  }
631
643
  async saveConfigFromJson(plugin, config) {
644
+ const { default: path } = await import('node:path');
645
+ const { promises } = await import('node:fs');
632
646
  if (!config.name || !config.type || config.name !== plugin.name) {
633
647
  this.log.error(`Error saving config file for plugin ${plg}${plugin.name}${er}. Wrong config data content:${rs}\n`, config);
634
648
  return;
635
649
  }
636
650
  const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
637
651
  try {
638
- await fs.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
652
+ await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
639
653
  plugin.configJson = config;
640
654
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
641
655
  }
@@ -645,10 +659,11 @@ export class PluginManager {
645
659
  }
646
660
  }
647
661
  async loadSchema(plugin) {
662
+ const { promises } = await import('node:fs');
648
663
  const schemaFile = plugin.path.replace('package.json', `${plugin.name}.schema.json`);
649
664
  try {
650
- await fs.access(schemaFile);
651
- const data = await fs.readFile(schemaFile, 'utf8');
665
+ await promises.access(schemaFile);
666
+ const data = await promises.readFile(schemaFile, 'utf8');
652
667
  const schema = JSON.parse(data);
653
668
  schema.title = plugin.description;
654
669
  schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
@@ -1,4 +1,4 @@
1
- import { assert } from 'console';
1
+ import { assert } from 'node:console';
2
2
  export function hslColorToRgbColor(hue, saturation, luminance) {
3
3
  if (hue === 360) {
4
4
  hue = 0;
@@ -1,2 +1,4 @@
1
1
  export * from './utils.js';
2
+ export * from './parameter.js';
3
+ export * from './isvalid.js';
2
4
  export * from './colorUtils.js';
@@ -0,0 +1,50 @@
1
+ export function isValidIpv4Address(ipv4Address) {
2
+ const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
3
+ return ipv4Regex.test(ipv4Address);
4
+ }
5
+ export function isValidNumber(value, min, max) {
6
+ if (value === undefined || value === null || typeof value !== 'number' || Number.isNaN(value))
7
+ return false;
8
+ if (min !== undefined && value < min)
9
+ return false;
10
+ if (max !== undefined && value > max)
11
+ return false;
12
+ return true;
13
+ }
14
+ export function isValidBoolean(value) {
15
+ return value !== undefined && value !== null && typeof value === 'boolean';
16
+ }
17
+ export function isValidString(value, minLength, maxLength) {
18
+ if (value === undefined || value === null || typeof value !== 'string')
19
+ return false;
20
+ if (minLength !== undefined && value.length < minLength)
21
+ return false;
22
+ if (maxLength !== undefined && value.length > maxLength)
23
+ return false;
24
+ return true;
25
+ }
26
+ export function isValidObject(value, minLength, maxLength) {
27
+ if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value))
28
+ return false;
29
+ const keys = Object.keys(value);
30
+ if (minLength !== undefined && keys.length < minLength)
31
+ return false;
32
+ if (maxLength !== undefined && keys.length > maxLength)
33
+ return false;
34
+ return true;
35
+ }
36
+ export function isValidArray(value, minLength, maxLength) {
37
+ if (value === undefined || value === null || !Array.isArray(value))
38
+ return false;
39
+ if (minLength !== undefined && value.length < minLength)
40
+ return false;
41
+ if (maxLength !== undefined && value.length > maxLength)
42
+ return false;
43
+ return true;
44
+ }
45
+ export function isValidNull(value) {
46
+ return value === null;
47
+ }
48
+ export function isValidUndefined(value) {
49
+ return value === undefined;
50
+ }