matterbridge 1.6.8-dev.8 → 1.7.0

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 (108) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README-DOCKER.md +8 -6
  3. package/README-EDGE.md +76 -0
  4. package/README-SERVICE.md +3 -3
  5. package/README.md +6 -3
  6. package/dist/cli.d.ts +25 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +26 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/cluster/export.d.ts +2 -0
  11. package/dist/cluster/export.d.ts.map +1 -0
  12. package/dist/cluster/export.js +2 -0
  13. package/dist/cluster/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +27 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +23 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +46 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +26 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/index.d.ts +40 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +30 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/export.d.ts +11 -0
  31. package/dist/matter/export.d.ts.map +1 -0
  32. package/dist/matter/export.js +4 -0
  33. package/dist/matter/export.js.map +1 -0
  34. package/dist/matterbridge.d.ts +483 -0
  35. package/dist/matterbridge.d.ts.map +1 -0
  36. package/dist/matterbridge.js +851 -106
  37. package/dist/matterbridge.js.map +1 -0
  38. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  39. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  40. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  41. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  42. package/dist/matterbridgeBehaviors.d.ts +942 -0
  43. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  44. package/dist/matterbridgeBehaviors.js +29 -1
  45. package/dist/matterbridgeBehaviors.js.map +1 -0
  46. package/dist/matterbridgeDevice.d.ts +7077 -0
  47. package/dist/matterbridgeDevice.d.ts.map +1 -0
  48. package/dist/matterbridgeDevice.js +996 -9
  49. package/dist/matterbridgeDevice.js.map +1 -0
  50. package/dist/matterbridgeDeviceTypes.d.ts +109 -0
  51. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  52. package/dist/matterbridgeDeviceTypes.js +82 -11
  53. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  54. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  55. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  56. package/dist/matterbridgeDynamicPlatform.js +33 -0
  57. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  58. package/dist/matterbridgeEdge.d.ts +91 -0
  59. package/dist/matterbridgeEdge.d.ts.map +1 -0
  60. package/dist/matterbridgeEdge.js +558 -11
  61. package/dist/matterbridgeEdge.js.map +1 -0
  62. package/dist/matterbridgeEndpoint.d.ts +10151 -0
  63. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  64. package/dist/matterbridgeEndpoint.js +1120 -11
  65. package/dist/matterbridgeEndpoint.js.map +1 -0
  66. package/dist/matterbridgePlatform.d.ts +140 -0
  67. package/dist/matterbridgePlatform.d.ts.map +1 -0
  68. package/dist/matterbridgePlatform.js +179 -7
  69. package/dist/matterbridgePlatform.js.map +1 -0
  70. package/dist/matterbridgeTypes.d.ts +169 -0
  71. package/dist/matterbridgeTypes.d.ts.map +1 -0
  72. package/dist/matterbridgeTypes.js +24 -0
  73. package/dist/matterbridgeTypes.js.map +1 -0
  74. package/dist/matterbridgeWebsocket.d.ts +49 -0
  75. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  76. package/dist/matterbridgeWebsocket.js +139 -17
  77. package/dist/matterbridgeWebsocket.js.map +1 -0
  78. package/dist/pluginManager.d.ts +238 -0
  79. package/dist/pluginManager.d.ts.map +1 -0
  80. package/dist/pluginManager.js +238 -3
  81. package/dist/pluginManager.js.map +1 -0
  82. package/dist/storage/export.d.ts +2 -0
  83. package/dist/storage/export.d.ts.map +1 -0
  84. package/dist/storage/export.js +1 -0
  85. package/dist/storage/export.js.map +1 -0
  86. package/dist/utils/colorUtils.d.ts +61 -0
  87. package/dist/utils/colorUtils.d.ts.map +1 -0
  88. package/dist/utils/colorUtils.js +205 -2
  89. package/dist/utils/colorUtils.js.map +1 -0
  90. package/dist/utils/export.d.ts +3 -0
  91. package/dist/utils/export.d.ts.map +1 -0
  92. package/dist/utils/export.js +1 -0
  93. package/dist/utils/export.js.map +1 -0
  94. package/dist/utils/utils.d.ts +221 -0
  95. package/dist/utils/utils.d.ts.map +1 -0
  96. package/dist/utils/utils.js +252 -7
  97. package/dist/utils/utils.js.map +1 -0
  98. package/frontend/build/asset-manifest.json +6 -6
  99. package/frontend/build/index.html +1 -1
  100. package/frontend/build/static/css/{main.823e08b6.css → main.f1fce054.css} +2 -2
  101. package/frontend/build/static/css/main.f1fce054.css.map +1 -0
  102. package/frontend/build/static/js/{main.4dd7e165.js → main.5caad8c7.js} +15 -15
  103. package/frontend/build/static/js/main.5caad8c7.js.map +1 -0
  104. package/npm-shrinkwrap.json +30 -17
  105. package/package.json +2 -1
  106. package/frontend/build/static/css/main.823e08b6.css.map +0 -1
  107. package/frontend/build/static/js/main.4dd7e165.js.map +0 -1
  108. /package/frontend/build/static/js/{main.4dd7e165.js.LICENSE.txt → main.5caad8c7.js.LICENSE.txt} +0 -0
@@ -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,27 +29,36 @@ 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, StorageService, Environment } 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';
26
54
  import { aggregator } from './matterbridgeDeviceTypes.js';
55
+ // Default colors
27
56
  const plg = '\u001B[38;5;33m';
28
57
  const dev = '\u001B[38;5;79m';
29
58
  const typ = '\u001B[38;5;207m';
