matterbridge 2.1.5-dev.8 → 2.1.6-dev.1

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/CHANGELOG.md CHANGED
@@ -33,7 +33,24 @@ matterbridge-zigbee2mqtt v. 2.4.4
33
33
  matterbridge-somfy-tahoma v. 1.2.3
34
34
  matterbridge-hass v. 0.0.8
35
35
 
36
- ## [2.1.5] - 2025-02-10
36
+ ## [2.1.6] - 2025-02-12
37
+
38
+ ### Added
39
+
40
+ - [docker]: Added health check directly inside the docker image. No need to change configuration.
41
+ - [platform]: Added saving to storage the selects for faster loading.
42
+ - [icon]: Added matterbridge svg icon (thanks: https://github.com/robvanoostenrijk https://github.com/stuntguy3000).
43
+ - [frontend]: Frontend v.2.4.2.
44
+
45
+ ### Changed
46
+
47
+ - [package]: Update matter.js to 0.12.4-alpha.0-20250212-b2729c9eb
48
+
49
+ <a href="https://www.buymeacoffee.com/luligugithub">
50
+ <img src="./yellow-button.png" alt="Buy me a coffee" width="120">
51
+ </a>
52
+
53
+ ## [2.1.5] - 2025-02-11
37
54
 
38
55
  ### Added
39
56
 
@@ -42,15 +59,17 @@ matterbridge-hass v. 0.0.8
42
59
  - [frontend]: Added cpuUsed, rss and heapUsed to SystemInformation.
43
60
  - [frontend]: Added UiProvider.
44
61
  - [frontend]: Added wssSendCpuUpdate, wssSendMemoryUpdate and wssSendSnackbarMessage.
62
+ - [docker]: Added health check to docker images. See README-DOCKER.md with the updated configuration.
45
63
 
46
64
  ### Changed
47
65
 
48
- - [matterbridge]: Calls getNpmPackageVersion() instead of npm to get latest version.
66
+ - [matterbridge]: Calls getNpmPackageVersion() instead of npm to get latest version to optimize memory and cpu usage.
49
67
  - [matterbridge]: Memory optimization on MatterbridgeEndpoint.
50
68
 
51
69
  ### Fixed
52
70
 
53
71
  - [matterbridge]: Refactor shutdown sequences for reset and factory reset.
72
+ - [matterbridge]: Refactor reset devices adding a wait of 1 sec to allow matter to deliver all messages before shutting down.
54
73
 
55
74
  <a href="https://www.buymeacoffee.com/luligugithub">
56
75
  <img src="./yellow-button.png" alt="Buy me a coffee" width="120">
package/README-DOCKER.md CHANGED
@@ -18,6 +18,22 @@
18
18
 
19
19
  The Matterbridge Docker image, which includes a manifest list for the linux/amd64, linux/arm64 and linux/arm/v7 architectures, is published on Docker Hub.
20
20
 
21
+ It is based on node:22-bookworm-slim and integrates the health check.
22
+
23
+ How Health Checks Work in Different Scenarios
24
+
25
+ With docker-compose
26
+
27
+ Docker monitors the health check and can restart the container if needed.
28
+
29
+ With docker run
30
+
31
+ The health check still runs in the background, but:
32
+ The container doesn’t restart automatically if it becomes unhealthy.
33
+ You must manually check the health status:
34
+
35
+ docker exec -it matterbridge curl -v http://localhost:8283/health
36
+
21
37
  ### First create the Matterbridge directories
22
38
 
23
39
  This will create the required directories in your home directory if they don't exist
@@ -73,7 +89,7 @@ services:
73
89
  - "/home/<USER>/.matterbridge:/root/.matterbridge" # Mounts the Matterbridge storage directory
74
90
  ```
75
91
 
76
- Replace USER with your user name (i.e. ubuntu or pi).
92
+ Replace USER with your user name (i.e. ubuntu or pi: "/home/ubuntu/Matterbridge:/root/Matterbridge").
77
93
 
78
94
  copy it in the home directory or edit the existing one to add the matterbridge service.
79
95
 
package/dist/frontend.js CHANGED
@@ -9,7 +9,6 @@ import { promises as fs } from 'fs';
9
9
  import { AnsiLogger, CYAN, db, debugStringify, er, nf, rs, stringify, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
10
10
  import { createZip, deepCopy, getIntParameter, hasParameter, isValidNumber, isValidObject, isValidString } from './utils/utils.js';
11
11
  import { plg } from './matterbridgeTypes.js';
12
- import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
13
12
  export const WS_ID_LOG = 0;
14
13
  export const WS_ID_REFRESH_NEEDED = 1;
15
14
  export const WS_ID_RESTART_NEEDED = 2;
@@ -255,8 +254,7 @@ export class Frontend {
255
254
  });
256
255
  this.expressApp.get('/api/plugins', async (req, res) => {
257
256
  this.log.debug('The frontend sent /api/plugins');
258
- const response = this.getBaseRegisteredPlugins();
259
- res.json(response);
257
+ res.json(this.getBaseRegisteredPlugins());
260
258
  });
261
259
  this.expressApp.get('/api/devices', (req, res) => {
262
260
  this.log.debug('The frontend sent /api/devices');
@@ -494,16 +492,7 @@ export class Frontend {
494
492
  this.log.logLevel = "fatal";
495
493
  }
496
494
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
497
- this.matterbridge.log.logLevel = this.log.logLevel;
498
- MatterbridgeEndpoint.logLevel = this.log.logLevel;
499
- this.matterbridge.devices.logLevel = this.log.logLevel;
500
- this.matterbridge.plugins.logLevel = this.log.logLevel;
501
- for (const plugin of this.matterbridge.plugins) {
502
- if (!plugin.platform || !plugin.platform.config)
503
- continue;
504
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
505
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
506
- }
495
+ await this.matterbridge.setLogLevel(this.log.logLevel);
507
496
  res.json({ message: 'Command received' });
508
497
  return;
509
498
  }
@@ -666,13 +655,17 @@ export class Frontend {
666
655
  if (command === 'installplugin') {
667
656
  param = param.replace(/\*/g, '\\');
668
657
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
658
+ this.wssSendSnackbarMessage(`Installing package ${param}`);
669
659
  try {
670
660
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
671
661
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
662
+ this.wssSendSnackbarMessage(`Installed package ${param}`);
672
663
  }
673
664
  catch (error) {
674
665
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
666
+ this.wssSendSnackbarMessage(`Package ${param} not installed`);
675
667
  }
668
+ this.wssSendSnackbarMessage(`Restart required`, 0);
676
669
  this.wssSendRestartRequired();
677
670
  param = param.split('@')[0];
678
671
  if (param === 'matterbridge') {
@@ -687,10 +680,11 @@ export class Frontend {
687
680
  if (this.matterbridge.bridgeMode === 'childbridge') {
688
681
  this.matterbridge.createDynamicPlugin(plugin, true);
689
682
  }
690
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
683
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
684
+ this.wssSendRefreshRequired();
685
+ });
691
686
  }
692
687
  res.json({ message: 'Command received' });
693
- this.wssSendRefreshRequired();
694
688
  return;
695
689
  }
696
690
  if (command === 'removeplugin') {
@@ -725,7 +719,9 @@ export class Frontend {
725
719
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
726
720
  this.matterbridge.createDynamicPlugin(plugin, true);
727
721
  }
728
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
722
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
723
+ this.wssSendRefreshRequired();
724
+ });
729
725
  }
730
726
  }
731
727
  res.json({ message: 'Command received' });
@@ -1116,13 +1112,27 @@ export class Frontend {
1116
1112
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/install' }));
1117
1113
  return;
1118
1114
  }
1115
+ this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}`);
1119
1116
  this.matterbridge
