@matterbridge/core 3.9.2-dev-20260620-9802e5b → 3.9.2-dev-20260621-75283ba

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/backend.d.ts CHANGED
@@ -29,9 +29,9 @@ export declare class Backend extends EventEmitter<BackendEvents> {
29
29
  private broadcastMsgHandler;
30
30
  start(port?: number): Promise<void>;
31
31
  stop(): Promise<void>;
32
- getApiSettings(): ApiSettings;
33
- getApiPlugins(): ApiPlugin[];
34
- getApiDevices(_pluginName?: string): ApiDevice[];
32
+ getApiSettings(): Promise<ApiSettings>;
33
+ getApiPlugins(): Promise<ApiPlugin[]>;
34
+ getApiDevices(pluginName?: string): Promise<ApiDevice[]>;
35
35
  generateDiagnostic(): Promise<void>;
36
36
  }
37
37
  export {};
package/dist/backend.js CHANGED
@@ -292,13 +292,16 @@ export class Backend extends EventEmitter {
292
292
  this.backendExpress = undefined;
293
293
  this.log.debug('Backend stopped');
294
294
  }
295
- getApiSettings() {
296
- return {};
295
+ async getApiSettings() {
296
+ const response = await this.server.fetch({ type: 'matterbridge_apisettings', src: 'frontend', dst: 'matterbridge', params: undefined });
297
+ return response.result.data;
297
298
  }
298
- getApiPlugins() {
299
- return [];
299
+ async getApiPlugins() {
300
+ const response = await this.server.fetch({ type: 'plugins_apipluginarray', src: 'frontend', dst: 'plugins', params: undefined });
301
+ return response.result.plugins;
300
302
  }
301
- getApiDevices(_pluginName) {
303
+ async getApiDevices(pluginName) {
304
+ const _response = await this.server.fetch({ type: 'devices_basearray', src: 'frontend', dst: 'devices', params: { pluginName } });
302
305
  return [];
303
306
  }
304
307
  async generateDiagnostic() { }
@@ -131,23 +131,23 @@ export class BackendExpress {
131
131
  };
132
132
  res.status(200).json(memoryReport);
133
133
  });
134
- this.expressApp.get('/api/settings', (req, res) => {
134
+ this.expressApp.get('/api/settings', async (req, res) => {
135
135
  this.log.debug('The frontend sent /api/settings');
136
136
  if (!this.validateReq(req, res))
137
137
  return;
138
- res.json(this.backend.getApiSettings());
138
+ res.json(await this.backend.getApiSettings());
139
139
  });
140
- this.expressApp.get('/api/plugins', (req, res) => {
140
+ this.expressApp.get('/api/plugins', async (req, res) => {
141
141
  this.log.debug('The frontend sent /api/plugins');
142
142
  if (!this.validateReq(req, res))
143
143
  return;
144
- res.json(this.backend.getApiPlugins());
144
+ res.json(await this.backend.getApiPlugins());
145
145
  });
146
- this.expressApp.get('/api/devices', (req, res) => {
146
+ this.expressApp.get('/api/devices', async (req, res) => {
147
147
  this.log.debug('The frontend sent /api/devices');
148
148
  if (!this.validateReq(req, res))
149
149
  return;
150
- res.json(this.backend.getApiDevices());
150
+ res.json(await this.backend.getApiDevices());
151
151
  });
152
152
  this.expressApp.get('/api/view-mblog', async (req, res) => {
153
153
  this.log.debug('The frontend sent /api/view-mblog');
@@ -1,5 +1,5 @@
1
1
  import type { EndpointNumber } from '@matter/types/datatype';
2
- import type { ApiMatter, RefreshRequiredChanged, SharedMatterbridge, WsMessageBroadcast } from '@matterbridge/types';
2
+ import type { ApiMatter, BridgeStatus, PluginStatusUpdate, RefreshRequiredChanged, SharedMatterbridge, WsMessageBroadcast } from '@matterbridge/types';
3
3
  import { WebSocketServer } from 'ws';
4
4
  import type { Backend } from './backend.js';
5
5
  export declare class BackendWsServer {
@@ -25,7 +25,10 @@ export declare class BackendWsServer {
25
25
  }): void;
26
26
  wssSendRestartRequired(snackbar?: boolean, fixed?: boolean): void;
27
27
  wssSendRestartNotRequired(snackbar?: boolean): void;
28
- wssSendUpdateRequired(devVersion?: boolean): void;
28
+ wssSendUpdateRequired(version: string, devVersion?: boolean): void;
29
+ wssSendPluginUpdateRequired(plugin: string, version: string, devVersion?: boolean): void;
30
+ wssSendPluginStatusUpdate(plugin: string, status: PluginStatusUpdate): void;
31
+ wssSendMatterbridgeStatusUpdate(status: BridgeStatus): void;
29
32
  wssSendCpuUpdate(cpuUsage: number, processCpuUsage: number): void;
30
33
  wssSendMemoryUpdate(totalMemory: string, freeMemory: string, rss: string, heapTotal: string, heapUsed: string, external: string, arrayBuffers: string): void;
31
34
  wssSendUptimeUpdate(systemUptime: string, processUptime: string): void;
@@ -232,12 +232,33 @@ export class BackendWsServer {
232
232
  this.wssSendCloseSnackbarMessage(`Restart required`);
233
233
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
234
234
  }
235
- wssSendUpdateRequired(devVersion = false) {
235
+ wssSendUpdateRequired(version, devVersion = false) {
236
236
  if (!this.hasActiveClients())
237
237
  return;
238
238
  if (this.verbose)
239
- this.log.debug('Sending an update required message to all connected clients');
240
- this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
239
+ this.log.debug('Sending a matterbridge version update required message to all connected clients');
240
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { version, devVersion } });
241
+ }
242
+ wssSendPluginUpdateRequired(plugin, version, devVersion = false) {
243
+ if (!this.hasActiveClients())
244
+ return;
245
+ if (this.verbose)
246
+ this.log.debug('Sending a plugin version update required message to all connected clients');
247
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_update_required', success: true, response: { plugin, version, devVersion } });
248
+ }
249
+ wssSendPluginStatusUpdate(plugin, status) {
250
+ if (!this.hasActiveClients())
251
+ return;
252
+ if (this.verbose)
253
+ this.log.debug('Sending a plugin status update message to all connected clients');
254
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_status_update', success: true, response: { plugin, status } });
255
+ }
256
+ wssSendMatterbridgeStatusUpdate(status) {
257
+ if (!this.hasActiveClients())
258
+ return;
259
+ if (this.verbose)
260
+ this.log.debug('Sending a matterbridge status update message to all connected clients');
261
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'matterbridge_status_update', success: true, response: { status } });
241
262
  }
242
263
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
243
264
  if (!this.hasActiveClients())
@@ -1,6 +1,6 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import { type EndpointNumber } from '@matter/types/datatype';
3
- import type { ApiClusters, ApiDevice, ApiMatter, ApiPlugin, ApiSettings, RefreshRequiredChanged, WsMessageBroadcast } from '@matterbridge/types';
3
+ import type { ApiClusters, ApiDevice, ApiMatter, ApiPlugin, ApiSettings, BridgeStatus, PluginStatusUpdate, RefreshRequiredChanged, WsMessageBroadcast } from '@matterbridge/types';
4
4
  import { LogLevel } from 'node-ansi-logger';
5
5
  import type { Matterbridge } from './matterbridge.js';
6
6
  interface FrontendEvents {
@@ -57,7 +57,10 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
57
57
  }): void;
58
58
  wssSendRestartRequired(snackbar?: boolean, fixed?: boolean): void;
59
59
  wssSendRestartNotRequired(snackbar?: boolean): void;
60
- wssSendUpdateRequired(devVersion?: boolean): void;
60
+ wssSendUpdateRequired(version: string, devVersion?: boolean): void;
61
+ wssSendPluginUpdateRequired(plugin: string, version: string, devVersion?: boolean): void;
62
+ wssSendPluginStatusUpdate(plugin: string, status: PluginStatusUpdate): void;
63
+ wssSendMatterbridgeStatusUpdate(status: BridgeStatus): void;
61
64
  wssSendCpuUpdate(cpuUsage: number, processCpuUsage: number): void;
62
65
  wssSendMemoryUpdate(totalMemory: string, freeMemory: string, rss: string, heapTotal: string, heapUsed: string, external: string, arrayBuffers: string): void;
63
66
  wssSendUptimeUpdate(systemUptime: string, processUptime: string): void;
package/dist/frontend.js CHANGED
@@ -93,13 +93,29 @@ export class Frontend extends EventEmitter {
93
93
  this.server.respond({ ...msg, result: { success: true } });
94
94
  break;
95
95
  case 'frontend_updaterequired':
96
- this.wssSendUpdateRequired(msg.params.devVersion);
96
+ this.wssSendUpdateRequired(msg.params.version, msg.params.devVersion);
97
+ this.server.respond({ ...msg, result: { success: true } });
98
+ break;
99
+ case 'frontend_pluginupdaterequired':
100
+ this.wssSendPluginUpdateRequired(msg.params.plugin, msg.params.version, msg.params.devVersion);
101
+ this.server.respond({ ...msg, result: { success: true } });
102
+ break;
103
+ case 'frontend_pluginstatusupdate':
104
+ this.wssSendPluginStatusUpdate(msg.params.plugin, msg.params.status);
105
+ this.server.respond({ ...msg, result: { success: true } });
106
+ break;
107
+ case 'frontend_matterbridgestatusupdate':
108
+ this.wssSendMatterbridgeStatusUpdate(msg.params.status);
97
109
  this.server.respond({ ...msg, result: { success: true } });
98
110
  break;
99
111
  case 'frontend_snackbarmessage':
100
112
  this.wssSendSnackbarMessage(msg.params.message, msg.params.timeout, msg.params.severity);
101
113
  this.server.respond({ ...msg, result: { success: true } });
102
114
  break;
115
+ case 'frontend_closesnackbarmessage':
116
+ this.wssSendCloseSnackbarMessage(msg.params.message);
117
+ this.server.respond({ ...msg, result: { success: true } });
118
+ break;
103
119
  case 'frontend_attributechanged':
104
120
  this.wssSendAttributeChangedMessage(msg.params.plugin, msg.params.serialNumber, msg.params.uniqueId, msg.params.number, msg.params.id, msg.params.cluster, msg.params.attribute, msg.params.value);
105
121
  this.server.respond({ ...msg, result: { success: true } });
@@ -931,6 +947,7 @@ export class Frontend extends EventEmitter {
931
947
  bridgeMode: this.matterbridge.bridgeMode,
932
948
  restartMode: this.matterbridge.restartMode,
933
949
  virtualMode: this.matterbridge.virtualMode,
950
+ bridgeStatus: this.matterbridge.bridgeStatus,
934
951
  profile: this.matterbridge.profile,
935
952
  loggerLevel: this.matterbridge.logLevel,
936
953
  fileLogger: this.matterbridge.fileLogger,
@@ -2113,12 +2130,30 @@ export class Frontend extends EventEmitter {
2113
2130
  this.wssSendCloseSnackbarMessage(`Restart required`);
2114
2131
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2115
2132
  }
2116
- wssSendUpdateRequired(devVersion = false) {
2133
+ wssSendUpdateRequired(version, devVersion = false) {
2117
2134
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2118
2135
  return;
2119
- this.log.debug('Sending an update required message to all connected clients');
2136
+ this.log.debug('Sending a matterbridge version update required message to all connected clients');
2120
2137
  this.updateRequired = true;
2121
- this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2138
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { version, devVersion } });
2139
+ }
2140
+ wssSendPluginUpdateRequired(plugin, version, devVersion = false) {
2141
+ if (!this.listening || this.webSocketServer?.clients.size === 0)
2142
+ return;
2143
+ this.log.debug('Sending a plugin version update required message to all connected clients');
2144
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_update_required', success: true, response: { plugin, version, devVersion } });
2145
+ }
2146
+ wssSendPluginStatusUpdate(plugin, status) {
2147
+ if (!this.listening || this.webSocketServer?.clients.size === 0)
2148
+ return;
2149
+ this.log.debug('Sending a plugin status update message to all connected clients');
2150
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_status_update', success: true, response: { plugin, status } });
2151
+ }
2152
+ wssSendMatterbridgeStatusUpdate(status) {
2153
+ if (!this.listening || this.webSocketServer?.clients.size === 0)
2154
+ return;
2155
+ this.log.debug('Sending a matterbridge status update message to all connected clients');
2156
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'matterbridge_status_update', success: true, response: { status } });
2122
2157
  }
2123
2158
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2124
2159
  if (!this.listening || this.webSocketServer?.clients.size === 0)
package/dist/helpers.js CHANGED
@@ -61,7 +61,7 @@ export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
61
61
  if (matterbridge.virtualMode !== 'disabled' && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
62
62
  matterbridge.log.notice(`Creating virtual devices for Matterbridge server node...`);
63
63
  await addVirtualDevice(aggregatorEndpoint, 'Restart Matterbridge', matterbridge.virtualMode, async () => {
64
- if (matterbridge.restartMode === '')
64
+ if (matterbridge.restartMode === 'none')
65
65
  await matterbridge.restartProcess();
66
66
  else
67
67
  await matterbridge.shutdownProcess();
@@ -5,7 +5,7 @@ import { Endpoint, ServerNode } from '@matter/node';
5
5
  import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
6
6
  import { type DeviceCertification } from '@matter/protocol';
7
7
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
8
- import type { ApiMatter, MaybePromise, PlatformMatterbridge, SharedMatterbridge, SystemInformation } from '@matterbridge/types';
8
+ import type { ApiMatter, ApiSettings, BridgeMode, BridgeStatus, MaybePromise, PlatformMatterbridge, RestartMode, SharedMatterbridge, SystemInformation, VirtualMode } from '@matterbridge/types';
9
9
  import { AnsiLogger, LogLevel } from 'node-ansi-logger';
10
10
  import { type NodeStorage, NodeStorageManager } from 'node-persist-manager';
11
11
  import { DeviceManager } from './deviceManager.js';
@@ -50,9 +50,10 @@ export declare class Matterbridge extends EventEmitter<MatterbridgeEvents> {
50
50
  dockerVersion: string | undefined;
51
51
  dockerLatestVersion: string | undefined;
52
52
  dockerDevVersion: string | undefined;
53
- bridgeMode: 'bridge' | 'childbridge' | 'controller' | '';
54
- restartMode: 'service' | 'docker' | '';
55
- virtualMode: 'disabled' | 'outlet' | 'light' | 'switch' | 'mounted_switch';
53
+ bridgeMode: BridgeMode;
54
+ restartMode: RestartMode;
55
+ virtualMode: VirtualMode;
56
+ bridgeStatus: BridgeStatus;
56
57
  readonly profile: string | undefined;
57
58
  readonly log: AnsiLogger;
58
59
  logLevel: LogLevel;
@@ -116,6 +117,7 @@ export declare class Matterbridge extends EventEmitter<MatterbridgeEvents> {
116
117
  destroy(): void;
117
118
  getPlatformMatterbridge(): PlatformMatterbridge;
118
119
  getSharedMatterbridge(): SharedMatterbridge;
120
+ getApiSettings(): ApiSettings;
119
121
  private msgHandler;
120
122
  static loadInstance(initialize?: boolean): Promise<Matterbridge>;
121
123
  private initialize;
@@ -134,6 +136,7 @@ export declare class Matterbridge extends EventEmitter<MatterbridgeEvents> {
134
136
  shutdownProcessAndReset(): Promise<void>;
135
137
  shutdownProcessAndFactoryReset(): Promise<void>;
136
138
  private cleanup;
139
+ private sendPluginStatusUpdate;
137
140
  private startBridge;
138
141
  private startChildbridge;
139
142
  private startController;
@@ -17,7 +17,7 @@ import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE,
17
17
  import { getIntParameter, getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
18
18
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
19
19
  import { createDirectory } from '@matterbridge/utils/create-dir';
20
- import { inspectError, logError } from '@matterbridge/utils/error';
20
+ import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/error';
21
21
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
22
22
  import { logModuleLoaded } from '@matterbridge/utils/loader';
23
23
  import { excludedInterfaceNamePattern } from '@matterbridge/utils/network';
@@ -72,9 +72,10 @@ export class Matterbridge extends EventEmitter {
72
72
  dockerVersion;
73
73
  dockerLatestVersion;
74
74
  dockerDevVersion;
75
- bridgeMode = '';
76
- restartMode = '';
75
+ bridgeMode = 'none';
76
+ restartMode = 'none';
77
77
  virtualMode = 'outlet';
78
+ bridgeStatus = 'inactive';
78
79
  profile = getParameter('profile');
79
80
  log = new AnsiLogger({
80
81
  logName: 'Matterbridge',
@@ -209,6 +210,49 @@ export class Matterbridge extends EventEmitter {
209
210
  passcode: this.passcode,
210
211
  };
211
212
  }
213
+ getApiSettings() {
214
+ return {
215
+ systemInformation: { ...this.systemInformation },
216
+ matterbridgeInformation: {
217
+ rootDirectory: this.rootDirectory,
218
+ homeDirectory: this.homeDirectory,
219
+ matterbridgeDirectory: this.matterbridgeDirectory,
220
+ matterbridgePluginDirectory: this.matterbridgePluginDirectory,
221
+ matterbridgeCertDirectory: this.matterbridgeCertDirectory,
222
+ globalModulesDirectory: this.globalModulesDirectory,
223
+ matterbridgeVersion: this.matterbridgeVersion,
224
+ matterbridgeLatestVersion: this.matterbridgeLatestVersion,
225
+ matterbridgeDevVersion: this.matterbridgeDevVersion,
226
+ frontendVersion: this.frontendVersion,
227
+ dockerDev: this.dockerDev,
228
+ dockerVersion: this.dockerVersion,
229
+ dockerLatestVersion: this.dockerLatestVersion,
230
+ dockerDevVersion: this.dockerDevVersion,
231
+ bridgeMode: this.bridgeMode,
232
+ restartMode: this.restartMode,
233
+ virtualMode: this.virtualMode,
234
+ bridgeStatus: this.bridgeStatus,
235
+ profile: this.profile,
236
+ readOnly: hasParameter('readonly'),
237
+ shellyBoard: false,
238
+ shellySysUpdate: false,
239
+ shellyMainUpdate: false,
240
+ loggerLevel: this.logLevel,
241
+ fileLogger: this.fileLogger,
242
+ matterLoggerLevel: this.matterLogLevel,
243
+ matterFileLogger: this.matterFileLogger,
244
+ matterMdnsInterface: this.mdnsInterface,
245
+ matterIpv4Address: this.ipv4Address,
246
+ matterIpv6Address: this.ipv6Address,
247
+ matterPort: this.port ?? 5540,
248
+ matterDiscriminator: this.discriminator,
249
+ matterPasscode: this.passcode,
250
+ restartRequired: false,
251
+ fixedRestartRequired: false,
252
+ updateRequired: false,
253
+ },
254
+ };
255
+ }
212
256
  async msgHandler(msg) {
213
257
  if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matterbridge')) {
214
258
  if (this.verbose)
@@ -249,6 +293,9 @@ export class Matterbridge extends EventEmitter {
249
293
  case 'matterbridge_shared':
250
294
  this.server.respond({ ...msg, result: { data: this.getSharedMatterbridge(), success: true } });
251
295
  break;
296
+ case 'matterbridge_apisettings':
297
+ this.server.respond({ ...msg, result: { data: this.getApiSettings(), success: true } });
298
+ break;
252
299
  case 'matterbridge_start_plugin_server':
253
300
  {
254
301
  const plugin = this.plugins.get(msg.params.pluginName);
@@ -299,7 +346,7 @@ export class Matterbridge extends EventEmitter {
299
346
  const packageName = msg.result.packageName.replace(/@.*$/, '');
300
347
  if (packageName === 'matterbridge') {
301
348
  this.log.info('Matterbridge has been updated. Full restart required.');
302
- if (this.restartMode !== '')
349
+ if (this.restartMode !== 'none')
303
350
  await this.cleanup('updating...', false);
304
351
  }
305
352
  }
@@ -440,7 +487,7 @@ export class Matterbridge extends EventEmitter {
440
487
  }
441
488
  }
442
489
  catch (error) {
443
- this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
490
+ this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${getErrorMessage(error)}`);
444
491
  }
445
492
  await this.nodeContext.set('matterport', this.port);
446
493
  await this.nodeContext.set('matterpasscode', this.passcode);
@@ -549,14 +596,14 @@ export class Matterbridge extends EventEmitter {
549
596
  this.mdnsInterface = undefined;
550
597
  }
551
598
  if (this.mdnsInterface) {
552
- if (!availableInterfaceNames.includes(this.mdnsInterface)) {
599
+ if (availableInterfaceNames.includes(this.mdnsInterface)) {
600
+ this.log.info(`Using mdnsinterface ${CYAN}${this.mdnsInterface}${nf} for the Matter MdnsBroadcaster.`);
601
+ }
602
+ else {
553
603
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
554
604
  this.mdnsInterface = undefined;
555
605
  await this.nodeContext.remove('mattermdnsinterface');
556
606
  }
557
- else {
558
- this.log.info(`Using mdnsinterface ${CYAN}${this.mdnsInterface}${nf} for the Matter MdnsBroadcaster.`);
559
- }
560
607
  }
561
608
  if (this.mdnsInterface)
562
609
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
@@ -683,12 +730,12 @@ export class Matterbridge extends EventEmitter {
683
730
  await plugin.nodeContext.set('author', plugin.author);
684
731
  }
685
732
  await this.logNodeAndSystemInfo();
686
- this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
687
- `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
688
- `${hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge') ? 'mode childbridge ' : ''}` +
689
- `${hasParameter('controller') ? 'mode controller ' : ''}` +
690
- `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
691
- `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
733
+ this.log.notice('Matterbridge version ' + this.matterbridgeVersion + ' ' +
734
+ (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : '') +
735
+ (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge') ? 'mode childbridge ' : '') +
736
+ (hasParameter('controller') ? 'mode controller ' : '') +
737
+ (this.restartMode === 'none' ? '' : 'restart mode ' + this.restartMode + ' ') +
738
+ 'running on ' + this.systemInformation.osType + ' (v.' + this.systemInformation.osRelease + ') platform ' + this.systemInformation.osPlatform + ' arch ' + this.systemInformation.osArch);
692
739
  const minNodeVersion = 20;
693
740
  const nodeVersion = process.versions.node;
694
741
  const [versionMajor] = nodeVersion.split('.').map(Number);
@@ -705,13 +752,13 @@ export class Matterbridge extends EventEmitter {
705
752
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
706
753
  let index = 0;
707
754
  for (const plugin of this.plugins) {
708
- if (index !== this.plugins.length - 1) {
709
- this.log.info(`├─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}enabled${nf}`);
710
- this.log.info(`│ └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
755
+ if (index === this.plugins.length - 1) {
756
+ this.log.info(`└─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}${plugin.enabled ? 'enabled' : 'disabled'}${nf}`);
757
+ this.log.info(` └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
711
758
  }
712
759
  else {
713
- this.log.info(`└─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}disabled${nf}`);
714
- this.log.info(` └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
760
+ this.log.info(`├─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}${plugin.enabled ? 'enabled' : 'disabled'}${nf}`);
761
+ this.log.info(`│ └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
715
762
  }
716
763
  index++;
717
764
  }
@@ -741,27 +788,31 @@ export class Matterbridge extends EventEmitter {
741
788
  this.shutdown = true;
742
789
  return;
743
790
  }
744
- if (getParameter('add')) {
745
- this.log.debug(`Adding plugin ${getParameter('add')}`);
746
- await this.plugins.add(getParameter('add'));
791
+ const addPlugin = getParameter('add');
792
+ if (addPlugin) {
793
+ this.log.debug(`Adding plugin ${addPlugin}`);
794
+ await this.plugins.add(addPlugin);
747
795
  this.shutdown = true;
748
796
  return;
749
797
  }
750
- if (getParameter('remove')) {
751
- this.log.debug(`Removing plugin ${getParameter('remove')}`);
752
- await this.plugins.remove(getParameter('remove'));
798
+ const removePlugin = getParameter('remove');
799
+ if (removePlugin) {
800
+ this.log.debug(`Removing plugin ${removePlugin}`);
801
+ await this.plugins.remove(removePlugin);
753
802
  this.shutdown = true;
754
803
  return;
755
804
  }
756
- if (getParameter('enable')) {
757
- this.log.debug(`Enabling plugin ${getParameter('enable')}`);
758
- await this.plugins.enable(getParameter('enable'));
805
+ const enablePlugin = getParameter('enable');
806
+ if (enablePlugin) {
807
+ this.log.debug(`Enabling plugin ${enablePlugin}`);
808
+ await this.plugins.enable(enablePlugin);
759
809
  this.shutdown = true;
760
810
  return;
761
811
  }
762
- if (getParameter('disable')) {
763
- this.log.debug(`Disabling plugin ${getParameter('disable')}`);
764
- await this.plugins.disable(getParameter('disable'));
812
+ const disablePlugin = getParameter('disable');
813
+ if (disablePlugin) {
814
+ this.log.debug(`Disabling plugin ${disablePlugin}`);
815
+ await this.plugins.disable(disablePlugin);
765
816
  this.shutdown = true;
766
817
  return;
767
818
  }
@@ -783,8 +834,8 @@ export class Matterbridge extends EventEmitter {
783
834
  }
784
835
  }
785
836
  catch (error) {
786
- this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
787
- throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`, { cause: error });
837
+ this.log.fatal(`Fatal error creating matter storage: ${getErrorMessage(error)}`);
838
+ throw new Error(`Fatal error creating matter storage: ${getErrorMessage(error)}`, { cause: error });
788
839
  }
789
840
  if (hasParameter('reset') && getParameter('reset') === undefined) {
790
841
  this.initialized = true;
@@ -792,15 +843,13 @@ export class Matterbridge extends EventEmitter {
792
843
  this.shutdown = true;
793
844
  return;
794
845
  }
795
- if (hasParameter('reset') && getParameter('reset') !== undefined) {
796
- this.log.debug(`Reset plugin ${getParameter('reset')}`);
797
- const plugin = this.plugins.get(getParameter('reset'));
846
+ const resetPlugin = getParameter('reset');
847
+ if (hasParameter('reset') && resetPlugin !== undefined) {
848
+ this.log.debug(`Reset plugin ${resetPlugin}`);
849
+ const plugin = this.plugins.get(resetPlugin);
798
850
  if (plugin) {
799
851
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
800
- if (!matterStorageManager) {
801
- this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
802
- }
803
- else {
852
+ if (matterStorageManager) {
804
853
  await matterStorageManager.createContext('events')?.clearAll();
805
854
  await matterStorageManager.createContext('fabrics')?.clearAll();
806
855
  await matterStorageManager.createContext('root')?.clearAll();
@@ -808,6 +857,9 @@ export class Matterbridge extends EventEmitter {
808
857
  await matterStorageManager.createContext('persist')?.clearAll();
809
858
  this.log.notice(`Reset commissioning for plugin ${plg}${plugin.name}${nt} done! Remove the device from the controller.`);
810
859
  }
860
+ else {
861
+ this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
862
+ }
811
863
  }
812
864
  else {
813
865
  this.log.warn(`Plugin ${plg}${getParameter('reset')}${wr} not registerd in matterbridge`);
@@ -845,13 +897,13 @@ export class Matterbridge extends EventEmitter {
845
897
  }
846
898
  if (hasParameter('delay') && os.uptime() <= 60 * 5) {
847
899
  const { wait } = await import('@matterbridge/utils/wait');
848
- const delay = getIntParameter('delay') || 120;
900
+ const delay = getIntParameter('delay') ?? 120;
849
901
  this.log.warn('Delay switch found with system uptime less then 5 minutes. Waiting for ' + delay + ' seconds before starting matterbridge...');
850
902
  await wait(delay * 1000, 'Race condition delay', true);
851
903
  }
852
904
  if (hasParameter('fixed_delay')) {
853
905
  const { wait } = await import('@matterbridge/utils/wait');
854
- const delay = getIntParameter('fixed_delay') || 120;
906
+ const delay = getIntParameter('fixed_delay') ?? 120;
855
907
  this.log.warn('Fixed delay switch found. Waiting for ' + delay + ' seconds before starting matterbridge...');
856
908
  await wait(delay * 1000, 'Fixed race condition delay', true);
857
909
  }
@@ -899,25 +951,29 @@ export class Matterbridge extends EventEmitter {
899
951
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
900
952
  process.removeAllListeners('uncaughtException');
901
953
  process.removeAllListeners('unhandledRejection');
902
- this.exceptionHandler = async (error) => {
903
- const errorMessage = error instanceof Error ? error.message : error;
954
+ this.exceptionHandler = (error) => {
955
+ const errorMessage = getErrorMessage(error);
904
956
  const errorInspect = inspect(error, { depth: 10 });
905
- this.log.error(`Unhandled Exception detected: ${errorMessage}\nstack: ${errorInspect}}`);
957
+ this.log.error(`Unhandled Exception detected: ${errorMessage}\nstack: ${errorInspect}`);
906
958
  };
907
959
  process.on('uncaughtException', this.exceptionHandler);
908
- this.rejectionHandler = async (reason, promise) => {
909
- const errorMessage = reason instanceof Error ? reason.message : reason;
960
+ this.rejectionHandler = (reason, _promise) => {
961
+ const errorMessage = getErrorMessage(reason);
910
962
  const errorInspect = inspect(reason, { depth: 10 });
911
- this.log.error(`Unhandled Rejection detected: ${promise}\nreason: ${errorMessage}\nstack: ${errorInspect}`);
963
+ this.log.error(`Unhandled Rejection detected: ${errorMessage}\nstack: ${errorInspect}`);
912
964
  };
913
965
  process.on('unhandledRejection', this.rejectionHandler);
914
966
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
915
- this.sigintHandler = async () => {
916
- await this.cleanup('SIGINT received, cleaning up...');
967
+ this.sigintHandler = () => {
968
+ this.cleanup('SIGINT received, cleaning up...').catch((error) => {
969
+ this.log.error(`Error cleaning up on SIGINT: ${getErrorMessage(error)}`);
970
+ });
917
971
  };
918
972
  process.on('SIGINT', this.sigintHandler);
919
- this.sigtermHandler = async () => {
920
- await this.cleanup('SIGTERM received, cleaning up...');
973
+ this.sigtermHandler = () => {
974
+ this.cleanup('SIGTERM received, cleaning up...').catch((error) => {
975
+ this.log.error(`Error cleaning up on SIGTERM: ${getErrorMessage(error)}`);
976
+ });
921
977
  };
922
978
  process.on('SIGTERM', this.sigtermHandler);
923
979
  }
@@ -1152,6 +1208,7 @@ export class Matterbridge extends EventEmitter {
1152
1208
  this.emit('cleanup_started');
1153
1209
  this.hasCleanupStarted = true;
1154
1210
  this.log.info(message);
1211
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'stopping') } });
1155
1212
  if (this.startMatterInterval) {
1156
1213
  clearInterval(this.startMatterInterval);
1157
1214
  this.startMatterInterval = undefined;
@@ -1300,6 +1357,7 @@ export class Matterbridge extends EventEmitter {
1300
1357
  unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, device.deviceName.replace(/[ .]/g, ''), 'root.subscriptions.subscriptions'), this.log);
1301
1358
  }
1302
1359
  }
1360
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'stopped') } });
1303
1361
  await this.frontend.stop();
1304
1362
  this.frontend.destroy();
1305
1363
  this.plugins.destroy();
@@ -1388,93 +1446,119 @@ export class Matterbridge extends EventEmitter {
1388
1446
  this.log.debug('Cleanup already started...');
1389
1447
  }
1390
1448
  }
1449
+ sendPluginStatusUpdate(plugin) {
1450
+ this.server.request({
1451
+ type: 'frontend_pluginstatusupdate',
1452
+ src: 'matterbridge',
1453
+ dst: 'frontend',
1454
+ params: {
1455
+ plugin: plugin.name,
1456
+ status: {
1457
+ locked: plugin.locked,
1458
+ error: plugin.error,
1459
+ enabled: plugin.enabled,
1460
+ loaded: plugin.loaded,
1461
+ started: plugin.started,
1462
+ configured: plugin.configured,
1463
+ registeredDevices: plugin.registeredDevices,
1464
+ },
1465
+ },
1466
+ });
1467
+ }
1391
1468
  async startBridge() {
1392
1469
  if (!this.matterStorageManager)
1393
1470
  throw new Error('No storage manager initialized');
1394
1471
  if (!this.matterbridgeContext)
1395
1472
  throw new Error('No storage context initialized');
1473
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'starting') } });
1396
1474
  this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1397
1475
  this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
1398
1476
  await this.serverNode.add(this.aggregatorNode);
1399
1477
  await addVirtualDevices(this, this.aggregatorNode);
1400
- await this.startPlugins();
1478
+ await this.startPlugins(false, true);
1401
1479
  this.log.debug('Starting start matter interval in bridge mode...');
1402
1480
  this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
1403
1481
  let failCount = 0;
1404
- this.startMatterInterval = setInterval(async () => {
1405
- if (failCount && failCount % 10 === 0) {
1406
- this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1407
- this.frontend.wssSendRefreshRequired('plugins');
1408
- }
1409
- for (const plugin of this.plugins) {
1410
- if (!plugin.enabled)
1411
- continue;
1412
- if (plugin.error) {
1413
- clearInterval(this.startMatterInterval);
1414
- this.startMatterInterval = undefined;
1415
- this.log.debug('Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1416
- this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1417
- this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1418
- this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1419
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1420
- this.frontend.wssSendSnackbarMessage(`The bridge is offline. Startup halted due to plugin errors.`, 0, 'error');
1421
- this.frontend.wssSendRefreshRequired('plugins');
1422
- this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1423
- return;
1424
- }
1425
- if (!plugin.loaded || !plugin.started) {
1426
- this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) in startMatterInterval interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1427
- failCount++;
1428
- if (failCount > this.failCountLimit) {
1429
- this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1430
- plugin.error = true;
1431
- }
1432
- return;
1482
+ this.startMatterInterval = setInterval(() => {
1483
+ void (async () => {
1484
+ if (failCount && failCount % 10 === 0) {
1485
+ this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1433
1486
  }
1434
- }
1435
- clearInterval(this.startMatterInterval);
1436
- this.startMatterInterval = undefined;
1437
- this.log.debug('Cleared startMatterInterval interval in bridge mode');
1438
- fireAndForget(this.startServerNode(this.serverNode), this.log, 'Start server node');
1439
- for (const device of this.devices.array()) {
1440
- if (device.mode === 'server' && device.serverNode) {
1441
- this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1442
- fireAndForget(this.startServerNode(device.serverNode), this.log, `Start server node for device ${device.deviceName}`);
1443
- }
1444
- }
1445
- this.configureTimeout = setTimeout(async () => {
1446
- for (const plugin of this.plugins.array()) {
1447
- if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1487
+ for (const plugin of this.plugins) {
1488
+ if (!plugin.enabled)
1448
1489
  continue;
1449
- try {
1450
- if ((await this.plugins.configure(plugin)) === undefined) {
1451
- if (plugin.configured !== true)
1452
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1490
+ if (plugin.error) {
1491
+ clearInterval(this.startMatterInterval);
1492
+ this.startMatterInterval = undefined;
1493
+ this.log.debug('Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1494
+ this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1495
+ this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1496
+ this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1497
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1498
+ this.frontend.wssSendSnackbarMessage(`The bridge is offline. Startup halted due to plugin errors.`, 0, 'error');
1499
+ this.frontend.wssSendRefreshRequired('plugins');
1500
+ this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1501
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'error') } });
1502
+ return;
1503
+ }
1504
+ this.sendPluginStatusUpdate(plugin);
1505
+ if (!plugin.loaded || !plugin.started) {
1506
+ this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) in startMatterInterval interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1507
+ failCount++;
1508
+ if (failCount > this.failCountLimit) {
1509
+ this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1510
+ plugin.error = true;
1453
1511
  }
1512
+ return;
1454
1513
  }
1455
- catch (error) {
1456
- plugin.error = true;
1457
- this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1514
+ }
1515
+ clearInterval(this.startMatterInterval);
1516
+ this.startMatterInterval = undefined;
1517
+ this.log.debug('Cleared startMatterInterval interval in bridge mode');
1518
+ fireAndForget(this.startServerNode(this.serverNode), this.log, 'Start server node');
1519
+ for (const device of this.devices.array()) {
1520
+ if (device.mode === 'server' && device.serverNode) {
1521
+ this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1522
+ fireAndForget(this.startServerNode(device.serverNode), this.log, `Start server node for device ${device.deviceName}`);
1458
1523
  }
1459
1524
  }
1460
- this.frontend.wssSendRefreshRequired('plugins');
1461
- }, 30 * 1000).unref();
1462
- this.reachabilityTimeout = setTimeout(() => {
1463
- this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1464
- if (this.aggregatorNode)
1465
- fireAndForget(this.setAggregatorReachability(this.aggregatorNode, true), this.log, `Set aggregator node reachability for Matterbridge`);
1466
- }, 60 * 1000).unref();
1467
- this.emit('bridge_started');
1468
- this.log.notice('Matterbridge bridge started successfully');
1469
- this.frontend.wssSendRefreshRequired('settings');
1470
- this.frontend.wssSendRefreshRequired('plugins');
1471
- this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1525
+ this.configureTimeout = setTimeout(() => {
1526
+ void (async () => {
1527
+ for (const plugin of this.plugins.array()) {
1528
+ if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1529
+ continue;
1530
+ try {
1531
+ if ((await this.plugins.configure(plugin)) === undefined) {
1532
+ if (plugin.configured !== true)
1533
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1534
+ }
1535
+ }
1536
+ catch (error) {
1537
+ plugin.error = true;
1538
+ this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1539
+ }
1540
+ this.sendPluginStatusUpdate(plugin);
1541
+ }
1542
+ })();
1543
+ }, 30 * 1000).unref();
1544
+ this.reachabilityTimeout = setTimeout(() => {
1545
+ this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1546
+ if (this.aggregatorNode)
1547
+ fireAndForget(this.setAggregatorReachability(this.aggregatorNode, true), this.log, `Failed to set aggregator node reachability for Matterbridge`);
1548
+ }, 60 * 1000).unref();
1549
+ this.emit('bridge_started');
1550
+ this.log.notice('Matterbridge bridge started successfully');
1551
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'started') } });
1552
+ this.server.request({ type: 'frontend_refreshrequired', src: 'matterbridge', dst: 'frontend', params: { changed: 'plugins' } });
1553
+ this.server.request({ type: 'frontend_closesnackbarmessage', src: 'matterbridge', dst: 'frontend', params: { message: `The bridge is starting...` } });
1554
+ })();
1472
1555
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1473
1556
  }
1474
1557
  async startChildbridge(delay = 1000) {
1475
1558
  if (!this.matterStorageManager)
1476
1559
  throw new Error('No storage manager initialized');
1477
1560
  const { wait } = await import('@matterbridge/utils/wait');
1561
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'starting') } });
1478
1562
  this.log.debug('Loading all plugins in childbridge mode...');
1479
1563
  await this.startPlugins(true, false);
1480
1564
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
@@ -1486,100 +1570,105 @@ export class Matterbridge extends EventEmitter {
1486
1570
  this.log.debug('Starting start matter interval in childbridge mode...');
1487
1571
  this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
1488
1572
  let failCount = 0;
1489
- this.startMatterInterval = setInterval(async () => {
1490
- if (failCount && failCount % 10 === 0) {
1491
- this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1492
- this.frontend.wssSendRefreshRequired('plugins');
1493
- }
1494
- let allStarted = true;
1495
- for (const plugin of this.plugins.array()) {
1496
- if (!plugin.enabled)
1497
- continue;
1498
- if (plugin.error) {
1499
- clearInterval(this.startMatterInterval);
1500
- this.startMatterInterval = undefined;
1501
- this.log.debug('Cleared startMatterInterval interval for a plugin in error state');
1502
- this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1503
- this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1504
- this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1505
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1506
- this.frontend.wssSendSnackbarMessage(`The bridge is offline. Startup halted due to plugin errors.`, 0, 'error');
1507
- this.frontend.wssSendRefreshRequired('plugins');
1508
- this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1509
- return;
1573
+ this.startMatterInterval = setInterval(() => {
1574
+ void (async () => {
1575
+ if (failCount && failCount % 10 === 0) {
1576
+ this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1510
1577
  }
1511
- this.log.debug(`Checking plugin ${plg}${plugin.name}${db} to start matter in childbridge mode...`);
1512
- if (!plugin.loaded || !plugin.started) {
1513
- allStarted = false;
1514
- this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1515
- failCount++;
1516
- if (failCount > this.failCountLimit) {
1517
- this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1518
- plugin.error = true;
1578
+ let allStarted = true;
1579
+ for (const plugin of this.plugins.array()) {
1580
+ if (!plugin.enabled)
1581
+ continue;
1582
+ if (plugin.error) {
1583
+ clearInterval(this.startMatterInterval);
1584
+ this.startMatterInterval = undefined;
1585
+ this.log.debug('Cleared startMatterInterval interval for a plugin in error state');
1586
+ this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1587
+ this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1588
+ this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1589
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1590
+ this.frontend.wssSendSnackbarMessage(`The bridge is offline. Startup halted due to plugin errors.`, 0, 'error');
1591
+ this.frontend.wssSendRefreshRequired('plugins');
1592
+ this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1593
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'error') } });
1594
+ return;
1595
+ }
1596
+ this.sendPluginStatusUpdate(plugin);
1597
+ this.log.debug(`Checking plugin ${plg}${plugin.name}${db} to start matter in childbridge mode...`);
1598
+ if (!plugin.loaded || !plugin.started) {
1599
+ allStarted = false;
1600
+ this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1601
+ failCount++;
1602
+ if (failCount > this.failCountLimit) {
1603
+ this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1604
+ plugin.error = true;
1605
+ }
1519
1606
  }
1520
1607
  }
1521
- }
1522
- if (!allStarted)
1523
- return;
1524
- clearInterval(this.startMatterInterval);
1525
- this.startMatterInterval = undefined;
1526
- if (delay > 0)
1527
- await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1528
- this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1529
- this.configureTimeout = setTimeout(async () => {
1608
+ if (!allStarted)
1609
+ return;
1610
+ clearInterval(this.startMatterInterval);
1611
+ this.startMatterInterval = undefined;
1612
+ if (delay > 0)
1613
+ await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1614
+ this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1615
+ this.configureTimeout = setTimeout(() => {
1616
+ void (async () => {
1617
+ for (const plugin of this.plugins.array()) {
1618
+ if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1619
+ continue;
1620
+ try {
1621
+ if ((await this.plugins.configure(plugin)) === undefined) {
1622
+ if (plugin.configured !== true)
1623
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1624
+ }
1625
+ }
1626
+ catch (error) {
1627
+ plugin.error = true;
1628
+ this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1629
+ }
1630
+ this.sendPluginStatusUpdate(plugin);
1631
+ }
1632
+ })();
1633
+ }, 30 * 1000).unref();
1530
1634
  for (const plugin of this.plugins.array()) {
1531
- if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1635
+ if (!plugin.enabled || plugin.error)
1636
+ continue;
1637
+ if (plugin.type !== 'DynamicPlatform' && (!plugin.registeredDevices || plugin.registeredDevices === 0)) {
1638
+ this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1532
1639
  continue;
1533
- try {
1534
- if ((await this.plugins.configure(plugin)) === undefined) {
1535
- if (plugin.configured !== true)
1536
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1537
- }
1538
1640
  }
1539
- catch (error) {
1540
- plugin.error = true;
1541
- this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1641
+ if (!plugin.serverNode) {
1642
+ this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1643
+ continue;
1542
1644
  }
1645
+ if (!plugin.storageContext) {
1646
+ this.log.error(`Storage context not found for plugin ${plg}${plugin.name}${er}`);
1647
+ continue;
1648
+ }
1649
+ if (!plugin.nodeContext) {
1650
+ this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1651
+ continue;
1652
+ }
1653
+ fireAndForget(this.startServerNode(plugin.serverNode), this.log, `Start server node for plugin ${plugin.name}`);
1654
+ plugin.reachabilityTimeout = setTimeout(() => {
1655
+ this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1656
+ if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1657
+ fireAndForget(this.setAggregatorReachability(plugin.aggregatorNode, true), this.log, `Set aggregator node reachability for plugin ${plugin.name}`);
1658
+ }, 60 * 1000).unref();
1543
1659
  }
1544
- this.frontend.wssSendRefreshRequired('plugins');
1545
- }, 30 * 1000).unref();
1546
- for (const plugin of this.plugins.array()) {
1547
- if (!plugin.enabled || plugin.error)
1548
- continue;
1549
- if (plugin.type !== 'DynamicPlatform' && (!plugin.registeredDevices || plugin.registeredDevices === 0)) {
1550
- this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1551
- continue;
1552
- }
1553
- if (!plugin.serverNode) {
1554
- this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1555
- continue;
1556
- }
1557
- if (!plugin.storageContext) {
1558
- this.log.error(`Storage context not found for plugin ${plg}${plugin.name}${er}`);
1559
- continue;
1560
- }
1561
- if (!plugin.nodeContext) {
1562
- this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1563
- continue;
1564
- }
1565
- fireAndForget(this.startServerNode(plugin.serverNode), this.log, `Start server node for plugin ${plugin.name}`);
1566
- plugin.reachabilityTimeout = setTimeout(() => {
1567
- this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1568
- if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1569
- fireAndForget(this.setAggregatorReachability(plugin.aggregatorNode, true), this.log, `Set aggregator node reachability for plugin ${plugin.name}`);
1570
- }, 60 * 1000).unref();
1571
- }
1572
- for (const device of this.devices.array()) {
1573
- if (device.mode === 'server' && device.serverNode) {
1574
- this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1575
- fireAndForget(this.startServerNode(device.serverNode), this.log, `Start server node for device ${device.deviceName}`);
1660
+ for (const device of this.devices.array()) {
1661
+ if (device.mode === 'server' && device.serverNode) {
1662
+ this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1663
+ fireAndForget(this.startServerNode(device.serverNode), this.log, `Start server node for device ${device.deviceName}`);
1664
+ }
1576
1665
  }
1577
- }
1578
- this.emit('childbridge_started');
1579
- this.log.notice('Matterbridge childbridge started successfully');
1580
- this.frontend.wssSendRefreshRequired('settings');
1581
- this.frontend.wssSendRefreshRequired('plugins');
1582
- this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1666
+ this.emit('childbridge_started');
1667
+ this.log.notice('Matterbridge childbridge started successfully');
1668
+ this.server.request({ type: 'frontend_matterbridgestatusupdate', src: 'matterbridge', dst: 'frontend', params: { status: (this.bridgeStatus = 'started') } });
1669
+ this.server.request({ type: 'frontend_refreshrequired', src: 'matterbridge', dst: 'frontend', params: { changed: 'plugins' } });
1670
+ this.server.request({ type: 'frontend_closesnackbarmessage', src: 'matterbridge', dst: 'frontend', params: { message: `The bridge is starting...` } });
1671
+ })();
1583
1672
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1584
1673
  }
1585
1674
  async startController() {
@@ -1732,9 +1821,13 @@ export class Matterbridge extends EventEmitter {
1732
1821
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1733
1822
  this.frontend.wssSendSnackbarMessage(`Server node for ${storeId} fully decommissioned successfully!`, 5, 'success');
1734
1823
  });
1735
- serverNode.lifecycle.online.on(async () => {
1824
+ serverNode.lifecycle.online.on(() => {
1736
1825
  this.log.notice(`Server node for ${storeId} is online`);
1737
- if (!serverNode.lifecycle.isCommissioned) {
1826
+ if (serverNode.lifecycle.isCommissioned) {
1827
+ this.log.notice(`Server node for ${storeId} is already commissioned.`);
1828
+ this.advertisingNodes.delete(storeId);
1829
+ }
1830
+ else {
1738
1831
  this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission.`);
1739
1832
  this.advertisingNodes.set(storeId, Date.now());
1740
1833
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
@@ -1742,10 +1835,6 @@ export class Matterbridge extends EventEmitter {
1742
1835
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1743
1836
  this.log.notice(`Manual pairing code ${CYAN}${manualPairingCode}${nt} discriminator ${CYAN}${discriminator}${nt} short discriminator ${CYAN}${pairingData.shortDiscriminator}${nt} passcode ${CYAN}${passcode}${nt}`);
1744
1837
  }
1745
- else {
1746
- this.log.notice(`Server node for ${storeId} is already commissioned.`);
1747
- this.advertisingNodes.delete(storeId);
1748
- }
1749
1838
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1750
1839
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1751
1840
  this.emit('online', storeId);
@@ -1790,7 +1879,7 @@ export class Matterbridge extends EventEmitter {
1790
1879
  return serverNode;
1791
1880
  }
1792
1881
  getServerNodeData(serverNode) {
1793
- const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1882
+ const advertiseTime = this.advertisingNodes.get(serverNode.id) ?? 0;
1794
1883
  return {
1795
1884
  id: serverNode.id,
1796
1885
  online: serverNode.lifecycle.isOnline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.9.2-dev-20260620-9802e5b",
3
+ "version": "3.9.2-dev-20260621-75283ba",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -130,16 +130,16 @@
130
130
  ],
131
131
  "dependencies": {
132
132
  "@matter/main": "0.17.3",
133
- "@matterbridge/dgram": "3.9.2-dev-20260620-9802e5b",
134
- "@matterbridge/thread": "3.9.2-dev-20260620-9802e5b",
135
- "@matterbridge/types": "3.9.2-dev-20260620-9802e5b",
136
- "@matterbridge/utils": "3.9.2-dev-20260620-9802e5b",
133
+ "@matterbridge/dgram": "3.9.2-dev-20260621-75283ba",
134
+ "@matterbridge/thread": "3.9.2-dev-20260621-75283ba",
135
+ "@matterbridge/types": "3.9.2-dev-20260621-75283ba",
136
+ "@matterbridge/utils": "3.9.2-dev-20260621-75283ba",
137
137
  "escape-html": "1.0.3",
138
138
  "express": "5.2.1",
139
139
  "express-rate-limit": "8.5.2",
140
140
  "multer": "2.2.0",
141
- "node-ansi-logger": "3.3.0-dev-20260617-6ebcff7",
142
- "node-persist-manager": "2.1.0-dev-20260617-3224082",
141
+ "node-ansi-logger": "3.3.0",
142
+ "node-persist-manager": "2.1.0",
143
143
  "ws": "8.21.0"
144
144
  }
145
145
  }