59
+ /**
60
+ * Represents the Matterbridge application.
61
+ */
30
62
  export class Matterbridge extends EventEmitter {
31
63
  systemInformation = {
32
64
  interfaceName: '',
@@ -63,7 +95,7 @@ export class Matterbridge extends EventEmitter {
63
95
  edge: hasParameter('edge'),
64
96
  readOnly: hasParameter('readonly'),
65
97
  profile: getParameter('profile'),
66
- loggerLevel: "info",
98
+ loggerLevel: "info" /* LogLevel.INFO */,
67
99
  fileLogger: false,
68
100
  matterLoggerLevel: MatterLogLevel.INFO,
69
101
  matterFileLogger: false,
@@ -102,6 +134,7 @@ export class Matterbridge extends EventEmitter {
102
134
  nodeContext;
103
135
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
104
136
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
137
+ // Cleanup
105
138
  hasCleanupStarted = false;
106
139
  initialized = false;
107
140
  execRunningCount = 0;
@@ -113,16 +146,18 @@ export class Matterbridge extends EventEmitter {
113
146
  sigtermHandler;
114
147
  exceptionHandler;
115
148
  rejectionHandler;
149
+ // Frontend
116
150
  expressApp;
117
151
  httpServer;
118
152
  httpsServer;
119
153
  webSocketServer;
120
- mdnsInterface;
121
- ipv4address;
122
- ipv6address;
123
- port = 5540;
124
- passcode;
125
- discriminator;
154
+ // Matter
155
+ mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
156
+ ipv4address; // matter commissioning server listeningAddressIpv4
157
+ ipv6address; // matter commissioning server listeningAddressIpv6
158
+ port = 5540; // first commissioning server port
159
+ passcode; // first commissioning server passcode
160
+ discriminator; // first commissioning server discriminator
126
161
  storageManager;
127
162
  matterbridgeContext;
128
163
  mattercontrollerContext;
@@ -133,13 +168,40 @@ export class Matterbridge extends EventEmitter {
133
168
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
134
169
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
135
170
  static instance;
171
+ // We load asyncronously so is private
136
172
  constructor() {
137
173
  super();
174
+ // Bind the handler to the instance
138
175
  this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
139
176
  }
177
+ /**
178
+ * Retrieves the list of Matterbridge devices.
179
+ * @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
180
+ */
181
+ getDevices() {
182
+ return this.devices.array();
183
+ }
184
+ /**
185
+ * Retrieves the list of registered plugins.
186
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
187
+ */
188
+ getPlugins() {
189
+ return this.plugins.array();
190
+ }
140
191
  matterbridgeMessageHandler;
192
+ /** ***********************************************************************************************************************************/
193
+ /** loadInstance() and cleanup() methods */
194
+ /** ***********************************************************************************************************************************/
195
+ /**
196
+ * Loads an instance of the Matterbridge class.
197
+ * If an instance already exists, return that instance.
198
+ *
199
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
200
+ * @returns The loaded Matterbridge instance.
201
+ */
141
202
  static async loadInstance(initialize = false) {
142
203
  if (!Matterbridge.instance) {
204
+ // eslint-disable-next-line no-console
143
205
  if (hasParameter('debug'))
144
206
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
145
207
  Matterbridge.instance = new Matterbridge();
@@ -148,6 +210,11 @@ export class Matterbridge extends EventEmitter {
148
210
  }
149
211
  return Matterbridge.instance;
150
212
  }
213
+ /**
214
+ * Call cleanup().
215
+ * @deprecated This method is deprecated and is only used for jest tests.
216
+ *
217
+ */
151
218
  async destroyInstance() {
152
219
  await this.cleanup('destroying instance...', false);
153
220
  await waiter('destroying instance...', () => {
@@ -155,39 +222,60 @@ export class Matterbridge extends EventEmitter {
155
222
  }, false, 60000, 100, false);
156
223
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
157
224
  }
225
+ /**
226
+ * Initializes the Matterbridge application.
227
+ *
228
+ * @remarks
229
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
230
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
231
+ * node version, registers signal handlers, initializes storage, and parses the command line.
232
+ *
233
+ * @returns A Promise that resolves when the initialization is complete.
234
+ */
158
235
  async initialize() {
236
+ // Set the restart mode
159
237
  if (hasParameter('service'))
160
238
  this.restartMode = 'service';
161
239
  if (hasParameter('docker'))
162
240
  this.restartMode = 'docker';
241
+ // Set the matterbridge directory
163
242
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
164
243
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
165
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
244
+ // Create matterbridge logger
245
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
246
+ // Initialize nodeStorage and nodeContext
166
247
  try {
167
248
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
168
249
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
169
250
  this.log.debug('Creating node storage context for matterbridge');
170
251
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
252
+ // TODO: Remove this code when node-persist-manager is updated
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
254
  const keys = (await this.nodeStorage?.storage.keys());
172
255
  for (const key of keys) {
173
256
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
258
  await this.nodeStorage?.storage.get(key);
175
259
  }
176
260
  const storages = await this.nodeStorage.getStorageNames();
177
261
  for (const storage of storages) {
178
262
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
179
263
  const nodeContext = await this.nodeStorage?.createStorage(storage);
264
+ // TODO: Remove this code when node-persist-manager is updated
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
266
  const keys = (await nodeContext?.storage.keys());
181
267
  keys.forEach(async (key) => {
182
268
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
183
269
  await nodeContext?.get(key);
184
270
  });
185
271
  }
272
+ // Creating a backup of the node storage since it is not corrupted
186
273
  this.log.debug('Creating node storage backup...');
187
274
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
188
275
  this.log.debug('Created node storage backup');
189
276
  }
190
277
  catch (error) {
278
+ // Restoring the backup of the node storage since it is corrupted
191
279
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
192
280
  if (hasParameter('norestore')) {
193
281
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -202,45 +290,51 @@ export class Matterbridge extends EventEmitter {
202
290
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
203
291
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
204
292
  }
293
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
205
294
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
295
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
206
296
  this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
297
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
207
298
  this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
208
299
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
300
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
209
301
  if (hasParameter('logger')) {
210
302
  const level = getParameter('logger');
211
303
  if (level === 'debug') {
212
- this.log.logLevel = "debug";
304
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
213
305
  }
214
306
  else if (level === 'info') {
215
- this.log.logLevel = "info";
307
+ this.log.logLevel = "info" /* LogLevel.INFO */;
216
308
  }
217
309
  else if (level === 'notice') {
218
- this.log.logLevel = "notice";
310
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
219
311
  }
220
312
  else if (level === 'warn') {
221
- this.log.logLevel = "warn";
313
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
222
314
  }
223
315
  else if (level === 'error') {
224
- this.log.logLevel = "error";
316
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
225
317
  }
226
318
  else if (level === 'fatal') {
227
- this.log.logLevel = "fatal";
319
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
228
320
  }
229
321
  else {
230
322
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
231
- this.log.logLevel = "info";
323
+ this.log.logLevel = "info" /* LogLevel.INFO */;
232
324
  }
233
325
  }
234
326
  else {
235
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
327
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
236
328
  }
237
329
  MatterbridgeDevice.logLevel = this.log.logLevel;
330
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
238
331
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
239
332
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
240
333
  this.matterbridgeInformation.fileLogger = true;
241
334
  }
242
335
  this.log.notice('Matterbridge is starting...');
243
336
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
337
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
244
338
  if (hasParameter('matterlogger')) {
245
339
  const level = getParameter('matterlogger');
246
340
  if (level === 'debug') {
@@ -271,6 +365,7 @@ export class Matterbridge extends EventEmitter {
271
365
  }
272
366
  Logger.format = MatterLogFormat.ANSI;
273
367
  Logger.setLogger('default', this.createMatterLogger());
368
+ // Create the file logger for matter.js (context: matterFileLog)
274
369
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
275
370
  this.matterbridgeInformation.matterFileLogger = true;
276
371
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -279,6 +374,7 @@ export class Matterbridge extends EventEmitter {
279
374
  });
280
375
  }
281
376
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
+ // Set the interface to use for the matter server mdnsInterface
282
378
  if (hasParameter('mdnsinterface')) {
283
379
  this.mdnsInterface = getParameter('mdnsinterface');
284
380
  }
@@ -287,6 +383,7 @@ export class Matterbridge extends EventEmitter {
287
383
  if (this.mdnsInterface === '')
288
384
  this.mdnsInterface = undefined;
289
385
  }
386
+ // Set the listeningAddressIpv4 for the matter commissioning server
290
387
  if (hasParameter('ipv4address')) {
291
388
  this.ipv4address = getParameter('ipv4address');
292
389
  }
@@ -295,6 +392,7 @@ export class Matterbridge extends EventEmitter {
295
392
  if (this.ipv4address === '')
296
393
  this.ipv4address = undefined;
297
394
  }
395
+ // Set the listeningAddressIpv6 for the matter commissioning server
298
396
  if (hasParameter('ipv6address')) {
299
397
  this.ipv6address = getParameter('ipv6address');
300
398
  }
@@ -303,17 +401,23 @@ export class Matterbridge extends EventEmitter {
303
401
  if (this.ipv6address === '')
304
402
  this.ipv6address = undefined;
305
403
  }
404
+ // Initialize PluginManager
306
405
  this.plugins = new PluginManager(this);
307
406
  await this.plugins.loadFromStorage();
407
+ // Initialize DeviceManager
308
408
  this.devices = new DeviceManager(this, this.nodeContext);
409
+ // Get the plugins from node storage and create the plugins node storage contexts
309
410
  for (const plugin of this.plugins) {
310
411
  const packageJson = await this.plugins.parse(plugin);
311
412
  if (packageJson === null && !hasParameter('add')) {
413
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
414
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
312
415
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
313
416
  try {
314
417
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
315
418
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
316
419
  plugin.error = false;
420
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
317
421
  }
318
422
  catch (error) {
319
423
  plugin.error = true;
@@ -330,6 +434,7 @@ export class Matterbridge extends EventEmitter {
330
434
  await plugin.nodeContext.set('description', plugin.description);
331
435
  await plugin.nodeContext.set('author', plugin.author);
332
436
  }
437
+ // Log system info and create .matterbridge directory
333
438
  await this.logNodeAndSystemInfo();
334
439
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
335
440
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -337,6 +442,7 @@ export class Matterbridge extends EventEmitter {
337
442
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
338
443
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
339
444
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
445
+ // Check node version and throw error
340
446
  const minNodeVersion = 18;
341
447
  const nodeVersion = process.versions.node;
342
448
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -344,10 +450,17 @@ export class Matterbridge extends EventEmitter {
344
450
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
345
451
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
346
452
  }
453
+ // Register process handlers
347
454
  this.registerProcessHandlers();
455
+ // Parse command line
348
456
  await this.parseCommandLine();
349
457
  this.initialized = true;
350
458
  }
459
+ /**
460
+ * Parses the command line arguments and performs the corresponding actions.
461
+ * @private
462
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
463
+ */
351
464
  async parseCommandLine() {
352
465
  if (hasParameter('help')) {
353
466
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -455,18 +568,39 @@ export class Matterbridge extends EventEmitter {
455
568
  }
456
569
  if (hasParameter('factoryreset')) {
457
570
  try {
458
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
571
+ // Delete old matter storage file
572
+ const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
573
+ this.log.info(`Unlinking old matter storage file: ${file}`);
574
+ await fs.unlink(file);
459
575
  }
460
576
  catch (err) {
461
- this.log.error(`Error deleting storage: ${err}`);
577
+ if (err instanceof Error && err.code !== 'ENOENT') {
578
+ this.log.error(`Error unlinking old matter storage file: ${err}`);
579
+ }
462
580
  }
463
581
  try {
464
- await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
582
+ // Delete matter node storage directory with its subdirectories
583
+ const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
584
+ this.log.info(`Removing matter node storage directory: ${dir}`);
585
+ await fs.rm(dir, { recursive: true });
465
586
  }
466
587
  catch (err) {
467
- this.log.error(`Error removing storage directory: ${err}`);
588
+ if (err instanceof Error && err.code !== 'ENOENT') {
589
+ this.log.error(`Error removing matter storage directory: ${err}`);
590
+ }
468
591
  }
469
- this.log.info('Factory reset done! Remove all paired devices from the controllers.');
592
+ try {
593
+ // Delete node storage directory with its subdirectories
594
+ const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
595
+ this.log.info(`Removing storage directory: ${dir}`);
596
+ await fs.rm(dir, { recursive: true });
597
+ }
598
+ catch (err) {
599
+ if (err instanceof Error && err.code !== 'ENOENT') {
600
+ this.log.error(`Error removing storage directory: ${err}`);
601
+ }
602
+ }
603
+ this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
470
604
  this.nodeContext = undefined;
471
605
  this.nodeStorage = undefined;
472
606
  this.plugins.clear();
@@ -474,6 +608,7 @@ export class Matterbridge extends EventEmitter {
474
608
  this.emit('shutdown');
475
609
  return;
476
610
  }
611
+ // Start the matter storage and create the matterbridge context
477
612
  try {
478
613
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
479
614
  }
@@ -481,7 +616,8 @@ export class Matterbridge extends EventEmitter {
481
616
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
482
617
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
483
618
  }
484
- if (hasParameter('reset') && getParameter('reset') === undefined) {
619
+ // Clear the matterbridge context if the reset parameter is set
620
+ if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
485
621
  this.log.info('Resetting Matterbridge commissioning information...');
486
622
  await this.matterbridgeContext?.clearAll();
487
623
  await this.stopMatterStorage();
@@ -489,7 +625,8 @@ export class Matterbridge extends EventEmitter {
489
625
  this.emit('shutdown');
490
626
  return;
491
627
  }
492
- if (getParameter('reset') && getParameter('reset') !== undefined) {
628
+ // Clear matterbridge plugin context if the reset parameter is set
629
+ if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
493
630
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
494
631
  const plugin = this.plugins.get(getParameter('reset'));
495
632
  if (plugin) {
@@ -508,28 +645,34 @@ export class Matterbridge extends EventEmitter {
508
645
  this.emit('shutdown');
509
646
  return;
510
647
  }
648
+ // Initialize frontend
511
649
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
512
650
  await this.initializeFrontend(getIntParameter('frontend'));
651
+ // Check each 60 minutes the latest versions
513
652
  this.checkUpdateInterval = setInterval(() => {
514
653
  this.getMatterbridgeLatestVersion();
515
654
  for (const plugin of this.plugins) {
516
655
  this.getPluginLatestVersion(plugin);
517
656
  }
518
657
  }, 60 * 60 * 1000);
658
+ // Start the matterbridge in mode test
519
659
  if (hasParameter('test')) {
520
660
  this.bridgeMode = 'bridge';
521
661
  MatterbridgeDevice.bridgeMode = 'bridge';
522
662
  return;
523
663
  }
664
+ // Start the matterbridge in mode controller
524
665
  if (hasParameter('controller')) {
525
666
  this.bridgeMode = 'controller';
526
667
  await this.startController();
527
668
  return;
528
669
  }
670
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
529
671
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
530
672
  this.log.info('Setting default matterbridge start mode to bridge');
531
673
  await this.nodeContext?.set('bridgeMode', 'bridge');
532
674
  }
675
+ // Start matterbridge in bridge mode
533
676
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
534
677
  this.bridgeMode = 'bridge';
535
678
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -538,6 +681,7 @@ export class Matterbridge extends EventEmitter {
538
681
  await this.startBridge();
539
682
  return;
540
683
  }
684
+ // Start matterbridge in childbridge mode
541
685
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
542
686
  this.bridgeMode = 'childbridge';
543
687
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -547,17 +691,28 @@ export class Matterbridge extends EventEmitter {
547
691
  return;
548
692
  }
549
693
  }
694
+ /**
695
+ * Asynchronously loads and starts the registered plugins.
696
+ *
697
+ * This method is responsible for initializing and staarting all enabled plugins.
698
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
699
+ *
700
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
701
+ */
550
702
  async startPlugins() {
703
+ // Check, load and start the plugins
551
704
  for (const plugin of this.plugins) {
552
705
  plugin.configJson = await this.plugins.loadConfig(plugin);
553
706
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
707
+ // Check if the plugin is available
554
708
  if (!(await this.plugins.resolve(plugin.path))) {
555
709
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
556
710
  plugin.enabled = false;
557
711
  plugin.error = true;
558
712
  continue;
559
713
  }
560
- this.getPluginLatestVersion(plugin);
714
+ // Check if the plugin has a new version
715
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
561
716
  if (!plugin.enabled) {
562
717
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
563
718
  continue;
@@ -572,20 +727,26 @@ export class Matterbridge extends EventEmitter {
572
727
  plugin.addedDevices = undefined;
573
728
  plugin.qrPairingCode = undefined;
574
729
  plugin.manualPairingCode = undefined;
575
- this.plugins.load(plugin, true, 'Matterbridge is starting');
730
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
576
731
  }
577
732
  this.wssSendRefreshRequired();
578
733
  }
734
+ /**
735
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
736
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
737
+ */
579
738
  registerProcessHandlers() {
580
739
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
581
740
  process.removeAllListeners('uncaughtException');
582
741
  process.removeAllListeners('unhandledRejection');
583
742
  this.exceptionHandler = async (error) => {
584
743
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
744
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
585
745
  };
586
746
  process.on('uncaughtException', this.exceptionHandler);
587
747
  this.rejectionHandler = async (reason, promise) => {
588
748
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
749
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
589
750
  };
590
751
  process.on('unhandledRejection', this.rejectionHandler);
591
752
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -598,6 +759,9 @@ export class Matterbridge extends EventEmitter {
598
759
  };
599
760
  process.on('SIGTERM', this.sigtermHandler);
600
761
  }
762
+ /**
763
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
764
+ */
601
765
  deregisterProcesslHandlers() {
602
766
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
603
767
  if (this.exceptionHandler)
@@ -614,7 +778,11 @@ export class Matterbridge extends EventEmitter {
614
778
  process.off('SIGTERM', this.sigtermHandler);
615
779
  this.sigtermHandler = undefined;
616
780
  }
781
+ /**
782
+ * Logs the node and system information.
783
+ */
617
784
  async logNodeAndSystemInfo() {
785
+ // IP address information
618
786
  const networkInterfaces = os.networkInterfaces();
619
787
  this.systemInformation.ipv4Address = '';
620
788
  this.systemInformation.ipv6Address = '';
@@ -634,7 +802,7 @@ export class Matterbridge extends EventEmitter {
634
802
  this.systemInformation.macAddress = detail.mac;
635
803
  }
636
804
  }
637
- if (this.systemInformation.ipv4Address !== '') {
805
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
638
806
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
639
807
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
640
808
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -642,19 +810,22 @@ export class Matterbridge extends EventEmitter {
642
810
  break;
643
811
  }
644
812
  }
813
+ // Node information
645
814
  this.systemInformation.nodeVersion = process.versions.node;
646
815
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
647
816
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
648
817
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
818
+ // Host system information
649
819
  this.systemInformation.hostname = os.hostname();
650
820
  this.systemInformation.user = os.userInfo().username;
651
- this.systemInformation.osType = os.type();
652
- this.systemInformation.osRelease = os.release();
653
- this.systemInformation.osPlatform = os.platform();
654
- this.systemInformation.osArch = os.arch();
655
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
656
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
657
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
821
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
822
+ this.systemInformation.osRelease = os.release(); // Kernel version
823
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
824
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
825
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
826
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
827
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
828
+ // Log the system information
658
829
  this.log.debug('Host System Information:');
659
830
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
660
831
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -670,15 +841,19 @@ export class Matterbridge extends EventEmitter {
670
841
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
671
842
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
672
843
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
844
+ // Home directory
673
845
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
674
846
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
675
847
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
848
+ // Package root directory
676
849
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
677
850
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
678
851
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
679
852
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
853
+ // Global node_modules directory
680
854
  if (this.nodeContext)
681
855
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
856
+ // First run of Matterbridge so the node storage is empty
682
857
  if (this.globalModulesDirectory === '') {
683
858
  try {
684
859
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -702,6 +877,7 @@ export class Matterbridge extends EventEmitter {
702
877
  this.log.error(`Error getting global node_modules directory: ${error}`);
703
878
  });
704
879
  }
880
+ // Create the data directory .matterbridge in the home directory
705
881
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
706
882
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
707
883
  try {
@@ -725,6 +901,7 @@ export class Matterbridge extends EventEmitter {
725
901
  }
726
902
  }
727
903
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
904
+ // Create the plugin directory Matterbridge in the home directory
728
905
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
729
906
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
730
907
  try {
@@ -748,19 +925,28 @@ export class Matterbridge extends EventEmitter {
748
925
  }
749
926
  }
750
927
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
928
+ // Matterbridge version
751
929
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
752
930
  this.matterbridgeVersion = packageJson.version;
753
931
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
754
932
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
933
+ // Matterbridge latest version
755
934
  if (this.nodeContext)
756
935
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
757
936
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
758
937
  this.getMatterbridgeLatestVersion();
938
+ // Current working directory
759
939
  const currentDir = process.cwd();
760
940
  this.log.debug(`Current Working Directory: ${currentDir}`);
941
+ // Command line arguments (excluding 'node' and the script name)
761
942
  const cmdArgs = process.argv.slice(2).join(' ');
762
943
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
763
944
  }
945
+ /**
946
+ * Retrieves the latest version of a package from the npm registry.
947
+ * @param packageName - The name of the package.
948
+ * @returns A Promise that resolves to the latest version of the package.
949
+ */
764
950
  async getLatestVersion(packageName) {
765
951
  return new Promise((resolve, reject) => {
766
952
  this.execRunningCount++;
@@ -775,6 +961,10 @@ export class Matterbridge extends EventEmitter {
775
961
  });
776
962
  });
777
963
  }
964
+ /**
965
+ * Retrieves the path to the global Node.js modules directory.
966
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
967
+ */
778
968
  async getGlobalNodeModules() {
779
969
  return new Promise((resolve, reject) => {
780
970
  this.execRunningCount++;
@@ -789,6 +979,11 @@ export class Matterbridge extends EventEmitter {
789
979
  });
790
980
  });
791
981
  }
982
+ /**
983
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
984
+ * @private
985
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
986
+ */
792
987
  async getMatterbridgeLatestVersion() {
793
988
  this.getLatestVersion('matterbridge')
794
989
  .then(async (matterbridgeLatestVersion) => {
@@ -805,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
805
1000
  })
806
1001
  .catch((error) => {
807
1002
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
1003
+ // error.stack && this.log.debug(error.stack);
808
1004
  });
809
1005
  }
1006
+ /**
1007
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
1008
+ * If the plugin's version is different from the latest version, logs a warning message.
1009
+ * If the plugin's version is the same as the latest version, logs an info message.
1010
+ * If there is an error retrieving the latest version, logs an error message.
1011
+ *
1012
+ * @private
1013
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
1014
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
1015
+ */
810
1016
  async getPluginLatestVersion(plugin) {
811
1017
  this.getLatestVersion(plugin.name)
812
1018
  .then(async (latestVersion) => {
@@ -818,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
818
1024
  })
819
1025
  .catch((error) => {
820
1026
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1027
+ // error.stack && this.log.debug(error.stack);
821
1028
  });
822
1029
  }
1030
+ /**
1031
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1032
+ *
1033
+ * @returns {Function} The MatterLogger function.
1034
+ */
823
1035
  createMatterLogger() {
824
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1036
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
825
1037
  return (_level, formattedLog) => {
826
1038
  const logger = formattedLog.slice(44, 44 + 20).trim();
827
1039
  const message = formattedLog.slice(65);
828
1040
  matterLogger.logName = logger;
829
1041
  switch (_level) {
830
1042
  case MatterLogLevel.DEBUG:
831
- matterLogger.log("debug", message);
1043
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
832
1044
  break;
833
1045
  case MatterLogLevel.INFO:
834
- matterLogger.log("info", message);
1046
+ matterLogger.log("info" /* LogLevel.INFO */, message);
835
1047
  break;
836
1048
  case MatterLogLevel.NOTICE:
837
- matterLogger.log("notice", message);
1049
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
838
1050
  break;
839
1051
  case MatterLogLevel.WARN:
840
- matterLogger.log("warn", message);
1052
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
841
1053
  break;
842
1054
  case MatterLogLevel.ERROR:
843
- matterLogger.log("error", message);
1055
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
844
1056
  break;
845
1057
  case MatterLogLevel.FATAL:
846
- matterLogger.log("fatal", message);
1058
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
847
1059
  break;
848
1060
  default:
849
- matterLogger.log("debug", message);
1061
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
850
1062
  break;
851
1063
  }
852
1064
  };
853
1065
  }
1066
+ /**
1067
+ * Creates a Matter File Logger.
1068
+ *
1069
+ * @param {string} filePath - The path to the log file.
1070
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1071
+ * @returns {Function} - A function that logs formatted messages to the log file.
1072
+ */
854
1073
  async createMatterFileLogger(filePath, unlink = false) {
1074
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
855
1075
  let fileSize = 0;
856
1076
  if (unlink) {
857
1077
  try {
@@ -900,53 +1120,86 @@ export class Matterbridge extends EventEmitter {
900
1120
  }
901
1121
  };
902
1122
  }
1123
+ /**
1124
+ * Update matterbridge and cleanup.
1125
+ */
903
1126
  async updateProcess() {
904
1127
  await this.cleanup('updating...', false);
905
1128
  }
1129
+ /**
1130
+ * Restarts the process by spawning a new process and exiting the current process.
1131
+ */
906
1132
  async restartProcess() {
907
1133
  await this.cleanup('restarting...', true);
908
1134
  }
1135
+ /**
1136
+ * Shut down the process by exiting the current process.
1137
+ */
909
1138
  async shutdownProcess() {
910
1139
  await this.cleanup('shutting down...', false);
911
1140
  }
1141
+ /**
1142
+ * Shut down the process and reset.
1143
+ */
912
1144
  async unregisterAndShutdownProcess() {
913
1145
  this.log.info('Unregistering all devices and shutting down...');
914
- for (const plugin of this.plugins) {
915
- await this.removeAllBridgedDevices(plugin.name);
1146
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
1147
+ if (this.edge)
1148
+ await this.removeAllBridgedEndpoints(plugin.name);
1149
+ else
1150
+ await this.removeAllBridgedDevices(plugin.name);
916
1151
  }
917
1152
  await this.cleanup('unregistered all devices and shutting down...', false);
918
1153
  }
1154
+ /**
1155
+ * Shut down the process and reset.
1156
+ */
919
1157
  async shutdownProcessAndReset() {
920
1158
  await this.cleanup('shutting down with reset...', false);
921
1159
  }
1160
+ /**
1161
+ * Shut down the process and factory reset.
1162
+ */
922
1163
  async shutdownProcessAndFactoryReset() {
923
1164
  await this.cleanup('shutting down with factory reset...', false);
924
1165
  }
1166
+ /**
1167
+ * Cleans up the Matterbridge instance.
1168
+ * @param message - The cleanup message.
1169
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1170
+ * @returns A promise that resolves when the cleanup is completed.
1171
+ */
925
1172
  async cleanup(message, restart = false) {
926
1173
  if (this.initialized && !this.hasCleanupStarted) {
927
1174
  this.hasCleanupStarted = true;
928
1175
  this.log.info(message);
1176
+ // Deregisters the process handlers
929
1177
  this.deregisterProcesslHandlers();
1178
+ // Clear the start matter interval
930
1179
  if (this.startMatterInterval) {
931
1180
  clearInterval(this.startMatterInterval);
932
1181
  this.startMatterInterval = undefined;
933
1182
  this.log.debug('Start matter interval cleared');
934
1183
  }
1184
+ // Clear the check update interval
935
1185
  if (this.checkUpdateInterval) {
936
1186
  clearInterval(this.checkUpdateInterval);
937
1187
  this.checkUpdateInterval = undefined;
938
1188
  this.log.debug('Check update interval cleared');
939
1189
  }
1190
+ // Clear the configure timeout
940
1191
  if (this.configureTimeout) {
941
1192
  clearTimeout(this.configureTimeout);
942
1193
  this.configureTimeout = undefined;
943
1194
  this.log.debug('Matterbridge configure timeout cleared');
944
1195
  }
1196
+ // Clear the reachability timeout
945
1197
  if (this.reachabilityTimeout) {
946
1198
  clearTimeout(this.reachabilityTimeout);
947
1199
  this.reachabilityTimeout = undefined;
948
1200
  this.log.debug('Matterbridge reachability timeout cleared');
949
1201
  }
1202
+ // Calling the shutdown method of each plugin and clear the reachability timeout
950
1203
  for (const plugin of this.plugins) {
951
1204
  if (!plugin.enabled || plugin.error)
952
1205
  continue;
@@ -957,24 +1210,42 @@ export class Matterbridge extends EventEmitter {
957
1210
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
958
1211
  }
959
1212
  }
1213
+ // Convert the matter storage to the new format
1214
+ if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
1215
+ if (this.bridgeMode === 'bridge') {
1216
+ await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
1217
+ }
1218
+ else if (this.bridgeMode === 'childbridge') {
1219
+ for (const plugin of this.plugins) {
1220
+ if (plugin.storageContext) {
1221
+ await this.convertStorage(plugin.storageContext, plugin.name);
1222
+ }
1223
+ }
1224
+ }
1225
+ }
1226
+ // Close the http server
960
1227
  if (this.httpServer) {
961
1228
  this.httpServer.close();
962
1229
  this.httpServer.removeAllListeners();
963
1230
  this.httpServer = undefined;
964
1231
  this.log.debug('Frontend http server closed successfully');
965
1232
  }
1233
+ // Close the https server
966
1234
  if (this.httpsServer) {
967
1235
  this.httpsServer.close();
968
1236
  this.httpsServer.removeAllListeners();
969
1237
  this.httpsServer = undefined;
970
1238
  this.log.debug('Frontend https server closed successfully');
971
1239
  }
1240
+ // Remove listeners from the express app
972
1241
  if (this.expressApp) {
973
1242
  this.expressApp.removeAllListeners();
974
1243
  this.expressApp = undefined;
975
1244
  this.log.debug('Frontend app closed successfully');
976
1245
  }
1246
+ // Close the WebSocket server
977
1247
  if (this.webSocketServer) {
1248
+ // Close all active connections
978
1249
  this.webSocketServer.clients.forEach((client) => {
979
1250
  if (client.readyState === WebSocket.OPEN) {
980
1251
  client.close();
@@ -990,29 +1261,35 @@ export class Matterbridge extends EventEmitter {
990
1261
  });
991
1262
  this.webSocketServer = undefined;
992
1263
  }
993
- if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
994
- await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
995
- }
1264
+ // Closing matter
996
1265
  await this.stopMatterServer();
1266
+ // Closing matter storage
997
1267
  await this.stopMatterStorage();
1268
+ // Remove the matterfilelogger
998
1269
  try {
999
1270
  Logger.removeLogger('matterfilelogger');
1271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1000
1272
  }
1001
1273
  catch (error) {
1274
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1002
1275
  }
1276
+ // Serialize registeredDevices
1003
1277
  if (this.nodeStorage && this.nodeContext) {
1004
1278
  this.log.info('Saving registered devices...');
1005
1279
  const serializedRegisteredDevices = [];
1006
1280
  this.devices.forEach(async (device) => {
1007
1281
  const serializedMatterbridgeDevice = device.serialize();
1282
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1008
1283
  if (serializedMatterbridgeDevice)
1009
1284
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1010
1285
  });
1011
1286
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1012
1287
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1288
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1013
1289
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1014
1290
  await this.nodeContext.close();
1015
1291
  this.nodeContext = undefined;
1292
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1016
1293
  for (const plugin of this.plugins) {
1017
1294
  if (plugin.nodeContext) {
1018
1295
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1042,17 +1319,37 @@ export class Matterbridge extends EventEmitter {
1042
1319
  }
1043
1320
  }
1044
1321
  else {
1045
- if (message === 'shutting down with reset...') {
1046
- this.log.info('Resetting Matterbridge commissioning information...');
1047
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1048
- this.log.info('Reset done! Remove all paired devices from the controllers.');
1322
+ if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
1323
+ try {
1324
+ // Delete old matter storage file
1325
+ const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1326
+ this.log.info(`Unlinking old matter storage file: ${file}`);
1327
+ await fs.unlink(file);
1328
+ }
1329
+ catch (error) {
1330
+ this.log.debug(`Error resetting old matter storage file: ${error}`);
1331
+ }
1332
+ try {
1333
+ // Delete matter node storage directory with its subdirectories
1334
+ const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1335
+ this.log.info(`Removing matter node storage directory: ${dir}`);
1336
+ await fs.rm(dir, { recursive: true });
1337
+ }
1338
+ catch (error) {
1339
+ this.log.debug(`Error resetting matter node storage file: ${error}`);
1340
+ }
1341
+ this.log.info('Reset done! Remove all paired fabrics from the controllers.');
1049
1342
  }
1050
1343
  if (message === 'shutting down with factory reset...') {
1051
- this.log.info('Resetting Matterbridge commissioning information...');
1052
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1053
- this.log.info('Resetting Matterbridge storage...');
1054
- await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1055
- this.log.info('Factory reset done! Remove all paired devices from the controllers.');
1344
+ try {
1345
+ // Delete node storage directory with its subdirectories
1346
+ this.log.info('Resetting Matterbridge storage...');
1347
+ await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1348
+ }
1349
+ catch (error) {
1350
+ this.log.debug(`Error resetting Matterbridge storage: ${error}`);
1351
+ }
1352
+ this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1056
1353
  }
1057
1354
  this.log.notice('Cleanup completed. Shutting down...');
1058
1355
  Matterbridge.instance = undefined;
@@ -1062,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
1062
1359
  this.initialized = false;
1063
1360
  }
1064
1361
  }
1362
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1065
1363
  async addBridgedEndpoint(pluginName, device) {
1364
+ // Nothing to do here
1066
1365
  }
1366
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1067
1367
  async removeBridgedEndpoint(pluginName, device) {
1368
+ // Nothing to do here
1068
1369
  }
1370
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1069
1371
  async removeAllBridgedEndpoints(pluginName) {
1372
+ // Nothing to do here
1070
1373
  }
1374
+ /**
1375
+ * Adds a bridged device to the Matterbridge.
1376
+ * @param pluginName - The name of the plugin.
1377
+ * @param device - The bridged device to add.
1378
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1379
+ */
1071
1380
  async addBridgedDevice(pluginName, device) {
1072
1381
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1382
+ // Check if the plugin is registered
1073
1383
  const plugin = this.plugins.get(pluginName);
1074
1384
  if (!plugin) {
1075
1385
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1076
1386
  return;
1077
1387
  }
1388
+ // Register and add the device to matterbridge aggregator in bridge mode
1078
1389
  if (this.bridgeMode === 'bridge') {
1079
1390
  if (!this.matterAggregator) {
1080
1391
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1082,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
1082
1393
  }
1083
1394
  this.matterAggregator.addBridgedDevice(device);
1084
1395
  }
1396
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1397
+ // Register and add the device in childbridge mode
1085
1398
  if (this.bridgeMode === 'childbridge') {
1086
1399
  if (plugin.type === 'AccessoryPlatform') {
1400
+ // Check if the plugin is locked with the commissioning server
1087
1401
  if (!plugin.locked) {
1088
1402
  plugin.locked = true;
1089
1403
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1097,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
1097
1411
  }
1098
1412
  }
1099
1413
  if (plugin.type === 'DynamicPlatform') {
1414
+ // Check if the plugin is locked with the commissioning server and the aggregator
1100
1415
  if (!plugin.locked) {
1101
1416
  plugin.locked = true;
1102
1417
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1117,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
1117
1432
  plugin.registeredDevices++;
1118
1433
  if (plugin.addedDevices !== undefined)
1119
1434
  plugin.addedDevices++;
1435
+ // Add the device to the DeviceManager
1120
1436
  this.devices.set(device);
1121
1437
  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}`);
1122
1438
  }
1439
+ /**
1440
+ * Removes a bridged device from the Matterbridge.
1441
+ * @param pluginName - The name of the plugin.
1442
+ * @param device - The device to be removed.
1443
+ * @returns A Promise that resolves when the device is successfully removed.
1444
+ */
1123
1445
  async removeBridgedDevice(pluginName, device) {
1124
1446
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1447
+ // Check if the plugin is registered
1125
1448
  const plugin = this.plugins.get(pluginName);
1126
1449
  if (!plugin) {
1127
1450
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1128
1451
  return;
1129
1452
  }
1453
+ // Remove the device from matterbridge aggregator in bridge mode
1130
1454
  if (this.bridgeMode === 'bridge') {
1131
1455
  if (!this.matterAggregator) {
1132
1456
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1136,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
1136
1460
  device.setBridgedDeviceReachability(false);
1137
1461
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1138
1462
  }
1463
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1464
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1139
1465
  this.matterAggregator?.removeBridgedDevice(device);
1140
1466
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1141
1467
  if (plugin.registeredDevices !== undefined)
@@ -1143,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
1143
1469
  if (plugin.addedDevices !== undefined)
1144
1470
  plugin.addedDevices--;
1145
1471
  }
1472
+ // Remove the device in childbridge mode
1146
1473
  if (this.bridgeMode === 'childbridge') {
1147
1474
  if (plugin.type === 'AccessoryPlatform') {
1148
1475
  if (!plugin.commissioningServer) {
@@ -1166,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
1166
1493
  plugin.registeredDevices--;
1167
1494
  if (plugin.addedDevices !== undefined)
1168
1495
  plugin.addedDevices--;
1496
+ // Remove the commissioning server
1169
1497
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1170
1498
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1171
1499
  plugin.commissioningServer = undefined;
1172
1500
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1173
1501
  }
1174
1502
  }
1503
+ // Remove the device from the DeviceManager
1175
1504
  this.devices.remove(device);
1176
1505
  }
1506
+ /**
1507
+ * Removes all bridged devices associated with a specific plugin.
1508
+ *
1509
+ * @param pluginName - The name of the plugin.
1510
+ * @returns A promise that resolves when all devices have been removed.
1511
+ */
1177
1512
  async removeAllBridgedDevices(pluginName) {
1178
1513
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1179
1514
  this.devices.forEach(async (device) => {
@@ -1182,12 +1517,18 @@ export class Matterbridge extends EventEmitter {
1182
1517
  }
1183
1518
  });
1184
1519
  }
1520
+ /**
1521
+ * Starts the Matterbridge in bridge mode.
1522
+ * @private
1523
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1524
+ */
1185
1525
  async startBridge() {
1526
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1186
1527
  if (!this.storageManager)
1187
1528
  throw new Error('No storage manager initialized');
1188
1529
  if (!this.matterbridgeContext)
1189
1530
  throw new Error('No storage context initialized');
1190
- this.matterServer = this.createMatterServer(this.storageManager);
1531
+ this.matterServer = await this.createMatterServer(this.storageManager);
1191
1532
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1192
1533
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1193
1534
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -1201,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
1201
1542
  let failCount = 0;
1202
1543
  this.startMatterInterval = setInterval(async () => {
1203
1544
  for (const plugin of this.plugins) {
1545
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1204
1546
  if (!plugin.enabled)
1205
1547
  continue;
1206
1548
  if (plugin.error) {
@@ -1225,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
1225
1567
  clearInterval(this.startMatterInterval);
1226
1568
  this.startMatterInterval = undefined;
1227
1569
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1570
+ // Start the Matter server
1228
1571
  await this.startMatterServer();
1229
1572
  this.log.notice('Matter server started');
1573
+ // Show the QR code for commissioning or log the already commissioned message
1230
1574
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1575
+ // Configure the plugins
1231
1576
  this.configureTimeout = setTimeout(async () => {
1232
1577
  for (const plugin of this.plugins) {
1233
1578
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1234
1579
  continue;
1235
1580
  try {
1236
- await this.plugins.configure(plugin);
1581
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1237
1582
  }
1238
1583
  catch (error) {
1239
1584
  plugin.error = true;
@@ -1242,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
1242
1587
  }
1243
1588
  this.wssSendRefreshRequired();
1244
1589
  }, 30 * 1000);
1590
+ // Setting reachability to true
1245
1591
  this.reachabilityTimeout = setTimeout(() => {
1246
1592
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1247
1593
  if (this.commissioningServer)
@@ -1251,16 +1597,24 @@ export class Matterbridge extends EventEmitter {
1251
1597
  }, 60 * 1000);
1252
1598
  }, 1000);
1253
1599
  }
1600
+ /**
1601
+ * Starts the Matterbridge in childbridge mode.
1602
+ * @private
1603
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1604
+ */
1254
1605
  async startChildbridge() {
1606
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1607
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1255
1608
  if (!this.storageManager)
1256
1609
  throw new Error('No storage manager initialized');
1257
- this.matterServer = this.createMatterServer(this.storageManager);
1610
+ this.matterServer = await this.createMatterServer(this.storageManager);
1258
1611
  await this.startPlugins();
1259
1612
  this.log.debug('Starting start matter interval in childbridge mode...');
1260
1613
  let failCount = 0;
1261
1614
  this.startMatterInterval = setInterval(async () => {
1262
1615
  let allStarted = true;
1263
1616
  for (const plugin of this.plugins) {
1617
+ // Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1264
1618
  if (!plugin.enabled)
1265
1619
  continue;
1266
1620
  if (plugin.error) {
@@ -1288,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
1288
1642
  clearInterval(this.startMatterInterval);
1289
1643
  this.startMatterInterval = undefined;
1290
1644
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1645
+ // Start the Matter server
1291
1646
  await this.startMatterServer();
1292
1647
  this.log.notice('Matter server started');
1648
+ // Configure the plugins
1293
1649
  this.configureTimeout = setTimeout(async () => {
1294
1650
  for (const plugin of this.plugins) {
1295
1651
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1296
1652
  continue;
1297
1653
  try {
1298
- await this.plugins.configure(plugin);
1654
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1299
1655
  }
1300
1656
  catch (error) {
1301
1657
  plugin.error = true;
@@ -1324,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
1324
1680
  continue;
1325
1681
  }
1326
1682
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1683
+ // Setting reachability to true
1327
1684
  plugin.reachabilityTimeout = setTimeout(() => {
1328
1685
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1329
1686
  if (plugin.commissioningServer)
@@ -1336,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
1336
1693
  }
1337
1694
  }, 1000);
1338
1695
  }
1696
+ /**
1697
+ * Starts the Matterbridge controller.
1698
+ * @private
1699
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1700
+ */
1339
1701
  async startController() {
1340
1702
  if (!this.storageManager) {
1341
1703
  this.log.error('No storage manager initialized');
@@ -1350,7 +1712,7 @@ export class Matterbridge extends EventEmitter {
1350
1712
  return;
1351
1713
  }
1352
1714
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1353
- this.matterServer = this.createMatterServer(this.storageManager);
1715
+ this.matterServer = await this.createMatterServer(this.storageManager);
1354
1716
  this.log.info('Creating matter commissioning controller');
1355
1717
  this.commissioningController = new CommissioningController({
1356
1718
  autoConnect: false,
@@ -1398,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
1398
1760
  const nodeId = await this.commissioningController.commissionNode(options);
1399
1761
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1400
1762
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1401
- }
1763
+ } // (hasParameter('pairingcode'))
1402
1764
  if (hasParameter('unpairall')) {
1403
1765
  this.log.info('***Commissioning controller unpairing all nodes...');
1404
1766
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1409,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
1409
1771
  return;
1410
1772
  }
1411
1773
  if (hasParameter('discover')) {
1774
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
+ // console.log(discover);
1412
1776
  }
1413
1777
  if (!this.commissioningController.isCommissioned()) {
1414
1778
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1449,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
1449
1813
  },
1450
1814
  });
1451
1815
  node.logStructure();
1816
+ // Get the interaction client
1452
1817
  this.log.info('Getting the interaction client');
1453
1818
  const interactionClient = await node.getInteractionClient();
1454
1819
  let cluster;
1455
1820
  let attributes;
1821
+ // Log BasicInformationCluster
1456
1822
  cluster = BasicInformationCluster;
1457
1823
  attributes = await interactionClient.getMultipleAttributes({
1458
1824
  attributes: [{ clusterId: cluster.id }],
@@ -1462,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
1462
1828
  attributes.forEach((attribute) => {
1463
1829
  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
1830
  });
1831
+ // Log PowerSourceCluster
1465
1832
  cluster = PowerSourceCluster;
1466
1833
  attributes = await interactionClient.getMultipleAttributes({
1467
1834
  attributes: [{ clusterId: cluster.id }],
@@ -1471,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
1471
1838
  attributes.forEach((attribute) => {
1472
1839
  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
1840
  });
1841
+ // Log ThreadNetworkDiagnostics
1474
1842
  cluster = ThreadNetworkDiagnosticsCluster;
1475
1843
  attributes = await interactionClient.getMultipleAttributes({
1476
1844
  attributes: [{ clusterId: cluster.id }],
@@ -1480,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
1480
1848
  attributes.forEach((attribute) => {
1481
1849
  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}`);
1482
1850
  });
1851
+ // Log SwitchCluster
1483
1852
  cluster = SwitchCluster;
1484
1853
  attributes = await interactionClient.getMultipleAttributes({
1485
1854
  attributes: [{ clusterId: cluster.id }],
@@ -1500,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
1500
1869
  this.log.info('Subscribed to all attributes and events');
1501
1870
  }
1502
1871
  }
1872
+ /** ***********************************************************************************************************************************/
1873
+ /** Matter.js methods */
1874
+ /** ***********************************************************************************************************************************/
1875
+ /**
1876
+ * Starts the matter storage process based on the specified storage type and name.
1877
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1878
+ * @param {string} storageName - The name of the storage file.
1879
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1880
+ */
1503
1881
  async startMatterStorage(storageType, storageName) {
1504
1882
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1505
1883
  if (storageType === 'disk') {
@@ -1548,25 +1926,39 @@ export class Matterbridge extends EventEmitter {
1548
1926
  await this.matterbridgeContext.set('passcode', this.passcode);
1549
1927
  await this.matterbridgeContext.set('discriminator', this.discriminator);
1550
1928
  }
1929
+ /**
1930
+ * Convert the old API matter storage to the new API format.
1931
+ * @param {StorageContext} context - The context of Matterbridge or of the plugin.
1932
+ * @param {string} pluginName - The name of the plugin or Matterbridge.
1933
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1934
+ */
1551
1935
  async convertStorage(context, pluginName) {
1936
+ if (this.edge !== false)
1937
+ return;
1552
1938
  try {
1553
1939
  const storageService = Environment.default.get(StorageService);
1554
1940
  Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
1555
- const nodeStorage = await storageService.open('Matterbridge');
1556
- if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', 0)) > 0) {
1941
+ const nodeStorage = await storageService.open(pluginName);
1942
+ if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
1557
1943
  this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
1558
1944
  return;
1559
1945
  }
1560
1946
  else {
1561
1947
  this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1562
1948
  }
1949
+ // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1563
1950
  const fabricManagerContext = context.createContext('FabricManager');
1564
1951
  const fabrics = (await fabricManagerContext.get('fabrics', []));
1565
- const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 1);
1952
+ const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
1953
+ // Read EventHandler from the old storage
1566
1954
  const eventHandlerContext = context.createContext('EventHandler');
1955
+ // Read SessionManager from the old storage
1567
1956
  const sessionManagerContext = context.createContext('SessionManager');
1957
+ // Read EndpointStructure from the old storage
1568
1958
  const endpointStructureContext = context.createContext('EndpointStructure');
1959
+ // Read generalCommissioning from the old storage
1569
1960
  const generalCommissioningContext = context.createContext('Cluster-0-48');
1961
+ // Read basicInformation from the old storage
1570
1962
  const basicInformationContext = context.createContext('Cluster-0-40');
1571
1963
  const fabricInfo = {};
1572
1964
  const fabricInfoArray = [];
@@ -1574,6 +1966,10 @@ export class Matterbridge extends EventEmitter {
1574
1966
  const trcArray = [];
1575
1967
  const aclArray = [];
1576
1968
  this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
1969
+ if (fabrics.length === 0 || nextFabricIndex === 0) {
1970
+ this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
1971
+ return;
1972
+ }
1577
1973
  for (const fabric of fabrics) {
1578
1974
  this.log.info(`- fabricIndex ${CYAN}${fabric.fabricIndex}${nf} fabricId ${CYAN}${fabric.fabricId}${nf} nodeId ${CYAN}${fabric.nodeId}${nf} rootNodeId ${CYAN}${fabric.rootNodeId}${nf} rootVendorId ${CYAN}${fabric.rootVendorId}${nf} label ${CYAN}${fabric.label}${nf}`);
1579
1975
  fabricInfo[fabric.fabricIndex] = {
@@ -1593,7 +1989,7 @@ export class Matterbridge extends EventEmitter {
1593
1989
  label: fabric.label,
1594
1990
  });
1595
1991
  nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
1596
- trcArray.push('{\"__object__\":\"Uint8Array\",\"__value__\":\"' + Buffer.from(fabric.rootCert).toString('hex') + '\"}');
1992
+ trcArray.push(fabric.rootCert);
1597
1993
  this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
1598
1994
  const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
1599
1995
  if (acl && acl.value.length > 0) {
@@ -1611,14 +2007,18 @@ export class Matterbridge extends EventEmitter {
1611
2007
  await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
1612
2008
  await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
1613
2009
  await nodeStorage.createContext('root').set('__number__', 0);
1614
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
1615
2010
  await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
1616
2011
  await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
1617
2012
  await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
1618
2013
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
1619
2014
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
2015
+ // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
1620
2016
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
2017
+ // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
1621
2018
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
2019
+ // ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
2020
+ // From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
2021
+ // [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
1622
2022
  await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
1623
2023
  await nodeStorage
1624
2024
  .createContext('root')
@@ -1629,9 +2029,39 @@ export class Matterbridge extends EventEmitter {
1629
2029
  .createContext('basicInformation')
1630
2030
  .set('location', await basicInformationContext.get('location', 'XX'));
1631
2031
  await nodeStorage.createContext('root').createContext('network').set('ble', false);
1632
- await nodeStorage.createContext('root').createContext('network').set('operationalPort', 5540);
1633
- await nodeStorage.createContext('root').createContext('productDescription').set('productId', 0x8000);
1634
- await nodeStorage.createContext('root').createContext('productDescription').set('vendorId', 0xfff1);
2032
+ await nodeStorage
2033
+ .createContext('root')
2034
+ .createContext('network')
2035
+ .set('operationalPort', await context.get('port', 5540));
2036
+ await nodeStorage
2037
+ .createContext('root')
2038
+ .createContext('productDescription')
2039
+ .set('productId', await context.get('productId', 0x8000));
2040
+ await nodeStorage
2041
+ .createContext('root')
2042
+ .createContext('productDescription')
2043
+ .set('vendorId', await context.get('vendorId', 0xfff1));
2044
+ /*
2045
+ "Matterbridge.EndpointStructure": {
2046
+ "unique_d60ca095a002f160-index_0": 1,
2047
+ "unique_d60ca095a002f160-index_0-custom_Switch0": 2,
2048
+ "unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
2049
+ "unique_d60ca095a002f160-index_0-custom_Light0": 4,
2050
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
2051
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
2052
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
2053
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
2054
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
2055
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
2056
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
2057
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
2058
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
2059
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
2060
+ "nextEndpointId": 5
2061
+ },
2062
+ */
2063
+ const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
2064
+ this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
1635
2065
  for (const key of await endpointStructureContext.keys()) {
1636
2066
  if (key === 'nextEndpointId') {
1637
2067
  await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
@@ -1640,20 +2070,49 @@ export class Matterbridge extends EventEmitter {
1640
2070
  const parts = key.split('-');
1641
2071
  const number = await endpointStructureContext.get(key);
1642
2072
  if (parts.length === 2) {
1643
- this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.__number__:${number}`);
1644
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', number);
1645
- }
1646
- else if (parts.length === 3 && parts[2].startsWith('custom_')) {
1647
- this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${parts[2].replace('custom_', '')}.__number__:${number}`);
1648
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').createContext('parts').createContext(parts[2].replace('custom_', '')).set('__number__', number);
2073
+ this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
2074
+ await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
1649
2075
  }
1650
2076
  else if (parts.length === 3 && parts[2].startsWith('unique_')) {
1651
2077
  const device = this.devices.get(parts[2].replace('unique_', ''));
1652
2078
  if (device && device.deviceName && device.maybeNumber) {
1653
- this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${device.maybeNumber}`);
1654
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
2079
+ this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
2080
+ await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
2081
+ }
2082
+ }
2083
+ else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
2084
+ const device = this.devices.get(parts[2].replace('unique_', ''));
2085
+ if (device && device.deviceName && device.maybeNumber) {
2086
+ const childEndpointName = parts[3].replace('custom_', '');
2087
+ const childEndpoint = device.getChildEndpointByName(childEndpointName);
2088
+ this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
2089
+ await nodeStorage
2090
+ .createContext('root')
2091
+ .createContext('parts')
2092
+ .createContext(rootDeviceName)
2093
+ .createContext('parts')
2094
+ .createContext(device.deviceName.replace(/[ .]/g, ''))
2095
+ .createContext('parts')
2096
+ .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2097
+ .set('__number__', childEndpoint?.number);
1655
2098
  }
1656
2099
  }
2100
+ else if (parts.length === 3 && parts[2].startsWith('custom_')) {
2101
+ this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2102
+ await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
2103
+ }
2104
+ else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
2105
+ this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2106
+ await nodeStorage
2107
+ .createContext('root')
2108
+ .createContext('parts')
2109
+ .createContext(rootDeviceName)
2110
+ .createContext('parts')
2111
+ .createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
2112
+ .createContext('parts')
2113
+ .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2114
+ .set('__number__', number);
2115
+ }
1657
2116
  }
1658
2117
  await nodeStorage.createContext('persist').set('converted', true);
1659
2118
  await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
@@ -1672,12 +2131,19 @@ export class Matterbridge extends EventEmitter {
1672
2131
  await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
1673
2132
  await context.set('converted', true);
1674
2133
  this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
1675
- this.log.notice(`If you want to try out matterbridge edge (beta) add -edge to the command line.`);
2134
+ this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
2135
+ this.log.notice(`All fabrics have been converted to the new storage format.`);
1676
2136
  }
1677
2137
  catch (error) {
1678
2138
  this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
1679
2139
  }
1680
2140
  }
2141
+ /**
2142
+ * Makes a backup copy of the specified matter JSON storage file.
2143
+ *
2144
+ * @param storageName - The name of the JSON storage file to be backed up.
2145
+ * @param backupName - The name of the backup file to be created.
2146
+ */
1681
2147
  async backupMatterStorage(storageName, backupName) {
1682
2148
  try {
1683
2149
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1698,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
1698
2164
  }
1699
2165
  }
1700
2166
  }
2167
+ /**
2168
+ * Restore the specified matter JSON storage file.
2169
+ *
2170
+ * @param backupName - The name of the backup file to restore from.
2171
+ * @param storageName - The name of the JSON storage file to restored.
2172
+ */
1701
2173
  async restoreMatterStorage(backupName, storageName) {
1702
2174
  try {
1703
2175
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1718,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
1718
2190
  }
1719
2191
  }
1720
2192
  }
2193
+ /**
2194
+ * Stops the matter storage.
2195
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2196
+ */
1721
2197
  async stopMatterStorage() {
1722
2198
  this.log.debug('Stopping storage');
1723
2199
  await this.storageManager?.close();
@@ -1726,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
1726
2202
  this.matterbridgeContext = undefined;
1727
2203
  this.mattercontrollerContext = undefined;
1728
2204
  }
1729
- createMatterServer(storageManager) {
2205
+ /**
2206
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
2207
+ * @param storageManager The storage manager to be used by the Matter server.
2208
+ *
2209
+ */
2210
+ async createMatterServer(storageManager) {
1730
2211
  this.log.debug('Creating matter server');
2212
+ // Validate mdnsInterface
1731
2213
  if (this.mdnsInterface) {
1732
2214
  const networkInterfaces = os.networkInterfaces();
1733
2215
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1743,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
1743
2225
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1744
2226
  return matterServer;
1745
2227
  }
2228
+ /**
2229
+ * Starts the Matter server.
2230
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
2231
+ */
1746
2232
  async startMatterServer() {
1747
2233
  if (!this.matterServer) {
1748
2234
  this.log.error('No matter server initialized');
@@ -1752,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
1752
2238
  this.log.debug('Starting matter server...');
1753
2239
  await this.matterServer.start();
1754
2240
  this.log.debug('Started matter server');
2241
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1755
2242
  }
2243
+ /**
2244
+ * Stops the Matter server, commissioningServer and commissioningController.
2245
+ */
1756
2246
  async stopMatterServer() {
1757
2247
  this.log.debug('Stopping matter commissioningServer');
1758
2248
  await this.commissioningServer?.close();
@@ -1766,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
1766
2256
  this.matterAggregator = undefined;
1767
2257
  this.matterServer = undefined;
1768
2258
  }
2259
+ /**
2260
+ * Creates a Matter Aggregator.
2261
+ * @param {StorageContext} context - The storage context.
2262
+ * @returns {Aggregator} - The created Matter Aggregator.
2263
+ */
1769
2264
  async createMatterAggregator(context, pluginName) {
1770
2265
  this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
1771
2266
  const matterAggregator = new Aggregator();
1772
2267
  return matterAggregator;
1773
2268
  }
2269
+ /**
2270
+ * Creates a matter commissioning server.
2271
+ *
2272
+ * @param {StorageContext} context - The storage context.
2273
+ * @param {string} pluginName - The name of the commissioning server.
2274
+ * @returns {CommissioningServer} The created commissioning server.
2275
+ */
1774
2276
  async createCommisioningServer(context, pluginName) {
1775
2277
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1776
2278
  const deviceName = await context.get('deviceName');
1777
2279
  const deviceType = await context.get('deviceType');
1778
2280
  const vendorId = await context.get('vendorId');
1779
- const vendorName = await context.get('vendorName');
2281
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1780
2282
  const productId = await context.get('productId');
1781
- const productName = await context.get('productName');
2283
+ const productName = await context.get('productName'); // Home app = Model
1782
2284
  const serialNumber = await context.get('serialNumber');
1783
2285
  const uniqueId = await context.get('uniqueId');
1784
2286
  const softwareVersion = await context.get('softwareVersion', 1);
1785
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2287
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1786
2288
  const hardwareVersion = await context.get('hardwareVersion', 1);
1787
2289
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1788
2290
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1790,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
1790
2292
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1791
2293
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1792
2294
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
2295
+ // Validate ipv4address
1793
2296
  if (this.ipv4address) {
1794
2297
  const networkInterfaces = os.networkInterfaces();
1795
2298
  const availableAddresses = Object.values(networkInterfaces)
@@ -1804,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
1804
2307
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1805
2308
  }
1806
2309
  }
2310
+ // Validate ipv6address
1807
2311
  if (this.ipv6address) {
1808
2312
  const networkInterfaces = os.networkInterfaces();
1809
2313
  const availableAddresses = Object.values(networkInterfaces)
@@ -1834,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
1834
2338
  nodeLabel: productName,
1835
2339
  productLabel: productName,
1836
2340
  softwareVersion,
1837
- softwareVersionString,
2341
+ softwareVersionString, // Home app = Firmware Revision
1838
2342
  hardwareVersion,
1839
2343
  hardwareVersionString,
1840
2344
  uniqueId,
@@ -1930,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
1930
2434
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1931
2435
  return commissioningServer;
1932
2436
  }
2437
+ /**
2438
+ * Creates a commissioning server storage context.
2439
+ *
2440
+ * @param pluginName - The name of the plugin.
2441
+ * @param deviceName - The name of the device.
2442
+ * @param deviceType - The type of the device.
2443
+ * @param vendorId - The vendor ID.
2444
+ * @param vendorName - The vendor name.
2445
+ * @param productId - The product ID.
2446
+ * @param productName - The product name.
2447
+ * @param serialNumber - The serial number of the device (optional).
2448
+ * @param uniqueId - The unique ID of the device (optional).
2449
+ * @param softwareVersion - The software version of the device (optional).
2450
+ * @param softwareVersionString - The software version string of the device (optional).
2451
+ * @param hardwareVersion - The hardware version of the device (optional).
2452
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2453
+ * @returns The storage context for the commissioning server.
2454
+ */
1933
2455
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1934
2456
  if (!this.storageManager)
1935
2457
  throw new Error('No storage manager initialized');
@@ -1957,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
1957
2479
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1958
2480
  return storageContext;
1959
2481
  }
2482
+ /**
2483
+ * Imports the commissioning server context for a specific plugin and device.
2484
+ * @param pluginName - The name of the plugin.
2485
+ * @param device - The MatterbridgeDevice object representing the device.
2486
+ * @returns The commissioning server context.
2487
+ * @throws Error if the BasicInformationCluster is not found.
2488
+ */
1960
2489
  async importCommissioningServerContext(pluginName, device) {
1961
2490
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1962
2491
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -1991,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
1991
2520
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1992
2521
  return storageContext;
1993
2522
  }
2523
+ /**
2524
+ * Shows the commissioning server QR code for a given plugin.
2525
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2526
+ * @param {StorageContext} storageContext - The storage context instance.
2527
+ * @param {NodeStorage} nodeContext - The node storage instance.
2528
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2529
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2530
+ */
1994
2531
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1995
2532
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
1996
2533
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -2001,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
2001
2538
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2002
2539
  const QrCode = new QrCodeSchema();
2003
2540
  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`);
2004
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2541
+ // eslint-disable-next-line no-console
2542
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
2005
2543
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
2006
2544
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2007
2545
  if (pluginName === 'Matterbridge') {
@@ -2048,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
2048
2586
  }
2049
2587
  this.wssSendRefreshRequired();
2050
2588
  }
2589
+ /**
2590
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2591
+ *
2592
+ * @param fabricInfo - The array of exposed fabric information objects.
2593
+ * @returns An array of sanitized exposed fabric information objects.
2594
+ */
2051
2595
  sanitizeFabricInformations(fabricInfo) {
2052
2596
  return fabricInfo.map((info) => {
2053
2597
  return {
@@ -2061,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
2061
2605
  };
2062
2606
  });
2063
2607
  }
2608
+ /**
2609
+ * Sanitizes the session information by converting bigint properties to string.
2610
+ *
2611
+ * @param sessionInfo - The array of session information objects.
2612
+ * @returns An array of sanitized session information objects.
2613
+ */
2064
2614
  sanitizeSessionInformation(sessionInfo) {
2065
2615
  return sessionInfo
2066
2616
  .filter((session) => session.isPeerActive)
@@ -2088,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
2088
2638
  };
2089
2639
  });
2090
2640
  }
2641
+ /**
2642
+ * Sets the reachability of a commissioning server and trigger.
2643
+ *
2644
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2645
+ * @param {boolean} reachable - The new reachability status.
2646
+ */
2091
2647
  setCommissioningServerReachability(commissioningServer, reachable) {
2092
2648
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2093
2649
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2095,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
2095
2651
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2096
2652
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2097
2653
  }
2654
+ /**
2655
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2656
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2657
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2658
+ */
2098
2659
  setAggregatorReachability(matterAggregator, reachable) {
2099
2660
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2100
2661
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2107,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
2107
2668
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2108
2669
  });
2109
2670
  }
2671
+ /**
2672
+ * Sets the reachability of a device and trigger.
2673
+ *
2674
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2675
+ * @param {boolean} reachable - The new reachability status of the device.
2676
+ */
2110
2677
  setDeviceReachability(device, reachable) {
2111
2678
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2112
2679
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2155,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
2155
2722
  }
2156
2723
  return vendorName;
2157
2724
  };
2725
+ /**
2726
+ * Retrieves the base registered plugins sanitized for res.json().
2727
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2728
+ */
2158
2729
  async getBaseRegisteredPlugins() {
2159
2730
  const baseRegisteredPlugins = [];
2160
2731
  for (const plugin of this.plugins) {
@@ -2186,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
2186
2757
  }
2187
2758
  return baseRegisteredPlugins;
2188
2759
  }
2760
+ /**
2761
+ * Spawns a child process with the given command and arguments.
2762
+ * @param {string} command - The command to execute.
2763
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2764
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2765
+ */
2189
2766
  async spawnCommand(command, args = []) {
2767
+ /*
2768
+ npm > npm.cmd on windows
2769
+ cmd.exe ['dir'] on windows
2770
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2771
+ process.on('unhandledRejection', (reason, promise) => {
2772
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2773
+ });
2774
+
2775
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2776
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2777
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2778
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2779
+ */
2190
2780
  const cmdLine = command + ' ' + args.join(' ');
2191
2781
  if (process.platform === 'win32' && command === 'npm') {
2782
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2192
2783
  const argstring = 'npm ' + args.join(' ');
2193
2784
  args.splice(0, args.length, '/c', argstring);
2194
2785
  command = 'cmd.exe';
2195
2786
  }
2787
+ // Decide when using sudo on linux
2788
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2789
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2196
2790
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2197
2791
  args.unshift(command);
2198
2792
  command = 'sudo';
@@ -2250,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
2250
2844
  }
2251
2845
  });
2252
2846
  }
2847
+ /**
2848
+ * Sends a WebSocket message to all connected clients.
2849
+ *
2850
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2851
+ * @param {string} time - The time string of the message
2852
+ * @param {string} name - The logger name of the message
2853
+ * @param {string} message - The content of the message.
2854
+ */
2253
2855
  wssSendMessage(level, time, name, message) {
2254
2856
  if (!level || !time || !name || !message)
2255
2857
  return;
2858
+ // Remove ANSI escape codes from the message
2859
+ // eslint-disable-next-line no-control-regex
2256
2860
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2861
+ // Remove leading asterisks from the message
2257
2862
  message = message.replace(/^\*+/, '');
2863
+ // Replace all occurrences of \t and \n
2258
2864
  message = message.replace(/[\t\n]/g, '');
2865
+ // Remove non-printable characters
2866
+ // eslint-disable-next-line no-control-regex
2259
2867
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2868
+ // Replace all occurrences of \" with "
2260
2869
  message = message.replace(/\\"/g, '"');
2870
+ // Define the maximum allowed length for continuous characters without a space
2261
2871
  const maxContinuousLength = 100;
2262
2872
  const keepStartLength = 20;
2263
2873
  const keepEndLength = 20;
2874
+ // Split the message into words
2264
2875
  message = message
2265
2876
  .split(' ')
2266
2877
  .map((word) => {
2878
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2267
2879
  if (word.length > maxContinuousLength) {
2268
2880
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2269
2881
  }
2270
2882
  return word;
2271
2883
  })
2272
2884
  .join(' ');
2885
+ // Send the message to all connected clients
2273
2886
  this.webSocketServer?.clients.forEach((client) => {
2274
2887
  if (client.readyState === WebSocket.OPEN) {
2275
2888
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2276
2889
  }
2277
2890
  });
2278
2891
  }
2892
+ /**
2893
+ * Sends a need to refresh WebSocket message to all connected clients.
2894
+ *
2895
+ */
2279
2896
  wssSendRefreshRequired() {
2280
2897
  this.matterbridgeInformation.refreshRequired = true;
2898
+ // Send the message to all connected clients
2281
2899
  this.webSocketServer?.clients.forEach((client) => {
2282
2900
  if (client.readyState === WebSocket.OPEN) {
2283
- client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2901
+ client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
2284
2902
  }
2285
2903
  });
2286
2904
  }
2905
+ /**
2906
+ * Sends a need to restart WebSocket message to all connected clients.
2907
+ *
2908
+ */
2287
2909
  wssSendRestartRequired() {
2288
2910
  this.matterbridgeInformation.restartRequired = true;
2911
+ // Send the message to all connected clients
2289
2912
  this.webSocketServer?.clients.forEach((client) => {
2290
2913
  if (client.readyState === WebSocket.OPEN) {
2291
- client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2914
+ client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
2292
2915
  }
2293
2916
  });
2294
2917
  }
2918
+ /**
2919
+ * Initializes the frontend of Matterbridge.
2920
+ *
2921
+ * @param port The port number to run the frontend server on. Default is 8283.
2922
+ */
2295
2923
  async initializeFrontend(port = 8283) {
2296
2924
  let initializeError = false;
2297
2925
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2926
+ // Create the express app that serves the frontend
2298
2927
  this.expressApp = express();
2928
+ // Log all requests to the server for debugging
2929
+ /*
2930
+ if (hasParameter('homedir')) {
2931
+ this.expressApp.use((req, res, next) => {
2932
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2933
+ next();
2934
+ });
2935
+ }
2936
+ */
2937
+ // Serve static files from '/static' endpoint
2299
2938
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2300
2939
  if (!hasParameter('ssl')) {
2940
+ // Create an HTTP server and attach the express app
2301
2941
  this.httpServer = createServer(this.expressApp);
2942
+ // Listen on the specified port
2302
2943
  if (hasParameter('ingress')) {
2303
2944
  this.httpServer.listen(port, '0.0.0.0', () => {
2304
2945
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2312,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
2312
2953
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2313
2954
  });
2314
2955
  }
2956
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2315
2957
  this.httpServer.on('error', (error) => {
2316
2958
  this.log.error(`Frontend http server error listening on ${port}`);
2317
2959
  switch (error.code) {
@@ -2327,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
2327
2969
  });
2328
2970
  }
2329
2971
  else {
2972
+ // Load the SSL certificate, the private key and optionally the CA certificate
2330
2973
  let cert;
2331
2974
  try {
2332
2975
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2354,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
2354
2997
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2355
2998
  }
2356
2999
  const serverOptions = { cert, key, ca };
3000
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2357
3001
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
3002
+ // Listen on the specified port
2358
3003
  if (hasParameter('ingress')) {
2359
3004
  this.httpsServer.listen(port, '0.0.0.0', () => {
2360
3005
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2368,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
2368
3013
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2369
3014
  });
2370
3015
  }
3016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2371
3017
  this.httpsServer.on('error', (error) => {
2372
3018
  this.log.error(`Frontend https server error listening on ${port}`);
2373
3019
  switch (error.code) {
@@ -2384,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
2384
3030
  }
2385
3031
  if (initializeError)
2386
3032
  return;
3033
+ // Createe a WebSocket server and attach it to the http or https server
2387
3034
  const wssPort = port;
2388
3035
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2389
3036
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2390
3037
  this.webSocketServer.on('connection', (ws, request) => {
2391
3038
  const clientIp = request.socket.remoteAddress;
2392
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
3039
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2393
3040
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2394
3041
  ws.on('message', (message) => {
2395
3042
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2422,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
2422
3069
  this.webSocketServer.on('error', (ws, error) => {
2423
3070
  this.log.error(`WebSocketServer error: ${error}`);
2424
3071
  });
3072
+ // Endpoint to validate login code
2425
3073
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2426
3074
  const { password } = req.body;
2427
3075
  this.log.debug('The frontend sent /api/login', password);
@@ -2440,12 +3088,14 @@ export class Matterbridge extends EventEmitter {
2440
3088
  this.log.warn('/api/login error wrong password');
2441
3089
  res.json({ valid: false });
2442
3090
  }
3091
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2443
3092
  }
2444
3093
  catch (error) {
2445
3094
  this.log.error('/api/login error getting password');
2446
3095
  res.json({ valid: false });
2447
3096
  }
2448
3097
  });
3098
+ // Endpoint to provide settings
2449
3099
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2450
3100
  this.log.debug('The frontend sent /api/settings');
2451
3101
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2465,14 +3115,18 @@ export class Matterbridge extends EventEmitter {
2465
3115
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2466
3116
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2467
3117
  this.matterbridgeInformation.profile = this.profile;
2468
- const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
3118
+ const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
3119
+ // this.log.debug('Response:', debugStringify(response));
2469
3120
  res.json(response);
2470
3121
  });
3122
+ // Endpoint to provide plugins
2471
3123
  this.expressApp.get('/api/plugins', async (req, res) => {
2472
3124
  this.log.debug('The frontend sent /api/plugins');
2473
3125
  const response = await this.getBaseRegisteredPlugins();
3126
+ // this.log.debug('Response:', debugStringify(response));
2474
3127
  res.json(response);
2475
3128
  });
3129
+ // Endpoint to provide devices
2476
3130
  this.expressApp.get('/api/devices', (req, res) => {
2477
3131
  this.log.debug('The frontend sent /api/devices');
2478
3132
  const devices = [];
@@ -2505,8 +3159,10 @@ export class Matterbridge extends EventEmitter {
2505
3159
  cluster: cluster,
2506
3160
  });
2507
3161
  });
3162
+ // this.log.debug('Response:', debugStringify(data));
2508
3163
  res.json(devices);
2509
3164
  });
3165
+ // Endpoint to provide the cluster servers of the devices
2510
3166
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2511
3167
  const selectedPluginName = req.params.selectedPluginName;
2512
3168
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2526,6 +3182,7 @@ export class Matterbridge extends EventEmitter {
2526
3182
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2527
3183
  if (clusterServer.name === 'EveHistory')
2528
3184
  return;
3185
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2529
3186
  let attributeValue;
2530
3187
  try {
2531
3188
  if (typeof value.getLocal() === 'object')
@@ -2536,6 +3193,7 @@ export class Matterbridge extends EventEmitter {
2536
3193
  catch (error) {
2537
3194
  attributeValue = 'Fabric-Scoped';
2538
3195
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3196
+ // console.log(error);
2539
3197
  }
2540
3198
  data.push({
2541
3199
  endpoint: device.number ? device.number.toString() : '...',
@@ -2548,12 +3206,14 @@ export class Matterbridge extends EventEmitter {
2548
3206
  });
2549
3207
  });
2550
3208
  device.getChildEndpoints().forEach((childEndpoint) => {
3209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2551
3210
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2552
3211
  const clusterServers = childEndpoint.getAllClusterServers();
2553
3212
  clusterServers.forEach((clusterServer) => {
2554
3213
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2555
3214
  if (clusterServer.name === 'EveHistory')
2556
3215
  return;
3216
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2557
3217
  let attributeValue;
2558
3218
  try {
2559
3219
  if (typeof value.getLocal() === 'object')
@@ -2564,6 +3224,7 @@ export class Matterbridge extends EventEmitter {
2564
3224
  catch (error) {
2565
3225
  attributeValue = 'Unavailable';
2566
3226
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3227
+ // console.log(error);
2567
3228
  }
2568
3229
  data.push({
2569
3230
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2580,6 +3241,7 @@ export class Matterbridge extends EventEmitter {
2580
3241
  });
2581
3242
  res.json(data);
2582
3243
  });
3244
+ // Endpoint to view the log
2583
3245
  this.expressApp.get('/api/view-log', async (req, res) => {
2584
3246
  this.log.debug('The frontend sent /api/log');
2585
3247
  try {
@@ -2592,10 +3254,12 @@ export class Matterbridge extends EventEmitter {
2592
3254
  res.status(500).send('Error reading log file');
2593
3255
  }
2594
3256
  });
3257
+ // Endpoint to download the matterbridge log
2595
3258
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2596
3259
  this.log.debug('The frontend sent /api/download-mblog');
2597
3260
  try {
2598
3261
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3262
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2599
3263
  }
2600
3264
  catch (error) {
2601
3265
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2607,10 +3271,12 @@ export class Matterbridge extends EventEmitter {
2607
3271
  }
2608
3272
  });
2609
3273
  });
3274
+ // Endpoint to download the matter log
2610
3275
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2611
3276
  this.log.debug('The frontend sent /api/download-mjlog');
2612
3277
  try {
2613
3278
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3279
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2614
3280
  }
2615
3281
  catch (error) {
2616
3282
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2622,6 +3288,7 @@ export class Matterbridge extends EventEmitter {
2622
3288
  }
2623
3289
  });
2624
3290
  });
3291
+ // Endpoint to download the matter storage file
2625
3292
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2626
3293
  this.log.debug('The frontend sent /api/download-mjstorage');
2627
3294
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2631,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
2631
3298
  }
2632
3299
  });
2633
3300
  });
3301
+ // Endpoint to download the matterbridge storage directory
2634
3302
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2635
3303
  this.log.debug('The frontend sent /api/download-mbstorage');
2636
3304
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2641,6 +3309,7 @@ export class Matterbridge extends EventEmitter {
2641
3309
  }
2642
3310
  });
2643
3311
  });
3312
+ // Endpoint to download the matterbridge plugin directory
2644
3313
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2645
3314
  this.log.debug('The frontend sent /api/download-pluginstorage');
2646
3315
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2651,9 +3320,11 @@ export class Matterbridge extends EventEmitter {
2651
3320
  }
2652
3321
  });
2653
3322
  });
3323
+ // Endpoint to download the matterbridge plugin config files
2654
3324
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2655
3325
  this.log.debug('The frontend sent /api/download-pluginconfig');
2656
3326
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3327
+ // 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')));
2657
3328
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2658
3329
  if (error) {
2659
3330
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2661,6 +3332,7 @@ export class Matterbridge extends EventEmitter {
2661
3332
  }
2662
3333
  });
2663
3334
  });
3335
+ // Endpoint to download the matterbridge plugin config files
2664
3336
  this.expressApp.get('/api/download-backup', async (req, res) => {
2665
3337
  this.log.debug('The frontend sent /api/download-backup');
2666
3338
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2670,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
2670
3342
  }
2671
3343
  });
2672
3344
  });
3345
+ // Endpoint to receive commands
2673
3346
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2674
3347
  const command = req.params.command;
2675
3348
  let param = req.params.param;
@@ -2679,13 +3352,15 @@ export class Matterbridge extends EventEmitter {
2679
3352
  return;
2680
3353
  }
2681
3354
  this.log.debug(`Received frontend command: ${command}:${param}`);
3355
+ // Handle the command setpassword from Settings
2682
3356
  if (command === 'setpassword') {
2683
- const password = param.slice(1, -1);
3357
+ const password = param.slice(1, -1); // Remove the first and last characters
2684
3358
  this.log.debug('setpassword', param, password);
2685
3359
  await this.nodeContext?.set('password', password);
2686
3360
  res.json({ message: 'Command received' });
2687
3361
  return;
2688
3362
  }
3363
+ // Handle the command setbridgemode from Settings
2689
3364
  if (command === 'setbridgemode') {
2690
3365
  this.log.debug(`setbridgemode: ${param}`);
2691
3366
  this.wssSendRestartRequired();
@@ -2693,6 +3368,7 @@ export class Matterbridge extends EventEmitter {
2693
3368
  res.json({ message: 'Command received' });
2694
3369
  return;
2695
3370
  }
3371
+ // Handle the command backup from Settings
2696
3372
  if (command === 'backup') {
2697
3373
  this.log.notice(`Prepairing the backup...`);
2698
3374
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2700,25 +3376,26 @@ export class Matterbridge extends EventEmitter {
2700
3376
  res.json({ message: 'Command received' });
2701
3377
  return;
2702
3378
  }
3379
+ // Handle the command setmbloglevel from Settings
2703
3380
  if (command === 'setmbloglevel') {
2704
3381
  this.log.debug('Matterbridge log level:', param);
2705
3382
  if (param === 'Debug') {
2706
- this.log.logLevel = "debug";
3383
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2707
3384
  }
2708
3385
  else if (param === 'Info') {
2709
- this.log.logLevel = "info";
3386
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2710
3387
  }
2711
3388
  else if (param === 'Notice') {
2712
- this.log.logLevel = "notice";
3389
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2713
3390
  }
2714
3391
  else if (param === 'Warn') {
2715
- this.log.logLevel = "warn";
3392
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2716
3393
  }
2717
3394
  else if (param === 'Error') {
2718
- this.log.logLevel = "error";
3395
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2719
3396
  }
2720
3397
  else if (param === 'Fatal') {
2721
- this.log.logLevel = "fatal";
3398
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2722
3399
  }
2723
3400
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2724
3401
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2726,12 +3403,13 @@ export class Matterbridge extends EventEmitter {
2726
3403
  for (const plugin of this.plugins) {
2727
3404
  if (!plugin.platform || !plugin.platform.config)
2728
3405
  continue;
2729
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2730
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3406
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3407
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2731
3408
  }
2732
3409
  res.json({ message: 'Command received' });
2733
3410
  return;
2734
3411
  }
3412
+ // Handle the command setmbloglevel from Settings
2735
3413
  if (command === 'setmjloglevel') {
2736
3414
  this.log.debug('Matter.js log level:', param);
2737
3415
  if (param === 'Debug') {
@@ -2756,30 +3434,34 @@ export class Matterbridge extends EventEmitter {
2756
3434
  res.json({ message: 'Command received' });
2757
3435
  return;
2758
3436
  }
3437
+ // Handle the command setmdnsinterface from Settings
2759
3438
  if (command === 'setmdnsinterface') {
2760
- param = param.slice(1, -1);
3439
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2761
3440
  this.matterbridgeInformation.mattermdnsinterface = param;
2762
3441
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2763
3442
  await this.nodeContext?.set('mattermdnsinterface', param);
2764
3443
  res.json({ message: 'Command received' });
2765
3444
  return;
2766
3445
  }
3446
+ // Handle the command setipv4address from Settings
2767
3447
  if (command === 'setipv4address') {
2768
- param = param.slice(1, -1);
3448
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2769
3449
  this.matterbridgeInformation.matteripv4address = param;
2770
3450
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2771
3451
  await this.nodeContext?.set('matteripv4address', param);
2772
3452
  res.json({ message: 'Command received' });
2773
3453
  return;
2774
3454
  }
3455
+ // Handle the command setipv6address from Settings
2775
3456
  if (command === 'setipv6address') {
2776
- param = param.slice(1, -1);
3457
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2777
3458
  this.matterbridgeInformation.matteripv6address = param;
2778
3459
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2779
3460
  await this.nodeContext?.set('matteripv6address', param);
2780
3461
  res.json({ message: 'Command received' });
2781
3462
  return;
2782
3463
  }
3464
+ // Handle the command setmatterport from Settings
2783
3465
  if (command === 'setmatterport') {
2784
3466
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2785
3467
  this.matterbridgeInformation.matterPort = port;
@@ -2788,6 +3470,7 @@ export class Matterbridge extends EventEmitter {
2788
3470
  res.json({ message: 'Command received' });
2789
3471
  return;
2790
3472
  }
3473
+ // Handle the command setmatterdiscriminator from Settings
2791
3474
  if (command === 'setmatterdiscriminator') {
2792
3475
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2793
3476
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2796,6 +3479,7 @@ export class Matterbridge extends EventEmitter {
2796
3479
  res.json({ message: 'Command received' });
2797
3480
  return;
2798
3481
  }
3482
+ // Handle the command setmatterpasscode from Settings
2799
3483
  if (command === 'setmatterpasscode') {
2800
3484
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2801
3485
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2804,17 +3488,20 @@ export class Matterbridge extends EventEmitter {
2804
3488
  res.json({ message: 'Command received' });
2805
3489
  return;
2806
3490
  }
3491
+ // Handle the command setmbloglevel from Settings
2807
3492
  if (command === 'setmblogfile') {
2808
3493
  this.log.debug('Matterbridge file log:', param);
2809
3494
  this.matterbridgeInformation.fileLogger = param === 'true';
2810
3495
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3496
+ // Create the file logger for matterbridge
2811
3497
  if (param === 'true')
2812
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3498
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2813
3499
  else
2814
3500
  AnsiLogger.setGlobalLogfile(undefined);
2815
3501
  res.json({ message: 'Command received' });
2816
3502
  return;
2817
3503
  }
3504
+ // Handle the command setmbloglevel from Settings
2818
3505
  if (command === 'setmjlogfile') {
2819
3506
  this.log.debug('Matter file log:', param);
2820
3507
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2841,36 +3528,43 @@ export class Matterbridge extends EventEmitter {
2841
3528
  res.json({ message: 'Command received' });
2842
3529
  return;
2843
3530
  }
3531
+ // Handle the command unregister from Settings
2844
3532
  if (command === 'unregister') {
2845
3533
  await this.unregisterAndShutdownProcess();
2846
3534
  res.json({ message: 'Command received' });
2847
3535
  return;
2848
3536
  }
3537
+ // Handle the command reset from Settings
2849
3538
  if (command === 'reset') {
2850
3539
  await this.shutdownProcessAndReset();
2851
3540
  res.json({ message: 'Command received' });
2852
3541
  return;
2853
3542
  }
3543
+ // Handle the command factoryreset from Settings
2854
3544
  if (command === 'factoryreset') {
2855
3545
  await this.shutdownProcessAndFactoryReset();
2856
3546
  res.json({ message: 'Command received' });
2857
3547
  return;
2858
3548
  }
3549
+ // Handle the command shutdown from Header
2859
3550
  if (command === 'shutdown') {
2860
3551
  await this.shutdownProcess();
2861
3552
  res.json({ message: 'Command received' });
2862
3553
  return;
2863
3554
  }
3555
+ // Handle the command restart from Header
2864
3556
  if (command === 'restart') {
2865
3557
  await this.restartProcess();
2866
3558
  res.json({ message: 'Command received' });
2867
3559
  return;
2868
3560
  }
3561
+ // Handle the command update from Header
2869
3562
  if (command === 'update') {
2870
3563
  this.log.info('Updating matterbridge...');
2871
3564
  try {
2872
3565
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2873
3566
  this.log.info('Matterbridge has been updated. Full restart required.');
3567
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2874
3568
  }
2875
3569
  catch (error) {
2876
3570
  this.log.error('Error updating matterbridge');
@@ -2880,9 +3574,11 @@ export class Matterbridge extends EventEmitter {
2880
3574
  res.json({ message: 'Command received' });
2881
3575
  return;
2882
3576
  }
3577
+ // Handle the command saveconfig from Home
2883
3578
  if (command === 'saveconfig') {
2884
3579
  param = param.replace(/\*/g, '\\');
2885
3580
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3581
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2886
3582
  if (!this.plugins.has(param)) {
2887
3583
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2888
3584
  }
@@ -2896,33 +3592,39 @@ export class Matterbridge extends EventEmitter {
2896
3592
  res.json({ message: 'Command received' });
2897
3593
  return;
2898
3594
  }
3595
+ // Handle the command installplugin from Home
2899
3596
  if (command === 'installplugin') {
2900
3597
  param = param.replace(/\*/g, '\\');
2901
3598
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2902
3599
  try {
2903
3600
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2904
3601
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3602
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2905
3603
  }
2906
3604
  catch (error) {
2907
3605
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2908
3606
  }
2909
3607
  this.wssSendRestartRequired();
2910
3608
  param = param.split('@')[0];
3609
+ // Also add the plugin to matterbridge so no return!
2911
3610
  if (param === 'matterbridge') {
3611
+ // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
2912
3612
  res.json({ message: 'Command received' });
2913
3613
  return;
2914
3614
  }
2915
3615
  }
3616
+ // Handle the command addplugin from Home
2916
3617
  if (command === 'addplugin' || command === 'installplugin') {
2917
3618
  param = param.replace(/\*/g, '\\');
2918
3619
  const plugin = await this.plugins.add(param);
2919
3620
  if (plugin) {
2920
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3621
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2921
3622
  }
2922
3623
  res.json({ message: 'Command received' });
2923
3624
  this.wssSendRefreshRequired();
2924
3625
  return;
2925
3626
  }
3627
+ // Handle the command removeplugin from Home
2926
3628
  if (command === 'removeplugin') {
2927
3629
  if (!this.plugins.has(param)) {
2928
3630
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2936,6 +3638,7 @@ export class Matterbridge extends EventEmitter {
2936
3638
  this.wssSendRefreshRequired();
2937
3639
  return;
2938
3640
  }
3641
+ // Handle the command enableplugin from Home
2939
3642
  if (command === 'enableplugin') {
2940
3643
  if (!this.plugins.has(param)) {
2941
3644
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2953,13 +3656,14 @@ export class Matterbridge extends EventEmitter {
2953
3656
  plugin.registeredDevices = undefined;
2954
3657
  plugin.addedDevices = undefined;
2955
3658
  await this.plugins.enable(param);
2956
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3659
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
2957
3660
  }
2958
3661
  }
2959
3662
  res.json({ message: 'Command received' });
2960
3663
  this.wssSendRefreshRequired();
2961
3664
  return;
2962
3665
  }
3666
+ // Handle the command disableplugin from Home
2963
3667
  if (command === 'disableplugin') {
2964
3668
  if (!this.plugins.has(param)) {
2965
3669
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2976,6 +3680,7 @@ export class Matterbridge extends EventEmitter {
2976
3680
  return;
2977
3681
  }
2978
3682
  });
3683
+ // Fallback for routing (must be the last route)
2979
3684
  this.expressApp.get('*', (req, res) => {
2980
3685
  this.log.debug('The frontend sent:', req.url);
2981
3686
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -2983,6 +3688,11 @@ export class Matterbridge extends EventEmitter {
2983
3688
  });
2984
3689
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2985
3690
  }
3691
+ /**
3692
+ * Retrieves the cluster text description from a given device.
3693
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3694
+ * @returns {string} The attributes description of the cluster servers in the device.
3695
+ */
2986
3696
  getClusterTextFromDevice(device) {
2987
3697
  const stringifyUserLabel = (endpoint) => {
2988
3698
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -3005,9 +3715,11 @@ export class Matterbridge extends EventEmitter {
3005
3715
  return '';
3006
3716
  };
3007
3717
  let attributes = '';
3718
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3008
3719
  const clusterServers = device.getAllClusterServers();
3009
3720
  clusterServers.forEach((clusterServer) => {
3010
3721
  try {
3722
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3011
3723
  if (clusterServer.name === 'OnOff')
3012
3724
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
3013
3725
  if (clusterServer.name === 'Switch')
@@ -3058,18 +3770,30 @@ export class Matterbridge extends EventEmitter {
3058
3770
  attributes += `${stringifyFixedLabel(device)} `;
3059
3771
  if (clusterServer.name === 'UserLabel')
3060
3772
  attributes += `${stringifyUserLabel(device)} `;
3773
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3061
3774
  }
3062
3775
  catch (error) {
3063
3776
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3064
3777
  }
3065
3778
  });
3779
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3066
3780
  return attributes;
3067
3781
  }
3782
+ /**
3783
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3784
+ * @deprecated This method is deprecated and will be removed in a future version.
3785
+ *
3786
+ * @returns A Promise that resolves when the initialization is complete.
3787
+ */
3068
3788
  async startExtension(dataPath, extensionVersion, port = 5540) {
3789
+ // Set the bridge mode
3069
3790
  this.bridgeMode = 'bridge';
3791
+ // Set the first port to use
3070
3792
  this.port = port;
3071
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3793
+ // Set Matterbridge logger
3794
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3072
3795
  this.log.debug('Matterbridge extension is starting...');
3796
+ // Initialize NodeStorage
3073
3797
  this.matterbridgeDirectory = dataPath;
3074
3798
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3075
3799
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -3088,10 +3812,13 @@ export class Matterbridge extends EventEmitter {
3088
3812
  };
3089
3813
  this.plugins.set(plugin);
3090
3814
  this.plugins.saveToStorage();
3815
+ // Log system info and create .matterbridge directory
3091
3816
  await this.logNodeAndSystemInfo();
3092
3817
  this.matterbridgeDirectory = dataPath;
3818
+ // Set matter.js logger level and format
3093
3819
  Logger.defaultLogLevel = MatterLogLevel.INFO;
3094
3820
  Logger.format = MatterLogFormat.ANSI;
3821
+ // Start the storage and create matterbridgeContext
3095
3822
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3096
3823
  if (!this.storageManager)
3097
3824
  return false;
@@ -3101,8 +3828,8 @@ export class Matterbridge extends EventEmitter {
3101
3828
  await this.matterbridgeContext.set('softwareVersion', 1);
3102
3829
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3103
3830
  await this.matterbridgeContext.set('hardwareVersion', 1);
3104
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3105
- this.matterServer = this.createMatterServer(this.storageManager);
3831
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
3832
+ this.matterServer = await this.createMatterServer(this.storageManager);
3106
3833
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3107
3834
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
3108
3835
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -3114,6 +3841,7 @@ export class Matterbridge extends EventEmitter {
3114
3841
  await this.startMatterServer();
3115
3842
  this.log.info('Matter server started');
3116
3843
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3844
+ // Set reachability to true and trigger event after 60 seconds
3117
3845
  setTimeout(() => {
3118
3846
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3119
3847
  if (this.commissioningServer)
@@ -3123,14 +3851,31 @@ export class Matterbridge extends EventEmitter {
3123
3851
  }, 60 * 1000);
3124
3852
  return this.commissioningServer.isCommissioned();
3125
3853
  }
3854
+ /**
3855
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3856
+ * @deprecated This method is deprecated and will be removed in a future version.
3857
+ *
3858
+ * @returns A Promise that resolves when the initialization is complete.
3859
+ */
3126
3860
  async stopExtension() {
3861
+ // Closing matter
3127
3862
  await this.stopMatterServer();
3863
+ // Clearing the session manager
3864
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3865
+ // Closing storage
3128
3866
  await this.stopMatterStorage();
3129
3867
  this.log.info('Matter server stopped');
3130
3868
  }
3869
+ /**
3870
+ * Checks if the extension is commissioned.
3871
+ * @deprecated This method is deprecated and will be removed in a future version.
3872
+ *
3873
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3874
+ */
3131
3875
  isExtensionCommissioned() {
3132
3876
  if (!this.commissioningServer)
3133
3877
  return false;
3134
3878
  return this.commissioningServer.isCommissioned();
3135
3879
  }
3136
3880
  }
3881
+ //# sourceMappingURL=matterbridge.js.map