1120
1117
  .spawnCommand('npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
1121
1118
  .then((response) => {
1122
1119
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
1120
+ this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`);
1121
+ if (data.params.restart !== true) {
1122
+ this.wssSendSnackbarMessage(`Restart required`, 0);
1123
+ }
1124
+ else {
1125
+ if (this.matterbridge.restartMode !== '') {
1126
+ this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1127
+ this.matterbridge.shutdownProcess();
1128
+ }
1129
+ else
1130
+ this.wssSendSnackbarMessage(`Restart required`, 0);
1131
+ }
1123
1132
  })
1124
1133
  .catch((error) => {
1125
1134
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error }));
1135
+ this.wssSendSnackbarMessage(`Package ${data.params.packageName} not installed`);
1126
1136
  });
1127
1137
  return;
1128
1138
  }
@@ -1131,6 +1141,7 @@ export class Frontend {
1131
1141
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1132
1142
  return;
1133
1143
  }
1144
+ this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}`);
1134
1145
  this.matterbridge
1135
1146
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
1136
1147
  .then((response) => {
@@ -1138,14 +1149,18 @@ export class Frontend {
1138
1149
  })
1139
1150
  .catch((error) => {
1140
1151
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error }));
1152
+ this.wssSendSnackbarMessage(`Uninstalled package ${data.params.packageName}`);
1153
+ this.wssSendSnackbarMessage(`Restart required`, 0);
1141
1154
  });
