matterbridge 2.2.1 → 2.2.2-dev.2

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cli.js +2 -37
  3. package/dist/cluster/export.js +0 -2
  4. package/dist/defaultConfigSchema.js +0 -23
  5. package/dist/deviceManager.js +1 -94
  6. package/dist/frontend.js +54 -294
  7. package/dist/index.js +1 -28
  8. package/dist/logger/export.js +0 -1
  9. package/dist/matter/behaviors.js +0 -2
  10. package/dist/matter/clusters.js +0 -2
  11. package/dist/matter/devices.js +0 -2
  12. package/dist/matter/endpoints.js +0 -2
  13. package/dist/matter/export.js +0 -2
  14. package/dist/matter/types.js +0 -2
  15. package/dist/matterbridge.js +75 -737
  16. package/dist/matterbridgeAccessoryPlatform.js +0 -33
  17. package/dist/matterbridgeBehaviors.js +1 -32
  18. package/dist/matterbridgeDeviceTypes.js +11 -112
  19. package/dist/matterbridgeDynamicPlatform.js +0 -33
  20. package/dist/matterbridgeEndpoint.js +6 -690
  21. package/dist/matterbridgeEndpointHelpers.js +9 -118
  22. package/dist/matterbridgePlatform.js +7 -185
  23. package/dist/matterbridgeTypes.js +0 -24
  24. package/dist/pluginManager.js +3 -229
  25. package/dist/shelly.js +6 -121
  26. package/dist/storage/export.js +0 -1
  27. package/dist/update.js +3 -47
  28. package/dist/utils/colorUtils.js +2 -205
  29. package/dist/utils/copyDirectory.js +1 -37
  30. package/dist/utils/createZip.js +2 -42
  31. package/dist/utils/deepCopy.js +0 -40
  32. package/dist/utils/deepEqual.js +1 -65
  33. package/dist/utils/export.js +0 -1
  34. package/dist/utils/isvalid.js +0 -86
  35. package/dist/utils/network.js +5 -77
  36. package/dist/utils/parameter.js +0 -41
  37. package/dist/utils/wait.js +5 -48
  38. package/frontend/build/asset-manifest.json +6 -6
  39. package/frontend/build/index.html +1 -1
  40. package/frontend/build/static/css/{main.cf25d33e.css → main.b9449869.css} +2 -2
  41. package/frontend/build/static/css/{main.cf25d33e.css.map → main.b9449869.css.map} +1 -1
  42. package/frontend/build/static/js/{main.025c65d2.js → main.eb443b23.js} +8 -8
  43. package/frontend/build/static/js/{main.025c65d2.js.map → main.eb443b23.js.map} +1 -1
  44. package/npm-shrinkwrap.json +7 -7
  45. package/package.json +1 -2
  46. package/dist/cli.d.ts +0 -29
  47. package/dist/cli.d.ts.map +0 -1
  48. package/dist/cli.js.map +0 -1
  49. package/dist/cluster/export.d.ts +0 -2
  50. package/dist/cluster/export.d.ts.map +0 -1
  51. package/dist/cluster/export.js.map +0 -1
  52. package/dist/defaultConfigSchema.d.ts +0 -27
  53. package/dist/defaultConfigSchema.d.ts.map +0 -1
  54. package/dist/defaultConfigSchema.js.map +0 -1
  55. package/dist/deviceManager.d.ts +0 -114
  56. package/dist/deviceManager.d.ts.map +0 -1
  57. package/dist/deviceManager.js.map +0 -1
  58. package/dist/frontend.d.ts +0 -178
  59. package/dist/frontend.d.ts.map +0 -1
  60. package/dist/frontend.js.map +0 -1
  61. package/dist/index.d.ts +0 -35
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/logger/export.d.ts +0 -2
  65. package/dist/logger/export.d.ts.map +0 -1
  66. package/dist/logger/export.js.map +0 -1
  67. package/dist/matter/behaviors.d.ts +0 -2
  68. package/dist/matter/behaviors.d.ts.map +0 -1
  69. package/dist/matter/behaviors.js.map +0 -1
  70. package/dist/matter/clusters.d.ts +0 -2
  71. package/dist/matter/clusters.d.ts.map +0 -1
  72. package/dist/matter/clusters.js.map +0 -1
  73. package/dist/matter/devices.d.ts +0 -2
  74. package/dist/matter/devices.d.ts.map +0 -1
  75. package/dist/matter/devices.js.map +0 -1
  76. package/dist/matter/endpoints.d.ts +0 -2
  77. package/dist/matter/endpoints.d.ts.map +0 -1
  78. package/dist/matter/endpoints.js.map +0 -1
  79. package/dist/matter/export.d.ts +0 -5
  80. package/dist/matter/export.d.ts.map +0 -1
  81. package/dist/matter/export.js.map +0 -1
  82. package/dist/matter/types.d.ts +0 -3
  83. package/dist/matter/types.d.ts.map +0 -1
  84. package/dist/matter/types.js.map +0 -1
  85. package/dist/matterbridge.d.ts +0 -411
  86. package/dist/matterbridge.d.ts.map +0 -1
  87. package/dist/matterbridge.js.map +0 -1
  88. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
  89. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  90. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  91. package/dist/matterbridgeBehaviors.d.ts +0 -1056
  92. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  93. package/dist/matterbridgeBehaviors.js.map +0 -1
  94. package/dist/matterbridgeDeviceTypes.d.ts +0 -177
  95. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  96. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  97. package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
  98. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  99. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  100. package/dist/matterbridgeEndpoint.d.ts +0 -835
  101. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  102. package/dist/matterbridgeEndpoint.js.map +0 -1
  103. package/dist/matterbridgeEndpointHelpers.d.ts +0 -2275
  104. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  105. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  106. package/dist/matterbridgePlatform.d.ts +0 -251
  107. package/dist/matterbridgePlatform.d.ts.map +0 -1
  108. package/dist/matterbridgePlatform.js.map +0 -1
  109. package/dist/matterbridgeTypes.d.ts +0 -177
  110. package/dist/matterbridgeTypes.d.ts.map +0 -1
  111. package/dist/matterbridgeTypes.js.map +0 -1
  112. package/dist/pluginManager.d.ts +0 -236
  113. package/dist/pluginManager.d.ts.map +0 -1
  114. package/dist/pluginManager.js.map +0 -1
  115. package/dist/shelly.d.ts +0 -77
  116. package/dist/shelly.d.ts.map +0 -1
  117. package/dist/shelly.js.map +0 -1
  118. package/dist/storage/export.d.ts +0 -2
  119. package/dist/storage/export.d.ts.map +0 -1
  120. package/dist/storage/export.js.map +0 -1
  121. package/dist/update.d.ts +0 -32
  122. package/dist/update.d.ts.map +0 -1
  123. package/dist/update.js.map +0 -1
  124. package/dist/utils/colorUtils.d.ts +0 -61
  125. package/dist/utils/colorUtils.d.ts.map +0 -1
  126. package/dist/utils/colorUtils.js.map +0 -1
  127. package/dist/utils/copyDirectory.d.ts +0 -32
  128. package/dist/utils/copyDirectory.d.ts.map +0 -1
  129. package/dist/utils/copyDirectory.js.map +0 -1
  130. package/dist/utils/createZip.d.ts +0 -38
  131. package/dist/utils/createZip.d.ts.map +0 -1
  132. package/dist/utils/createZip.js.map +0 -1
  133. package/dist/utils/deepCopy.d.ts +0 -31
  134. package/dist/utils/deepCopy.d.ts.map +0 -1
  135. package/dist/utils/deepCopy.js.map +0 -1
  136. package/dist/utils/deepEqual.d.ts +0 -53
  137. package/dist/utils/deepEqual.d.ts.map +0 -1
  138. package/dist/utils/deepEqual.js.map +0 -1
  139. package/dist/utils/export.d.ts +0 -10
  140. package/dist/utils/export.d.ts.map +0 -1
  141. package/dist/utils/export.js.map +0 -1
  142. package/dist/utils/isvalid.d.ts +0 -87
  143. package/dist/utils/isvalid.d.ts.map +0 -1
  144. package/dist/utils/isvalid.js.map +0 -1
  145. package/dist/utils/network.d.ts +0 -70
  146. package/dist/utils/network.d.ts.map +0 -1
  147. package/dist/utils/network.js.map +0 -1
  148. package/dist/utils/parameter.d.ts +0 -44
  149. package/dist/utils/parameter.d.ts.map +0 -1
  150. package/dist/utils/parameter.js.map +0 -1
  151. package/dist/utils/wait.d.ts +0 -43
  152. package/dist/utils/wait.d.ts.map +0 -1
  153. package/dist/utils/wait.js.map +0 -1
  154. /package/frontend/build/static/js/{main.025c65d2.js.LICENSE.txt → main.eb443b23.js.LICENSE.txt} +0 -0
