matterbridge 3.3.0-dev-20251004-43d8106 → 3.3.1-dev-20251007-4e5eaac

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.
@@ -9,9 +9,13 @@ import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat a
9
9
  import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
10
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
11
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
- import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
12
+ import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
13
+ import { copyDirectory } from './utils/copyDirectory.js';
14
+ import { createDirectory } from './utils/createDirectory.js';
15
+ import { isValidString, parseVersionString, isValidNumber } from './utils/isvalid.js';
16
+ import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
13
17
  import { withTimeout, waiter, wait } from './utils/wait.js';
14
- import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ, } from './matterbridgeTypes.js';
18
+ import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from './matterbridgeTypes.js';
15
19
  import { PluginManager } from './pluginManager.js';
16
20
  import { DeviceManager } from './deviceManager.js';
17
21
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
@@ -40,38 +44,6 @@ export class Matterbridge extends EventEmitter {
40
44
  heapTotal: '',
41
45
  heapUsed: '',
42
46
  };
43
- matterbridgeInformation = {
44
- homeDirectory: '',
45
- rootDirectory: '',
46
- matterbridgeDirectory: '',
47
- matterbridgePluginDirectory: '',
48
- matterbridgeCertDirectory: '',
49
- globalModulesDirectory: '',
50
- matterbridgeVersion: '',
51
- matterbridgeLatestVersion: '',
52
- matterbridgeDevVersion: '',
53
- bridgeMode: '',
54
- restartMode: '',
55
- virtualMode: 'outlet',
56
- readOnly: hasParameter('readonly') || hasParameter('shelly'),
57
- shellyBoard: hasParameter('shelly'),
58
- shellySysUpdate: false,
59
- shellyMainUpdate: false,
60
- profile: getParameter('profile'),
61
- loggerLevel: "info",
62
- fileLogger: false,
63
- matterLoggerLevel: MatterLogLevel.INFO,
64
- matterFileLogger: false,
65
- matterMdnsInterface: undefined,
66
- matterIpv4Address: undefined,
67
- matterIpv6Address: undefined,
68
- matterPort: 5540,
69
- matterDiscriminator: undefined,
70
- matterPasscode: undefined,
71
- restartRequired: false,
72
- fixedRestartRequired: false,
73
- updateRequired: false,
74
- };
75
47
  homeDirectory = '';
76
48
  rootDirectory = '';
77
49
  matterbridgeDirectory = '';
