matterbridge 1.6.5-dev.3 → 1.6.5

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 (96) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/cli.d.ts +25 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +26 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +46 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +26 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/index.d.ts +40 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +30 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger/export.d.ts +2 -0
  23. package/dist/logger/export.d.ts.map +1 -0
  24. package/dist/logger/export.js +1 -0
  25. package/dist/logger/export.js.map +1 -0
  26. package/dist/matter/export.d.ts +5 -0
  27. package/dist/matter/export.d.ts.map +1 -0
  28. package/dist/matter/export.js +1 -0
  29. package/dist/matter/export.js.map +1 -0
  30. package/dist/matterbridge.d.ts +466 -0
  31. package/dist/matterbridge.d.ts.map +1 -0
  32. package/dist/matterbridge.js +702 -62
  33. package/dist/matterbridge.js.map +1 -0
  34. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  35. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  36. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  37. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  38. package/dist/matterbridgeBehaviors.d.ts +934 -0
  39. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  40. package/dist/matterbridgeBehaviors.js +29 -1
  41. package/dist/matterbridgeBehaviors.js.map +1 -0
  42. package/dist/matterbridgeDevice.d.ts +6504 -0
  43. package/dist/matterbridgeDevice.d.ts.map +1 -0
  44. package/dist/matterbridgeDevice.js +919 -9
  45. package/dist/matterbridgeDevice.js.map +1 -0
  46. package/dist/matterbridgeDeviceTypes.d.ts +65 -0
  47. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  48. package/dist/matterbridgeDeviceTypes.js +40 -12
  49. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  50. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  51. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  52. package/dist/matterbridgeDynamicPlatform.js +33 -0
  53. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  54. package/dist/matterbridgeEdge.d.ts +89 -0
  55. package/dist/matterbridgeEdge.d.ts.map +1 -0
  56. package/dist/matterbridgeEdge.js +525 -0
  57. package/dist/matterbridgeEdge.js.map +1 -0
  58. package/dist/matterbridgeEndpoint.d.ts +8529 -0
  59. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  60. package/dist/matterbridgeEndpoint.js +997 -16
  61. package/dist/matterbridgeEndpoint.js.map +1 -0
  62. package/dist/matterbridgePlatform.d.ts +96 -0
  63. package/dist/matterbridgePlatform.d.ts.map +1 -0
  64. package/dist/matterbridgePlatform.js +74 -3
  65. package/dist/matterbridgePlatform.js.map +1 -0
  66. package/dist/matterbridgeTypes.d.ts +147 -0
  67. package/dist/matterbridgeTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeTypes.js +24 -0
  69. package/dist/matterbridgeTypes.js.map +1 -0
  70. package/dist/matterbridgeWebsocket.d.ts +49 -0
  71. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  72. package/dist/matterbridgeWebsocket.js +45 -0
  73. package/dist/matterbridgeWebsocket.js.map +1 -0
  74. package/dist/pluginManager.d.ts +238 -0
  75. package/dist/pluginManager.d.ts.map +1 -0
  76. package/dist/pluginManager.js +231 -3
  77. package/dist/pluginManager.js.map +1 -0
  78. package/dist/storage/export.d.ts +2 -0
  79. package/dist/storage/export.d.ts.map +1 -0
  80. package/dist/storage/export.js +1 -0
  81. package/dist/storage/export.js.map +1 -0
  82. package/dist/utils/colorUtils.d.ts +61 -0
  83. package/dist/utils/colorUtils.d.ts.map +1 -0
  84. package/dist/utils/colorUtils.js +78 -2
  85. package/dist/utils/colorUtils.js.map +1 -0
  86. package/dist/utils/export.d.ts +3 -0
  87. package/dist/utils/export.d.ts.map +1 -0
  88. package/dist/utils/export.js +1 -0
  89. package/dist/utils/export.js.map +1 -0
  90. package/dist/utils/utils.d.ts +221 -0
  91. package/dist/utils/utils.d.ts.map +1 -0
  92. package/dist/utils/utils.js +252 -7
  93. package/dist/utils/utils.js.map +1 -0
  94. package/npm-shrinkwrap.json +8 -5
  95. package/package.json +1 -1
  96. package/tsconfig.production.json +3 -9
@@ -1,3 +1,26 @@
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
1
24
  import { fileURLToPath } from 'url';
2
25
  import { promises as fs } from 'fs';
3
26
  import { exec, spawn } from 'child_process';
@@ -6,26 +29,35 @@ import EventEmitter from 'events';
6
29
  import os from 'os';
7
30
  import path from 'path';
8
31
  import { randomBytes } from 'crypto';
32
+ // Package modules
9
33
  import https from 'https';
10
34
  import express from 'express';
11
35
  import WebSocket, { WebSocketServer } from 'ws';
36
+ // NodeStorage and AnsiLogger modules
12
37
  import { NodeStorageManager } from 'node-persist-manager';
13
38
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
39
+ // Matterbridge
14
40
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
15
41
  import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
16
42
  import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
17
43
  import { PluginManager } from './pluginManager.js';
18
44
  import { DeviceManager } from './deviceManager.js';
19
45
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
46
+ // @matter
20
47
  import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer } from '@matter/main';
21
48
  import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
22
49
  import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
23
50
  import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
51
+ // @project-chip
24
52
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
25
53
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
54
+ // Default colors
26
55
  const plg = '\u001B[38;5;33m';
27
56
  const dev = '\u001B[38;5;79m';
28
57
  const typ = '\u001B[38;5;207m';