@@ -1,35 +1,9 @@
1
- /**
2
- * This file contains the class Matterbridge.
3
- *
4
- * @file matterbridge.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 1.5.2
8
- *
9
- * Copyright 2023, 2024, 2025 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- // Node.js modules
24
1
  import os from 'node:os';
25
2
  import path from 'node:path';
26
3
  import { promises as fs } from 'node:fs';
27
4
  import EventEmitter from 'node:events';
28
- // AnsiLogger module
29
5
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
30
- // NodeStorage module
31
6
  import { NodeStorageManager } from './storage/export.js';
32
- // Matterbridge
33
7
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
34
8
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
35
9
  import { PluginManager } from './pluginManager.js';
@@ -37,18 +11,13 @@ import { DeviceManager } from './deviceManager.js';
37
11
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
38
12
  import { bridge } from './matterbridgeDeviceTypes.js';
39
13
  import { Frontend } from './frontend.js';
40
- // @matter
41
14
  import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
42
15
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
43
16
  import { AggregatorEndpoint } from '@matter/main/endpoints';
44
17
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
45
- // Default colors
46
18
  const plg = '\u001B[38;5;33m';
47
19
  const dev = '\u001B[38;5;79m';
48
20
  const typ = '\u001B[38;5;207m';
49
- /**
50
- * Represents the Matterbridge application.
51
- */
52
21
  export class Matterbridge extends EventEmitter {
53
22
  systemInformation = {
54
23
  interfaceName: '',
@@ -92,7 +61,7 @@ export class Matterbridge extends EventEmitter {
92
61
  shellySysUpdate: false,
93
62
  shellyMainUpdate: false,
94
63
  profile: getParameter('profile'),
95
- loggerLevel: "info" /* LogLevel.INFO */,
64
+ loggerLevel: "info",
96
65
  fileLogger: false,
97
66
  matterLoggerLevel: MatterLogLevel.INFO,
98
67
  matterFileLogger: false,
@@ -103,7 +72,7 @@ export class Matterbridge extends EventEmitter {
103
72
  matterDiscriminator: undefined,
104
73
  matterPasscode: undefined,
105
74
  restartRequired: false,
106
- refreshRequired: false,
75
+ updateRequired: false,
107
76
  };
108
77
  homeDirectory = '';
109
78
  rootDirectory = '';
@@ -122,17 +91,16 @@ export class Matterbridge extends EventEmitter {
122
91
  profile = getParameter('profile');
123
92
  shutdown = false;
124
93
  edge = true;
94
+ failCountLimit = hasParameter('shelly') ? 120 : 60;
125
95
  log;
126
96
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
127
97
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
128
98
  plugins;
129
99
  devices;
130
100
  frontend = new Frontend(this);
131
- // Matterbridge storage
132
101
  nodeStorage;
133
102
  nodeContext;
134
103
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
135
- // Cleanup
136
104
  hasCleanupStarted = false;
137
105
  initialized = false;
138
106
  execRunningCount = 0;
@@ -145,70 +113,38 @@ export class Matterbridge extends EventEmitter {
145
113
  sigtermHandler;
146
114
  exceptionHandler;
147
115
  rejectionHandler;
148
- // Matter environment
149
116
  environment = Environment.default;
150
- // Matter storage
151
117
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
152
118
  matterStorageService;
153
119
  matterStorageManager;
154
120
  matterbridgeContext;
155
121
  mattercontrollerContext;
156
- // Matter parameters
157
- mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
158
- ipv4address; // matter server node listeningAddressIpv4
159
- ipv6address; // matter server node listeningAddressIpv6
160
- port; // first server node port
161
- passcode; // first server node passcode
162
- discriminator; // first server node discriminator
122
+ mdnsInterface;
123
+ ipv4address;
124
+ ipv6address;
125
+ port;
126
+ passcode;
127
+ discriminator;
163
128
  serverNode;
164
129
  aggregatorNode;
165
130
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
166
131
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
167
132
  static instance;
168
- // We load asyncronously so is private
169
133
  constructor() {
170
134
  super();
171
135
  }
172
- /**
173
- * Emits an event of the specified type with the provided arguments.
174
- *
175
- * @template K - The type of the event.
176
- * @param {K} eventName - The name of the event to emit.
177
- * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
178
- * @returns {boolean} - Returns true if the event had listeners, false otherwise.
179
- */
180
136
  emit(eventName, ...args) {
181
137
  return super.emit(eventName, ...args);
182
138
  }
183
- /**
184
- * Registers an event listener for the specified event type.
185
- *
186
- * @template K - The type of the event.
187
- * @param {K} eventName - The name of the event to listen for.
188
- * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
189
- * @returns {this} - Returns the instance of the Matterbridge class.
190
- */
191
139
  on(eventName, listener) {
192
140
  return super.on(eventName, listener);
193
141
  }
194
- /**
195
- * Retrieves the list of Matterbridge devices.
196
- * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
197
- */
198
142
  getDevices() {
199
143
  return this.devices.array();
200
144
  }
201
- /**
202
- * Retrieves the list of registered plugins.
203
- * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
204
- */
205
145
  getPlugins() {
206
146
  return this.plugins.array();
207
147
  }
208
- /**
209
- * Set the logger logLevel for the Matterbridge classes.
210
- * @param {LogLevel} logLevel The logger logLevel to set.
211
- */
212
148
  async setLogLevel(logLevel) {
213
149
  if (this.log)
214
150
  this.log.logLevel = logLevel;
@@ -222,31 +158,19 @@ export class Matterbridge extends EventEmitter {
222
158
  for (const plugin of this.plugins) {
223
159
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
224
160
  continue;
225
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
226
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
227
- }
228
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
229
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
230
- if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
231
- callbackLogLevel = "info" /* LogLevel.INFO */;
232
- if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
233
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
161
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
162
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
163
+ }
164
+ let callbackLogLevel = "notice";
165
+ if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
166
+ callbackLogLevel = "info";
167
+ if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
168
+ callbackLogLevel = "debug";
234
169
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
235
170
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
236
171
  }
237
- /** ***********************************************************************************************************************************/
238
- /** loadInstance() and cleanup() methods */
239
- /** ***********************************************************************************************************************************/
240
- /**
241
- * Loads an instance of the Matterbridge class.
242
- * If an instance already exists, return that instance.
243
- *
244
- * @param initialize - Whether to initialize the Matterbridge instance after loading.
245
- * @returns The loaded Matterbridge instance.
246
- */
247
172
  static async loadInstance(initialize = false) {
248
173
  if (!Matterbridge.instance) {
249
- // eslint-disable-next-line no-console
250
174
  if (hasParameter('debug'))
251
175
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
252
176
  Matterbridge.instance = new Matterbridge();
@@ -255,14 +179,8 @@ export class Matterbridge extends EventEmitter {
255
179
  }
256
180
  return Matterbridge.instance;
257
181
  }
258
- /**
259
- * Call cleanup().
260
- * @deprecated This method is deprecated and is only used for jest tests.
261
- *
262
- */
263
182
  async destroyInstance() {
264
183
  this.log.info(`Destroy instance...`);
265
- // Save server nodes to close
266
184
  const servers = [];
267
185
  if (this.bridgeMode === 'bridge') {
268
186
  if (this.serverNode)
@@ -274,81 +192,55 @@ export class Matterbridge extends EventEmitter {
274
192
  servers.push(plugin.serverNode);
275
193
  }
276
194
  }
277
- // Cleanup
278
195
  await this.cleanup('destroying instance...', false);
279
- // Close servers mdns service
280
196
  this.log.info(`Dispose ${servers.length} MdnsService...`);
281
197
  for (const server of servers) {
282
198
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
283
199
  this.log.info(`Closed ${server.id} MdnsService`);
284
200
  }
285
- // Wait for the cleanup to finish
286
201
  await new Promise((resolve) => {
287
202
  setTimeout(resolve, 1000);
288
203
  });
289
204
  }
290
- /**
291
- * Initializes the Matterbridge application.
292
- *
293
- * @remarks
294
- * This method performs the necessary setup and initialization steps for the Matterbridge application.
295
- * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
296
- * node version, registers signal handlers, initializes storage, and parses the command line.
297
- *
298
- * @returns A Promise that resolves when the initialization is complete.
299
- */
300
205
  async initialize() {
301
- // Set the restart mode
302
206
  if (hasParameter('service'))
303
207
  this.restartMode = 'service';
304
208
  if (hasParameter('docker'))
305
209
  this.restartMode = 'docker';
306
- // Set the matterbridge directory
307
210
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
308
211
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
309
- // Setup the matter environment
310
212
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
311
213
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
312
214
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
313
215
  this.environment.vars.set('runtime.signals', false);
314
216
  this.environment.vars.set('runtime.exitcode', false);
315
- // Create the matterbridge logger
316
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
317
- // Register process handlers
217
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
318
218
  this.registerProcessHandlers();
319
- // Initialize nodeStorage and nodeContext
320
219
  try {
321
220
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
322
221
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
323
222
  this.log.debug('Creating node storage context for matterbridge');
324
223
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
325
- // TODO: Remove this code when node-persist-manager is updated
326
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
224
  const keys = (await this.nodeStorage?.storage.keys());
328
225
  for (const key of keys) {
329
226
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
330
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
227
  await this.nodeStorage?.storage.get(key);
332
228
  }
333
229
  const storages = await this.nodeStorage.getStorageNames();
334
230
  for (const storage of storages) {
335
231
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
336
232
  const nodeContext = await this.nodeStorage?.createStorage(storage);
337
- // TODO: Remove this code when node-persist-manager is updated
338
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
339
233
  const keys = (await nodeContext?.storage.keys());
340
234
  keys.forEach(async (key) => {
341
235
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
342
236
  await nodeContext?.get(key);
343
237
  });
344
238
  }
345
- // Creating a backup of the node storage since it is not corrupted
346
239
  this.log.debug('Creating node storage backup...');
347
240
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
348
241
  this.log.debug('Created node storage backup');
349
242
  }
350
243
  catch (error) {
351
- // Restoring the backup of the node storage since it is corrupted
352
244
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
353
245
  if (hasParameter('norestore')) {
354
246
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -363,46 +255,41 @@ export class Matterbridge extends EventEmitter {
363
255
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
364
256
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
365
257
  }
366
- // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
367
258
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
368
- // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
369
259
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
370
- // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
371
260
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
372
261
  this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
373
- // Set matterbridge logger level (context: matterbridgeLogLevel)
374
262
  if (hasParameter('logger')) {
375
263
  const level = getParameter('logger');
376
264
  if (level === 'debug') {
377
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
265
+ this.log.logLevel = "debug";
378
266
  }
379
267
  else if (level === 'info') {
380
- this.log.logLevel = "info" /* LogLevel.INFO */;
268
+ this.log.logLevel = "info";
381
269
  }
382
270
  else if (level === 'notice') {
383
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
271
+ this.log.logLevel = "notice";
384
272
  }
385
273
  else if (level === 'warn') {
386
- this.log.logLevel = "warn" /* LogLevel.WARN */;
274
+ this.log.logLevel = "warn";
387
275
  }
388
276
  else if (level === 'error') {
389
- this.log.logLevel = "error" /* LogLevel.ERROR */;
277
+ this.log.logLevel = "error";
390
278
  }
391
279
  else if (level === 'fatal') {
392
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
280
+ this.log.logLevel = "fatal";
393
281
  }
394
282
  else {
395
283
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
396
- this.log.logLevel = "info" /* LogLevel.INFO */;
284
+ this.log.logLevel = "info";
397
285
  }
398
286
  }
399
287
  else {
400
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
288
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
401
289
  }
402
290
  this.frontend.logLevel = this.log.logLevel;
403
291
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
404
292
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
405
- // Create the file logger for matterbridge (context: matterbridgeFileLog)
406
293
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
407
294
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
408
295
  this.matterbridgeInformation.fileLogger = true;
@@ -411,7 +298,6 @@ export class Matterbridge extends EventEmitter {
411
298
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
412
299
  if (this.profile !== undefined)
413
300
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
414
- // Set matter.js logger level, format and logger (context: matterLogLevel)
415
301
  if (hasParameter('matterlogger')) {
416
302
  const level = getParameter('matterlogger');
417
303
  if (level === 'debug') {
@@ -443,7 +329,6 @@ export class Matterbridge extends EventEmitter {
443
329
  Logger.format = MatterLogFormat.ANSI;
444
330
  Logger.setLogger('default', this.createMatterLogger());
445
331
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
446
- // Create the file logger for matter.js (context: matterFileLog)
447
332
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
448
333
  this.matterbridgeInformation.matterFileLogger = true;
449
334
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -452,7 +337,6 @@ export class Matterbridge extends EventEmitter {
452
337
  });
453
338
  }
454
339
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
455
- // Set the interface to use for matter server node mdnsInterface
456
340
  if (hasParameter('mdnsinterface')) {
457
341
  this.mdnsInterface = getParameter('mdnsinterface');
458
342
  }
@@ -461,7 +345,6 @@ export class Matterbridge extends EventEmitter {
461
345
  if (this.mdnsInterface === '')
462
346
  this.mdnsInterface = undefined;
463
347
  }
464
- // Validate mdnsInterface
465
348
  if (this.mdnsInterface) {
466
349
  const networkInterfaces = os.networkInterfaces();
467
350
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -475,7 +358,6 @@ export class Matterbridge extends EventEmitter {
475
358
  }
476
359
  if (this.mdnsInterface)
477
360
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
478
- // Set the listeningAddressIpv4 for the matter commissioning server
479
361
  if (hasParameter('ipv4address')) {
480
362
  this.ipv4address = getParameter('ipv4address');
481
363
  }
@@ -484,7 +366,6 @@ export class Matterbridge extends EventEmitter {
484
366
  if (this.ipv4address === '')
485
367
  this.ipv4address = undefined;
486
368
  }
487
- // Set the listeningAddressIpv6 for the matter commissioning server
488
369
  if (hasParameter('ipv6address')) {
489
370
  this.ipv6address = getParameter('ipv6address');
490
371
  }
@@ -493,19 +374,14 @@ export class Matterbridge extends EventEmitter {
493
374
  if (this.ipv6address === '')
494
375
  this.ipv6address = undefined;
495
376
  }
496
- // Initialize PluginManager
497
377
  this.plugins = new PluginManager(this);
498
378
  await this.plugins.loadFromStorage();
499
379
  this.plugins.logLevel = this.log.logLevel;
500
- // Initialize DeviceManager
501
380
  this.devices = new DeviceManager(this, this.nodeContext);
502
381
  this.devices.logLevel = this.log.logLevel;
503
- // Get the plugins from node storage and create the plugins node storage contexts
504
382
  for (const plugin of this.plugins) {
505
383
  const packageJson = await this.plugins.parse(plugin);
506
384
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
507
- // Try to reinstall the plugin from npm (for Docker pull and external plugins)
508
- // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
509
385
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
510
386
  try {
511
387
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -527,7 +403,6 @@ export class Matterbridge extends EventEmitter {
527
403
  await plugin.nodeContext.set('description', plugin.description);
528
404
  await plugin.nodeContext.set('author', plugin.author);
529
405
  }
530
- // Log system info and create .matterbridge directory
531
406
  await this.logNodeAndSystemInfo();
532
407
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
533
408
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -535,7 +410,6 @@ export class Matterbridge extends EventEmitter {
535
410
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
536
411
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
537
412
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
538
- // Check node version and throw error
539
413
  const minNodeVersion = 18;
540
414
  const nodeVersion = process.versions.node;
541
415
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -543,15 +417,9 @@ export class Matterbridge extends EventEmitter {
543
417
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
544
418
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
545
419
  }
546
- // Parse command line
547
420
  await this.parseCommandLine();
548
421
  this.initialized = true;
549
422
  }
550
- /**
551
- * Parses the command line arguments and performs the corresponding actions.
552
- * @private
553
- * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
554
- */
555
423
  async parseCommandLine() {
556
424
  if (hasParameter('help')) {
557
425
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -663,7 +531,6 @@ export class Matterbridge extends EventEmitter {
663
531
  this.shutdown = true;
664
532
  return;
665
533
  }
666
- // Start the matter storage and create the matterbridge context
667
534
  try {
668
535
  await this.startMatterStorage();
669
536
  }
@@ -671,14 +538,12 @@ export class Matterbridge extends EventEmitter {
671
538
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
672
539
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
673
540
  }
674
- // Clear the matterbridge context if the reset parameter is set
675
541
  if (hasParameter('reset') && getParameter('reset') === undefined) {
676
542
  this.initialized = true;
677
543
  await this.shutdownProcessAndReset();
678
544
  this.shutdown = true;
679
545
  return;
680
546
  }
681
- // Clear matterbridge plugin context if the reset parameter is set
682
547
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
683
548
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
684
549
  const plugin = this.plugins.get(getParameter('reset'));
@@ -703,37 +568,30 @@ export class Matterbridge extends EventEmitter {
703
568
  this.shutdown = true;
704
569
  return;
705
570
  }
706
- // Initialize frontend
707
571
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
708
572
  await this.frontend.start(getIntParameter('frontend'));
709
- // Check in 30 seconds the latest versions
710
573
  this.checkUpdateTimeout = setTimeout(async () => {
711
574
  const { checkUpdates } = await import('./update.js');
712
575
  checkUpdates(this);
713
576
  }, 30 * 1000).unref();
714
- // Check each 24 hours the latest versions
715
577
  this.checkUpdateInterval = setInterval(async () => {
716
578
  const { checkUpdates } = await import('./update.js');
717
579
  checkUpdates(this);
718
580
  }, 24 * 60 * 60 * 1000).unref();
719
- // Start the matterbridge in mode test
720
581
  if (hasParameter('test')) {
721
582
  this.bridgeMode = 'bridge';
722
583
  MatterbridgeEndpoint.bridgeMode = 'bridge';
723
584
  return;
724
585
  }
725
- // Start the matterbridge in mode controller
726
586
  if (hasParameter('controller')) {
727
587
  this.bridgeMode = 'controller';
728
588
  await this.startController();
729
589
  return;
730
590
  }
731
- // Check if the bridge mode is set and start matterbridge in bridge mode if not set
732
591
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
733
592
  this.log.info('Setting default matterbridge start mode to bridge');
734
593
  await this.nodeContext?.set('bridgeMode', 'bridge');
735
594
  }
736
- // Start matterbridge in bridge mode
737
595
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
738
596
  this.bridgeMode = 'bridge';
739
597
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -741,7 +599,6 @@ export class Matterbridge extends EventEmitter {
741
599
  await this.startBridge();
742
600
  return;
743
601
  }
744
- // Start matterbridge in childbridge mode
745
602
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
746
603
  this.bridgeMode = 'childbridge';
747
604
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -750,20 +607,10 @@ export class Matterbridge extends EventEmitter {
750
607
  return;
751
608
  }
752
609
  }
753
- /**
754
- * Asynchronously loads and starts the registered plugins.
755
- *
756
- * This method is responsible for initializing and staarting all enabled plugins.
757
- * It ensures that each plugin is properly loaded and started before the bridge starts.
758
- *
759
- * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
760
- */
761
610
  async startPlugins() {
762
- // Check, load and start the plugins
763
611
  for (const plugin of this.plugins) {
764
612
  plugin.configJson = await this.plugins.loadConfig(plugin);
765
613
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
766
- // Check if the plugin is available
767
614
  if (!(await this.plugins.resolve(plugin.path))) {
768
615
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
769
616
  plugin.enabled = false;
@@ -783,26 +630,20 @@ export class Matterbridge extends EventEmitter {
783
630
  plugin.addedDevices = undefined;
784
631
  plugin.qrPairingCode = undefined;
785
632
  plugin.manualPairingCode = undefined;
786
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
633
+ this.plugins.load(plugin, true, 'Matterbridge is starting');
787
634
  }
788
- this.frontend.wssSendRefreshRequired();
635
+ this.frontend.wssSendRefreshRequired('plugins');
789
636
  }
790
- /**
791
- * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
792
- * When either of these signals are received, the cleanup method is called with an appropriate message.
793
- */
794
637
  registerProcessHandlers() {
795
638
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
796
639
  process.removeAllListeners('uncaughtException');
797
640
  process.removeAllListeners('unhandledRejection');
798
641
  this.exceptionHandler = async (error) => {
799
642
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
800
- // await this.cleanup('Unhandled Exception detected, cleaning up...');
801
643
  };
802
644
  process.on('uncaughtException', this.exceptionHandler);
803
645
  this.rejectionHandler = async (reason, promise) => {
804
646
  this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
805
- // await this.cleanup('Unhandled Rejection detected, cleaning up...');
806
647
  };
807
648
  process.on('unhandledRejection', this.rejectionHandler);
808
649
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -815,9 +656,6 @@ export class Matterbridge extends EventEmitter {
815
656
  };
816
657
  process.on('SIGTERM', this.sigtermHandler);
817
658
  }
818
- /**
819
- * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
820
- */
821
659
  deregisterProcesslHandlers() {
822
660
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
823
661
  if (this.exceptionHandler)
@@ -834,17 +672,12 @@ export class Matterbridge extends EventEmitter {
834
672
  process.off('SIGTERM', this.sigtermHandler);
835
673
  this.sigtermHandler = undefined;
836
674
  }
837
- /**
838
- * Logs the node and system information.
839
- */
840
675
  async logNodeAndSystemInfo() {
841
- // IP address information
842
676
  const networkInterfaces = os.networkInterfaces();
843
677
  this.systemInformation.interfaceName = '';
844
678
  this.systemInformation.ipv4Address = '';
845
679
  this.systemInformation.ipv6Address = '';
846
680
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
847
- // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
848
681
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
849
682
  continue;
850
683
  if (!interfaceDetails) {
@@ -870,22 +703,19 @@ export class Matterbridge extends EventEmitter {
870
703
  break;
871
704
  }
872
705
  }
873
- // Node information
874
706
  this.systemInformation.nodeVersion = process.versions.node;
875
707
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
876
708
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
877
709
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
878
- // Host system information
879
710
  this.systemInformation.hostname = os.hostname();
880
711
  this.systemInformation.user = os.userInfo().username;
881
- this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
882
- this.systemInformation.osRelease = os.release(); // Kernel version
883
- this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
884
- this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
885
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
886
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
887
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
888
- // Log the system information
712
+ this.systemInformation.osType = os.type();
713
+ this.systemInformation.osRelease = os.release();
714
+ this.systemInformation.osPlatform = os.platform();
715
+ this.systemInformation.osArch = os.arch();
716
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
717
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
718
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
889
719
  this.log.debug('Host System Information:');
890
720
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
891
721
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -901,20 +731,16 @@ export class Matterbridge extends EventEmitter {
901
731
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
902
732
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
903
733
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
904
- // Home directory
905
734
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
906
735
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
907
736
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
908
- // Package root directory
909
737
  const { fileURLToPath } = await import('node:url');
910
738
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
911
739
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
912
740
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
913
741
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
914
- // Global node_modules directory
915
742
  if (this.nodeContext)
916
743
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
917
- // First run of Matterbridge so the node storage is empty
918
744
  if (this.globalModulesDirectory === '') {
919
745
  try {
920
746
  this.execRunningCount++;
@@ -930,20 +756,6 @@ export class Matterbridge extends EventEmitter {
930
756
  }
931
757
  else
932
758
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
933
- /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
934
- else {
935
- this.getGlobalNodeModules()
936
- .then(async (globalModulesDirectory) => {
937
- this.globalModulesDirectory = globalModulesDirectory;
938
- this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
939
- this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
940
- await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
941
- })
942
- .catch((error) => {
943
- this.log.error(`Error getting global node_modules directory: ${error}`);
944
- });
945
- }*/
946
- // Create the data directory .matterbridge in the home directory
947
759
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
948
760
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
949
761
  try {
@@ -967,7 +779,6 @@ export class Matterbridge extends EventEmitter {
967
779
  }
968
780
  }
969
781
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
970
- // Create the plugin directory Matterbridge in the home directory
971
782
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
972
783
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
973
784
  try {
@@ -991,68 +802,50 @@ export class Matterbridge extends EventEmitter {
991
802
  }
992
803
  }
993
804
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
994
- // Matterbridge version
995
805
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
996
806
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
997
807
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
998
808
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
999
- // Matterbridge latest version
1000
809
  if (this.nodeContext)
1001
810
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
1002
811
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1003
- // this.getMatterbridgeLatestVersion();
1004
- // Current working directory
1005
812
  const currentDir = process.cwd();
1006
813
  this.log.debug(`Current Working Directory: ${currentDir}`);
1007
- // Command line arguments (excluding 'node' and the script name)
1008
814
  const cmdArgs = process.argv.slice(2).join(' ');
1009
815
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
1010
816
  }
1011
- /**
1012
- * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1013
- *
1014
- * @returns {Function} The MatterLogger function.
1015
- */
1016
817
  createMatterLogger() {
1017
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
818
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1018
819
  return (_level, formattedLog) => {
1019
820
  const logger = formattedLog.slice(44, 44 + 20).trim();
1020
821
  const message = formattedLog.slice(65);
1021
822
  matterLogger.logName = logger;
1022
823
  switch (_level) {
1023
824
  case MatterLogLevel.DEBUG:
1024
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
825
+ matterLogger.log("debug", message);
1025
826
  break;
1026
827
  case MatterLogLevel.INFO:
1027
- matterLogger.log("info" /* LogLevel.INFO */, message);
828
+ matterLogger.log("info", message);
1028
829
  break;
1029
830
  case MatterLogLevel.NOTICE:
1030
- matterLogger.log("notice" /* LogLevel.NOTICE */, message);
831
+ matterLogger.log("notice", message);
1031
832
  break;
1032
833
  case MatterLogLevel.WARN:
1033
- matterLogger.log("warn" /* LogLevel.WARN */, message);
834
+ matterLogger.log("warn", message);
1034
835
  break;
1035
836
  case MatterLogLevel.ERROR:
1036
- matterLogger.log("error" /* LogLevel.ERROR */, message);
837
+ matterLogger.log("error", message);
1037
838
  break;
1038
839
  case MatterLogLevel.FATAL:
1039
- matterLogger.log("fatal" /* LogLevel.FATAL */, message);
840
+ matterLogger.log("fatal", message);
1040
841
  break;
1041
842
  default:
1042
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
843
+ matterLogger.log("debug", message);
1043
844
  break;
1044
845
  }
1045
846
  };
1046
847
  }
1047
- /**
1048
- * Creates a Matter File Logger.
1049
- *
1050
- * @param {string} filePath - The path to the log file.
1051
- * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1052
- * @returns {Function} - A function that logs formatted messages to the log file.
1053
- */
1054
848
  async createMatterFileLogger(filePath, unlink = false) {
1055
- // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
1056
849
  let fileSize = 0;
1057
850
  if (unlink) {
1058
851
  try {
@@ -1101,21 +894,12 @@ export class Matterbridge extends EventEmitter {
1101
894
  }
1102
895
  };
1103
896
  }
1104
- /**
1105
- * Restarts the process by exiting the current instance and loading a new instance.
1106
- */
1107
897
  async restartProcess() {
1108
898
  await this.cleanup('restarting...', true);
1109
899
  }
1110
- /**
1111
- * Shut down the process by exiting the current process.
1112
- */
1113
900
  async shutdownProcess() {
1114
901
  await this.cleanup('shutting down...', false);
1115
902
  }
1116
- /**
1117
- * Update matterbridge and and shut down the process.
1118
- */
1119
903
  async updateProcess() {
1120
904
  this.log.info('Updating matterbridge...');
1121
905
  try {
@@ -1128,72 +912,51 @@ export class Matterbridge extends EventEmitter {
1128
912
  this.frontend.wssSendRestartRequired();
1129
913
  await this.cleanup('updating...', false);
1130
914
  }
1131
- /**
1132
- * Unregister all devices and shut down the process.
1133
- */
1134
915
  async unregisterAndShutdownProcess() {
1135
916
  this.log.info('Unregistering all devices and shutting down...');
1136
917
  for (const plugin of this.plugins) {
1137
918
  await this.removeAllBridgedEndpoints(plugin.name, 250);
1138
919
  }
1139
920
  this.log.debug('Waiting for the MessageExchange to finish...');
1140
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
921
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1141
922
  this.log.debug('Cleaning up and shutting down...');
1142
923
  await this.cleanup('unregistered all devices and shutting down...', false);
1143
924
  }
1144
- /**
1145
- * Reset commissioning and shut down the process.
1146
- */
1147
925
  async shutdownProcessAndReset() {
1148
926
  await this.cleanup('shutting down with reset...', false);
1149
927
  }
1150
- /**
1151
- * Factory reset and shut down the process.
1152
- */
1153
928
  async shutdownProcessAndFactoryReset() {
1154
929
  await this.cleanup('shutting down with factory reset...', false);
1155
930
  }
1156
- /**
1157
- * Cleans up the Matterbridge instance.
1158
- * @param message - The cleanup message.
1159
- * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1160
- * @returns A promise that resolves when the cleanup is completed.
1161
- */
1162
931
  async cleanup(message, restart = false) {
1163
932
  if (this.initialized && !this.hasCleanupStarted) {
1164
933
  this.hasCleanupStarted = true;
1165
934
  this.log.info(message);
1166
- // Clear the start matter interval
1167
935
  if (this.startMatterInterval) {
1168
936
  clearInterval(this.startMatterInterval);
1169
937
  this.startMatterInterval = undefined;
1170
938
  this.log.debug('Start matter interval cleared');
1171
939
  }
1172
- // Clear the check update timeout
1173
940
  if (this.checkUpdateTimeout) {
1174
941
  clearInterval(this.checkUpdateTimeout);
1175
942
  this.checkUpdateTimeout = undefined;
1176
943
  this.log.debug('Check update timeout cleared');
1177
944
  }
1178
- // Clear the check update interval
1179
945
  if (this.checkUpdateInterval) {
1180
946
  clearInterval(this.checkUpdateInterval);
1181
947
  this.checkUpdateInterval = undefined;
1182
948
  this.log.debug('Check update interval cleared');
1183
949
  }
1184
- // Clear the configure timeout
1185
950
  if (this.configureTimeout) {
1186
951
  clearTimeout(this.configureTimeout);
1187
952
  this.configureTimeout = undefined;
1188
953
  this.log.debug('Matterbridge configure timeout cleared');
1189
954
  }
1190
- // Clear the reachability timeout
1191
955
  if (this.reachabilityTimeout) {
1192
956
  clearTimeout(this.reachabilityTimeout);
1193
957
  this.reachabilityTimeout = undefined;
1194
958
  this.log.debug('Matterbridge reachability timeout cleared');
1195
959
  }
1196
- // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1197
960
  for (const plugin of this.plugins) {
1198
961
  if (!plugin.enabled || plugin.error)
1199
962
  continue;
@@ -1204,10 +967,9 @@ export class Matterbridge extends EventEmitter {
1204
967
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1205
968
  }
1206
969
  }
1207
- // Stopping matter server nodes
1208
970
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1209
971
  this.log.debug('Waiting for the MessageExchange to finish...');
1210
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
972
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1211
973
  if (this.bridgeMode === 'bridge') {
1212
974
  if (this.serverNode) {
1213
975
  await this.stopServerNode(this.serverNode);
@@ -1223,7 +985,6 @@ export class Matterbridge extends EventEmitter {
1223
985
  }
1224
986
  }
1225
987
  this.log.notice('Stopped matter server nodes');
1226
- // Matter commisioning reset
1227
988
  if (message === 'shutting down with reset...') {
1228
989
  this.log.info('Resetting Matterbridge commissioning information...');
1229
990
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1233,37 +994,17 @@ export class Matterbridge extends EventEmitter {
1233
994
  await this.matterbridgeContext?.clearAll();
1234
995
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1235
996
  }
1236
- // Stop matter storage
1237
997
  await this.stopMatterStorage();
1238
- // Stop the frontend
1239
998
  await this.frontend.stop();
1240
- // Remove the matterfilelogger
1241
999
  try {
1242
1000
  Logger.removeLogger('matterfilelogger');
1243
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1244
1001
  }
1245
1002
  catch (error) {
1246
- // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1247
1003
  }
1248
- // Serialize registeredDevices
1249
1004
  if (this.nodeStorage && this.nodeContext) {
1250
- /*
1251
- TODO: Implement serialization of registered devices in edge mode
1252
- this.log.info('Saving registered devices...');
1253
- const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1254
- this.devices.forEach(async (device) => {
1255
- const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1256
- // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1257
- if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1258
- });
1259
- await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1260
- this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1261
- */
1262
- // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1263
1005
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1264
1006
  await this.nodeContext.close();
1265
1007
  this.nodeContext = undefined;
1266
- // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1267
1008
  for (const plugin of this.plugins) {
1268
1009
  if (plugin.nodeContext) {
1269
1010
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1280,10 +1021,8 @@ export class Matterbridge extends EventEmitter {
1280
1021
  }
1281
1022
  this.plugins.clear();
1282
1023
  this.devices.clear();
1283
- // Factory reset
1284
1024
  if (message === 'shutting down with factory reset...') {
1285
1025
  try {
1286
- // Delete old matter storage file and backup
1287
1026
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1288
1027
  this.log.info(`Unlinking old matter storage file: ${file}`);
1289
1028
  await fs.unlink(file);
@@ -1297,7 +1036,6 @@ export class Matterbridge extends EventEmitter {
1297
1036
  }
1298
1037
  }
1299
1038
  try {
1300
- // Delete matter node storage directory with its subdirectories and backup
1301
1039
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1302
1040
  this.log.info(`Removing matter node storage directory: ${dir}`);
1303
1041
  await fs.rm(dir, { recursive: true });
@@ -1311,7 +1049,6 @@ export class Matterbridge extends EventEmitter {
1311
1049
  }
1312
1050
  }
1313
1051
  try {
1314
- // Delete node storage directory with its subdirectories and backup
1315
1052
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1316
1053
  this.log.info(`Removing storage directory: ${dir}`);
1317
1054
  await fs.rm(dir, { recursive: true });
@@ -1326,13 +1063,12 @@ export class Matterbridge extends EventEmitter {
1326
1063
  }
1327
1064
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1328
1065
  }
1329
- // Deregisters the process handlers
1330
1066
  this.deregisterProcesslHandlers();
1331
1067
  if (restart) {
1332
1068
  if (message === 'updating...') {
1333
1069
  this.log.info('Cleanup completed. Updating...');
1334
1070
  Matterbridge.instance = undefined;
1335
- this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1071
+ this.emit('update');
1336
1072
  }
1337
1073
  else if (message === 'restarting...') {
1338
1074
  this.log.info('Cleanup completed. Restarting...');
@@ -1352,14 +1088,6 @@ export class Matterbridge extends EventEmitter {
1352
1088
  this.log.debug('Cleanup already started...');
1353
1089
  }
1354
1090
  }
1355
- /**
1356
- * Creates and configures the server node for an accessory plugin for a given device.
1357
- *
1358
- * @param {RegisteredPlugin} plugin - The plugin to configure.
1359
- * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1360
- * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1361
- * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1362
- */
1363
1091
  async createAccessoryPlugin(plugin, device, start = false) {
1364
1092
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1365
1093
  plugin.locked = true;
@@ -1372,13 +1100,6 @@ export class Matterbridge extends EventEmitter {
1372
1100
  await this.startServerNode(plugin.serverNode);
1373
1101
  }
1374
1102
  }
1375
- /**
1376
- * Creates and configures the server node for a dynamic plugin.
1377
- *
1378
- * @param {RegisteredPlugin} plugin - The plugin to configure.
1379
- * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1380
- * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1381
- */
1382
1103
  async createDynamicPlugin(plugin, start = false) {
1383
1104
  if (!plugin.locked) {
1384
1105
  plugin.locked = true;
@@ -1390,13 +1111,7 @@ export class Matterbridge extends EventEmitter {
1390
1111
  await this.startServerNode(plugin.serverNode);
1391
1112
  }
1392
1113
  }
1393
- /**
1394
- * Starts the Matterbridge in bridge mode.
1395
- * @private
1396
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1397
- */
1398
1114
  async startBridge() {
1399
- // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1400
1115
  if (!this.matterStorageManager)
1401
1116
  throw new Error('No storage manager initialized');
1402
1117
  if (!this.matterbridgeContext)
@@ -1418,12 +1133,13 @@ export class Matterbridge extends EventEmitter {
1418
1133
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1419
1134
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1420
1135
  this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1136
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1421
1137
  return;
1422
1138
  }
1423
1139
  if (!plugin.loaded || !plugin.started) {
1424
- this.log.debug(`Waiting (failSafeCount=${failCount}/60) in startMatterInterval interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1140
+ this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) in startMatterInterval interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1425
1141
  failCount++;
1426
- if (failCount > 60) {
1142
+ if (failCount > this.failCountLimit) {
1427
1143
  this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1428
1144
  plugin.error = true;
1429
1145
  }
@@ -1433,37 +1149,31 @@ export class Matterbridge extends EventEmitter {
1433
1149
  clearInterval(this.startMatterInterval);
1434
1150
  this.startMatterInterval = undefined;
1435
1151
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1436
- // Start the Matter server node
1437
1152
  this.startServerNode(this.serverNode);
1438
- // Configure the plugins
1439
1153
  this.configureTimeout = setTimeout(async () => {
1440
1154
  for (const plugin of this.plugins) {
1441
1155
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1442
1156
  continue;
1443
1157
  try {
1444
- await this.plugins.configure(plugin);
1158
+ if ((await this.plugins.configure(plugin)) === undefined) {
1159
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1160
+ }
1445
1161
  }
1446
1162
  catch (error) {
1447
1163
  plugin.error = true;
1448
1164
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1449
1165
  }
1450
1166
  }
1451
- this.frontend.wssSendRefreshRequired();
1167
+ this.frontend.wssSendRefreshRequired('plugins');
1452
1168
  }, 30 * 1000);
1453
- // Setting reachability to true
1454
1169
  this.reachabilityTimeout = setTimeout(() => {
1455
1170
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1456
1171
  if (this.aggregatorNode)
1457
1172
  this.setAggregatorReachability(this.aggregatorNode, true);
1458
- this.frontend.wssSendRefreshRequired();
1173
+ this.frontend.wssSendRefreshRequired('reachability');
1459
1174
  }, 60 * 1000);
1460
1175
  }, 1000);
1461
1176
  }
1462
- /**
1463
- * Starts the Matterbridge in childbridge mode.
1464
- * @private
1465
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1466
- */
1467
1177
  async startChildbridge() {
1468
1178
  if (!this.matterStorageManager)
1469
1179
  throw new Error('No storage manager initialized');
@@ -1489,14 +1199,15 @@ export class Matterbridge extends EventEmitter {
1489
1199
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1490
1200
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1491
1201
  this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
1202
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} is in error state. Check the logs.`, 0, 'error');
1492
1203
  return;
1493
1204
  }
1494
1205
  this.log.debug(`Checking plugin ${plg}${plugin.name}${db} to start matter in childbridge mode...`);
1495
1206
  if (!plugin.loaded || !plugin.started) {
1496
1207
  allStarted = false;
1497
- this.log.debug(`Waiting (failSafeCount=${failCount}/60) for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1208
+ this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1498
1209
  failCount++;
1499
- if (failCount > 60) {
1210
+ if (failCount > this.failCountLimit) {
1500
1211
  this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error mode.`);
1501
1212
  plugin.error = true;
1502
1213
  }
@@ -1507,20 +1218,21 @@ export class Matterbridge extends EventEmitter {
1507
1218
  clearInterval(this.startMatterInterval);
1508
1219
  this.startMatterInterval = undefined;
1509
1220
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1510
- // Configure the plugins
1511
1221
  this.configureTimeout = setTimeout(async () => {
1512
1222
  for (const plugin of this.plugins) {
1513
1223
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1514
1224
  continue;
1515
1225
  try {
1516
- await this.plugins.configure(plugin); // TODO No await do it in parallel
1226
+ if ((await this.plugins.configure(plugin)) === undefined) {
1227
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1228
+ }
1517
1229
  }
1518
1230
  catch (error) {
1519
1231
  plugin.error = true;
1520
1232
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1521
1233
  }
1522
1234
  }
1523
- this.frontend.wssSendRefreshRequired();
1235
+ this.frontend.wssSendRefreshRequired('plugins');
1524
1236
  }, 30 * 1000);
1525
1237
  for (const plugin of this.plugins) {
1526
1238
  if (!plugin.enabled || plugin.error)
@@ -1541,231 +1253,19 @@ export class Matterbridge extends EventEmitter {
1541
1253
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1542
1254
  continue;
1543
1255
  }
1544
- // Start the Matter server node
1545
1256
  this.startServerNode(plugin.serverNode);
1546
- // Setting reachability to true
1547
1257
  plugin.reachabilityTimeout = setTimeout(() => {
1548
1258
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1549
1259
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1550
1260
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1551
- this.frontend.wssSendRefreshRequired();
1261
+ this.frontend.wssSendRefreshRequired('reachability');
1552
1262
  }, 60 * 1000);
1553
1263
  }
1554
1264
  }, 1000);
1555
1265
  }
1556
- /**
1557
- * Starts the Matterbridge controller.
1558
- * @private
1559
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1560
- */
1561
1266
  async startController() {
1562
- /*
1563
- if (!this.storageManager) {
1564
- this.log.error('No storage manager initialized');
1565
- await this.cleanup('No storage manager initialized');
1566
- return;
1567
- }
1568
- this.log.info('Creating context: mattercontrollerContext');
1569
- this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1570
- if (!this.mattercontrollerContext) {
1571
- this.log.error('No storage context mattercontrollerContext initialized');
1572
- await this.cleanup('No storage context mattercontrollerContext initialized');
1573
- return;
1574
- }
1575
-
1576
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1577
- this.matterServer = await this.createMatterServer(this.storageManager);
1578
- this.log.info('Creating matter commissioning controller');
1579
- this.commissioningController = new CommissioningController({
1580
- autoConnect: false,
1581
- });
1582
- this.log.info('Adding matter commissioning controller to matter server');
1583
- await this.matterServer.addCommissioningController(this.commissioningController);
1584
-
1585
- this.log.info('Starting matter server');
1586
- await this.matterServer.start();
1587
- this.log.info('Matter server started');
1588
-
1589
- if (hasParameter('pairingcode')) {
1590
- this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1591
- const pairingCode = getParameter('pairingcode');
1592
- const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1593
- const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1594
-
1595
- let longDiscriminator, setupPin, shortDiscriminator;
1596
- if (pairingCode !== undefined) {
1597
- const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1598
- shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1599
- longDiscriminator = undefined;
1600
- setupPin = pairingCodeCodec.passcode;
1601
- this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1602
- } else {
1603
- longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1604
- if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1605
- setupPin = this.mattercontrollerContext.get('pin', 20202021);
1606
- }
1607
- if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1608
- throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1609
- }
1610
-
1611
- const commissioningOptions: ControllerCommissioningFlowOptions = {
1612
- regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1613
- regulatoryCountryCode: 'XX',
1614
- };
1615
- const options = {
1616
- commissioning: commissioningOptions,
1617
- discovery: {
1618
- knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1619
- identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1620
- },
1621
- passcode: setupPin,
1622
- } as NodeCommissioningOptions;
1623
- this.log.info('Commissioning with options:', options);
1624
- const nodeId = await this.commissioningController.commissionNode(options);
1625
- this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1626
- this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1627
- } // (hasParameter('pairingcode'))
1628
-
1629
- if (hasParameter('unpairall')) {
1630
- this.log.info('***Commissioning controller unpairing all nodes...');
1631
- const nodeIds = this.commissioningController.getCommissionedNodes();
1632
- for (const nodeId of nodeIds) {
1633
- this.log.info('***Commissioning controller unpairing node:', nodeId);
1634
- await this.commissioningController.removeNode(nodeId);
1635
- }
1636
- return;
1637
- }
1638
-
1639
- if (hasParameter('discover')) {
1640
- // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1641
- // console.log(discover);
1642
- }
1643
-
1644
- if (!this.commissioningController.isCommissioned()) {
1645
- this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1646
- return;
1647
- }
1648
-
1649
- const nodeIds = this.commissioningController.getCommissionedNodes();
1650
- this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1651
- for (const nodeId of nodeIds) {
1652
- this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1653
-
1654
- const node = await this.commissioningController.connectNode(nodeId, {
1655
- autoSubscribe: false,
1656
- attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1657
- this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1658
- eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1659
- this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1660
- stateInformationCallback: (peerNodeId, info) => {
1661
- switch (info) {
1662
- case NodeStateInformation.Connected:
1663
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1664
- break;
1665
- case NodeStateInformation.Disconnected:
1666
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1667
- break;
1668
- case NodeStateInformation.Reconnecting:
1669
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1670
- break;
1671
- case NodeStateInformation.WaitingForDeviceDiscovery:
1672
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1673
- break;
1674
- case NodeStateInformation.StructureChanged:
1675
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1676
- break;
1677
- case NodeStateInformation.Decommissioned:
1678
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1679
- break;
1680
- default:
1681
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1682
- break;
1683
- }
1684
- },
1685
- });
1686
-
1687
- node.logStructure();
1688
-
1689
- // Get the interaction client
1690
- this.log.info('Getting the interaction client');
1691
- const interactionClient = await node.getInteractionClient();
1692
- let cluster;
1693
- let attributes;
1694
-
1695
- // Log BasicInformationCluster
1696
- cluster = BasicInformationCluster;
1697
- attributes = await interactionClient.getMultipleAttributes({
1698
- attributes: [{ clusterId: cluster.id }],
1699
- });
1700
- if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1701
- attributes.forEach((attribute) => {
1702
- this.log.info(
1703
- `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1704
- );
1705
- });
1706
-
1707
- // Log PowerSourceCluster
1708
- cluster = PowerSourceCluster;
1709
- attributes = await interactionClient.getMultipleAttributes({
1710
- attributes: [{ clusterId: cluster.id }],
1711
- });
1712
- if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1713
- attributes.forEach((attribute) => {
1714
- this.log.info(
1715
- `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1716
- );
1717
- });
1718
-
1719
- // Log ThreadNetworkDiagnostics
1720
- cluster = ThreadNetworkDiagnosticsCluster;
1721
- attributes = await interactionClient.getMultipleAttributes({
1722
- attributes: [{ clusterId: cluster.id }],
1723
- });
1724
- if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1725
- attributes.forEach((attribute) => {
1726
- this.log.info(
1727
- `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1728
- );
1729
- });
1730
-
1731
- // Log SwitchCluster
1732
- cluster = SwitchCluster;
1733
- attributes = await interactionClient.getMultipleAttributes({
1734
- attributes: [{ clusterId: cluster.id }],
1735
- });
1736
- if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1737
- attributes.forEach((attribute) => {
1738
- this.log.info(
1739
- `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1740
- );
1741
- });
1742
-
1743
- this.log.info('Subscribing to all attributes and events');
1744
- await node.subscribeAllAttributesAndEvents({
1745
- ignoreInitialTriggers: false,
1746
- attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1747
- this.log.info(
1748
- `***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
1749
- ),
1750
- eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1751
- this.log.info(
1752
- `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1753
- );
1754
- },
1755
- });
1756
- this.log.info('Subscribed to all attributes and events');
1757
- }
1758
- */
1759
1267
  }
1760
- /** ***********************************************************************************************************************************/
1761
- /** Matter.js methods */
1762
- /** ***********************************************************************************************************************************/
1763
- /**
1764
- * Starts the matter storage process with name Matterbridge.
1765
- * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1766
- */
1767
1268
  async startMatterStorage() {
1768
- // Setup Matter storage
1769
1269
  this.log.info(`Starting matter node storage...`);
1770
1270
  this.matterStorageService = this.environment.get(StorageService);
1771
1271
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1773,25 +1273,13 @@ export class Matterbridge extends EventEmitter {
1773
1273
  this.log.info('Matter node storage manager "Matterbridge" created');
1774
1274
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1775
1275
  this.log.info('Matter node storage started');
1776
- // Backup matter storage since it is created/opened correctly
1777
1276
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1778
1277
  }
1779
- /**
1780
- * Makes a backup copy of the specified matter storage directory.
1781
- *
1782
- * @param storageName - The name of the storage directory to be backed up.
1783
- * @param backupName - The name of the backup directory to be created.
1784
- * @returns {Promise<void>} A promise that resolves when the has been done.
1785
- */
1786
1278
  async backupMatterStorage(storageName, backupName) {
1787
1279
  this.log.info('Creating matter node storage backup...');
1788
1280
  await copyDirectory(storageName, backupName);
1789
1281
  this.log.info('Created matter node storage backup');
1790
1282
  }
1791
- /**
1792
- * Stops the matter storage.
1793
- * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1794
- */
1795
1283
  async stopMatterStorage() {
1796
1284
  this.log.info('Closing matter node storage...');
1797
1285
  this.matterStorageManager?.close();
@@ -1800,19 +1288,6 @@ export class Matterbridge extends EventEmitter {
1800
1288
  this.matterbridgeContext = undefined;
1801
1289
  this.log.info('Matter node storage closed');
1802
1290
  }
1803
- /**
1804
- * Creates a server node storage context.
1805
- *
1806
- * @param {string} pluginName - The name of the plugin.
1807
- * @param {string} deviceName - The name of the device.
1808
- * @param {DeviceTypeId} deviceType - The device type of the device.
1809
- * @param {number} vendorId - The vendor ID.
1810
- * @param {string} vendorName - The vendor name.
1811
- * @param {number} productId - The product ID.
1812
- * @param {string} productName - The product name.
1813
- * @param {string} [serialNumber] - The serial number of the device (optional).
1814
- * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1815
- */
1816
1291
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1817
1292
  const { randomBytes } = await import('node:crypto');
1818
1293
  if (!this.matterStorageService)
@@ -1846,15 +1321,6 @@ export class Matterbridge extends EventEmitter {
1846
1321
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1847
1322
  return storageContext;
1848
1323
  }
1849
- /**
1850
- * Creates a server node.
1851
- *
1852
- * @param {StorageContext} storageContext - The storage context for the server node.
1853
- * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1854
- * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1855
- * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1856
- * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1857
- */
1858
1324
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1859
1325
  const storeId = await storageContext.get('storeId');
1860
1326
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1864,33 +1330,21 @@ export class Matterbridge extends EventEmitter {
1864
1330
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1865
1331
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1866
1332
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1867
- /**
1868
- * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1869
- */
1870
1333
  const serverNode = await ServerNode.create({
1871
- // Required: Give the Node a unique ID which is used to store the state of this node
1872
1334
  id: storeId,
1873
- // Provide Network relevant configuration like the port
1874
- // Optional when operating only one device on a host, Default port is 5540
1875
1335
  network: {
1876
1336
  listeningAddressIpv4: this.ipv4address,
1877
1337
  listeningAddressIpv6: this.ipv6address,
1878
1338
  port,
1879
1339
  },
1880
- // Provide Commissioning relevant settings
1881
- // Optional for development/testing purposes
1882
1340
  commissioning: {
1883
1341
  passcode,
1884
1342
  discriminator,
1885
1343
  },
1886
- // Provide Node announcement settings
1887
- // Optional: If Ommitted some development defaults are used
1888
1344
  productDescription: {
1889
1345
  name: await storageContext.get('deviceName'),
1890
1346
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1891
1347
  },
1892
- // Provide defaults for the BasicInformation cluster on the Root endpoint
1893
- // Optional: If Omitted some development defaults are used
1894
1348
  basicInformation: {
1895
1349
  vendorId: VendorId(await storageContext.get('vendorId')),
1896
1350
  vendorName: await storageContext.get('vendorName'),
@@ -1907,13 +1361,12 @@ export class Matterbridge extends EventEmitter {
1907
1361
  },
1908
1362
  });
1909
1363
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1910
- // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1911
1364
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1912
1365
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1913
1366
  if (this.bridgeMode === 'bridge') {
1914
1367
  this.matterbridgeFabricInformations = sanitizedFabrics;
1915
1368
  if (resetSessions)
1916
- this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1369
+ this.matterbridgeSessionInformations = undefined;
1917
1370
  this.matterbridgePaired = true;
1918
1371
  }
1919
1372
  if (this.bridgeMode === 'childbridge') {
@@ -1921,19 +1374,13 @@ export class Matterbridge extends EventEmitter {
1921
1374
  if (plugin) {
1922
1375
  plugin.fabricInformations = sanitizedFabrics;
1923
1376
  if (resetSessions)
1924
- plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1377
+ plugin.sessionInformations = undefined;
1925
1378
  plugin.paired = true;
1926
1379
  }
1927
1380
  }
1928
1381
  };
1929
- /**
1930
- * This event is triggered when the device is initially commissioned successfully.
1931
- * This means: It is added to the first fabric.
1932
- */
1933
1382
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1934
- /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1935
1383
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1936
- /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1937
1384
  serverNode.lifecycle.online.on(async () => {
1938
1385
  this.log.notice(`Server node for ${storeId} is online`);
1939
1386
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1977,10 +1424,10 @@ export class Matterbridge extends EventEmitter {
1977
1424
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
1978
1425
  sanitizeFabrics(serverNode.state.commissioning.fabrics, true);
1979
1426
  }
1980
- this.frontend.wssSendRefreshRequired();
1981
- this.frontend.wssSendSnackbarMessage(`${storeId} is online`);
1427
+ this.frontend.wssSendRefreshRequired('plugins');
1428
+ this.frontend.wssSendRefreshRequired('settings');
1429
+ this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1982
1430
  });
1983
- /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1984
1431
  serverNode.lifecycle.offline.on(() => {
1985
1432
  this.log.notice(`Server node for ${storeId} is offline`);
1986
1433
  if (this.bridgeMode === 'bridge') {
@@ -2000,12 +1447,10 @@ export class Matterbridge extends EventEmitter {
2000
1447
  plugin.paired = undefined;
2001
1448
  }
2002
1449
  }
2003
- this.frontend.wssSendRefreshRequired();
1450
+ this.frontend.wssSendRefreshRequired('plugins');
1451
+ this.frontend.wssSendRefreshRequired('settings');
1452
+ this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
2004
1453
  });
2005
- /**
2006
- * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2007
- * information is needed.
2008
- */
2009
1454
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
2010
1455
  let action = '';
2011
1456
  switch (fabricAction) {
@@ -2021,7 +1466,7 @@ export class Matterbridge extends EventEmitter {
2021
1466
  }
2022
1467
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
2023
1468
  sanitizeFabrics(serverNode.state.commissioning.fabrics);
2024
- this.frontend.wssSendRefreshRequired();
1469
+ this.frontend.wssSendRefreshRequired('fabrics');
2025
1470
  });
2026
1471
  const sanitizeSessions = (sessions) => {
2027
1472
  const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
@@ -2039,68 +1484,42 @@ export class Matterbridge extends EventEmitter {
2039
1484
  }
2040
1485
  }
2041
1486
  };
2042
- /**
2043
- * This event is triggered when an operative new session was opened by a Controller.
2044
- * It is not triggered for the initial commissioning process, just afterwards for real connections.
2045
- */
2046
1487
  serverNode.events.sessions.opened.on((session) => {
2047
1488
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
2048
1489
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2049
- this.frontend.wssSendRefreshRequired();
1490
+ this.frontend.wssSendRefreshRequired('sessions');
2050
1491
  });
2051
- /**
2052
- * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2053
- */
2054
1492
  serverNode.events.sessions.closed.on((session) => {
2055
1493
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
2056
1494
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2057
- this.frontend.wssSendRefreshRequired();
1495
+ this.frontend.wssSendRefreshRequired('sessions');
2058
1496
  });
2059
- /** This event is triggered when a subscription gets added or removed on an operative session. */
2060
1497
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
2061
1498
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
2062
1499
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2063
- this.frontend.wssSendRefreshRequired();
1500
+ this.frontend.wssSendRefreshRequired('sessions');
2064
1501
  });
2065
1502
  this.log.info(`Created server node for ${storeId}`);
2066
1503
  return serverNode;
2067
1504
  }
2068
- /**
2069
- * Starts the specified server node.
2070
- *
2071
- * @param {ServerNode} [matterServerNode] - The server node to start.
2072
- * @returns {Promise<void>} A promise that resolves when the server node has started.
2073
- */
2074
1505
  async startServerNode(matterServerNode) {
2075
1506
  if (!matterServerNode)
2076
1507
  return;
2077
1508
  this.log.notice(`Starting ${matterServerNode.id} server node`);
2078
1509
  await matterServerNode.start();
2079
1510
  }
2080
- /**
2081
- * Stops the specified server node.
2082
- *
2083
- * @param {ServerNode} matterServerNode - The server node to stop.
2084
- * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2085
- */
2086
1511
  async stopServerNode(matterServerNode) {
2087
1512
  if (!matterServerNode)
2088
1513
  return;
2089
1514
  this.log.notice(`Closing ${matterServerNode.id} server node`);
2090
1515
  try {
2091
- await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1516
+ await withTimeout(matterServerNode.close(), 30000);
2092
1517
  this.log.info(`Closed ${matterServerNode.id} server node`);
2093
1518
  }
2094
1519
  catch (error) {
2095
1520
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
2096
1521
  }
2097
1522
  }
2098
- /**
2099
- * Advertises the specified server node.
2100
- *
2101
- * @param {ServerNode} [matterServerNode] - The server node to advertise.
2102
- * @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
2103
- */
2104
1523
  async advertiseServerNode(matterServerNode) {
2105
1524
  if (matterServerNode) {
2106
1525
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -2109,44 +1528,23 @@ export class Matterbridge extends EventEmitter {
2109
1528
  return { qrPairingCode, manualPairingCode };
2110
1529
  }
2111
1530
  }
2112
- /**
2113
- * Stop advertise the specified server node.
2114
- *
2115
- * @param {ServerNode} [matterServerNode] - The server node to advertise.
2116
- * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2117
- */
2118
1531
  async stopAdvertiseServerNode(matterServerNode) {
2119
1532
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
2120
1533
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
2121
1534
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
2122
1535
  }
2123
1536
  }
2124
- /**
2125
- * Creates an aggregator node with the specified storage context.
2126
- *
2127
- * @param {StorageContext} storageContext - The storage context for the aggregator node.
2128
- * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2129
- */
2130
1537
  async createAggregatorNode(storageContext) {
2131
1538
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
2132
1539
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
2133
1540
  return aggregatorNode;
2134
1541
  }
2135
- /**
2136
- * Adds a MatterbridgeEndpoint to the specified plugin.
2137
- *
2138
- * @param {string} pluginName - The name of the plugin.
2139
- * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2140
- * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2141
- */
2142
1542
  async addBridgedEndpoint(pluginName, device) {
2143
- // Check if the plugin is registered
2144
1543
  const plugin = this.plugins.get(pluginName);
2145
1544
  if (!plugin) {
2146
1545
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
2147
1546
  return;
2148
1547
  }
2149
- // Register and add the device to the matterbridge aggregator node
2150
1548
  if (this.bridgeMode === 'bridge') {
2151
1549
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
2152
1550
  if (!this.aggregatorNode)
@@ -2169,26 +1567,16 @@ export class Matterbridge extends EventEmitter {
2169
1567
  plugin.registeredDevices++;
2170
1568
  if (plugin.addedDevices !== undefined)
2171
1569
  plugin.addedDevices++;
2172
- // Add the device to the DeviceManager
2173
1570
  this.devices.set(device);
2174
1571
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
2175
1572
  }
2176
- /**
2177
- * Removes a MatterbridgeEndpoint from the specified plugin.
2178
- *
2179
- * @param {string} pluginName - The name of the plugin.
2180
- * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2181
- * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2182
- */
2183
1573
  async removeBridgedEndpoint(pluginName, device) {
2184
1574
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2185
- // Check if the plugin is registered
2186
1575
  const plugin = this.plugins.get(pluginName);
2187
1576
  if (!plugin) {
2188
1577
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
2189
1578
  return;
2190
1579
  }
2191
- // Register and add the device to the matterbridge aggregator node
2192
1580
  if (this.bridgeMode === 'bridge') {
2193
1581
  if (!this.aggregatorNode) {
2194
1582
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -2203,7 +1591,6 @@ export class Matterbridge extends EventEmitter {
2203
1591
  }
2204
1592
  else if (this.bridgeMode === 'childbridge') {
2205
1593
  if (plugin.type === 'AccessoryPlatform') {
2206
- // Nothing to do here since the server node has no aggregator node but only the device itself
2207
1594
  }
2208
1595
  else if (plugin.type === 'DynamicPlatform') {
2209
1596
  if (!plugin.aggregatorNode) {
@@ -2218,16 +1605,8 @@ export class Matterbridge extends EventEmitter {
2218
1605
  if (plugin.addedDevices !== undefined)
2219
1606
  plugin.addedDevices--;
2220
1607
  }
2221
- // Remove the device from the DeviceManager
2222
1608
  this.devices.remove(device);
2223
1609
  }
2224
- /**
2225
- * Removes all bridged endpoints from the specified plugin.
2226
- *
2227
- * @param {string} pluginName - The name of the plugin.
2228
- * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2229
- * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2230
- */
2231
1610
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
2232
1611
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
2233
1612
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -2236,12 +1615,6 @@ export class Matterbridge extends EventEmitter {
2236
1615
  await new Promise((resolve) => setTimeout(resolve, delay));
2237
1616
  }
2238
1617
  }
2239
- /**
2240
- * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2241
- *
2242
- * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2243
- * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2244
- */
2245
1618
  sanitizeFabricInformations(fabricInfo) {
2246
1619
  return fabricInfo.map((info) => {
2247
1620
  return {
@@ -2255,12 +1628,6 @@ export class Matterbridge extends EventEmitter {
2255
1628
  };
2256
1629
  });
2257
1630
  }
2258
- /**
2259
- * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2260
- *
2261
- * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2262
- * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2263
- */
2264
1631
  sanitizeSessionInformation(sessionInfo) {
2265
1632
  return sessionInfo
2266
1633
  .filter((session) => session.isPeerActive)
@@ -2288,11 +1655,6 @@ export class Matterbridge extends EventEmitter {
2288
1655
  };
2289
1656
  });
2290
1657
  }
2291
- /**
2292
- * Sets the reachability of the specified aggregator node bridged devices and trigger.
2293
- * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2294
- * @param {boolean} reachable - A boolean indicating the reachability status to set.
2295
- */
2296
1658
  async setAggregatorReachability(aggregatorNode, reachable) {
2297
1659
  for (const child of aggregatorNode.parts) {
2298
1660
  this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
@@ -2338,37 +1700,14 @@ export class Matterbridge extends EventEmitter {
2338
1700
  }
2339
1701
  return vendorName;
2340
1702
  };
2341
- /**
2342
- * Spawns a child process with the given command and arguments.
2343
- * @param {string} command - The command to execute.
2344
- * @param {string[]} args - The arguments to pass to the command (default: []).
2345
- * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2346
- */
2347
1703
  async spawnCommand(command, args = []) {
2348
1704
  const { spawn } = await import('node:child_process');
2349
- /*
2350
- npm > npm.cmd on windows
2351
- cmd.exe ['dir'] on windows
2352
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2353
- process.on('unhandledRejection', (reason, promise) => {
2354
- this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2355
- });
2356
-
2357
- spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2358
- spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2359
- debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2360
- debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2361
- */
2362
1705
  const cmdLine = command + ' ' + args.join(' ');
2363
1706
  if (process.platform === 'win32' && command === 'npm') {
2364
- // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2365
1707
  const argstring = 'npm ' + args.join(' ');
2366
1708
  args.splice(0, args.length, '/c', argstring);
2367
1709
  command = 'cmd.exe';
2368
1710
  }
2369
- // Decide when using sudo on linux
2370
- // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2371
- // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2372
1711
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2373
1712
  args.unshift(command);
2374
1713
  command = 'sudo';
@@ -2427,4 +1766,3 @@ export class Matterbridge extends EventEmitter {
2427
1766
  });
2428
1767
  }
2429
1768
  }
2430
- //# sourceMappingURL=matterbridge.js.map