@@ -81,18 +53,30 @@ export class Matterbridge extends EventEmitter {
81
53
  matterbridgeVersion = '';
82
54
  matterbridgeLatestVersion = '';
83
55
  matterbridgeDevVersion = '';
56
+ frontendVersion = '';
84
57
  bridgeMode = '';
85
58
  restartMode = '';
59
+ virtualMode = 'outlet';
86
60
  profile = getParameter('profile');
87
- shutdown = false;
88
- failCountLimit = hasParameter('shelly') ? 600 : 120;
89
61
  log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
62
+ fileLogger = false;
90
63
  matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
64
+ matterFileLogger = false;
65
+ readOnly = hasParameter('readonly') || hasParameter('shelly');
66
+ shellyBoard = hasParameter('shelly');
67
+ shellySysUpdate = false;
68
+ shellyMainUpdate = false;
69
+ restartRequired = false;
70
+ fixedRestartRequired = false;
71
+ updateRequired = false;
91
72
  plugins = new PluginManager(this);
92
- devices = new DeviceManager(this);
73
+ devices = new DeviceManager();
93
74
  frontend = new Frontend(this);
94
75
  nodeStorage;
95
76
  nodeContext;
77
+ static instance;
78
+ shutdown = false;
79
+ failCountLimit = hasParameter('shelly') ? 600 : 120;
96
80
  hasCleanupStarted = false;
97
81
  initialized = false;
98
82
  startMatterInterval;
@@ -111,8 +95,8 @@ export class Matterbridge extends EventEmitter {
111
95
  matterbridgeContext;
112
96
  controllerContext;
113
97
  mdnsInterface;
114
- ipv4address;
115
- ipv6address;
98
+ ipv4Address;
99
+ ipv6Address;
116
100
  port;
117
101
  passcode;
118
102
  discriminator;
@@ -127,35 +111,10 @@ export class Matterbridge extends EventEmitter {
127
111
  aggregatorSerialNumber = getParameter('serialNumber');
128
112
  aggregatorUniqueId = getParameter('uniqueId');
129
113
  advertisingNodes = new Map();
130
- static instance;
131
114
  constructor() {
132
115
  super();
133
116
  this.log.logNameColor = '\x1b[38;5;115m';
134
117
  }
135
- async setLogLevel(logLevel) {
136
- if (this.log)
137
- this.log.logLevel = logLevel;
138
- this.matterbridgeInformation.loggerLevel = logLevel;
139
- this.frontend.logLevel = logLevel;
140
- MatterbridgeEndpoint.logLevel = logLevel;
141
- if (this.devices)
142
- this.devices.logLevel = logLevel;
143
- if (this.plugins)
144
- this.plugins.logLevel = logLevel;
145
- for (const plugin of this.plugins) {
146
- if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
147
- continue;
148
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
149
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
150
- }
151
- let callbackLogLevel = "notice";
152
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
153
- callbackLogLevel = "info";
154
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
155
- callbackLogLevel = "debug";
156
- AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
157
- this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
158
- }
159
118
  static async loadInstance(initialize = false) {
160
119
  if (!Matterbridge.instance) {
161
120
  if (hasParameter('debug'))
@@ -203,23 +162,18 @@ export class Matterbridge extends EventEmitter {
203
162
  if (hasParameter('docker'))
204
163
  this.restartMode = 'docker';
205
164
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
206
- this.matterbridgeInformation.homeDirectory = this.homeDirectory;
207
165
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
208
166
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
209
- this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
210
167
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
211
168
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
212
169
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
213
170
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
214
- this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
215
171
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
216
172
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
217
- this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
218
173
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
219
174
  const { fileURLToPath } = await import('node:url');
220
175
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
221
176
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
222
- this.matterbridgeInformation.rootDirectory = this.rootDirectory;
223
177
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
224
178
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
225
179
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
@@ -349,17 +303,16 @@ export class Matterbridge extends EventEmitter {
349
303
  }
350
304
  }
351
305
  else {
352
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
306
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
353
307
  }
354
308
  this.frontend.logLevel = this.log.logLevel;
355
309
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
356
- this.matterbridgeInformation.loggerLevel = this.log.logLevel;
357
310
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
358
311
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
359
- this.matterbridgeInformation.fileLogger = true;
312
+ this.fileLogger = true;
360
313
  }
361
314
  this.log.notice('Matterbridge is starting...');
362
- this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
315
+ this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
363
316
  if (this.profile !== undefined)
364
317
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
365
318
  if (hasParameter('matterlogger')) {
@@ -388,18 +341,17 @@ export class Matterbridge extends EventEmitter {
388
341
  }
389
342
  }
390
343
  else {
391
- Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
344
+ Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
392
345
  }
393
346
  Logger.format = MatterLogFormat.ANSI;
394
347
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
395
- this.matterbridgeInformation.matterFileLogger = true;
348
+ this.matterFileLogger = true;
396
349
  }
397
- Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
398
- this.matterbridgeInformation.matterLoggerLevel = Logger.level;
399
- this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
350
+ Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
351
+ this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
400
352
  const networkInterfaces = os.networkInterfaces();
401
353
  const availableAddresses = Object.entries(networkInterfaces);
402
- const availableInterfaces = Object.keys(networkInterfaces);
354
+ const availableInterfaceNames = Object.keys(networkInterfaces);
403
355
  for (const [ifaceName, ifaces] of availableAddresses) {
404
356
  if (ifaces && ifaces.length > 0) {
405
357
  this.log.debug(`Network interface ${BLUE}${ifaceName}${db}:`);
@@ -418,8 +370,8 @@ export class Matterbridge extends EventEmitter {
418
370
  this.mdnsInterface = undefined;
419
371
  }
420
372
  if (this.mdnsInterface) {
421
- if (!availableInterfaces.includes(this.mdnsInterface)) {
422
- this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
373
+ if (!availableInterfaceNames.includes(this.mdnsInterface)) {
374
+ this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
423
375
  this.mdnsInterface = undefined;
424
376
  await this.nodeContext.remove('mattermdnsinterface');
425
377
  }
@@ -430,64 +382,64 @@ export class Matterbridge extends EventEmitter {
430
382
  if (this.mdnsInterface)
431
383
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
432
384
  if (hasParameter('ipv4address')) {
433
- this.ipv4address = getParameter('ipv4address');
385
+ this.ipv4Address = getParameter('ipv4address');
434
386
  }
435
387
  else {
436
- this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
437
- if (this.ipv4address === '')
438
- this.ipv4address = undefined;
388
+ this.ipv4Address = await this.nodeContext.get('matteripv4address', undefined);
389
+ if (this.ipv4Address === '')
390
+ this.ipv4Address = undefined;
439
391
  }
440
- if (this.ipv4address) {
392
+ if (this.ipv4Address) {
441
393
  let isValid = false;
442
394
  for (const [ifaceName, ifaces] of availableAddresses) {
443
- if (ifaces && ifaces.find((iface) => iface.address === this.ipv4address)) {
444
- this.log.info(`Using ipv4address ${CYAN}${this.ipv4address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
395
+ if (ifaces && ifaces.find((iface) => iface.address === this.ipv4Address)) {
396
+ this.log.info(`Using ipv4address ${CYAN}${this.ipv4Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
445
397
  isValid = true;
446
398
  break;
447
399
  }
448
400
  }
449
401
  if (!isValid) {
450
- this.log.error(`Invalid ipv4address: ${this.ipv4address}. Using all available addresses.`);
451
- this.ipv4address = undefined;
402
+ this.log.error(`Invalid ipv4address: ${this.ipv4Address}. Using all available addresses.`);
403
+ this.ipv4Address = undefined;
452
404
  await this.nodeContext.remove('matteripv4address');
453
405
  }
454
406
  }
455
407
  if (hasParameter('ipv6address')) {
456
- this.ipv6address = getParameter('ipv6address');
408
+ this.ipv6Address = getParameter('ipv6address');
457
409
  }
458
410
  else {
459
- this.ipv6address = await this.nodeContext?.get('matteripv6address', undefined);
460
- if (this.ipv6address === '')
461
- this.ipv6address = undefined;
411
+ this.ipv6Address = await this.nodeContext?.get('matteripv6address', undefined);
412
+ if (this.ipv6Address === '')
413
+ this.ipv6Address = undefined;
462
414
  }
463
- if (this.ipv6address) {
415
+ if (this.ipv6Address) {
464
416
  let isValid = false;
465
417
  for (const [ifaceName, ifaces] of availableAddresses) {
466
- if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.ipv6address)) {
467
- this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
418
+ if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.ipv6Address)) {
419
+ this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
468
420
  isValid = true;
469
421
  break;
470
422
  }
471
- if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
472
- this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
423
+ if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
424
+ this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
473
425
  isValid = true;
474
426
  break;
475
427
  }
476
428
  }
477
429
  if (!isValid) {
478
- this.log.error(`Invalid ipv6address: ${this.ipv6address}. Using all available addresses.`);
479
- this.ipv6address = undefined;
430
+ this.log.error(`Invalid ipv6address: ${this.ipv6Address}. Using all available addresses.`);
431
+ this.ipv6Address = undefined;
480
432
  await this.nodeContext.remove('matteripv6address');
481
433
  }
482
434
  }
483
435
  if (hasParameter('novirtual')) {
484
- this.matterbridgeInformation.virtualMode = 'disabled';
436
+ this.virtualMode = 'disabled';
485
437
  await this.nodeContext.set('virtualmode', 'disabled');
486
438
  }
487
439
  else {
488
- this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
440
+ this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
489
441
  }
490
- this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
442
+ this.log.debug(`Virtual mode ${this.virtualMode}.`);
491
443
  this.plugins.logLevel = this.log.logLevel;
492
444
  await this.plugins.loadFromStorage();
493
445
  this.devices.logLevel = this.log.logLevel;
@@ -497,7 +449,7 @@ export class Matterbridge extends EventEmitter {
497
449
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
498
450
  try {
499
451
  const { spawnCommand } = await import('./utils/spawn.js');
500
- await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], plugin.name);
452
+ await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], 'install', plugin.name);
501
453
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
502
454
  plugin.error = false;
503
455
  }
@@ -801,6 +753,7 @@ export class Matterbridge extends EventEmitter {
801
753
  this.systemInformation.interfaceName = '';
802
754
  this.systemInformation.ipv4Address = '';
803
755
  this.systemInformation.ipv6Address = '';
756
+ this.systemInformation.macAddress = '';
804
757
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
805
758
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
806
759
  continue;
@@ -837,9 +790,14 @@ export class Matterbridge extends EventEmitter {
837
790
  this.systemInformation.osRelease = os.release();
838
791
  this.systemInformation.osPlatform = os.platform();
839
792
  this.systemInformation.osArch = os.arch();
840
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
841
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
842
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
793
+ this.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
794
+ this.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
795
+ this.systemInformation.systemUptime = formatOsUpTime(os.uptime());
796
+ this.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
797
+ this.systemInformation.cpuUsage = '0.00 %';
798
+ this.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
799
+ this.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
800
+ this.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
843
801
  this.log.debug('Host System Information:');
844
802
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
845
803
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -855,18 +813,22 @@ export class Matterbridge extends EventEmitter {
855
813
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
856
814
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
857
815
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
816
+ this.log.debug(`- Process Uptime: ${this.systemInformation.processUptime}`);
817
+ this.log.debug(`- RSS: ${this.systemInformation.rss}`);
818
+ this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
819
+ this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
858
820
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
859
821
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
860
822
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
861
823
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
862
824
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
863
825
  if (this.nodeContext)
864
- this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
826
+ this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
865
827
  if (this.globalModulesDirectory === '') {
866
828
  this.log.debug(`Getting global node_modules directory...`);
867
829
  try {
868
830
  const { getGlobalNodeModules } = await import('./utils/network.js');
869
- this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
831
+ this.globalModulesDirectory = await getGlobalNodeModules();
870
832
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
871
833
  await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
872
834
  }
@@ -878,7 +840,7 @@ export class Matterbridge extends EventEmitter {
878
840
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
879
841
  try {
880
842
  const { getGlobalNodeModules } = await import('./utils/network.js');
881
- this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
843
+ this.globalModulesDirectory = await getGlobalNodeModules();
882
844
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
883
845
  await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
884
846
  }
@@ -886,21 +848,51 @@ export class Matterbridge extends EventEmitter {
886
848
  this.log.error(`Error checking global node_modules directory: ${error}`);
887
849
  }
888
850
  }
851
+ this.log.debug(`Reading matterbridge package.json...`);
889
852
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
890
853
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
891
- this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
892
854
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
893
855
  if (this.nodeContext)
894
- this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
856
+ this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
895
857
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
896
858
  if (this.nodeContext)
897
- this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
859
+ this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
898
860
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
861
+ this.log.debug(`Reading frontend package.json...`);
862
+ const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
863
+ this.frontendVersion = frontendPackageJson.version;
864
+ this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
899
865
  const currentDir = process.cwd();
900
866
  this.log.debug(`Current Working Directory: ${currentDir}`);
901
867
  const cmdArgs = process.argv.slice(2).join(' ');
902
868
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
903
869
  }
870
+ async setLogLevel(logLevel) {
871
+ if (this.log)
872
+ this.log.logLevel = logLevel;
873
+ this.frontend.logLevel = logLevel;
874
+ MatterbridgeEndpoint.logLevel = logLevel;
875
+ if (this.devices)
876
+ this.devices.logLevel = logLevel;
877
+ if (this.plugins)
878
+ this.plugins.logLevel = logLevel;
879
+ for (const plugin of this.plugins) {
880
+ if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
881
+ continue;
882
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
883
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
884
+ }
885
+ let callbackLogLevel = "notice";
886
+ if (this.log.logLevel === "info" || Logger.level === MatterLogLevel.INFO)
887
+ callbackLogLevel = "info";
888
+ if (this.log.logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
889
+ callbackLogLevel = "debug";
890
+ AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
891
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
892
+ }
893
+ getLogLevel() {
894
+ return this.log.logLevel;
895
+ }
904
896
  createDestinationMatterLogger(fileLogger) {
905
897
  this.matterLog.logNameColor = '\x1b[34m';
906
898
  if (fileLogger) {
@@ -942,7 +934,7 @@ export class Matterbridge extends EventEmitter {
942
934
  this.log.info('Updating matterbridge...');
943
935
  try {
944
936
  const { spawnCommand } = await import('./utils/spawn.js');
945
- await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'matterbridge');
937
+ await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'install', 'matterbridge');
946
938
  this.log.info('Matterbridge has been updated. Full restart required.');
947
939
  }
948
940
  catch (error) {
@@ -1066,6 +1058,9 @@ export class Matterbridge extends EventEmitter {
1066
1058
  }
1067
1059
  await this.stopMatterStorage();
1068
1060
  await this.frontend.stop();
1061
+ this.frontend.destroy();
1062
+ this.plugins.destroy();
1063
+ this.devices.destroy();
1069
1064
  if (this.nodeStorage && this.nodeContext) {
1070
1065
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1071
1066
  await this.nodeContext.close();
@@ -1400,8 +1395,8 @@ export class Matterbridge extends EventEmitter {
1400
1395
  const serverNode = await ServerNode.create({
1401
1396
  id: storeId,
1402
1397
  network: {
1403
- listeningAddressIpv4: this.ipv4address,
1404
- listeningAddressIpv6: this.ipv6address,
1398
+ listeningAddressIpv4: this.ipv4Address,
1399
+ listeningAddressIpv6: this.ipv6Address,
1405
1400
  port,
1406
1401
  },
1407
1402
  operationalCredentials: {
@@ -12,7 +12,7 @@ export class MatterbridgePlatform {
12
12
  log;
13
13
  config;
14
14
  name = '';
15
- type = '';
15
+ type = 'AnyPlatform';
16
16
  version = '1.0.0';
17
17
  storage;
18
18
  context;
@@ -1,16 +1,59 @@
1
1
  import EventEmitter from 'node:events';
2
- import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from 'node-ansi-logger';
2
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr, debugStringify, CYAN } from 'node-ansi-logger';
3
3
  import { plg, typ } from './matterbridgeTypes.js';
4
- import { logError } from './utils/error.js';
4
+ import { inspectError, logError } from './utils/error.js';
5
+ import { BroadcastServer } from './broadcastServer.js';
5
6
  export class PluginManager extends EventEmitter {
6
7
  _plugins = new Map();
7
8
  matterbridge;
8
9
  log;
10
+ server;
9
11
  constructor(matterbridge) {
10
12
  super();
11
13
  this.matterbridge = matterbridge;
12
14
  this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
13
15
  this.log.debug('Matterbridge plugin manager starting...');
16
+ this.server = new BroadcastServer('plugins', this.log);
17
+ this.server.on('broadcast_message', this.msgHandler.bind(this));
18
+ this.log.debug('Matterbridge plugin manager started');
19
+ }
20
+ destroy() {
21
+ this.server.close();
22
+ }
23
+ async msgHandler(msg) {
24
+ if (!this.server.isWorkerRequest(msg, msg.type))
25
+ return;
26
+ if (!msg.id || (msg.dst !== 'all' && msg.dst !== 'plugins'))
27
+ return;
28
+ this.log.debug(`**Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
29
+ switch (msg.type) {
30
+ case 'plugins_length':
31
+ this.server.respond({ ...msg, id: msg.id, response: { length: this.length } });
32
+ break;
33
+ case 'plugins_size':
34
+ this.server.respond({ ...msg, id: msg.id, response: { size: this.size } });
35
+ break;
36
+ case 'plugins_has':
37
+ this.server.respond({ ...msg, id: msg.id, response: { has: this.has(msg.params.name) } });
38
+ break;
39
+ case 'plugins_get':
40
+ this.server.respond({ ...msg, id: msg.id, response: { plugin: this.get(msg.params.name) } });
41
+ break;
42
+ case 'plugins_set':
43
+ this.server.respond({ ...msg, id: msg.id, response: { plugin: this.set(msg.params.plugin) } });
44
+ break;
45
+ case 'plugins_baseArray':
46
+ this.server.respond({ ...msg, id: msg.id, response: { plugins: this.baseArray() } });
47
+ break;
48
+ case 'plugins_install':
49
+ this.server.respond({ ...msg, id: msg.id, response: { packageName: msg.params.packageName, success: await this.install(msg.params.packageName) } });
50
+ break;
51
+ case 'plugins_uninstall':
52
+ this.server.respond({ ...msg, id: msg.id, response: { packageName: msg.params.packageName, success: await this.uninstall(msg.params.packageName) } });
53
+ break;
54
+ default:
55
+ this.log.warn(`Unknown broadcast message ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
56
+ }
14
57
  }
15
58
  get length() {
16
59
  return this._plugins.size;
@@ -34,6 +77,39 @@ export class PluginManager extends EventEmitter {
34
77
  array() {
35
78
  return Array.from(this._plugins.values());
36
79
  }
80
+ baseArray() {
81
+ const basePlugins = [];
82
+ for (const plugin of this._plugins.values()) {
83
+ basePlugins.push({
84
+ name: plugin.name,
85
+ version: plugin.version,
86
+ description: plugin.description,
87
+ author: plugin.author,
88
+ path: plugin.path,
89
+ type: plugin.type,
90
+ latestVersion: plugin.latestVersion,
91
+ devVersion: plugin.devVersion,
92
+ homepage: plugin.homepage,
93
+ help: plugin.help,
94
+ changelog: plugin.changelog,
95
+ funding: plugin.funding,
96
+ locked: plugin.locked,
97
+ error: plugin.error,
98
+ enabled: plugin.enabled,
99
+ loaded: plugin.loaded,
100
+ started: plugin.started,
101
+ configured: plugin.configured,
102
+ restartRequired: plugin.restartRequired,
103
+ registeredDevices: plugin.registeredDevices,
104
+ configJson: plugin.configJson,
105
+ schemaJson: plugin.schemaJson,
106
+ hasWhiteList: plugin.hasWhiteList,
107
+ hasBlackList: plugin.hasBlackList,
108
+ matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
109
+ });
110
+ }
111
+ return basePlugins;
112
+ }
37
113
  [Symbol.iterator]() {
38
114
  return this._plugins.values();
39
115
  }
@@ -67,8 +143,7 @@ export class PluginManager extends EventEmitter {
67
143
  throw new Error('loadFromStorage: node context is not available.');
68
144
  }
69
145
  const plugins = [];
70
- const pluginArrayFromMap = Array.from(this._plugins.values());
71
- for (const plugin of pluginArrayFromMap) {
146
+ for (const plugin of this.array()) {
72
147
  plugins.push({
73
148
  name: plugin.name,
74
149
  path: plugin.path,
@@ -162,6 +237,52 @@ export class PluginManager extends EventEmitter {
162
237
  return null;
163
238
  }
164
239
  }
240
+ async install(packageName) {
241
+ const { spawnCommand } = await import('./utils/spawn.js');
242
+ try {
243
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', packageName, '--omit=dev', '--verbose'], 'install', packageName);
244
+ this.matterbridge.restartRequired = true;
245
+ this.matterbridge.fixedRestartRequired = true;
246
+ packageName = packageName.replace(/@.*$/, '');
247
+ if (packageName !== 'matterbridge') {
248
+ if (!this.has(packageName))
249
+ await this.add(packageName);
250
+ const plugin = this.get(packageName);
251
+ if (plugin && !plugin.loaded)
252
+ await this.load(plugin);
253
+ }
254
+ else {
255
+ if (this.matterbridge.restartMode !== '') {
256
+ await this.matterbridge.shutdownProcess();
257
+ }
258
+ }
259
+ return true;
260
+ }
261
+ catch (error) {
262
+ inspectError(this.log, `Failed to install package ${plg}${packageName}${er}`, error);
263
+ return false;
264
+ }
265
+ }
266
+ async uninstall(packageName) {
267
+ const { spawnCommand } = await import('./utils/spawn.js');
268
+ packageName = packageName.replace(/@.*$/, '');
269
+ if (packageName === 'matterbridge')
270
+ return false;
271
+ try {
272
+ if (this.has(packageName)) {
273
+ const plugin = this.get(packageName);
274
+ if (plugin && plugin.loaded)
275
+ await this.shutdown(plugin, 'Matterbridge is uninstalling the plugin');
276
+ await this.remove(packageName);
277
+ }
278
+ await spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', packageName, '--verbose'], 'uninstall', packageName);
279
+ return true;
280
+ }
281
+ catch (error) {
282
+ inspectError(this.log, `Failed to uninstall package ${plg}${packageName}${er}`, error);
283
+ return false;
284
+ }
285
+ }
165
286
  getAuthor(packageJson) {
166
287
  if (packageJson.author && typeof packageJson.author === 'string')
167
288
  return packageJson.author;
@@ -424,12 +545,16 @@ export class PluginManager extends EventEmitter {
424
545
  version: packageJson.version,
425
546
  description: packageJson.description,
426
547
  author: this.getAuthor(packageJson),
548
+ homepage: this.getHomepage(packageJson),
549
+ help: this.getHelp(packageJson),
550
+ changelog: this.getChangelog(packageJson),
551
+ funding: this.getFunding(packageJson),
427
552
  });
428
553
  this.log.info(`Added plugin ${plg}${packageJson.name}${nf}`);
429
554
  await this.saveToStorage();
430
555
  const plugin = this._plugins.get(packageJson.name);
431
556
  this.emit('added', packageJson.name);
432
- return plugin || null;
557
+ return plugin;
433
558
  }
434
559
  catch (err) {
435
560
  logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);