1142
1155
  return;
1143
1156
  }
1144
1157
  else if (data.method === '/api/restart') {
1158
+ this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1145
1159
  await this.matterbridge.restartProcess();
1146
1160
  return;
1147
1161
  }
1148
1162
  else if (data.method === '/api/shutdown') {
1163
+ this.wssSendSnackbarMessage(`Shutting down matterbridge...`, 0);
1149
1164
  await this.matterbridge.shutdownProcess();
1150
1165
  return;
1151
1166
  }
@@ -132,6 +132,30 @@ export class Matterbridge extends EventEmitter {
132
132
  getPlugins() {
133
133
  return this.plugins.array();
134
134
  }
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.wssSendMessage.bind(this.frontend), callbackLogLevel);
157
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
158
+ }
135
159
  static async loadInstance(initialize = false) {
136
160
  if (!Matterbridge.instance) {
137
161
  if (hasParameter('debug'))
@@ -248,6 +272,7 @@ export class Matterbridge extends EventEmitter {
248
272
  else {
249
273
  this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
250
274
  }
275
+ this.frontend.logLevel = this.log.logLevel;
251
276
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
252
277
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
253
278
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
@@ -521,7 +546,6 @@ export class Matterbridge extends EventEmitter {
521
546
  }
522
547
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
523
548
  await this.frontend.start(getIntParameter('frontend'));
524
- this.frontend.logLevel = this.log.logLevel;
525
549
  this.getMatterbridgeLatestVersion();
526
550
  for (const plugin of this.plugins) {
527
551
  this.getPluginLatestVersion(plugin);
@@ -766,20 +790,6 @@ export class Matterbridge extends EventEmitter {
766
790
  const cmdArgs = process.argv.slice(2).join(' ');
767
791
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
768
792
  }
769
- async getLatestVersion(packageName) {
770
- return new Promise((resolve, reject) => {
771
- this.execRunningCount++;
772
- exec(`npm view ${packageName} version`, (error, stdout) => {
773
- this.execRunningCount--;
774
- if (error) {
775
- reject(error);
776
- }
777
- else {
778
- resolve(stdout.trim());
779
- }
780
- });
781
- });
782
- }
783
793
  async getGlobalNodeModules() {
784
794
  return new Promise((resolve, reject) => {
785
795
  this.execRunningCount++;
@@ -809,7 +819,7 @@ export class Matterbridge extends EventEmitter {
809
819
  this.frontend.wssSendRefreshRequired();
810
820
  })
811
821
  .catch((error) => {
812
- this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
822
+ this.log.warn(`Error getting Matterbridge latest version: ${error.message}`);
813
823
  });
814
824
  }
815
825
  async getPluginLatestVersion(plugin) {
@@ -822,7 +832,7 @@ export class Matterbridge extends EventEmitter {
822
832
  this.log.debug(`The plugin ${plg}${plugin.name}${db} is up to date. Current version: ${plugin.version}. Latest version: ${plugin.latestVersion}.`);
823
833
  })
824
834
  .catch((error) => {
825
- this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
835
+ this.log.warn(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
826
836
  });
827
837
  }
828
838
  createMatterLogger() {
@@ -20,7 +20,7 @@ export class MatterbridgePlatform {
20
20
  this.matterbridge = matterbridge;
21
21
  this.log = log;
22
22
  this.config = config;
23
- if (!isValidString(this.config.name))
23
+ if (!isValidString(this.config.name) || this.config.name === '')
24
24
  return;
25
25
  this.log.debug(`Creating storage for plugin ${this.config.name} in ${path.join(this.matterbridge.matterbridgeDirectory, this.config.name)}`);
26
26
  this.storage = new NodeStorageManager({
@@ -30,6 +30,27 @@ export class MatterbridgePlatform {
30
30
  logging: false,
31
31
  forgiveParseErrors: true,
32
32
  });
33
+ this.log.debug(`Creating context for plugin ${this.config.name}`);
34
+ this.storage.createStorage('context').then((context) => {
35
+ this.context = context;
36
+ this.log.debug(`Created context for plugin ${this.config.name}`);
37
+ });
38
+ this.log.debug(`Loading selectDevice for plugin ${this.config.name}`);
39
+ this.storage.createStorage('selectDevice').then((context) => {
40
+ context.get('selectDevice', []).then((selectDevice) => {
41
+ for (const device of selectDevice)
42
+ this.selectDevice.set(device.serial, device);
43
+ });
44
+ this.log.debug(`Loaded ${this.selectDevice.size} selectDevice for plugin ${this.config.name}`);
45
+ });
46
+ this.log.debug(`Loading selectEntity for plugin ${this.config.name}`);
47
+ this.storage.createStorage('selectEntity').then((context) => {
48
+ context.get('selectEntity', []).then((selectEntity) => {
49
+ for (const entity of selectEntity)
50
+ this.selectEntity.set(entity.name, entity);
51
+ });
52
+ this.log.debug(`Loaded ${this.selectEntity.size} selectEntity for plugin ${this.config.name}`);
53
+ });
33
54
  }
34
55
  async onStart(reason) {
35
56
  this.log.error('Plugins must override onStart.', reason);
@@ -41,11 +62,22 @@ export class MatterbridgePlatform {
41
62
  }
42
63
  async onShutdown(reason) {
43
64
  this.log.debug(`Shutting down platform ${this.name}`, reason);
65
+ if (this.storage) {
66
+ this.log.debug(`Saving ${this.selectDevice.size} selectDevice...`);
67
+ const selectDevice = await this.storage.createStorage('selectDevice');
68
+ await selectDevice.set('selectDevice', Array.from(this.selectDevice.values()));
69
+ await selectDevice.close();
70
+ this.log.debug(`Saving ${this.selectEntity.size} selectEntity...`);
71
+ const selectEntity = await this.storage.createStorage('selectEntity');
72
+ await selectEntity.set('selectEntity', Array.from(this.selectEntity.values()));
73
+ await selectEntity.close();
74
+ }
44
75
  await this.checkEndpointNumbers();
45
76
  this.selectDevice.clear();
46
77
  this.selectEntity.clear();
47
78
  this.registeredEndpoints.clear();
48
79
  this.registeredEndpointsByName.clear();
80
+ this.log.debug('Saving context...');
49
81
  await this.context?.close();
50
82
  this.context = undefined;
51
83
  await this.storage?.close();
@@ -165,7 +197,7 @@ export class MatterbridgePlatform {
165
197
  if (!this.storage)
166
198
  return -1;
167
199
  this.log.debug('Checking endpoint numbers...');
168
- const context = await this.storage.createStorage('context');
200
+ const context = await this.storage.createStorage('endpointNumbers');
169
201
  const separator = '|.|';
170
202
  const endpointMap = new Map(await context.get('endpointMap', []));
171
203
  for (const device of this.matterbridge.getDevices().filter((d) => d.plugin === this.name)) {
@@ -195,7 +227,9 @@ export class MatterbridgePlatform {
195
227
  }
196
228
  }
197
229
  }
230
+ this.log.debug('Saving endpointNumbers...');
198
231
  await context.set('endpointMap', Array.from(endpointMap.entries()));
232
+ await context.close();
199
233
  this.log.debug('Endpoint numbers check completed.');
200
234
  return endpointMap.size;
201
235
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "./static/css/main.cf25d33e.css",
4
- "main.js": "./static/js/main.cd192588.js",
4
+ "main.js": "./static/js/main.a241d4f0.js",
5
5
  "static/js/453.abd36b29.chunk.js": "./static/js/453.abd36b29.chunk.js",
6
6
  "static/media/roboto-latin-700-normal.woff2": "./static/media/roboto-latin-700-normal.4535474e1cf8598695ad.woff2",
7
7
  "static/media/roboto-latin-500-normal.woff2": "./static/media/roboto-latin-500-normal.7077203b1982951ecf76.woff2",
@@ -61,11 +61,11 @@
61
61
  "static/media/roboto-greek-ext-400-normal.woff": "./static/media/roboto-greek-ext-400-normal.16eb83b4a3b1ea994243.woff",
62
62
  "index.html": "./index.html",
63
63
  "main.cf25d33e.css.map": "./static/css/main.cf25d33e.css.map",
64
- "main.cd192588.js.map": "./static/js/main.cd192588.js.map",
64
+ "main.a241d4f0.js.map": "./static/js/main.a241d4f0.js.map",
65
65
  "453.abd36b29.chunk.js.map": "./static/js/453.abd36b29.chunk.js.map"
66
66
  },
67
67
  "entrypoints": [
68
68
  "static/css/main.cf25d33e.css",
69
- "static/js/main.cd192588.js"
69
+ "static/js/main.a241d4f0.js"
70
70
  ]
71
71
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.cd192588.js"></script><link href="./static/css/main.cf25d33e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.a241d4f0.js"></script><link href="./static/css/main.cf25d33e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>