58
+ /**
59
+ * Represents the Matterbridge application.
60
+ */
29
61
  export class Matterbridge extends EventEmitter {
30
62
  systemInformation = {
31
63
  interfaceName: '',
@@ -61,7 +93,7 @@ export class Matterbridge extends EventEmitter {
61
93
  restartMode: '',
62
94
  edge: hasParameter('edge'),
63
95
  profile: getParameter('profile'),
64
- loggerLevel: "info",
96
+ loggerLevel: "info" /* LogLevel.INFO */,
65
97
  fileLogger: false,
66
98
  matterLoggerLevel: MatterLogLevel.INFO,
67
99
  matterFileLogger: false,
@@ -97,6 +129,7 @@ export class Matterbridge extends EventEmitter {
97
129
  nodeContext;
98
130
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
99
131
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
132
+ // Cleanup
100
133
  hasCleanupStarted = false;
101
134
  initialized = false;
102
135
  execRunningCount = 0;
@@ -108,16 +141,18 @@ export class Matterbridge extends EventEmitter {
108
141
  sigtermHandler;
109
142
  exceptionHandler;
110
143
  rejectionHandler;
144
+ // Frontend
111
145
  expressApp;
112
146
  httpServer;
113
147
  httpsServer;
114
148
  webSocketServer;
115
- mdnsInterface;
116
- ipv4address;
117
- ipv6address;
118
- port = 5540;
119
- passcode;
120
- discriminator;
149
+ // Matter
150
+ mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
151
+ ipv4address; // matter commissioning server listeningAddressIpv4
152
+ ipv6address; // matter commissioning server listeningAddressIpv6
153
+ port = 5540; // first commissioning server port
154
+ passcode; // first commissioning server passcode
155
+ discriminator; // first commissioning server discriminator
121
156
  storageManager;
122
157
  matterbridgeContext;
123
158
  mattercontrollerContext;
@@ -128,13 +163,26 @@ export class Matterbridge extends EventEmitter {
128
163
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
129
164
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
130
165
  static instance;
166
+ // We load asyncronously so is private
131
167
  constructor() {
132
168
  super();
169
+ // Bind the handler to the instance
133
170
  this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
134
171
  }
135
172
  matterbridgeMessageHandler;
173
+ /** ***********************************************************************************************************************************/
174
+ /** loadInstance() and cleanup() methods */
175
+ /** ***********************************************************************************************************************************/
176
+ /**
177
+ * Loads an instance of the Matterbridge class.
178
+ * If an instance already exists, return that instance.
179
+ *
180
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
181
+ * @returns The loaded Matterbridge instance.
182
+ */
136
183
  static async loadInstance(initialize = false) {
137
184
  if (!Matterbridge.instance) {
185
+ // eslint-disable-next-line no-console
138
186
  if (hasParameter('debug'))
139
187
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
140
188
  Matterbridge.instance = new Matterbridge();
@@ -143,6 +191,11 @@ export class Matterbridge extends EventEmitter {
143
191
  }
144
192
  return Matterbridge.instance;
145
193
  }
194
+ /**
195
+ * Call cleanup().
196
+ * @deprecated This method is deprecated and is only used for jest tests.
197
+ *
198
+ */
146
199
  async destroyInstance() {
147
200
  await this.cleanup('destroying instance...', false);
148
201
  await waiter('destroying instance...', () => {
@@ -150,42 +203,66 @@ export class Matterbridge extends EventEmitter {
150
203
  }, false, 60000, 100, false);
151
204
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
152
205
  }
206
+ /**
207
+ * Initializes the Matterbridge application.
208
+ *
209
+ * @remarks
210
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
211
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
212
+ * node version, registers signal handlers, initializes storage, and parses the command line.
213
+ *
214
+ * @returns A Promise that resolves when the initialization is complete.
215
+ */
153
216
  async initialize() {
217
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
154
218
  this.port = getIntParameter('port') ?? 5540;
219
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
155
220
  this.passcode = getIntParameter('passcode');
221
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
156
222
  this.discriminator = getIntParameter('discriminator');
223
+ // Set the restart mode
157
224
  if (hasParameter('service'))
158
225
  this.restartMode = 'service';
159
226
  if (hasParameter('docker'))
160
227
  this.restartMode = 'docker';
228
+ // Set the matterbridge directory
161
229
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
162
230
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
163
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
231
+ // Create matterbridge logger
232
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
233
+ // Initialize nodeStorage and nodeContext
164
234
  try {
165
235
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
166
236
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
167
237
  this.log.debug('Creating node storage context for matterbridge');
168
238
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
239
+ // TODO: Remove this code when node-persist-manager is updated
240
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
241
  const keys = (await this.nodeStorage?.storage.keys());
170
242
  for (const key of keys) {
171
243
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
245
  await this.nodeStorage?.storage.get(key);
173
246
  }
174
247
  const storages = await this.nodeStorage.getStorageNames();
175
248
  for (const storage of storages) {
176
249
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
177
250
  const nodeContext = await this.nodeStorage?.createStorage(storage);
251
+ // TODO: Remove this code when node-persist-manager is updated
252
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
253
  const keys = (await nodeContext?.storage.keys());
179
254
  keys.forEach(async (key) => {
180
255
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
181
256
  await nodeContext?.get(key);
182
257
  });
183
258
  }
259
+ // Creating a backup of the node storage since it is not corrupted
184
260
  this.log.debug('Creating node storage backup...');
185
261
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
186
262
  this.log.debug('Created node storage backup');
187
263
  }
188
264
  catch (error) {
265
+ // Restoring the backup of the node storage since it is corrupted
189
266
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
190
267
  if (hasParameter('norestore')) {
191
268
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -200,41 +277,44 @@ export class Matterbridge extends EventEmitter {
200
277
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
201
278
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
202
279
  }
280
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
203
281
  if (hasParameter('logger')) {
204
282
  const level = getParameter('logger');
205
283
  if (level === 'debug') {
206
- this.log.logLevel = "debug";
284
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
207
285
  }
208
286
  else if (level === 'info') {
209
- this.log.logLevel = "info";
287
+ this.log.logLevel = "info" /* LogLevel.INFO */;
210
288
  }
211
289
  else if (level === 'notice') {
212
- this.log.logLevel = "notice";
290
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
213
291
  }
214
292
  else if (level === 'warn') {
215
- this.log.logLevel = "warn";
293
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
216
294
  }
217
295
  else if (level === 'error') {
218
- this.log.logLevel = "error";
296
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
219
297
  }
220
298
  else if (level === 'fatal') {
221
- this.log.logLevel = "fatal";
299
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
222
300
  }
223
301
  else {
224
302
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
225
- this.log.logLevel = "info";
303
+ this.log.logLevel = "info" /* LogLevel.INFO */;
226
304
  }
227
305
  }
228
306
  else {
229
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
307
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
230
308
  }
231
309
  MatterbridgeDevice.logLevel = this.log.logLevel;
310
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
232
311
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
233
312
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
234
313
  this.matterbridgeInformation.fileLogger = true;
235
314
  }
236
315
  this.log.notice('Matterbridge is starting...');
237
316
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
317
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
238
318
  if (hasParameter('matterlogger')) {
239
319
  const level = getParameter('matterlogger');
240
320
  if (level === 'debug') {
@@ -265,6 +345,7 @@ export class Matterbridge extends EventEmitter {
265
345
  }
266
346
  Logger.format = MatterLogFormat.ANSI;
267
347
  Logger.setLogger('default', this.createMatterLogger());
348
+ // Create the file logger for matter.js (context: matterFileLog)
268
349
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
269
350
  this.matterbridgeInformation.matterFileLogger = true;
270
351
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -273,6 +354,7 @@ export class Matterbridge extends EventEmitter {
273
354
  });
274
355
  }
275
356
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
357
+ // Set the interface to use for the matter server mdnsInterface
276
358
  if (hasParameter('mdnsinterface')) {
277
359
  this.mdnsInterface = getParameter('mdnsinterface');
278
360
  }
@@ -281,6 +363,7 @@ export class Matterbridge extends EventEmitter {
281
363
  if (this.mdnsInterface === '')
282
364
  this.mdnsInterface = undefined;
283
365
  }
366
+ // Set the listeningAddressIpv4 for the matter commissioning server
284
367
  if (hasParameter('ipv4address')) {
285
368
  this.ipv4address = getParameter('ipv4address');
286
369
  }
@@ -289,6 +372,7 @@ export class Matterbridge extends EventEmitter {
289
372
  if (this.ipv4address === '')
290
373
  this.ipv4address = undefined;
291
374
  }
375
+ // Set the listeningAddressIpv6 for the matter commissioning server
292
376
  if (hasParameter('ipv6address')) {
293
377
  this.ipv6address = getParameter('ipv6address');
294
378
  }
@@ -297,17 +381,23 @@ export class Matterbridge extends EventEmitter {
297
381
  if (this.ipv6address === '')
298
382
  this.ipv6address = undefined;
299
383
  }
384
+ // Initialize PluginManager
300
385
  this.plugins = new PluginManager(this);
301
386
  await this.plugins.loadFromStorage();
387
+ // Initialize DeviceManager
302
388
  this.devices = new DeviceManager(this, this.nodeContext);
389
+ // Get the plugins from node storage and create the plugins node storage contexts
303
390
  for (const plugin of this.plugins) {
304
391
  const packageJson = await this.plugins.parse(plugin);
305
392
  if (packageJson === null && !hasParameter('add')) {
393
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
394
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
306
395
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
307
396
  try {
308
397
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
309
398
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
310
399
  plugin.error = false;
400
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
311
401
  }
312
402
  catch (error) {
313
403
  plugin.error = true;
@@ -324,6 +414,7 @@ export class Matterbridge extends EventEmitter {
324
414
  await plugin.nodeContext.set('description', plugin.description);
325
415
  await plugin.nodeContext.set('author', plugin.author);
326
416
  }
417
+ // Log system info and create .matterbridge directory
327
418
  await this.logNodeAndSystemInfo();
328
419
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
329
420
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -331,6 +422,7 @@ export class Matterbridge extends EventEmitter {
331
422
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
332
423
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
333
424
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
425
+ // Check node version and throw error
334
426
  const minNodeVersion = 18;
335
427
  const nodeVersion = process.versions.node;
336
428
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -338,10 +430,17 @@ export class Matterbridge extends EventEmitter {
338
430
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
339
431
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
340
432
  }
433
+ // Register process handlers
341
434
  this.registerProcessHandlers();
435
+ // Parse command line
342
436
  await this.parseCommandLine();
343
437
  this.initialized = true;
344
438
  }
439
+ /**
440
+ * Parses the command line arguments and performs the corresponding actions.
441
+ * @private
442
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
443
+ */
345
444
  async parseCommandLine() {
346
445
  if (hasParameter('help')) {
347
446
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -449,12 +548,14 @@ export class Matterbridge extends EventEmitter {
449
548
  }
450
549
  if (hasParameter('factoryreset')) {
451
550
  try {
551
+ // Delete matter storage file
452
552
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
453
553
  }
454
554
  catch (err) {
455
555
  this.log.error(`Error deleting storage: ${err}`);
456
556
  }
457
557
  try {
558
+ // Delete node storage directory with its subdirectories
458
559
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
459
560
  }
460
561
  catch (err) {
@@ -468,6 +569,7 @@ export class Matterbridge extends EventEmitter {
468
569
  this.emit('shutdown');
469
570
  return;
470
571
  }
572
+ // Start the matter storage and create the matterbridge context
471
573
  try {
472
574
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
473
575
  }
@@ -502,28 +604,34 @@ export class Matterbridge extends EventEmitter {
502
604
  this.emit('shutdown');
503
605
  return;
504
606
  }
607
+ // Initialize frontend
505
608
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
506
609
  await this.initializeFrontend(getIntParameter('frontend'));
610
+ // Check each 60 minutes the latest versions
507
611
  this.checkUpdateInterval = setInterval(() => {
508
612
  this.getMatterbridgeLatestVersion();
509
613
  for (const plugin of this.plugins) {
510
614
  this.getPluginLatestVersion(plugin);
511
615
  }
512
616
  }, 60 * 60 * 1000);
617
+ // Start the matterbridge in mode test
513
618
  if (hasParameter('test')) {
514
619
  this.bridgeMode = 'bridge';
515
620
  MatterbridgeDevice.bridgeMode = 'bridge';
516
621
  return;
517
622
  }
623
+ // Start the matterbridge in mode controller
518
624
  if (hasParameter('controller')) {
519
625
  this.bridgeMode = 'controller';
520
626
  await this.startController();
521
627
  return;
522
628
  }
629
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
523
630
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
524
631
  this.log.info('Setting default matterbridge start mode to bridge');
525
632
  await this.nodeContext?.set('bridgeMode', 'bridge');
526
633
  }
634
+ // Start matterbridge in bridge mode
527
635
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
528
636
  this.bridgeMode = 'bridge';
529
637
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -532,6 +640,7 @@ export class Matterbridge extends EventEmitter {
532
640
  await this.startBridge();
533
641
  return;
534
642
  }
643
+ // Start matterbridge in childbridge mode
535
644
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
536
645
  this.bridgeMode = 'childbridge';
537
646
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -541,17 +650,28 @@ export class Matterbridge extends EventEmitter {
541
650
  return;
542
651
  }
543
652
  }
653
+ /**
654
+ * Asynchronously loads and starts the registered plugins.
655
+ *
656
+ * This method is responsible for initializing and staarting all enabled plugins.
657
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
658
+ *
659
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
660
+ */
544
661
  async startPlugins() {
662
+ // Check, load and start the plugins
545
663
  for (const plugin of this.plugins) {
546
664
  plugin.configJson = await this.plugins.loadConfig(plugin);
547
665
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
666
+ // Check if the plugin is available
548
667
  if (!(await this.plugins.resolve(plugin.path))) {
549
668
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
550
669
  plugin.enabled = false;
551
670
  plugin.error = true;
552
671
  continue;
553
672
  }
554
- this.getPluginLatestVersion(plugin);
673
+ // Check if the plugin has a new version
674
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
555
675
  if (!plugin.enabled) {
556
676
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
557
677
  continue;
@@ -566,20 +686,26 @@ export class Matterbridge extends EventEmitter {
566
686
  plugin.addedDevices = undefined;
567
687
  plugin.qrPairingCode = undefined;
568
688
  plugin.manualPairingCode = undefined;
569
- this.plugins.load(plugin, true, 'Matterbridge is starting');
689
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
570
690
  }
571
691
  this.wssSendRefreshRequired();
572
692
  }
693
+ /**
694
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
695
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
696
+ */
573
697
  registerProcessHandlers() {
574
698
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
575
699
  process.removeAllListeners('uncaughtException');
576
700
  process.removeAllListeners('unhandledRejection');
577
701
  this.exceptionHandler = async (error) => {
578
702
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
703
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
579
704
  };
580
705
  process.on('uncaughtException', this.exceptionHandler);
581
706
  this.rejectionHandler = async (reason, promise) => {
582
707
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
708
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
583
709
  };
584
710
  process.on('unhandledRejection', this.rejectionHandler);
585
711
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -592,6 +718,9 @@ export class Matterbridge extends EventEmitter {
592
718
  };
593
719
  process.on('SIGTERM', this.sigtermHandler);
594
720
  }
721
+ /**
722
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
723
+ */
595
724
  deregisterProcesslHandlers() {
596
725
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
597
726
  if (this.exceptionHandler)
@@ -608,7 +737,11 @@ export class Matterbridge extends EventEmitter {
608
737
  process.off('SIGTERM', this.sigtermHandler);
609
738
  this.sigtermHandler = undefined;
610
739
  }
740
+ /**
741
+ * Logs the node and system information.
742
+ */
611
743
  async logNodeAndSystemInfo() {
744
+ // IP address information
612
745
  const networkInterfaces = os.networkInterfaces();
613
746
  this.systemInformation.ipv4Address = '';
614
747
  this.systemInformation.ipv6Address = '';
@@ -628,7 +761,7 @@ export class Matterbridge extends EventEmitter {
628
761
  this.systemInformation.macAddress = detail.mac;
629
762
  }
630
763
  }
631
- if (this.systemInformation.ipv4Address !== '') {
764
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
632
765
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
633
766
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
634
767
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -636,19 +769,22 @@ export class Matterbridge extends EventEmitter {
636
769
  break;
637
770
  }
638
771
  }
772
+ // Node information
639
773
  this.systemInformation.nodeVersion = process.versions.node;
640
774
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
641
775
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
642
776
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
777
+ // Host system information
643
778
  this.systemInformation.hostname = os.hostname();
644
779
  this.systemInformation.user = os.userInfo().username;
645
- this.systemInformation.osType = os.type();
646
- this.systemInformation.osRelease = os.release();
647
- this.systemInformation.osPlatform = os.platform();
648
- this.systemInformation.osArch = os.arch();
649
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
650
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
651
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
780
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
781
+ this.systemInformation.osRelease = os.release(); // Kernel version
782
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
783
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
784
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
785
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
786
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
787
+ // Log the system information
652
788
  this.log.debug('Host System Information:');
653
789
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
654
790
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -664,15 +800,19 @@ export class Matterbridge extends EventEmitter {
664
800
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
665
801
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
666
802
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
803
+ // Home directory
667
804
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
668
805
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
669
806
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
807
+ // Package root directory
670
808
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
671
809
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
672
810
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
673
811
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
812
+ // Global node_modules directory
674
813
  if (this.nodeContext)
675
814
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
815
+ // First run of Matterbridge so the node storage is empty
676
816
  if (this.globalModulesDirectory === '') {
677
817
  try {
678
818
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -696,6 +836,7 @@ export class Matterbridge extends EventEmitter {
696
836
  this.log.error(`Error getting global node_modules directory: ${error}`);
697
837
  });
698
838
  }
839
+ // Create the data directory .matterbridge in the home directory
699
840
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
700
841
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
701
842
  try {
@@ -719,6 +860,7 @@ export class Matterbridge extends EventEmitter {
719
860
  }
720
861
  }
721
862
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
863
+ // Create the plugin directory Matterbridge in the home directory
722
864
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
723
865
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
724
866
  try {
@@ -742,19 +884,28 @@ export class Matterbridge extends EventEmitter {
742
884
  }
743
885
  }
744
886
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
887
+ // Matterbridge version
745
888
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
746
889
  this.matterbridgeVersion = packageJson.version;
747
890
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
748
891
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
892
+ // Matterbridge latest version
749
893
  if (this.nodeContext)
750
894
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
751
895
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
752
896
  this.getMatterbridgeLatestVersion();
897
+ // Current working directory
753
898
  const currentDir = process.cwd();
754
899
  this.log.debug(`Current Working Directory: ${currentDir}`);
900
+ // Command line arguments (excluding 'node' and the script name)
755
901
  const cmdArgs = process.argv.slice(2).join(' ');
756
902
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
757
903
  }
904
+ /**
905
+ * Retrieves the latest version of a package from the npm registry.
906
+ * @param packageName - The name of the package.
907
+ * @returns A Promise that resolves to the latest version of the package.
908
+ */
758
909
  async getLatestVersion(packageName) {
759
910
  return new Promise((resolve, reject) => {
760
911
  this.execRunningCount++;
@@ -769,6 +920,10 @@ export class Matterbridge extends EventEmitter {
769
920
  });
770
921
  });
771
922
  }
923
+ /**
924
+ * Retrieves the path to the global Node.js modules directory.
925
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
926
+ */
772
927
  async getGlobalNodeModules() {
773
928
  return new Promise((resolve, reject) => {
774
929
  this.execRunningCount++;
@@ -783,6 +938,11 @@ export class Matterbridge extends EventEmitter {
783
938
  });
784
939
  });
785
940
  }
941
+ /**
942
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
943
+ * @private
944
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
945
+ */
786
946
  async getMatterbridgeLatestVersion() {
787
947
  this.getLatestVersion('matterbridge')
788
948
  .then(async (matterbridgeLatestVersion) => {
@@ -799,8 +959,19 @@ export class Matterbridge extends EventEmitter {
799
959
  })
800
960
  .catch((error) => {
801
961
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
962
+ // error.stack && this.log.debug(error.stack);
802
963
  });
803
964
  }
965
+ /**
966
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
967
+ * If the plugin's version is different from the latest version, logs a warning message.
968
+ * If the plugin's version is the same as the latest version, logs an info message.
969
+ * If there is an error retrieving the latest version, logs an error message.
970
+ *
971
+ * @private
972
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
973
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
974
+ */
804
975
  async getPluginLatestVersion(plugin) {
805
976
  this.getLatestVersion(plugin.name)
806
977
  .then(async (latestVersion) => {
@@ -812,40 +983,54 @@ export class Matterbridge extends EventEmitter {
812
983
  })
813
984
  .catch((error) => {
814
985
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
986
+ // error.stack && this.log.debug(error.stack);
815
987
  });
816
988
  }
989
+ /**
990
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
991
+ *
992
+ * @returns {Function} The MatterLogger function.
993
+ */
817
994
  createMatterLogger() {
818
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
995
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
819
996
  return (_level, formattedLog) => {
820
997
  const logger = formattedLog.slice(44, 44 + 20).trim();
821
998
  const message = formattedLog.slice(65);
822
999
  matterLogger.logName = logger;
823
1000
  switch (_level) {
824
1001
  case MatterLogLevel.DEBUG:
825
- matterLogger.log("debug", message);
1002
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
826
1003
  break;
827
1004
  case MatterLogLevel.INFO:
828
- matterLogger.log("info", message);
1005
+ matterLogger.log("info" /* LogLevel.INFO */, message);
829
1006
  break;
830
1007
  case MatterLogLevel.NOTICE:
831
- matterLogger.log("notice", message);
1008
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
832
1009
  break;
833
1010
  case MatterLogLevel.WARN:
834
- matterLogger.log("warn", message);
1011
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
835
1012
  break;
836
1013
  case MatterLogLevel.ERROR:
837
- matterLogger.log("error", message);
1014
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
838
1015
  break;
839
1016
  case MatterLogLevel.FATAL:
840
- matterLogger.log("fatal", message);
1017
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
841
1018
  break;
842
1019
  default:
843
- matterLogger.log("debug", message);
1020
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
844
1021
  break;
845
1022
  }
846
1023
  };
847
1024
  }
1025
+ /**
1026
+ * Creates a Matter File Logger.
1027
+ *
1028
+ * @param {string} filePath - The path to the log file.
1029
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1030
+ * @returns {Function} - A function that logs formatted messages to the log file.
1031
+ */
848
1032
  async createMatterFileLogger(filePath, unlink = false) {
1033
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
849
1034
  let fileSize = 0;
850
1035
  if (unlink) {
851
1036
  try {
@@ -894,53 +1079,83 @@ export class Matterbridge extends EventEmitter {
894
1079
  }
895
1080
  };
896
1081
  }
1082
+ /**
1083
+ * Update matterbridge and cleanup.
1084
+ */
897
1085
  async updateProcess() {
898
1086
  await this.cleanup('updating...', false);
899
1087
  }
1088
+ /**
1089
+ * Restarts the process by spawning a new process and exiting the current process.
1090
+ */
900
1091
  async restartProcess() {
901
1092
  await this.cleanup('restarting...', true);
902
1093
  }
1094
+ /**
1095
+ * Shut down the process by exiting the current process.
1096
+ */
903
1097
  async shutdownProcess() {
904
1098
  await this.cleanup('shutting down...', false);
905
1099
  }
1100
+ /**
1101
+ * Shut down the process and reset.
1102
+ */
906
1103
  async unregisterAndShutdownProcess() {
907
1104
  this.log.info('Unregistering all devices and shutting down...');
908
- for (const plugin of this.plugins) {
1105
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
909
1106
  await this.removeAllBridgedDevices(plugin.name);
910
1107
  }
911
1108
  await this.cleanup('unregistered all devices and shutting down...', false);
912
1109
  }
1110
+ /**
1111
+ * Shut down the process and reset.
1112
+ */
913
1113
  async shutdownProcessAndReset() {
914
1114
  await this.cleanup('shutting down with reset...', false);
915
1115
  }
1116
+ /**
1117
+ * Shut down the process and factory reset.
1118
+ */
916
1119
  async shutdownProcessAndFactoryReset() {
917
1120
  await this.cleanup('shutting down with factory reset...', false);
918
1121
  }
1122
+ /**
1123
+ * Cleans up the Matterbridge instance.
1124
+ * @param message - The cleanup message.
1125
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1126
+ * @returns A promise that resolves when the cleanup is completed.
1127
+ */
919
1128
  async cleanup(message, restart = false) {
920
1129
  if (this.initialized && !this.hasCleanupStarted) {
921
1130
  this.hasCleanupStarted = true;
922
1131
  this.log.info(message);
1132
+ // Deregisters the process handlers
923
1133
  this.deregisterProcesslHandlers();
1134
+ // Clear the start matter interval
924
1135
  if (this.startMatterInterval) {
925
1136
  clearInterval(this.startMatterInterval);
926
1137
  this.startMatterInterval = undefined;
927
1138
  this.log.debug('Start matter interval cleared');
928
1139
  }
1140
+ // Clear the check update interval
929
1141
  if (this.checkUpdateInterval) {
930
1142
  clearInterval(this.checkUpdateInterval);
931
1143
  this.checkUpdateInterval = undefined;
932
1144
  this.log.debug('Check update interval cleared');
933
1145
  }
1146
+ // Clear the configure timeout
934
1147
  if (this.configureTimeout) {
935
1148
  clearTimeout(this.configureTimeout);
936
1149
  this.configureTimeout = undefined;
937
1150
  this.log.debug('Matterbridge configure timeout cleared');
938
1151
  }
1152
+ // Clear the reachability timeout
939
1153
  if (this.reachabilityTimeout) {
940
1154
  clearTimeout(this.reachabilityTimeout);
941
1155
  this.reachabilityTimeout = undefined;
942
1156
  this.log.debug('Matterbridge reachability timeout cleared');
943
1157
  }
1158
+ // Calling the shutdown method of each plugin and clear the reachability timeout
944
1159
  for (const plugin of this.plugins) {
945
1160
  if (!plugin.enabled || plugin.error)
946
1161
  continue;
@@ -951,24 +1166,29 @@ export class Matterbridge extends EventEmitter {
951
1166
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
952
1167
  }
953
1168
  }
1169
+ // Close the http server
954
1170
  if (this.httpServer) {
955
1171
  this.httpServer.close();
956
1172
  this.httpServer.removeAllListeners();
957
1173
  this.httpServer = undefined;
958
1174
  this.log.debug('Frontend http server closed successfully');
959
1175
  }
1176
+ // Close the https server
960
1177
  if (this.httpsServer) {
961
1178
  this.httpsServer.close();
962
1179
  this.httpsServer.removeAllListeners();
963
1180
  this.httpsServer = undefined;
964
1181
  this.log.debug('Frontend https server closed successfully');
965
1182
  }
1183
+ // Remove listeners from the express app
966
1184
  if (this.expressApp) {
967
1185
  this.expressApp.removeAllListeners();
968
1186
  this.expressApp = undefined;
969
1187
  this.log.debug('Frontend app closed successfully');
970
1188
  }
1189
+ // Close the WebSocket server
971
1190
  if (this.webSocketServer) {
1191
+ // Close all active connections
972
1192
  this.webSocketServer.clients.forEach((client) => {
973
1193
  if (client.readyState === WebSocket.OPEN) {
974
1194
  client.close();
@@ -984,26 +1204,35 @@ export class Matterbridge extends EventEmitter {
984
1204
  });
985
1205
  this.webSocketServer = undefined;
986
1206
  }
1207
+ // Closing matter
987
1208
  await this.stopMatterServer();
1209
+ // Closing matter storage
988
1210
  await this.stopMatterStorage();
1211
+ // Remove the matterfilelogger
989
1212
  try {
990
1213
  Logger.removeLogger('matterfilelogger');
1214
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
991
1215
  }
992
1216
  catch (error) {
1217
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
993
1218
  }
1219
+ // Serialize registeredDevices
994
1220
  if (this.nodeStorage && this.nodeContext) {
995
1221
  this.log.info('Saving registered devices...');
996
1222
  const serializedRegisteredDevices = [];
997
1223
  this.devices.forEach(async (device) => {
998
1224
  const serializedMatterbridgeDevice = device.serialize();
1225
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
999
1226
  if (serializedMatterbridgeDevice)
1000
1227
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1001
1228
  });
1002
1229
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1003
1230
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1231
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1004
1232
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1005
1233
  await this.nodeContext.close();
1006
1234
  this.nodeContext = undefined;
1235
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1007
1236
  for (const plugin of this.plugins) {
1008
1237
  if (plugin.nodeContext) {
1009
1238
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1034,13 +1263,16 @@ export class Matterbridge extends EventEmitter {
1034
1263
  }
1035
1264
  else {
1036
1265
  if (message === 'shutting down with reset...') {
1266
+ // Delete matter storage file
1037
1267
  this.log.info('Resetting Matterbridge commissioning information...');
1038
1268
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1039
1269
  this.log.info('Reset done! Remove all paired devices from the controllers.');
1040
1270
  }
1041
1271
  if (message === 'shutting down with factory reset...') {
1272
+ // Delete matter storage file
1042
1273
  this.log.info('Resetting Matterbridge commissioning information...');
1043
1274
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1275
+ // Delete node storage directory with its subdirectories
1044
1276
  this.log.info('Resetting Matterbridge storage...');
1045
1277
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1046
1278
  this.log.info('Factory reset done! Remove all paired devices from the controllers.');
@@ -1053,19 +1285,33 @@ export class Matterbridge extends EventEmitter {
1053
1285
  this.initialized = false;
1054
1286
  }
1055
1287
  }
1288
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1056
1289
  async addBridgedEndpoint(pluginName, device) {
1290
+ // Nothing to do here
1057
1291
  }
1292
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1058
1293
  async removeBridgedEndpoint(pluginName, device) {
1294
+ // Nothing to do here
1059
1295
  }
1296
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1060
1297
  async removeAllBridgedEndpoints(pluginName) {
1298
+ // Nothing to do here
1061
1299
  }
1300
+ /**
1301
+ * Adds a bridged device to the Matterbridge.
1302
+ * @param pluginName - The name of the plugin.
1303
+ * @param device - The bridged device to add.
1304
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1305
+ */
1062
1306
  async addBridgedDevice(pluginName, device) {
1063
1307
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1308
+ // Check if the plugin is registered
1064
1309
  const plugin = this.plugins.get(pluginName);
1065
1310
  if (!plugin) {
1066
1311
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1067
1312
  return;
1068
1313
  }
1314
+ // Register and add the device to matterbridge aggregator in bridge mode
1069
1315
  if (this.bridgeMode === 'bridge') {
1070
1316
  if (!this.matterAggregator) {
1071
1317
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1073,8 +1319,11 @@ export class Matterbridge extends EventEmitter {
1073
1319
  }
1074
1320
  this.matterAggregator.addBridgedDevice(device);
1075
1321
  }
1322
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1323
+ // Register and add the device in childbridge mode
1076
1324
  if (this.bridgeMode === 'childbridge') {
1077
1325
  if (plugin.type === 'AccessoryPlatform') {
1326
+ // Check if the plugin is locked with the commissioning server
1078
1327
  if (!plugin.locked) {
1079
1328
  plugin.locked = true;
1080
1329
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1088,6 +1337,7 @@ export class Matterbridge extends EventEmitter {
1088
1337
  }
1089
1338
  }
1090
1339
  if (plugin.type === 'DynamicPlatform') {
1340
+ // Check if the plugin is locked with the commissioning server and the aggregator
1091
1341
  if (!plugin.locked) {
1092
1342
  plugin.locked = true;
1093
1343
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1095,7 +1345,7 @@ export class Matterbridge extends EventEmitter {
1095
1345
  this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1096
1346
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1097
1347
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1098
- plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
1348
+ plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name); // Generate serialNumber and uniqueId
1099
1349
  this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1100
1350
  plugin.commissioningServer.addDevice(plugin.aggregator);
1101
1351
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
@@ -1108,16 +1358,25 @@ export class Matterbridge extends EventEmitter {
1108
1358
  plugin.registeredDevices++;
1109
1359
  if (plugin.addedDevices !== undefined)
1110
1360
  plugin.addedDevices++;
1361
+ // Add the device to the DeviceManager
1111
1362
  this.devices.set(device);
1112
1363
  this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1113
1364
  }
1365
+ /**
1366
+ * Removes a bridged device from the Matterbridge.
1367
+ * @param pluginName - The name of the plugin.
1368
+ * @param device - The device to be removed.
1369
+ * @returns A Promise that resolves when the device is successfully removed.
1370
+ */
1114
1371
  async removeBridgedDevice(pluginName, device) {
1115
1372
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1373
+ // Check if the plugin is registered
1116
1374
  const plugin = this.plugins.get(pluginName);
1117
1375
  if (!plugin) {
1118
1376
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1119
1377
  return;
1120
1378
  }
1379
+ // Remove the device from matterbridge aggregator in bridge mode
1121
1380
  if (this.bridgeMode === 'bridge') {
1122
1381
  if (!this.matterAggregator) {
1123
1382
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1127,6 +1386,8 @@ export class Matterbridge extends EventEmitter {
1127
1386
  device.setBridgedDeviceReachability(false);
1128
1387
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1129
1388
  }
1389
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1390
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1130
1391
  this.matterAggregator?.removeBridgedDevice(device);
1131
1392
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1132
1393
  if (plugin.registeredDevices !== undefined)
@@ -1134,6 +1395,7 @@ export class Matterbridge extends EventEmitter {
1134
1395
  if (plugin.addedDevices !== undefined)
1135
1396
  plugin.addedDevices--;
1136
1397
  }
1398
+ // Remove the device in childbridge mode
1137
1399
  if (this.bridgeMode === 'childbridge') {
1138
1400
  if (plugin.type === 'AccessoryPlatform') {
1139
1401
  if (!plugin.commissioningServer) {
@@ -1157,14 +1419,22 @@ export class Matterbridge extends EventEmitter {
1157
1419
  plugin.registeredDevices--;
1158
1420
  if (plugin.addedDevices !== undefined)
1159
1421
  plugin.addedDevices--;
1422
+ // Remove the commissioning server
1160
1423
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1161
1424
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1162
1425
  plugin.commissioningServer = undefined;
1163
1426
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1164
1427
  }
1165
1428
  }
1429
+ // Remove the device from the DeviceManager
1166
1430
  this.devices.remove(device);
1167
1431
  }
1432
+ /**
1433
+ * Removes all bridged devices associated with a specific plugin.
1434
+ *
1435
+ * @param pluginName - The name of the plugin.
1436
+ * @returns A promise that resolves when all devices have been removed.
1437
+ */
1168
1438
  async removeAllBridgedDevices(pluginName) {
1169
1439
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1170
1440
  this.devices.forEach(async (device) => {
@@ -1173,7 +1443,13 @@ export class Matterbridge extends EventEmitter {
1173
1443
  }
1174
1444
  });
1175
1445
  }
1446
+ /**
1447
+ * Starts the Matterbridge in bridge mode.
1448
+ * @private
1449
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1450
+ */
1176
1451
  async startBridge() {
1452
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1177
1453
  if (!this.storageManager)
1178
1454
  throw new Error('No storage manager initialized');
1179
1455
  if (!this.matterbridgeContext)
@@ -1192,6 +1468,7 @@ export class Matterbridge extends EventEmitter {
1192
1468
  let failCount = 0;
1193
1469
  this.startMatterInterval = setInterval(async () => {
1194
1470
  for (const plugin of this.plugins) {
1471
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1195
1472
  if (!plugin.enabled)
1196
1473
  continue;
1197
1474
  if (plugin.error) {
@@ -1216,15 +1493,18 @@ export class Matterbridge extends EventEmitter {
1216
1493
  clearInterval(this.startMatterInterval);
1217
1494
  this.startMatterInterval = undefined;
1218
1495
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1496
+ // Start the Matter server
1219
1497
  await this.startMatterServer();
1220
1498
  this.log.notice('Matter server started');
1499
+ // Show the QR code for commissioning or log the already commissioned message
1221
1500
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1501
+ // Configure the plugins
1222
1502
  this.configureTimeout = setTimeout(async () => {
1223
1503
  for (const plugin of this.plugins) {
1224
1504
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1225
1505
  continue;
1226
1506
  try {
1227
- await this.plugins.configure(plugin);
1507
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1228
1508
  }
1229
1509
  catch (error) {
1230
1510
  plugin.error = true;
@@ -1233,6 +1513,7 @@ export class Matterbridge extends EventEmitter {
1233
1513
  }
1234
1514
  this.wssSendRefreshRequired();
1235
1515
  }, 30 * 1000);
1516
+ // Setting reachability to true
1236
1517
  this.reachabilityTimeout = setTimeout(() => {
1237
1518
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1238
1519
  if (this.commissioningServer)
@@ -1242,7 +1523,14 @@ export class Matterbridge extends EventEmitter {
1242
1523
  }, 60 * 1000);
1243
1524
  }, 1000);
1244
1525
  }
1526
+ /**
1527
+ * Starts the Matterbridge in childbridge mode.
1528
+ * @private
1529
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1530
+ */
1245
1531
  async startChildbridge() {
1532
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1533
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1246
1534
  if (!this.storageManager)
1247
1535
  throw new Error('No storage manager initialized');
1248
1536
  this.matterServer = this.createMatterServer(this.storageManager);
@@ -1252,6 +1540,7 @@ export class Matterbridge extends EventEmitter {
1252
1540
  this.startMatterInterval = setInterval(async () => {
1253
1541
  let allStarted = true;
1254
1542
  for (const plugin of this.plugins) {
1543
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1255
1544
  if (!plugin.enabled)
1256
1545
  continue;
1257
1546
  if (plugin.error) {
@@ -1279,14 +1568,16 @@ export class Matterbridge extends EventEmitter {
1279
1568
  clearInterval(this.startMatterInterval);
1280
1569
  this.startMatterInterval = undefined;
1281
1570
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1571
+ // Start the Matter server
1282
1572
  await this.startMatterServer();
1283
1573
  this.log.notice('Matter server started');
1574
+ // Configure the plugins
1284
1575
  this.configureTimeout = setTimeout(async () => {
1285
1576
  for (const plugin of this.plugins) {
1286
1577
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1287
1578
  continue;
1288
1579
  try {
1289
- await this.plugins.configure(plugin);
1580
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1290
1581
  }
1291
1582
  catch (error) {
1292
1583
  plugin.error = true;
@@ -1315,6 +1606,7 @@ export class Matterbridge extends EventEmitter {
1315
1606
  continue;
1316
1607
  }
1317
1608
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1609
+ // Setting reachability to true
1318
1610
  plugin.reachabilityTimeout = setTimeout(() => {
1319
1611
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1320
1612
  if (plugin.commissioningServer)
@@ -1327,6 +1619,11 @@ export class Matterbridge extends EventEmitter {
1327
1619
  }
1328
1620
  }, 1000);
1329
1621
  }
1622
+ /**
1623
+ * Starts the Matterbridge controller.
1624
+ * @private
1625
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1626
+ */
1330
1627
  async startController() {
1331
1628
  if (!this.storageManager) {
1332
1629
  this.log.error('No storage manager initialized');
@@ -1389,7 +1686,7 @@ export class Matterbridge extends EventEmitter {
1389
1686
  const nodeId = await this.commissioningController.commissionNode(options);
1390
1687
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1391
1688
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1392
- }
1689
+ } // (hasParameter('pairingcode'))
1393
1690
  if (hasParameter('unpairall')) {
1394
1691
  this.log.info('***Commissioning controller unpairing all nodes...');
1395
1692
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1400,6 +1697,8 @@ export class Matterbridge extends EventEmitter {
1400
1697
  return;
1401
1698
  }
1402
1699
  if (hasParameter('discover')) {
1700
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1701
+ // console.log(discover);
1403
1702
  }
1404
1703
  if (!this.commissioningController.isCommissioned()) {
1405
1704
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1440,10 +1739,12 @@ export class Matterbridge extends EventEmitter {
1440
1739
  },
1441
1740
  });
1442
1741
  node.logStructure();
1742
+ // Get the interaction client
1443
1743
  this.log.info('Getting the interaction client');
1444
1744
  const interactionClient = await node.getInteractionClient();
1445
1745
  let cluster;
1446
1746
  let attributes;
1747
+ // Log BasicInformationCluster
1447
1748
  cluster = BasicInformationCluster;
1448
1749
  attributes = await interactionClient.getMultipleAttributes({
1449
1750
  attributes: [{ clusterId: cluster.id }],
@@ -1453,6 +1754,7 @@ export class Matterbridge extends EventEmitter {
1453
1754
  attributes.forEach((attribute) => {
1454
1755
  this.log.info(`- 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}`);
1455
1756
  });
1757
+ // Log PowerSourceCluster
1456
1758
  cluster = PowerSourceCluster;
1457
1759
  attributes = await interactionClient.getMultipleAttributes({
1458
1760
  attributes: [{ clusterId: cluster.id }],
@@ -1462,6 +1764,7 @@ export class Matterbridge extends EventEmitter {
1462
1764
  attributes.forEach((attribute) => {
1463
1765
  this.log.info(`- 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}`);
1464
1766
  });
1767
+ // Log ThreadNetworkDiagnostics
1465
1768
  cluster = ThreadNetworkDiagnosticsCluster;
1466
1769
  attributes = await interactionClient.getMultipleAttributes({
1467
1770
  attributes: [{ clusterId: cluster.id }],
@@ -1471,6 +1774,7 @@ export class Matterbridge extends EventEmitter {
1471
1774
  attributes.forEach((attribute) => {
1472
1775
  this.log.info(`- 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}`);
1473
1776
  });
1777
+ // Log SwitchCluster
1474
1778
  cluster = SwitchCluster;
1475
1779
  attributes = await interactionClient.getMultipleAttributes({
1476
1780
  attributes: [{ clusterId: cluster.id }],
@@ -1491,6 +1795,15 @@ export class Matterbridge extends EventEmitter {
1491
1795
  this.log.info('Subscribed to all attributes and events');
1492
1796
  }
1493
1797
  }
1798
+ /** ***********************************************************************************************************************************/
1799
+ /** Matter.js methods */
1800
+ /** ***********************************************************************************************************************************/
1801
+ /**
1802
+ * Starts the matter storage process based on the specified storage type and name.
1803
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1804
+ * @param {string} storageName - The name of the storage file.
1805
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1806
+ */
1494
1807
  async startMatterStorage(storageType, storageName) {
1495
1808
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1496
1809
  if (storageType === 'disk') {
@@ -1536,6 +1849,12 @@ export class Matterbridge extends EventEmitter {
1536
1849
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1537
1850
  this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1538
1851
  }
1852
+ /**
1853
+ * Makes a backup copy of the specified matter JSON storage file.
1854
+ *
1855
+ * @param storageName - The name of the JSON storage file to be backed up.
1856
+ * @param backupName - The name of the backup file to be created.
1857
+ */
1539
1858
  async backupMatterStorage(storageName, backupName) {
1540
1859
  try {
1541
1860
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1556,6 +1875,12 @@ export class Matterbridge extends EventEmitter {
1556
1875
  }
1557
1876
  }
1558
1877
  }
1878
+ /**
1879
+ * Restore the specified matter JSON storage file.
1880
+ *
1881
+ * @param backupName - The name of the backup file to restore from.
1882
+ * @param storageName - The name of the JSON storage file to restored.
1883
+ */
1559
1884
  async restoreMatterStorage(backupName, storageName) {
1560
1885
  try {
1561
1886
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1576,6 +1901,10 @@ export class Matterbridge extends EventEmitter {
1576
1901
  }
1577
1902
  }
1578
1903
  }
1904
+ /**
1905
+ * Stops the matter storage.
1906
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1907
+ */
1579
1908
  async stopMatterStorage() {
1580
1909
  this.log.debug('Stopping storage');
1581
1910
  await this.storageManager?.close();
@@ -1584,8 +1913,14 @@ export class Matterbridge extends EventEmitter {
1584
1913
  this.matterbridgeContext = undefined;
1585
1914
  this.mattercontrollerContext = undefined;
1586
1915
  }
1916
+ /**
1917
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
1918
+ * @param storageManager The storage manager to be used by the Matter server.
1919
+ *
1920
+ */
1587
1921
  createMatterServer(storageManager) {
1588
1922
  this.log.debug('Creating matter server');
1923
+ // Validate mdnsInterface
1589
1924
  if (this.mdnsInterface) {
1590
1925
  const networkInterfaces = os.networkInterfaces();
1591
1926
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1601,6 +1936,10 @@ export class Matterbridge extends EventEmitter {
1601
1936
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1602
1937
  return matterServer;
1603
1938
  }
1939
+ /**
1940
+ * Starts the Matter server.
1941
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1942
+ */
1604
1943
  async startMatterServer() {
1605
1944
  if (!this.matterServer) {
1606
1945
  this.log.error('No matter server initialized');
@@ -1610,7 +1949,11 @@ export class Matterbridge extends EventEmitter {
1610
1949
  this.log.debug('Starting matter server...');
1611
1950
  await this.matterServer.start();
1612
1951
  this.log.debug('Started matter server');
1952
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1613
1953
  }
1954
+ /**
1955
+ * Stops the Matter server, commissioningServer and commissioningController.
1956
+ */
1614
1957
  async stopMatterServer() {
1615
1958
  this.log.debug('Stopping matter commissioningServer');
1616
1959
  await this.commissioningServer?.close();
@@ -1624,22 +1967,78 @@ export class Matterbridge extends EventEmitter {
1624
1967
  this.matterAggregator = undefined;
1625
1968
  this.matterServer = undefined;
1626
1969
  }
1970
+ /**
1971
+ * Creates a Matter Aggregator.
1972
+ * @param {StorageContext} context - The storage context.
1973
+ * @returns {Aggregator} - The created Matter Aggregator.
1974
+ */
1975
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1627
1976
  async createMatterAggregator(context, pluginName) {
1977
+ /*
1978
+ const random = randomBytes(8).toString('hex');
1979
+ await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
1980
+ await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
1981
+
1982
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with uniqueId ${await context.get<string>('aggregatorUniqueId')} serialNumber ${await context.get<string>('aggregatorSerialNumber')}`);
1983
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with softwareVersion ${await context.get<number>('softwareVersion', 1)} softwareVersionString ${await context.get<string>('softwareVersionString', '1.0.0')}`);
1984
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with hardwareVersion ${await context.get<number>('hardwareVersion', 1)} hardwareVersionString ${await context.get<string>('hardwareVersionString', '1.0.0')}`);
1985
+ */
1628
1986
  const matterAggregator = new Aggregator();
1987
+ /*
1988
+ matterAggregator.addClusterServer(
1989
+ ClusterServer(
1990
+ BasicInformationCluster,
1991
+ {
1992
+ dataModelRevision: 1,
1993
+ location: 'FR',
1994
+ vendorId: VendorId(0xfff1),
1995
+ vendorName: 'Matterbridge',
1996
+ productId: 0x8000,
1997
+ productName: 'Matterbridge aggregator',
1998
+ productLabel: 'Matterbridge aggregator',
1999
+ nodeLabel: 'Matterbridge aggregator',
2000
+ serialNumber: await context.get<string>('aggregatorSerialNumber'),
2001
+ uniqueId: await context.get<string>('aggregatorUniqueId'),
2002
+ softwareVersion: await context.get<number>('softwareVersion', 1),
2003
+ softwareVersionString: await context.get<string>('softwareVersionString', '1.0.0'),
2004
+ hardwareVersion: await context.get<number>('hardwareVersion', 1),
2005
+ hardwareVersionString: await context.get<string>('hardwareVersionString', '1.0.0'),
2006
+ reachable: true,
2007
+ capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 },
2008
+ specificationVersion: Specification.SPECIFICATION_VERSION,
2009
+ maxPathsPerInvoke: 1,
2010
+ },
2011
+ {},
2012
+ {
2013
+ startUp: true,
2014
+ shutDown: true,
2015
+ leave: true,
2016
+ reachableChanged: true,
2017
+ },
2018
+ ),
2019
+ );
2020
+ */
1629
2021
  return matterAggregator;
1630
2022
  }
2023
+ /**
2024
+ * Creates a matter commissioning server.
2025
+ *
2026
+ * @param {StorageContext} context - The storage context.
2027
+ * @param {string} pluginName - The name of the commissioning server.
2028
+ * @returns {CommissioningServer} The created commissioning server.
2029
+ */
1631
2030
  async createCommisioningServer(context, pluginName) {
1632
2031
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1633
2032
  const deviceName = await context.get('deviceName');
1634
2033
  const deviceType = await context.get('deviceType');
1635
2034
  const vendorId = await context.get('vendorId');
1636
- const vendorName = await context.get('vendorName');
2035
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1637
2036
  const productId = await context.get('productId');
1638
- const productName = await context.get('productName');
2037
+ const productName = await context.get('productName'); // Home app = Model
1639
2038
  const serialNumber = await context.get('serialNumber');
1640
2039
  const uniqueId = await context.get('uniqueId');
1641
2040
  const softwareVersion = await context.get('softwareVersion', 1);
1642
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2041
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1643
2042
  const hardwareVersion = await context.get('hardwareVersion', 1);
1644
2043
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1645
2044
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1647,6 +2046,7 @@ export class Matterbridge extends EventEmitter {
1647
2046
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1648
2047
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1649
2048
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${this.port} passcode ${this.passcode} discriminator ${this.discriminator}`);
2049
+ // Validate ipv4address
1650
2050
  if (this.ipv4address) {
1651
2051
  const networkInterfaces = os.networkInterfaces();
1652
2052
  const availableAddresses = Object.values(networkInterfaces)
@@ -1661,6 +2061,7 @@ export class Matterbridge extends EventEmitter {
1661
2061
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1662
2062
  }
1663
2063
  }
2064
+ // Validate ipv6address
1664
2065
  if (this.ipv6address) {
1665
2066
  const networkInterfaces = os.networkInterfaces();
1666
2067
  const availableAddresses = Object.values(networkInterfaces)
@@ -1691,7 +2092,7 @@ export class Matterbridge extends EventEmitter {
1691
2092
  nodeLabel: productName,
1692
2093
  productLabel: productName,
1693
2094
  softwareVersion,
1694
- softwareVersionString,
2095
+ softwareVersionString, // Home app = Firmware Revision
1695
2096
  hardwareVersion,
1696
2097
  hardwareVersionString,
1697
2098
  uniqueId,
@@ -1787,6 +2188,24 @@ export class Matterbridge extends EventEmitter {
1787
2188
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1788
2189
  return commissioningServer;
1789
2190
  }
2191
+ /**
2192
+ * Creates a commissioning server storage context.
2193
+ *
2194
+ * @param pluginName - The name of the plugin.
2195
+ * @param deviceName - The name of the device.
2196
+ * @param deviceType - The type of the device.
2197
+ * @param vendorId - The vendor ID.
2198
+ * @param vendorName - The vendor name.
2199
+ * @param productId - The product ID.
2200
+ * @param productName - The product name.
2201
+ * @param serialNumber - The serial number of the device (optional).
2202
+ * @param uniqueId - The unique ID of the device (optional).
2203
+ * @param softwareVersion - The software version of the device (optional).
2204
+ * @param softwareVersionString - The software version string of the device (optional).
2205
+ * @param hardwareVersion - The hardware version of the device (optional).
2206
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2207
+ * @returns The storage context for the commissioning server.
2208
+ */
1790
2209
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1791
2210
  if (!this.storageManager)
1792
2211
  throw new Error('No storage manager initialized');
@@ -1814,6 +2233,13 @@ export class Matterbridge extends EventEmitter {
1814
2233
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1815
2234
  return storageContext;
1816
2235
  }
2236
+ /**
2237
+ * Imports the commissioning server context for a specific plugin and device.
2238
+ * @param pluginName - The name of the plugin.
2239
+ * @param device - The MatterbridgeDevice object representing the device.
2240
+ * @returns The commissioning server context.
2241
+ * @throws Error if the BasicInformationCluster is not found.
2242
+ */
1817
2243
  async importCommissioningServerContext(pluginName, device) {
1818
2244
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1819
2245
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -1848,6 +2274,14 @@ export class Matterbridge extends EventEmitter {
1848
2274
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1849
2275
  return storageContext;
1850
2276
  }
2277
+ /**
2278
+ * Shows the commissioning server QR code for a given plugin.
2279
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2280
+ * @param {StorageContext} storageContext - The storage context instance.
2281
+ * @param {NodeStorage} nodeContext - The node storage instance.
2282
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2283
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2284
+ */
1851
2285
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1852
2286
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
1853
2287
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -1858,7 +2292,8 @@ export class Matterbridge extends EventEmitter {
1858
2292
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1859
2293
  const QrCode = new QrCodeSchema();
1860
2294
  this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
1861
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2295
+ // eslint-disable-next-line no-console
2296
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
1862
2297
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
1863
2298
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
1864
2299
  if (pluginName === 'Matterbridge') {
@@ -1905,6 +2340,12 @@ export class Matterbridge extends EventEmitter {
1905
2340
  }
1906
2341
  this.wssSendRefreshRequired();
1907
2342
  }
2343
+ /**
2344
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2345
+ *
2346
+ * @param fabricInfo - The array of exposed fabric information objects.
2347
+ * @returns An array of sanitized exposed fabric information objects.
2348
+ */
1908
2349
  sanitizeFabricInformations(fabricInfo) {
1909
2350
  return fabricInfo.map((info) => {
1910
2351
  return {
@@ -1918,6 +2359,12 @@ export class Matterbridge extends EventEmitter {
1918
2359
  };
1919
2360
  });
1920
2361
  }
2362
+ /**
2363
+ * Sanitizes the session information by converting bigint properties to string.
2364
+ *
2365
+ * @param sessionInfo - The array of session information objects.
2366
+ * @returns An array of sanitized session information objects.
2367
+ */
1921
2368
  sanitizeSessionInformation(sessionInfo) {
1922
2369
  return sessionInfo
1923
2370
  .filter((session) => session.isPeerActive)
@@ -1945,6 +2392,12 @@ export class Matterbridge extends EventEmitter {
1945
2392
  };
1946
2393
  });
1947
2394
  }
2395
+ /**
2396
+ * Sets the reachability of a commissioning server and trigger.
2397
+ *
2398
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2399
+ * @param {boolean} reachable - The new reachability status.
2400
+ */
1948
2401
  setCommissioningServerReachability(commissioningServer, reachable) {
1949
2402
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
1950
2403
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1952,6 +2405,11 @@ export class Matterbridge extends EventEmitter {
1952
2405
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1953
2406
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1954
2407
  }
2408
+ /**
2409
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2410
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2411
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2412
+ */
1955
2413
  setAggregatorReachability(matterAggregator, reachable) {
1956
2414
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
1957
2415
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1964,6 +2422,12 @@ export class Matterbridge extends EventEmitter {
1964
2422
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
1965
2423
  });
1966
2424
  }
2425
+ /**
2426
+ * Sets the reachability of a device and trigger.
2427
+ *
2428
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2429
+ * @param {boolean} reachable - The new reachability status of the device.
2430
+ */
1967
2431
  setDeviceReachability(device, reachable) {
1968
2432
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
1969
2433
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2012,6 +2476,10 @@ export class Matterbridge extends EventEmitter {
2012
2476
  }
2013
2477
  return vendorName;
2014
2478
  };
2479
+ /**
2480
+ * Retrieves the base registered plugins sanitized for res.json().
2481
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2482
+ */
2015
2483
  async getBaseRegisteredPlugins() {
2016
2484
  const baseRegisteredPlugins = [];
2017
2485
  for (const plugin of this.plugins) {
@@ -2043,13 +2511,36 @@ export class Matterbridge extends EventEmitter {
2043
2511
  }
2044
2512
  return baseRegisteredPlugins;
2045
2513
  }
2514
+ /**
2515
+ * Spawns a child process with the given command and arguments.
2516
+ * @param {string} command - The command to execute.
2517
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2518
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2519
+ */
2046
2520
  async spawnCommand(command, args = []) {
2521
+ /*
2522
+ npm > npm.cmd on windows
2523
+ cmd.exe ['dir'] on windows
2524
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2525
+ process.on('unhandledRejection', (reason, promise) => {
2526
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2527
+ });
2528
+
2529
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2530
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2531
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2532
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2533
+ */
2047
2534
  const cmdLine = command + ' ' + args.join(' ');
2048
2535
  if (process.platform === 'win32' && command === 'npm') {
2536
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2049
2537
  const argstring = 'npm ' + args.join(' ');
2050
2538
  args.splice(0, args.length, '/c', argstring);
2051
2539
  command = 'cmd.exe';
2052
2540
  }
2541
+ // Decide when using sudo on linux
2542
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2543
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2053
2544
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2054
2545
  args.unshift(command);
2055
2546
  command = 'sudo';
@@ -2107,55 +2598,102 @@ export class Matterbridge extends EventEmitter {
2107
2598
  }
2108
2599
  });
2109
2600
  }
2601
+ /**
2602
+ * Sends a WebSocket message to all connected clients.
2603
+ *
2604
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2605
+ * @param {string} time - The time string of the message
2606
+ * @param {string} name - The logger name of the message
2607
+ * @param {string} message - The content of the message.
2608
+ */
2110
2609
  wssSendMessage(level, time, name, message) {
2111
2610
  if (!level || !time || !name || !message)
2112
2611
  return;
2612
+ // Remove ANSI escape codes from the message
2613
+ // eslint-disable-next-line no-control-regex
2113
2614
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2615
+ // Remove leading asterisks from the message
2114
2616
  message = message.replace(/^\*+/, '');
2617
+ // Replace all occurrences of \t and \n
2115
2618
  message = message.replace(/[\t\n]/g, '');
2619
+ // Remove non-printable characters
2620
+ // eslint-disable-next-line no-control-regex
2116
2621
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2622
+ // Replace all occurrences of \" with "
2117
2623
  message = message.replace(/\\"/g, '"');
2624
+ // Define the maximum allowed length for continuous characters without a space
2118
2625
  const maxContinuousLength = 100;
2119
2626
  const keepStartLength = 20;
2120
2627
  const keepEndLength = 20;
2628
+ // Split the message into words
2121
2629
  message = message
2122
2630
  .split(' ')
2123
2631
  .map((word) => {
2632
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2124
2633
  if (word.length > maxContinuousLength) {
2125
2634
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2126
2635
  }
2127
2636
  return word;
2128
2637
  })
2129
2638
  .join(' ');
2639
+ // Send the message to all connected clients
2130
2640
  this.webSocketServer?.clients.forEach((client) => {
2131
2641
  if (client.readyState === WebSocket.OPEN) {
2132
2642
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2133
2643
  }
2134
2644
  });
2135
2645
  }
2646
+ /**
2647
+ * Sends a need to refresh WebSocket message to all connected clients.
2648
+ *
2649
+ */
2136
2650
  wssSendRefreshRequired() {
2137
2651
  this.matterbridgeInformation.refreshRequired = true;
2652
+ // Send the message to all connected clients
2138
2653
  this.webSocketServer?.clients.forEach((client) => {
2139
2654
  if (client.readyState === WebSocket.OPEN) {
2140
2655
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2141
2656
  }
2142
2657
  });
2143
2658
  }
2659
+ /**
2660
+ * Sends a need to restart WebSocket message to all connected clients.
2661
+ *
2662
+ */
2144
2663
  wssSendRestartRequired() {
2145
2664
  this.matterbridgeInformation.restartRequired = true;
2665
+ // Send the message to all connected clients
2146
2666
  this.webSocketServer?.clients.forEach((client) => {
2147
2667
  if (client.readyState === WebSocket.OPEN) {
2148
2668
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2149
2669
  }
2150
2670
  });
2151
2671
  }
2672
+ /**
2673
+ * Initializes the frontend of Matterbridge.
2674
+ *
2675
+ * @param port The port number to run the frontend server on. Default is 8283.
2676
+ */
2152
2677
  async initializeFrontend(port = 8283) {
2153
2678
  let initializeError = false;
2154
2679
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2680
+ // Create the express app that serves the frontend
2155
2681
  this.expressApp = express();
2682
+ // Log all requests to the server for debugging
2683
+ /*
2684
+ if (hasParameter('homedir')) {
2685
+ this.expressApp.use((req, res, next) => {
2686
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2687
+ next();
2688
+ });
2689
+ }
2690
+ */
2691
+ // Serve static files from '/static' endpoint
2156
2692
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2157
2693
  if (!hasParameter('ssl')) {
2694
+ // Create an HTTP server and attach the express app
2158
2695
  this.httpServer = createServer(this.expressApp);
2696
+ // Listen on the specified port
2159
2697
  if (hasParameter('ingress')) {
2160
2698
  this.httpServer.listen(port, '0.0.0.0', () => {
2161
2699
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2169,6 +2707,7 @@ export class Matterbridge extends EventEmitter {
2169
2707
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2170
2708
  });
2171
2709
  }
2710
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2172
2711
  this.httpServer.on('error', (error) => {
2173
2712
  this.log.error(`Frontend http server error listening on ${port}`);
2174
2713
  switch (error.code) {
@@ -2184,6 +2723,7 @@ export class Matterbridge extends EventEmitter {
2184
2723
  });
2185
2724
  }
2186
2725
  else {
2726
+ // Load the SSL certificate, the private key and optionally the CA certificate
2187
2727
  let cert;
2188
2728
  try {
2189
2729
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2211,7 +2751,9 @@ export class Matterbridge extends EventEmitter {
2211
2751
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2212
2752
  }
2213
2753
  const serverOptions = { cert, key, ca };
2754
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2214
2755
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
2756
+ // Listen on the specified port
2215
2757
  if (hasParameter('ingress')) {
2216
2758
  this.httpsServer.listen(port, '0.0.0.0', () => {
2217
2759
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2225,6 +2767,7 @@ export class Matterbridge extends EventEmitter {
2225
2767
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2226
2768
  });
2227
2769
  }
2770
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2228
2771
  this.httpsServer.on('error', (error) => {
2229
2772
  this.log.error(`Frontend https server error listening on ${port}`);
2230
2773
  switch (error.code) {
@@ -2241,12 +2784,13 @@ export class Matterbridge extends EventEmitter {
2241
2784
  }
2242
2785
  if (initializeError)
2243
2786
  return;
2787
+ // Createe a WebSocket server and attach it to the http or https server
2244
2788
  const wssPort = port;
2245
2789
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2246
2790
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2247
2791
  this.webSocketServer.on('connection', (ws, request) => {
2248
2792
  const clientIp = request.socket.remoteAddress;
2249
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
2793
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2250
2794
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2251
2795
  ws.on('message', (message) => {
2252
2796
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2279,6 +2823,7 @@ export class Matterbridge extends EventEmitter {
2279
2823
  this.webSocketServer.on('error', (ws, error) => {
2280
2824
  this.log.error(`WebSocketServer error: ${error}`);
2281
2825
  });
2826
+ // Endpoint to validate login code
2282
2827
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2283
2828
  const { password } = req.body;
2284
2829
  this.log.debug('The frontend sent /api/login', password);
@@ -2297,12 +2842,14 @@ export class Matterbridge extends EventEmitter {
2297
2842
  this.log.warn('/api/login error wrong password');
2298
2843
  res.json({ valid: false });
2299
2844
  }
2845
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2300
2846
  }
2301
2847
  catch (error) {
2302
2848
  this.log.error('/api/login error getting password');
2303
2849
  res.json({ valid: false });
2304
2850
  }
2305
2851
  });
2852
+ // Endpoint to provide settings
2306
2853
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2307
2854
  this.log.debug('The frontend sent /api/settings');
2308
2855
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2320,13 +2867,17 @@ export class Matterbridge extends EventEmitter {
2320
2867
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2321
2868
  this.matterbridgeInformation.profile = this.profile;
2322
2869
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2870
+ // this.log.debug('Response:', debugStringify(response));
2323
2871
  res.json(response);
2324
2872
  });
2873
+ // Endpoint to provide plugins
2325
2874
  this.expressApp.get('/api/plugins', async (req, res) => {
2326
2875
  this.log.debug('The frontend sent /api/plugins');
2327
2876
  const response = await this.getBaseRegisteredPlugins();
2877
+ // this.log.debug('Response:', debugStringify(response));
2328
2878
  res.json(response);
2329
2879
  });
2880
+ // Endpoint to provide devices
2330
2881
  this.expressApp.get('/api/devices', (req, res) => {
2331
2882
  this.log.debug('The frontend sent /api/devices');
2332
2883
  const data = [];
@@ -2354,8 +2905,10 @@ export class Matterbridge extends EventEmitter {
2354
2905
  cluster: cluster,
2355
2906
  });
2356
2907
  });
2908
+ // this.log.debug('Response:', debugStringify(data));
2357
2909
  res.json(data);
2358
2910
  });
2911
+ // Endpoint to provide the cluster servers of the devices
2359
2912
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2360
2913
  const selectedPluginName = req.params.selectedPluginName;
2361
2914
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2375,6 +2928,7 @@ export class Matterbridge extends EventEmitter {
2375
2928
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2376
2929
  if (clusterServer.name === 'EveHistory')
2377
2930
  return;
2931
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2378
2932
  let attributeValue;
2379
2933
  try {
2380
2934
  if (typeof value.getLocal() === 'object')
@@ -2385,6 +2939,7 @@ export class Matterbridge extends EventEmitter {
2385
2939
  catch (error) {
2386
2940
  attributeValue = 'Fabric-Scoped';
2387
2941
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2942
+ // console.log(error);
2388
2943
  }
2389
2944
  data.push({
2390
2945
  endpoint: device.number ? device.number.toString() : '...',
@@ -2397,12 +2952,14 @@ export class Matterbridge extends EventEmitter {
2397
2952
  });
2398
2953
  });
2399
2954
  device.getChildEndpoints().forEach((childEndpoint) => {
2955
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2400
2956
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2401
2957
  const clusterServers = childEndpoint.getAllClusterServers();
2402
2958
  clusterServers.forEach((clusterServer) => {
2403
2959
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2404
2960
  if (clusterServer.name === 'EveHistory')
2405
2961
  return;
2962
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2406
2963
  let attributeValue;
2407
2964
  try {
2408
2965
  if (typeof value.getLocal() === 'object')
@@ -2413,6 +2970,7 @@ export class Matterbridge extends EventEmitter {
2413
2970
  catch (error) {
2414
2971
  attributeValue = 'Unavailable';
2415
2972
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2973
+ // console.log(error);
2416
2974
  }
2417
2975
  data.push({
2418
2976
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2429,6 +2987,7 @@ export class Matterbridge extends EventEmitter {
2429
2987
  });
2430
2988
  res.json(data);
2431
2989
  });
2990
+ // Endpoint to view the log
2432
2991
  this.expressApp.get('/api/view-log', async (req, res) => {
2433
2992
  this.log.debug('The frontend sent /api/log');
2434
2993
  try {
@@ -2441,10 +3000,12 @@ export class Matterbridge extends EventEmitter {
2441
3000
  res.status(500).send('Error reading log file');
2442
3001
  }
2443
3002
  });
3003
+ // Endpoint to download the matterbridge log
2444
3004
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2445
3005
  this.log.debug('The frontend sent /api/download-mblog');
2446
3006
  try {
2447
3007
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3008
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2448
3009
  }
2449
3010
  catch (error) {
2450
3011
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2456,10 +3017,12 @@ export class Matterbridge extends EventEmitter {
2456
3017
  }
2457
3018
  });
2458
3019
  });
3020
+ // Endpoint to download the matter log
2459
3021
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2460
3022
  this.log.debug('The frontend sent /api/download-mjlog');
2461
3023
  try {
2462
3024
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3025
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2463
3026
  }
2464
3027
  catch (error) {
2465
3028
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2471,6 +3034,7 @@ export class Matterbridge extends EventEmitter {
2471
3034
  }
2472
3035
  });
2473
3036
  });
3037
+ // Endpoint to download the matter storage file
2474
3038
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2475
3039
  this.log.debug('The frontend sent /api/download-mjstorage');
2476
3040
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2480,6 +3044,7 @@ export class Matterbridge extends EventEmitter {
2480
3044
  }
2481
3045
  });
2482
3046
  });
3047
+ // Endpoint to download the matterbridge storage directory
2483
3048
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2484
3049
  this.log.debug('The frontend sent /api/download-mbstorage');
2485
3050
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2490,6 +3055,7 @@ export class Matterbridge extends EventEmitter {
2490
3055
  }
2491
3056
  });
2492
3057
  });
3058
+ // Endpoint to download the matterbridge plugin directory
2493
3059
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2494
3060
  this.log.debug('The frontend sent /api/download-pluginstorage');
2495
3061
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2500,9 +3066,11 @@ export class Matterbridge extends EventEmitter {
2500
3066
  }
2501
3067
  });
2502
3068
  });
3069
+ // Endpoint to download the matterbridge plugin config files
2503
3070
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2504
3071
  this.log.debug('The frontend sent /api/download-pluginconfig');
2505
3072
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3073
+ // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
2506
3074
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2507
3075
  if (error) {
2508
3076
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2510,6 +3078,7 @@ export class Matterbridge extends EventEmitter {
2510
3078
  }
2511
3079
  });
2512
3080
  });
3081
+ // Endpoint to download the matterbridge plugin config files
2513
3082
  this.expressApp.get('/api/download-backup', async (req, res) => {
2514
3083
  this.log.debug('The frontend sent /api/download-backup');
2515
3084
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2519,6 +3088,7 @@ export class Matterbridge extends EventEmitter {
2519
3088
  }
2520
3089
  });
2521
3090
  });
3091
+ // Endpoint to receive commands
2522
3092
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2523
3093
  const command = req.params.command;
2524
3094
  let param = req.params.param;
@@ -2528,13 +3098,15 @@ export class Matterbridge extends EventEmitter {
2528
3098
  return;
2529
3099
  }
2530
3100
  this.log.debug(`Received frontend command: ${command}:${param}`);
3101
+ // Handle the command setpassword from Settings
2531
3102
  if (command === 'setpassword') {
2532
- const password = param.slice(1, -1);
3103
+ const password = param.slice(1, -1); // Remove the first and last characters
2533
3104
  this.log.debug('setpassword', param, password);
2534
3105
  await this.nodeContext?.set('password', password);
2535
3106
  res.json({ message: 'Command received' });
2536
3107
  return;
2537
3108
  }
3109
+ // Handle the command setbridgemode from Settings
2538
3110
  if (command === 'setbridgemode') {
2539
3111
  this.log.debug(`setbridgemode: ${param}`);
2540
3112
  this.wssSendRestartRequired();
@@ -2542,6 +3114,7 @@ export class Matterbridge extends EventEmitter {
2542
3114
  res.json({ message: 'Command received' });
2543
3115
  return;
2544
3116
  }
3117
+ // Handle the command backup from Settings
2545
3118
  if (command === 'backup') {
2546
3119
  this.log.notice(`Prepairing the backup...`);
2547
3120
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2549,25 +3122,26 @@ export class Matterbridge extends EventEmitter {
2549
3122
  res.json({ message: 'Command received' });
2550
3123
  return;
2551
3124
  }
3125
+ // Handle the command setmbloglevel from Settings
2552
3126
  if (command === 'setmbloglevel') {
2553
3127
  this.log.debug('Matterbridge log level:', param);
2554
3128
  if (param === 'Debug') {
2555
- this.log.logLevel = "debug";
3129
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2556
3130
  }
2557
3131
  else if (param === 'Info') {
2558
- this.log.logLevel = "info";
3132
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2559
3133
  }
2560
3134
  else if (param === 'Notice') {
2561
- this.log.logLevel = "notice";
3135
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2562
3136
  }
2563
3137
  else if (param === 'Warn') {
2564
- this.log.logLevel = "warn";
3138
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2565
3139
  }
2566
3140
  else if (param === 'Error') {
2567
- this.log.logLevel = "error";
3141
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2568
3142
  }
2569
3143
  else if (param === 'Fatal') {
2570
- this.log.logLevel = "fatal";
3144
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2571
3145
  }
2572
3146
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2573
3147
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2575,12 +3149,13 @@ export class Matterbridge extends EventEmitter {
2575
3149
  for (const plugin of this.plugins) {
2576
3150
  if (!plugin.platform || !plugin.platform.config)
2577
3151
  continue;
2578
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2579
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3152
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3153
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2580
3154
  }
2581
3155
  res.json({ message: 'Command received' });
2582
3156
  return;
2583
3157
  }
3158
+ // Handle the command setmbloglevel from Settings
2584
3159
  if (command === 'setmjloglevel') {
2585
3160
  this.log.debug('Matter.js log level:', param);
2586
3161
  if (param === 'Debug') {
@@ -2605,41 +3180,47 @@ export class Matterbridge extends EventEmitter {
2605
3180
  res.json({ message: 'Command received' });
2606
3181
  return;
2607
3182
  }
3183
+ // Handle the command setmdnsinterface from Settings
2608
3184
  if (command === 'setmdnsinterface') {
2609
- param = param.slice(1, -1);
3185
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2610
3186
  this.matterbridgeInformation.mattermdnsinterface = param;
2611
3187
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2612
3188
  await this.nodeContext?.set('mattermdnsinterface', param);
2613
3189
  res.json({ message: 'Command received' });
2614
3190
  return;
2615
3191
  }
3192
+ // Handle the command setipv4address from Settings
2616
3193
  if (command === 'setipv4address') {
2617
- param = param.slice(1, -1);
3194
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2618
3195
  this.matterbridgeInformation.matteripv4address = param;
2619
3196
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2620
3197
  await this.nodeContext?.set('matteripv4address', param);
2621
3198
  res.json({ message: 'Command received' });
2622
3199
  return;
2623
3200
  }
3201
+ // Handle the command setipv6address from Settings
2624
3202
  if (command === 'setipv6address') {
2625
- param = param.slice(1, -1);
3203
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2626
3204
  this.matterbridgeInformation.matteripv6address = param;
2627
3205
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2628
3206
  await this.nodeContext?.set('matteripv6address', param);
2629
3207
  res.json({ message: 'Command received' });
2630
3208
  return;
2631
3209
  }
3210
+ // Handle the command setmbloglevel from Settings
2632
3211
  if (command === 'setmblogfile') {
2633
3212
  this.log.debug('Matterbridge file log:', param);
2634
3213
  this.matterbridgeInformation.fileLogger = param === 'true';
2635
3214
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3215
+ // Create the file logger for matterbridge
2636
3216
  if (param === 'true')
2637
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3217
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2638
3218
  else
2639
3219
  AnsiLogger.setGlobalLogfile(undefined);
2640
3220
  res.json({ message: 'Command received' });
2641
3221
  return;
2642
3222
  }
3223
+ // Handle the command setmbloglevel from Settings
2643
3224
  if (command === 'setmjlogfile') {
2644
3225
  this.log.debug('Matter file log:', param);
2645
3226
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2666,36 +3247,43 @@ export class Matterbridge extends EventEmitter {
2666
3247
  res.json({ message: 'Command received' });
2667
3248
  return;
2668
3249
  }
3250
+ // Handle the command unregister from Settings
2669
3251
  if (command === 'unregister') {
2670
3252
  await this.unregisterAndShutdownProcess();
2671
3253
  res.json({ message: 'Command received' });
2672
3254
  return;
2673
3255
  }
3256
+ // Handle the command reset from Settings
2674
3257
  if (command === 'reset') {
2675
3258
  await this.shutdownProcessAndReset();
2676
3259
  res.json({ message: 'Command received' });
2677
3260
  return;
2678
3261
  }
3262
+ // Handle the command factoryreset from Settings
2679
3263
  if (command === 'factoryreset') {
2680
3264
  await this.shutdownProcessAndFactoryReset();
2681
3265
  res.json({ message: 'Command received' });
2682
3266
  return;
2683
3267
  }
3268
+ // Handle the command shutdown from Header
2684
3269
  if (command === 'shutdown') {
2685
3270
  await this.shutdownProcess();
2686
3271
  res.json({ message: 'Command received' });
2687
3272
  return;
2688
3273
  }
3274
+ // Handle the command restart from Header
2689
3275
  if (command === 'restart') {
2690
3276
  await this.restartProcess();
2691
3277
  res.json({ message: 'Command received' });
2692
3278
  return;
2693
3279
  }
3280
+ // Handle the command update from Header
2694
3281
  if (command === 'update') {
2695
3282
  this.log.info('Updating matterbridge...');
2696
3283
  try {
2697
3284
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2698
3285
  this.log.info('Matterbridge has been updated. Full restart required.');
3286
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2699
3287
  }
2700
3288
  catch (error) {
2701
3289
  this.log.error('Error updating matterbridge');
@@ -2705,9 +3293,11 @@ export class Matterbridge extends EventEmitter {
2705
3293
  res.json({ message: 'Command received' });
2706
3294
  return;
2707
3295
  }
3296
+ // Handle the command saveconfig from Home
2708
3297
  if (command === 'saveconfig') {
2709
3298
  param = param.replace(/\*/g, '\\');
2710
3299
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3300
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2711
3301
  if (!this.plugins.has(param)) {
2712
3302
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2713
3303
  }
@@ -2721,28 +3311,35 @@ export class Matterbridge extends EventEmitter {
2721
3311
  res.json({ message: 'Command received' });
2722
3312
  return;
2723
3313
  }
3314
+ // Handle the command installplugin from Home
2724
3315
  if (command === 'installplugin') {
2725
3316
  param = param.replace(/\*/g, '\\');
2726
3317
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2727
3318
  try {
2728
3319
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2729
3320
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3321
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2730
3322
  }
2731
3323
  catch (error) {
2732
3324
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2733
3325
  }
2734
3326
  this.wssSendRestartRequired();
3327
+ // Also add the plugin to matterbridge so no return!
3328
+ // res.json({ message: 'Command received' });
3329
+ // return;
2735
3330
  }
3331
+ // Handle the command addplugin from Home
2736
3332
  if (command === 'addplugin' || command === 'installplugin') {
2737
3333
  param = param.replace(/\*/g, '\\');
2738
3334
  const plugin = await this.plugins.add(param);
2739
3335
  if (plugin) {
2740
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3336
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2741
3337
  }
2742
3338
  res.json({ message: 'Command received' });
2743
3339
  this.wssSendRefreshRequired();
2744
3340
  return;
2745
3341
  }
3342
+ // Handle the command removeplugin from Home
2746
3343
  if (command === 'removeplugin') {
2747
3344
  if (!this.plugins.has(param)) {
2748
3345
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2756,6 +3353,7 @@ export class Matterbridge extends EventEmitter {
2756
3353
  this.wssSendRefreshRequired();
2757
3354
  return;
2758
3355
  }
3356
+ // Handle the command enableplugin from Home
2759
3357
  if (command === 'enableplugin') {
2760
3358
  if (!this.plugins.has(param)) {
2761
3359
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2773,13 +3371,14 @@ export class Matterbridge extends EventEmitter {
2773
3371
  plugin.registeredDevices = undefined;
2774
3372
  plugin.addedDevices = undefined;
2775
3373
  await this.plugins.enable(param);
2776
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3374
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
2777
3375
  }
2778
3376
  }
2779
3377
  res.json({ message: 'Command received' });
2780
3378
  this.wssSendRefreshRequired();
2781
3379
  return;
2782
3380
  }
3381
+ // Handle the command disableplugin from Home
2783
3382
  if (command === 'disableplugin') {
2784
3383
  if (!this.plugins.has(param)) {
2785
3384
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2796,6 +3395,7 @@ export class Matterbridge extends EventEmitter {
2796
3395
  return;
2797
3396
  }
2798
3397
  });
3398
+ // Fallback for routing (must be the last route)
2799
3399
  this.expressApp.get('*', (req, res) => {
2800
3400
  this.log.debug('The frontend sent:', req.url);
2801
3401
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -2803,6 +3403,11 @@ export class Matterbridge extends EventEmitter {
2803
3403
  });
2804
3404
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2805
3405
  }
3406
+ /**
3407
+ * Retrieves the cluster text description from a given device.
3408
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3409
+ * @returns {string} The attributes description of the cluster servers in the device.
3410
+ */
2806
3411
  getClusterTextFromDevice(device) {
2807
3412
  const stringifyUserLabel = (endpoint) => {
2808
3413
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -2825,9 +3430,11 @@ export class Matterbridge extends EventEmitter {
2825
3430
  return '';
2826
3431
  };
2827
3432
  let attributes = '';
3433
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2828
3434
  const clusterServers = device.getAllClusterServers();
2829
3435
  clusterServers.forEach((clusterServer) => {
2830
3436
  try {
3437
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2831
3438
  if (clusterServer.name === 'OnOff')
2832
3439
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
2833
3440
  if (clusterServer.name === 'Switch')
@@ -2878,18 +3485,30 @@ export class Matterbridge extends EventEmitter {
2878
3485
  attributes += `${stringifyFixedLabel(device)} `;
2879
3486
  if (clusterServer.name === 'UserLabel')
2880
3487
  attributes += `${stringifyUserLabel(device)} `;
3488
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2881
3489
  }
2882
3490
  catch (error) {
2883
3491
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
2884
3492
  }
2885
3493
  });
3494
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2886
3495
  return attributes;
2887
3496
  }
3497
+ /**
3498
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3499
+ * @deprecated This method is deprecated and will be removed in a future version.
3500
+ *
3501
+ * @returns A Promise that resolves when the initialization is complete.
3502
+ */
2888
3503
  async startExtension(dataPath, extensionVersion, port = 5540) {
3504
+ // Set the bridge mode
2889
3505
  this.bridgeMode = 'bridge';
3506
+ // Set the first port to use
2890
3507
  this.port = port;
2891
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3508
+ // Set Matterbridge logger
3509
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
2892
3510
  this.log.debug('Matterbridge extension is starting...');
3511
+ // Initialize NodeStorage
2893
3512
  this.matterbridgeDirectory = dataPath;
2894
3513
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
2895
3514
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -2908,10 +3527,13 @@ export class Matterbridge extends EventEmitter {
2908
3527
  };
2909
3528
  this.plugins.set(plugin);
2910
3529
  this.plugins.saveToStorage();
3530
+ // Log system info and create .matterbridge directory
2911
3531
  await this.logNodeAndSystemInfo();
2912
3532
  this.matterbridgeDirectory = dataPath;
3533
+ // Set matter.js logger level and format
2913
3534
  Logger.defaultLogLevel = MatterLogLevel.INFO;
2914
3535
  Logger.format = MatterLogFormat.ANSI;
3536
+ // Start the storage and create matterbridgeContext
2915
3537
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
2916
3538
  if (!this.storageManager)
2917
3539
  return false;
@@ -2921,7 +3543,7 @@ export class Matterbridge extends EventEmitter {
2921
3543
  await this.matterbridgeContext.set('softwareVersion', 1);
2922
3544
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
2923
3545
  await this.matterbridgeContext.set('hardwareVersion', 1);
2924
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3546
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
2925
3547
  this.matterServer = this.createMatterServer(this.storageManager);
2926
3548
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
2927
3549
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
@@ -2934,6 +3556,7 @@ export class Matterbridge extends EventEmitter {
2934
3556
  await this.startMatterServer();
2935
3557
  this.log.info('Matter server started');
2936
3558
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3559
+ // Set reachability to true and trigger event after 60 seconds
2937
3560
  setTimeout(() => {
2938
3561
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
2939
3562
  if (this.commissioningServer)
@@ -2943,14 +3566,31 @@ export class Matterbridge extends EventEmitter {
2943
3566
  }, 60 * 1000);
2944
3567
  return this.commissioningServer.isCommissioned();
2945
3568
  }
3569
+ /**
3570
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3571
+ * @deprecated This method is deprecated and will be removed in a future version.
3572
+ *
3573
+ * @returns A Promise that resolves when the initialization is complete.
3574
+ */
2946
3575
  async stopExtension() {
3576
+ // Closing matter
2947
3577
  await this.stopMatterServer();
3578
+ // Clearing the session manager
3579
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3580
+ // Closing storage
2948
3581
  await this.stopMatterStorage();
2949
3582
  this.log.info('Matter server stopped');
2950
3583
  }
3584
+ /**
3585
+ * Checks if the extension is commissioned.
3586
+ * @deprecated This method is deprecated and will be removed in a future version.
3587
+ *
3588
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3589
+ */
2951
3590
  isExtensionCommissioned() {
2952
3591
  if (!this.commissioningServer)
2953
3592
  return false;
2954
3593
  return this.commissioningServer.isCommissioned();
2955
3594
  }
2956
3595
  }
3596
+ //# sourceMappingURL=matterbridge.js.map