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.
package/dist/frontend.js CHANGED
@@ -8,15 +8,17 @@ import { appendFile } from 'node:fs/promises';
8
8
  import express from 'express';
9
9
  import WebSocket, { WebSocketServer } from 'ws';
10
10
  import multer from 'multer';
11
- import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
11
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
12
12
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
13
13
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
14
14
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
15
15
  import { CommissioningOptions } from '@matter/main/types';
16
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
16
17
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
17
- import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from './matterbridgeTypes.js';
18
+ import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
18
19
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
19
20
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
21
+ import { BroadcastServer } from './broadcastServer.js';
20
22
  export class Frontend extends EventEmitter {
21
23
  matterbridge;
22
24
  log;
@@ -26,11 +28,55 @@ export class Frontend extends EventEmitter {
26
28
  httpServer;
27
29
  httpsServer;
28
30
  webSocketServer;
31
+ server;
29
32
  constructor(matterbridge) {
30
33
  super();
31
34
  this.matterbridge = matterbridge;
32
35
  this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
33
36
  this.log.logNameColor = '\x1b[38;5;97m';
37
+ this.server = new BroadcastServer('plugins', this.log);
38
+ this.server.on('broadcast_message', this.msgHandler.bind(this));
39
+ }
40
+ destroy() {
41
+ this.server.close();
42
+ }
43
+ async msgHandler(msg) {
44
+ if (this.server.isWorkerRequest(msg, msg.type)) {
45
+ this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
46
+ switch (msg.type) {
47
+ default:
48
+ this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
49
+ }
50
+ }
51
+ if (this.server.isWorkerResponse(msg, msg.type)) {
52
+ this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
53
+ switch (msg.type) {
54
+ case 'plugins_install':
55
+ this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
56
+ if (msg.response.success) {
57
+ this.wssSendRestartRequired(true, true);
58
+ this.wssSendRefreshRequired('plugins');
59
+ this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
60
+ }
61
+ else {
62
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
63
+ }
64
+ break;
65
+ case 'plugins_uninstall':
66
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
67
+ if (msg.response.success) {
68
+ this.wssSendRestartRequired(true, true);
69
+ this.wssSendRefreshRequired('plugins');
70
+ this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
71
+ }
72
+ else {
73
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
74
+ }
75
+ break;
76
+ default:
77
+ this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
78
+ }
79
+ }
34
80
  }
35
81
  set logLevel(logLevel) {
36
82
  this.log.logLevel = logLevel;
@@ -42,15 +88,6 @@ export class Frontend extends EventEmitter {
42
88
  const upload = multer({ dest: uploadDir });
43
89
  this.expressApp = express();
44
90
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
45
- try {
46
- this.log.debug(`Reading frontend package.json...`);
47
- const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
48
- this.matterbridge.matterbridgeInformation.frontendVersion = JSON.parse(frontendJson)?.version;
49
- this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
50
- }
51
- catch (error) {
52
- this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
53
- }
54
91
  if (!hasParameter('ssl')) {
55
92
  try {
56
93
  this.log.debug(`Creating HTTP server...`);
@@ -195,16 +232,14 @@ export class Frontend extends EventEmitter {
195
232
  return;
196
233
  });
197
234
  }
198
- const wssPort = this.port;
199
- const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
200
- this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
235
+ this.log.debug(`Creating WebSocketServer...`);
201
236
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
202
237
  this.webSocketServer.on('connection', (ws, request) => {
203
238
  const clientIp = request.socket.remoteAddress;
204
239
  let callbackLogLevel = "notice";
205
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
240
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
206
241
  callbackLogLevel = "info";
207
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
242
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
208
243
  callbackLogLevel = "debug";
209
244
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
210
245
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
@@ -234,8 +269,8 @@ export class Frontend extends EventEmitter {
234
269
  this.log.debug(`WebSocketServer closed`);
235
270
  });
236
271
  this.webSocketServer.on('listening', () => {
237
- this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
238
- this.emit('websocket_server_listening', wssHost);
272
+ this.log.info(`The WebSocketServer is listening`);
273
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
239
274
  });
240
275
  this.webSocketServer.on('error', (ws, error) => {
241
276
  this.log.error(`WebSocketServer error: ${error}`);
@@ -287,22 +322,22 @@ export class Frontend extends EventEmitter {
287
322
  this.log.debug('Express received /memory');
288
323
  const memoryUsageRaw = process.memoryUsage();
289
324
  const memoryUsage = {
290
- rss: this.formatMemoryUsage(memoryUsageRaw.rss),
291
- heapTotal: this.formatMemoryUsage(memoryUsageRaw.heapTotal),
292
- heapUsed: this.formatMemoryUsage(memoryUsageRaw.heapUsed),
293
- external: this.formatMemoryUsage(memoryUsageRaw.external),
294
- arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
325
+ rss: formatMemoryUsage(memoryUsageRaw.rss),
326
+ heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
327
+ heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
328
+ external: formatMemoryUsage(memoryUsageRaw.external),
329
+ arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
295
330
  };
296
331
  const { default: v8 } = await import('node:v8');
297
332
  const heapStatsRaw = v8.getHeapStatistics();
298
333
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
299
- const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
334
+ const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
300
335
  const heapSpaces = heapSpacesRaw.map((space) => ({
301
336
  ...space,
302
- space_size: this.formatMemoryUsage(space.space_size),
303
- space_used_size: this.formatMemoryUsage(space.space_used_size),
304
- space_available_size: this.formatMemoryUsage(space.space_available_size),
305
- physical_space_size: this.formatMemoryUsage(space.physical_space_size),
337
+ space_size: formatMemoryUsage(space.space_size),
338
+ space_used_size: formatMemoryUsage(space.space_used_size),
339
+ space_available_size: formatMemoryUsage(space.space_available_size),
340
+ physical_space_size: formatMemoryUsage(space.physical_space_size),
306
341
  }));
307
342
  const { createRequire } = await import('node:module');
308
343
  const require = createRequire(import.meta.url);
@@ -321,12 +356,11 @@ export class Frontend extends EventEmitter {
321
356
  });
322
357
  this.expressApp.get('/api/plugins', async (req, res) => {
323
358
  this.log.debug('The frontend sent /api/plugins');
324
- res.json(this.getPlugins());
359
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
325
360
  });
326
361
  this.expressApp.get('/api/devices', async (req, res) => {
327
362
  this.log.debug('The frontend sent /api/devices');
328
- const devices = this.getDevices();
329
- res.json(devices);
363
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
330
364
  });
331
365
  this.expressApp.get('/api/view-mblog', async (req, res) => {
332
366
  this.log.debug('The frontend sent /api/view-mblog');
@@ -531,7 +565,7 @@ export class Frontend extends EventEmitter {
531
565
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
532
566
  if (filename.endsWith('.tgz')) {
533
567
  const { spawnCommand } = await import('./utils/spawn.js');
534
- await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
568
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
535
569
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
536
570
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
537
571
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -605,53 +639,49 @@ export class Frontend extends EventEmitter {
605
639
  }
606
640
  this.log.debug('Frontend stopped successfully');
607
641
  }
608
- formatMemoryUsage = (bytes) => {
609
- if (bytes >= 1024 ** 3) {
610
- return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
611
- }
612
- else if (bytes >= 1024 ** 2) {
613
- return `${(bytes / 1024 ** 2).toFixed(2)} MB`;
614
- }
615
- else {
616
- return `${(bytes / 1024).toFixed(2)} KB`;
617
- }
618
- };
619
- formatOsUpTime = (seconds) => {
620
- if (seconds >= 86400) {
621
- const days = Math.floor(seconds / 86400);
622
- return `${days} day${days !== 1 ? 's' : ''}`;
623
- }
624
- if (seconds >= 3600) {
625
- const hours = Math.floor(seconds / 3600);
626
- return `${hours} hour${hours !== 1 ? 's' : ''}`;
627
- }
628
- if (seconds >= 60) {
629
- const minutes = Math.floor(seconds / 60);
630
- return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
631
- }
632
- return `${seconds} second${seconds !== 1 ? 's' : ''}`;
633
- };
634
642
  async getApiSettings() {
635
- this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
636
- this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
637
- this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
638
- this.matterbridge.systemInformation.processUptime = this.formatOsUpTime(Math.floor(process.uptime()));
643
+ this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
644
+ this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
645
+ this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
646
+ this.matterbridge.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
639
647
  this.matterbridge.systemInformation.cpuUsage = lastCpuUsage.toFixed(2) + ' %';
640
- this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
641
- this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
642
- this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
643
- this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
644
- this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
645
- this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
646
- this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
647
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
648
- this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
649
- this.matterbridge.matterbridgeInformation.matterIpv4Address = this.matterbridge.ipv4address;
650
- this.matterbridge.matterbridgeInformation.matterIpv6Address = this.matterbridge.ipv6address;
651
- this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
652
- this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
653
- this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
654
- return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
648
+ this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
649
+ this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
650
+ this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
651
+ const info = {
652
+ homeDirectory: this.matterbridge.homeDirectory,
653
+ rootDirectory: this.matterbridge.rootDirectory,
654
+ matterbridgeDirectory: this.matterbridge.matterbridgeDirectory,
655
+ matterbridgePluginDirectory: this.matterbridge.matterbridgePluginDirectory,
656
+ matterbridgeCertDirectory: this.matterbridge.matterbridgeCertDirectory,
657
+ globalModulesDirectory: this.matterbridge.globalModulesDirectory,
658
+ matterbridgeVersion: this.matterbridge.matterbridgeVersion,
659
+ matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
660
+ matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
661
+ frontendVersion: this.matterbridge.frontendVersion,
662
+ bridgeMode: this.matterbridge.bridgeMode,
663
+ restartMode: this.matterbridge.restartMode,
664
+ virtualMode: this.matterbridge.virtualMode,
665
+ profile: this.matterbridge.profile,
666
+ readOnly: this.matterbridge.readOnly,
667
+ shellyBoard: this.matterbridge.shellyBoard,
668
+ shellySysUpdate: this.matterbridge.shellySysUpdate,
669
+ shellyMainUpdate: this.matterbridge.shellyMainUpdate,
670
+ loggerLevel: await this.matterbridge.getLogLevel(),
671
+ fileLogger: this.matterbridge.fileLogger,
672
+ matterLoggerLevel: Logger.level,
673
+ matterFileLogger: this.matterbridge.matterFileLogger,
674
+ matterMdnsInterface: this.matterbridge.mdnsInterface,
675
+ matterIpv4Address: this.matterbridge.ipv4Address,
676
+ matterIpv6Address: this.matterbridge.ipv6Address,
677
+ matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
678
+ matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
679
+ matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
680
+ restartRequired: this.matterbridge.restartRequired,
681
+ fixedRestartRequired: this.matterbridge.fixedRestartRequired,
682
+ updateRequired: this.matterbridge.updateRequired,
683
+ };
684
+ return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
655
685
  }
656
686
  getReachability(device) {
657
687
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
@@ -802,11 +832,9 @@ export class Frontend extends EventEmitter {
802
832
  return attributes.trimStart().trimEnd();
803
833
  }
804
834
  getPlugins() {
805
- if (this.matterbridge.hasCleanupStarted)
806
- return [];
807
- const baseRegisteredPlugins = [];
808
- for (const plugin of this.matterbridge.plugins) {
809
- baseRegisteredPlugins.push({
835
+ const plugins = [];
836
+ for (const plugin of this.matterbridge.plugins.array()) {
837
+ plugins.push({
810
838
  path: plugin.path,
811
839
  type: plugin.type,
812
840
  name: plugin.name,
@@ -834,11 +862,9 @@ export class Frontend extends EventEmitter {
834
862
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
835
863
  });
836
864
  }
837
- return baseRegisteredPlugins;
865
+ return plugins;
838
866
  }
839
867
  getDevices(pluginName) {
840
- if (this.matterbridge.hasCleanupStarted)
841
- return [];
842
868
  const devices = [];
843
869
  for (const device of this.matterbridge.devices.array()) {
844
870
  if (pluginName && pluginName !== device.plugin)
@@ -972,95 +998,24 @@ export class Frontend extends EventEmitter {
972
998
  }
973
999
  }
974
1000
  else if (data.method === '/api/install') {
975
- const localData = data;
976
- if (!isValidString(data.params.packageName, 10) || !isValidBoolean(data.params.restart)) {
1001
+ if (isValidString(data.params.packageName, 14) && isValidBoolean(data.params.restart)) {
1002
+ this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1003
+ this.server.request({ type: 'plugins_install', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1004
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1005
+ }
1006
+ else {
977
1007
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' });
978
- return;
979
1008
  }
980
- this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
981
- const { spawnCommand } = await import('./utils/spawn.js');
982
- spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'], data.params.packageName)
983
- .then((_response) => {
984
- sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
985
- this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
986
- this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
987
- const packageName = localData.params.packageName.replace(/@.*$/, '');
988
- if (localData.params.restart === false && packageName !== 'matterbridge') {
989
- this.matterbridge.plugins
990
- .add(packageName)
991
- .then((plugin) => {
992
- if (plugin) {
993
- this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
994
- if (this.matterbridge.bridgeMode === 'childbridge')
995
- this.wssSendRestartRequired(true, true);
996
- this.matterbridge.plugins
997
- .load(plugin, true, 'The plugin has been added', true)
998
- .then(() => {
999
- this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
1000
- this.wssSendRefreshRequired('plugins');
1001
- this.wssSendRefreshRequired('devices');
1002
- return;
1003
- })
1004
- .catch((_error) => {
1005
- });
1006
- }
1007
- else {
1008
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1009
- this.wssSendRefreshRequired('plugins');
1010
- this.wssSendRestartRequired(true, true);
1011
- }
1012
- return;
1013
- })
1014
- .catch((_error) => {
1015
- });
1016
- }
1017
- else {
1018
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1019
- if (this.matterbridge.restartMode !== '') {
1020
- this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1021
- this.matterbridge.shutdownProcess();
1022
- }
1023
- else {
1024
- this.wssSendRestartRequired(true, true);
1025
- }
1026
- }
1027
- return;
1028
- })
1029
- .catch((error) => {
1030
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
1031
- this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
1032
- this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not installed`, 10, 'error');
1033
- });
1034
1009
  }
1035
1010
  else if (data.method === '/api/uninstall') {
1036
- const localData = data;
1037
- if (!isValidString(data.params.packageName, 10)) {
1011
+ if (isValidString(data.params.packageName, 14)) {
1012
+ this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1013
+ this.server.request({ type: 'plugins_uninstall', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1014
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1015
+ }
1016
+ else {
1038
1017
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1039
- return;
1040
1018
  }
1041
- const plugin = this.matterbridge.plugins.get(data.params.packageName);
1042
- if (plugin) {
1043
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1044
- await this.matterbridge.plugins.remove(data.params.packageName);
1045
- this.wssSendSnackbarMessage(`Removed plugin ${data.params.packageName}`, 5, 'success');
1046
- this.wssSendRefreshRequired('plugins');
1047
- this.wssSendRefreshRequired('devices');
1048
- }
1049
- this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1050
- const { spawnCommand } = await import('./utils/spawn.js');
1051
- spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
1052
- .then((_response) => {
1053
- sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
1054
- this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
1055
- this.wssSendSnackbarMessage(`Uninstalled package ${localData.params.packageName}`, 5, 'success');
1056
- return;
1057
- })
1058
- .catch((error) => {
1059
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
1060
- this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
1061
- this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not uninstalled`, 10, 'error');
1062
- this.wssSendSnackbarMessage(`Restart required`, 0);
1063
- });
1064
1019
  }
1065
1020
  else if (data.method === '/api/addplugin') {
1066
1021
  const localData = data;
@@ -1333,11 +1288,11 @@ export class Frontend extends EventEmitter {
1333
1288
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
1334
1289
  }
1335
1290
  else if (data.method === '/api/plugins') {
1336
- const plugins = this.getPlugins();
1291
+ const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
1337
1292
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1338
1293
  }
1339
1294
  else if (data.method === '/api/devices') {
1340
- const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1295
+ const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1341
1296
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1342
1297
  }
1343
1298
  else if (data.method === '/api/clusters') {
@@ -1461,10 +1416,10 @@ export class Frontend extends EventEmitter {
1461
1416
  case 'setmblogfile':
1462
1417
  if (isValidBoolean(data.params.value)) {
1463
1418
  this.log.debug('Matterbridge file log:', data.params.value);
1464
- this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1419
+ this.matterbridge.fileLogger = data.params.value;
1465
1420
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1466
1421
  if (data.params.value)
1467
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1422
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1468
1423
  else
1469
1424
  AnsiLogger.setGlobalLogfile(undefined);
1470
1425
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1491,7 +1446,6 @@ export class Frontend extends EventEmitter {
1491
1446
  else if (data.params.value === 'Fatal') {
1492
1447
  Logger.level = MatterLogLevel.FATAL;
1493
1448
  }
1494
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
1495
1449
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
1496
1450
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1497
1451
  }
@@ -1499,7 +1453,7 @@ export class Frontend extends EventEmitter {
1499
1453
  case 'setmjlogfile':
1500
1454
  if (isValidBoolean(data.params.value)) {
1501
1455
  this.log.debug('Matter file log:', data.params.value);
1502
- this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
1456
+ this.matterbridge.fileLogger = data.params.value;
1503
1457
  await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
1504
1458
  if (data.params.value) {
1505
1459
  this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
@@ -1514,86 +1468,92 @@ export class Frontend extends EventEmitter {
1514
1468
  if (isValidString(data.params.value)) {
1515
1469
  this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1516
1470
  this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
1517
- this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
1518
1471
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
1519
1472
  this.wssSendRestartRequired();
1520
1473
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1474
+ this.wssSendSnackbarMessage(`Mdns interface changed to ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1521
1475
  }
1522
1476
  break;
1523
1477
  case 'setipv4address':
1524
1478
  if (isValidString(data.params.value)) {
1525
1479
  this.log.debug(`Matter.js ipv4 address: ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1526
- this.matterbridge.ipv4address = data.params.value === '' ? undefined : data.params.value;
1527
- this.matterbridge.matterbridgeInformation.matterIpv4Address = this.matterbridge.ipv4address;
1480
+ this.matterbridge.ipv4Address = data.params.value === '' ? undefined : data.params.value;
1528
1481
  await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
1529
1482
  this.wssSendRestartRequired();
1530
1483
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1484
+ this.wssSendSnackbarMessage(`IPv4 address changed to ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1531
1485
  }
1532
1486
  break;
1533
1487
  case 'setipv6address':
1534
1488
  if (isValidString(data.params.value)) {
1535
1489
  this.log.debug(`Matter.js ipv6 address: ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1536
- this.matterbridge.ipv6address = data.params.value === '' ? undefined : data.params.value;
1537
- this.matterbridge.matterbridgeInformation.matterIpv6Address = this.matterbridge.ipv6address;
1490
+ this.matterbridge.ipv6Address = data.params.value === '' ? undefined : data.params.value;
1538
1491
  await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
1539
1492
  this.wssSendRestartRequired();
1540
1493
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1494
+ this.wssSendSnackbarMessage(`IPv6 address changed to ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1541
1495
  }
1542
1496
  break;
1543
1497
  case 'setmatterport':
1544
1498
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1545
1499
  if (isValidNumber(port, 5540, 5600)) {
1546
1500
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
1547
- this.matterbridge.matterbridgeInformation.matterPort = port;
1501
+ this.matterbridge.port = port;
1548
1502
  await this.matterbridge.nodeContext?.set('matterport', port);
1549
1503
  this.wssSendRestartRequired();
1550
1504
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1505
+ this.wssSendSnackbarMessage(`Matter port changed to ${port}`);
1551
1506
  }
1552
1507
  else {
1553
1508
  this.log.debug(`Reset matter commissioning port to ${CYAN}5540${db}`);
1554
- this.matterbridge.matterbridgeInformation.matterPort = 5540;
1509
+ this.matterbridge.port = 5540;
1555
1510
  await this.matterbridge.nodeContext?.set('matterport', 5540);
1556
1511
  this.wssSendRestartRequired();
1557
1512
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning port to default 5540' });
1513
+ this.wssSendSnackbarMessage(`Matter port reset to default 5540`, undefined, 'error');
1558
1514
  }
1559
1515
  break;
1560
1516
  case 'setmatterdiscriminator':
1561
1517
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1562
1518
  if (isValidNumber(discriminator, 0, 4095)) {
1563
1519
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
1564
- this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
1520
+ this.matterbridge.discriminator = discriminator;
1565
1521
  await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
1566
1522
  this.wssSendRestartRequired();
1567
1523
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1524
+ this.wssSendSnackbarMessage(`Matter discriminator changed to ${discriminator}`);
1568
1525
  }
1569
1526
  else {
1570
1527
  this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1571
- this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1528
+ this.matterbridge.discriminator = undefined;
1572
1529
  await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1573
1530
  this.wssSendRestartRequired();
1574
1531
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning discriminator to default undefined' });
1532
+ this.wssSendSnackbarMessage(`Matter discriminator reset to default`, undefined, 'error');
1575
1533
  }
1576
1534
  break;
1577
1535
  case 'setmatterpasscode':
1578
1536
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1579
1537
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1580
- this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
1538
+ this.matterbridge.passcode = passcode;
1581
1539
  this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
1582
1540
  await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
1583
1541
  this.wssSendRestartRequired();
1584
1542
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1543
+ this.wssSendSnackbarMessage(`Matter passcode changed to ${passcode}`);
1585
1544
  }
1586
1545
  else {
1587
1546
  this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1588
- this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1547
+ this.matterbridge.passcode = undefined;
1589
1548
  await this.matterbridge.nodeContext?.remove('matterpasscode');
1590
1549
  this.wssSendRestartRequired();
1591
1550
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning passcode to default undefined' });
1551
+ this.wssSendSnackbarMessage(`Matter passcode reset to default`, undefined, 'error');
1592
1552
  }
1593
1553
  break;
1594
1554
  case 'setvirtualmode':
1595
1555
  if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
1596
- this.matterbridge.matterbridgeInformation.virtualMode = data.params.value;
1556
+ this.matterbridge.virtualMode = data.params.value;
1597
1557
  this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
1598
1558
  await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
1599
1559
  this.wssSendRestartRequired();
@@ -1740,22 +1700,22 @@ export class Frontend extends EventEmitter {
1740
1700
  }
1741
1701
  wssSendRestartRequired(snackbar = true, fixed = false) {
1742
1702
  this.log.debug('Sending a restart required message to all connected clients');
1743
- this.matterbridge.matterbridgeInformation.restartRequired = true;
1744
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1703
+ this.matterbridge.restartRequired = true;
1704
+ this.matterbridge.fixedRestartRequired = fixed;
1745
1705
  if (snackbar === true)
1746
1706
  this.wssSendSnackbarMessage(`Restart required`, 0);
1747
1707
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1748
1708
  }
1749
1709
  wssSendRestartNotRequired(snackbar = true) {
1750
1710
  this.log.debug('Sending a restart not required message to all connected clients');
1751
- this.matterbridge.matterbridgeInformation.restartRequired = false;
1711
+ this.matterbridge.restartRequired = false;
1752
1712
  if (snackbar === true)
1753
1713
  this.wssSendCloseSnackbarMessage(`Restart required`);
1754
1714
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1755
1715
  }
1756
1716
  wssSendUpdateRequired(devVersion = false) {
1757
1717
  this.log.debug('Sending an update required message to all connected clients');
1758
- this.matterbridge.matterbridgeInformation.updateRequired = true;
1718
+ this.matterbridge.updateRequired = true;
1759
1719
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1760
1720
  }
1761
1721
  wssSendCpuUpdate(cpuUsage) {
package/dist/helpers.js CHANGED
@@ -45,15 +45,15 @@ export async function addVirtualDevice(aggregatorEndpoint, name, type, callback)
45
45
  return device;
46
46
  }
47
47
  export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
48
- if (matterbridge.matterbridgeInformation.virtualMode !== 'disabled' && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
48
+ if (matterbridge.virtualMode !== 'disabled' && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
49
49
  matterbridge.log.notice(`Creating virtual devices for Matterbridge server node...`);
50
- await addVirtualDevice(aggregatorEndpoint, 'Restart Matterbridge', matterbridge.matterbridgeInformation.virtualMode, async () => {
50
+ await addVirtualDevice(aggregatorEndpoint, 'Restart Matterbridge', matterbridge.virtualMode, async () => {
51
51
  if (matterbridge.restartMode === '')
52
52
  await matterbridge.restartProcess();
53
53
  else
54
54
  await matterbridge.shutdownProcess();
55
55
  });
56
- await addVirtualDevice(aggregatorEndpoint, 'Update Matterbridge', matterbridge.matterbridgeInformation.virtualMode, async () => {
56
+ await addVirtualDevice(aggregatorEndpoint, 'Update Matterbridge', matterbridge.virtualMode, async () => {
57
57
  if (hasParameter('shelly')) {
58
58
  const { getShelly } = await import('./shelly.js');
59
59
  getShelly('/api/updates/sys/perform', 10 * 1000)
@@ -78,7 +78,7 @@ export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
78
78
  }
79
79
  });
80
80
  if (hasParameter('shelly')) {
81
- await addVirtualDevice(aggregatorEndpoint, 'Reboot Matterbridge', matterbridge.matterbridgeInformation.virtualMode, async () => {
81
+ await addVirtualDevice(aggregatorEndpoint, 'Reboot Matterbridge', matterbridge.virtualMode, async () => {
82
82
  const { postShelly } = await import('./shelly.js');
83
83
  postShelly('/api/system/reboot', {}, 60 * 1000)
84
84
  .then(() => {
package/dist/index.js CHANGED
@@ -1,6 +1,3 @@
1
- import { AnsiLogger } from 'node-ansi-logger';
2
- import { Matterbridge } from './matterbridge.js';
3
- import { hasParameter } from './utils/commandLine.js';
4
1
  export * from './matterbridge.js';
5
2
  export * from './matterbridgeTypes.js';
6
3
  export * from './matterbridgeEndpoint.js';
@@ -11,12 +8,3 @@ export * from './matterbridgePlatform.js';
11
8
  export * from './matterbridgeAccessoryPlatform.js';
12
9
  export * from './matterbridgeDynamicPlatform.js';
13
10
  export { addVirtualDevice } from './helpers.js';
14
- const log = new AnsiLogger({ logName: 'Main', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
15
- async function main() {
16
- log.debug('***Matterbridge.loadInstance() called');
17
- await Matterbridge.loadInstance();
18
- log.debug('***Matterbridge.loadInstance() exited');
19
- }
20
- main().catch((error) => {
21
- log.error(`Matterbridge.loadInstance() failed with error: ${error instanceof Error ? error.message : error}`);
22
- });