matterbridge 1.6.8-dev.9 → 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 +21 -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 +818 -110
  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 +126 -4
  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,19 +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
+ */
140
181
  getDevices() {
141
182
  return this.devices.array();
142
183
  }
184
+ /**
185
+ * Retrieves the list of registered plugins.
186
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
187
+ */
143
188
  getPlugins() {
144
189
  return this.plugins.array();
145
190
  }
146
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
+ */
147
202
  static async loadInstance(initialize = false) {
148
203
  if (!Matterbridge.instance) {
204
+ // eslint-disable-next-line no-console
149
205
  if (hasParameter('debug'))
150
206
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
151
207
  Matterbridge.instance = new Matterbridge();
@@ -154,6 +210,11 @@ export class Matterbridge extends EventEmitter {
154
210
  }
155
211
  return Matterbridge.instance;
156
212
  }
213
+ /**
214
+ * Call cleanup().
215
+ * @deprecated This method is deprecated and is only used for jest tests.
216
+ *
217
+ */
157
218
  async destroyInstance() {
158
219
  await this.cleanup('destroying instance...', false);
159
220
  await waiter('destroying instance...', () => {
@@ -161,39 +222,60 @@ export class Matterbridge extends EventEmitter {
161
222
  }, false, 60000, 100, false);
162
223
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
163
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
+ */
164
235
  async initialize() {
236
+ // Set the restart mode
165
237
  if (hasParameter('service'))
166
238
  this.restartMode = 'service';
167
239
  if (hasParameter('docker'))
168
240
  this.restartMode = 'docker';
241
+ // Set the matterbridge directory
169
242
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
170
243
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
171
- 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
172
247
  try {
173
248
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
174
249
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
175
250
  this.log.debug('Creating node storage context for matterbridge');
176
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
177
254
  const keys = (await this.nodeStorage?.storage.keys());
178
255
  for (const key of keys) {
179
256
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
258
  await this.nodeStorage?.storage.get(key);
181
259
  }
182
260
  const storages = await this.nodeStorage.getStorageNames();
183
261
  for (const storage of storages) {
184
262
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
185
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
186
266
  const keys = (await nodeContext?.storage.keys());
187
267
  keys.forEach(async (key) => {
188
268
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
189
269
  await nodeContext?.get(key);
190
270
  });
191
271
  }
272
+ // Creating a backup of the node storage since it is not corrupted
192
273
  this.log.debug('Creating node storage backup...');
193
274
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
194
275
  this.log.debug('Created node storage backup');
195
276
  }
196
277
  catch (error) {
278
+ // Restoring the backup of the node storage since it is corrupted
197
279
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
198
280
  if (hasParameter('norestore')) {
199
281
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -208,45 +290,51 @@ export class Matterbridge extends EventEmitter {
208
290
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
209
291
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
210
292
  }
293
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
211
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)
212
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)
213
298
  this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
214
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)
215
301
  if (hasParameter('logger')) {
216
302
  const level = getParameter('logger');
217
303
  if (level === 'debug') {
218
- this.log.logLevel = "debug";
304
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
219
305
  }
220
306
  else if (level === 'info') {
221
- this.log.logLevel = "info";
307
+ this.log.logLevel = "info" /* LogLevel.INFO */;
222
308
  }
223
309
  else if (level === 'notice') {
224
- this.log.logLevel = "notice";
310
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
225
311
  }
226
312
  else if (level === 'warn') {
227
- this.log.logLevel = "warn";
313
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
228
314
  }
229
315
  else if (level === 'error') {
230
- this.log.logLevel = "error";
316
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
231
317
  }
232
318
  else if (level === 'fatal') {
233
- this.log.logLevel = "fatal";
319
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
234
320
  }
235
321
  else {
236
322
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
237
- this.log.logLevel = "info";
323
+ this.log.logLevel = "info" /* LogLevel.INFO */;
238
324
  }
239
325
  }
240
326
  else {
241
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
327
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
242
328
  }
243
329
  MatterbridgeDevice.logLevel = this.log.logLevel;
330
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
244
331
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
245
332
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
246
333
  this.matterbridgeInformation.fileLogger = true;
247
334
  }
248
335
  this.log.notice('Matterbridge is starting...');
249
336
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
337
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
250
338
  if (hasParameter('matterlogger')) {
251
339
  const level = getParameter('matterlogger');
252
340
  if (level === 'debug') {
@@ -277,6 +365,7 @@ export class Matterbridge extends EventEmitter {
277
365
  }
278
366
  Logger.format = MatterLogFormat.ANSI;
279
367
  Logger.setLogger('default', this.createMatterLogger());
368
+ // Create the file logger for matter.js (context: matterFileLog)
280
369
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
281
370
  this.matterbridgeInformation.matterFileLogger = true;
282
371
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -285,6 +374,7 @@ export class Matterbridge extends EventEmitter {
285
374
  });
286
375
  }
287
376
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
+ // Set the interface to use for the matter server mdnsInterface
288
378
  if (hasParameter('mdnsinterface')) {
289
379
  this.mdnsInterface = getParameter('mdnsinterface');
290
380
  }
@@ -293,6 +383,7 @@ export class Matterbridge extends EventEmitter {
293
383
  if (this.mdnsInterface === '')
294
384
  this.mdnsInterface = undefined;
295
385
  }
386
+ // Set the listeningAddressIpv4 for the matter commissioning server
296
387
  if (hasParameter('ipv4address')) {
297
388
  this.ipv4address = getParameter('ipv4address');
298
389
  }
@@ -301,6 +392,7 @@ export class Matterbridge extends EventEmitter {
301
392
  if (this.ipv4address === '')
302
393
  this.ipv4address = undefined;
303
394
  }
395
+ // Set the listeningAddressIpv6 for the matter commissioning server
304
396
  if (hasParameter('ipv6address')) {
305
397
  this.ipv6address = getParameter('ipv6address');
306
398
  }
@@ -309,17 +401,23 @@ export class Matterbridge extends EventEmitter {
309
401
  if (this.ipv6address === '')
310
402
  this.ipv6address = undefined;
311
403
  }
404
+ // Initialize PluginManager
312
405
  this.plugins = new PluginManager(this);
313
406
  await this.plugins.loadFromStorage();
407
+ // Initialize DeviceManager
314
408
  this.devices = new DeviceManager(this, this.nodeContext);
409
+ // Get the plugins from node storage and create the plugins node storage contexts
315
410
  for (const plugin of this.plugins) {
316
411
  const packageJson = await this.plugins.parse(plugin);
317
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
318
415
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
319
416
  try {
320
417
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
321
418
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
322
419
  plugin.error = false;
420
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
323
421
  }
324
422
  catch (error) {
325
423
  plugin.error = true;
@@ -336,6 +434,7 @@ export class Matterbridge extends EventEmitter {
336
434
  await plugin.nodeContext.set('description', plugin.description);
337
435
  await plugin.nodeContext.set('author', plugin.author);
338
436
  }
437
+ // Log system info and create .matterbridge directory
339
438
  await this.logNodeAndSystemInfo();
340
439
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
341
440
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -343,6 +442,7 @@ export class Matterbridge extends EventEmitter {
343
442
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
344
443
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
345
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
346
446
  const minNodeVersion = 18;
347
447
  const nodeVersion = process.versions.node;
348
448
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -350,10 +450,17 @@ export class Matterbridge extends EventEmitter {
350
450
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
351
451
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
352
452
  }
453
+ // Register process handlers
353
454
  this.registerProcessHandlers();
455
+ // Parse command line
354
456
  await this.parseCommandLine();
355
457
  this.initialized = true;
356
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
+ */
357
464
  async parseCommandLine() {
358
465
  if (hasParameter('help')) {
359
466
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -461,18 +568,39 @@ export class Matterbridge extends EventEmitter {
461
568
  }
462
569
  if (hasParameter('factoryreset')) {
463
570
  try {
464
- 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);
465
575
  }
466
576
  catch (err) {
467
- 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
+ }
468
580
  }
469
581
  try {
470
- 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 });
471
586
  }
472
587
  catch (err) {
473
- 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
+ }
591
+ }
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 });
474
597
  }
475
- this.log.info('Factory reset done! Remove all paired devices from the controllers.');
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.');
476
604
  this.nodeContext = undefined;
477
605
  this.nodeStorage = undefined;
478
606
  this.plugins.clear();
@@ -480,6 +608,7 @@ export class Matterbridge extends EventEmitter {
480
608
  this.emit('shutdown');
481
609
  return;
482
610
  }
611
+ // Start the matter storage and create the matterbridge context
483
612
  try {
484
613
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
485
614
  }
@@ -487,7 +616,8 @@ export class Matterbridge extends EventEmitter {
487
616
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
488
617
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
489
618
  }
490
- 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) {
491
621
  this.log.info('Resetting Matterbridge commissioning information...');
492
622
  await this.matterbridgeContext?.clearAll();
493
623
  await this.stopMatterStorage();
@@ -495,7 +625,8 @@ export class Matterbridge extends EventEmitter {
495
625
  this.emit('shutdown');
496
626
  return;
497
627
  }
498
- 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) {
499
630
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
500
631
  const plugin = this.plugins.get(getParameter('reset'));
501
632
  if (plugin) {
@@ -514,28 +645,34 @@ export class Matterbridge extends EventEmitter {
514
645
  this.emit('shutdown');
515
646
  return;
516
647
  }
648
+ // Initialize frontend
517
649
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
518
650
  await this.initializeFrontend(getIntParameter('frontend'));
651
+ // Check each 60 minutes the latest versions
519
652
  this.checkUpdateInterval = setInterval(() => {
520
653
  this.getMatterbridgeLatestVersion();
521
654
  for (const plugin of this.plugins) {
522
655
  this.getPluginLatestVersion(plugin);
523
656
  }
524
657
  }, 60 * 60 * 1000);
658
+ // Start the matterbridge in mode test
525
659
  if (hasParameter('test')) {
526
660
  this.bridgeMode = 'bridge';
527
661
  MatterbridgeDevice.bridgeMode = 'bridge';
528
662
  return;
529
663
  }
664
+ // Start the matterbridge in mode controller
530
665
  if (hasParameter('controller')) {
531
666
  this.bridgeMode = 'controller';
532
667
  await this.startController();
533
668
  return;
534
669
  }
670
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
535
671
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
536
672
  this.log.info('Setting default matterbridge start mode to bridge');
537
673
  await this.nodeContext?.set('bridgeMode', 'bridge');
538
674
  }
675
+ // Start matterbridge in bridge mode
539
676
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
540
677
  this.bridgeMode = 'bridge';
541
678
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -544,6 +681,7 @@ export class Matterbridge extends EventEmitter {
544
681
  await this.startBridge();
545
682
  return;
546
683
  }
684
+ // Start matterbridge in childbridge mode
547
685
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
548
686
  this.bridgeMode = 'childbridge';
549
687
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -553,17 +691,28 @@ export class Matterbridge extends EventEmitter {
553
691
  return;
554
692
  }
555
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
+ */
556
702
  async startPlugins() {
703
+ // Check, load and start the plugins
557
704
  for (const plugin of this.plugins) {
558
705
  plugin.configJson = await this.plugins.loadConfig(plugin);
559
706
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
707
+ // Check if the plugin is available
560
708
  if (!(await this.plugins.resolve(plugin.path))) {
561
709
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
562
710
  plugin.enabled = false;
563
711
  plugin.error = true;
564
712
  continue;
565
713
  }
566
- this.getPluginLatestVersion(plugin);
714
+ // Check if the plugin has a new version
715
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
567
716
  if (!plugin.enabled) {
568
717
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
569
718
  continue;
@@ -578,20 +727,26 @@ export class Matterbridge extends EventEmitter {
578
727
  plugin.addedDevices = undefined;
579
728
  plugin.qrPairingCode = undefined;
580
729
  plugin.manualPairingCode = undefined;
581
- this.plugins.load(plugin, true, 'Matterbridge is starting');
730
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
582
731
  }
583
732
  this.wssSendRefreshRequired();
584
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
+ */
585
738
  registerProcessHandlers() {
586
739
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
587
740
  process.removeAllListeners('uncaughtException');
588
741
  process.removeAllListeners('unhandledRejection');
589
742
  this.exceptionHandler = async (error) => {
590
743
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
744
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
591
745
  };
592
746
  process.on('uncaughtException', this.exceptionHandler);
593
747
  this.rejectionHandler = async (reason, promise) => {
594
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...');
595
750
  };
596
751
  process.on('unhandledRejection', this.rejectionHandler);
597
752
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -604,6 +759,9 @@ export class Matterbridge extends EventEmitter {
604
759
  };
605
760
  process.on('SIGTERM', this.sigtermHandler);
606
761
  }
762
+ /**
763
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
764
+ */
607
765
  deregisterProcesslHandlers() {
608
766
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
609
767
  if (this.exceptionHandler)
@@ -620,7 +778,11 @@ export class Matterbridge extends EventEmitter {
620
778
  process.off('SIGTERM', this.sigtermHandler);
621
779
  this.sigtermHandler = undefined;
622
780
  }
781
+ /**
782
+ * Logs the node and system information.
783
+ */
623
784
  async logNodeAndSystemInfo() {
785
+ // IP address information
624
786
  const networkInterfaces = os.networkInterfaces();
625
787
  this.systemInformation.ipv4Address = '';
626
788
  this.systemInformation.ipv6Address = '';
@@ -640,7 +802,7 @@ export class Matterbridge extends EventEmitter {
640
802
  this.systemInformation.macAddress = detail.mac;
641
803
  }
642
804
  }
643
- if (this.systemInformation.ipv4Address !== '') {
805
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
644
806
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
645
807
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
646
808
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -648,19 +810,22 @@ export class Matterbridge extends EventEmitter {
648
810
  break;
649
811
  }
650
812
  }
813
+ // Node information
651
814
  this.systemInformation.nodeVersion = process.versions.node;
652
815
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
653
816
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
654
817
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
818
+ // Host system information
655
819
  this.systemInformation.hostname = os.hostname();
656
820
  this.systemInformation.user = os.userInfo().username;
657
- this.systemInformation.osType = os.type();
658
- this.systemInformation.osRelease = os.release();
659
- this.systemInformation.osPlatform = os.platform();
660
- this.systemInformation.osArch = os.arch();
661
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
662
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
663
- 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
664
829
  this.log.debug('Host System Information:');
665
830
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
666
831
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -676,15 +841,19 @@ export class Matterbridge extends EventEmitter {
676
841
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
677
842
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
678
843
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
844
+ // Home directory
679
845
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
680
846
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
681
847
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
848
+ // Package root directory
682
849
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
683
850
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
684
851
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
685
852
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
853
+ // Global node_modules directory
686
854
  if (this.nodeContext)
687
855
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
856
+ // First run of Matterbridge so the node storage is empty
688
857
  if (this.globalModulesDirectory === '') {
689
858
  try {
690
859
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -708,6 +877,7 @@ export class Matterbridge extends EventEmitter {
708
877
  this.log.error(`Error getting global node_modules directory: ${error}`);
709
878
  });
710
879
  }
880
+ // Create the data directory .matterbridge in the home directory
711
881
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
712
882
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
713
883
  try {
@@ -731,6 +901,7 @@ export class Matterbridge extends EventEmitter {
731
901
  }
732
902
  }
733
903
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
904
+ // Create the plugin directory Matterbridge in the home directory
734
905
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
735
906
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
736
907
  try {
@@ -754,19 +925,28 @@ export class Matterbridge extends EventEmitter {
754
925
  }
755
926
  }
756
927
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
928
+ // Matterbridge version
757
929
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
758
930
  this.matterbridgeVersion = packageJson.version;
759
931
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
760
932
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
933
+ // Matterbridge latest version
761
934
  if (this.nodeContext)
762
935
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
763
936
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
764
937
  this.getMatterbridgeLatestVersion();
938
+ // Current working directory
765
939
  const currentDir = process.cwd();
766
940
  this.log.debug(`Current Working Directory: ${currentDir}`);
941
+ // Command line arguments (excluding 'node' and the script name)
767
942
  const cmdArgs = process.argv.slice(2).join(' ');
768
943
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
769
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
+ */
770
950
  async getLatestVersion(packageName) {
771
951
  return new Promise((resolve, reject) => {
772
952
  this.execRunningCount++;
@@ -781,6 +961,10 @@ export class Matterbridge extends EventEmitter {
781
961
  });
782
962
  });
783
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
+ */
784
968
  async getGlobalNodeModules() {
785
969
  return new Promise((resolve, reject) => {
786
970
  this.execRunningCount++;
@@ -795,6 +979,11 @@ export class Matterbridge extends EventEmitter {
795
979
  });
796
980
  });
797
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
+ */
798
987
  async getMatterbridgeLatestVersion() {
799
988
  this.getLatestVersion('matterbridge')
800
989
  .then(async (matterbridgeLatestVersion) => {
@@ -811,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
811
1000
  })
812
1001
  .catch((error) => {
813
1002
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
1003
+ // error.stack && this.log.debug(error.stack);
814
1004
  });
815
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
+ */
816
1016
  async getPluginLatestVersion(plugin) {
817
1017
  this.getLatestVersion(plugin.name)
818
1018
  .then(async (latestVersion) => {
@@ -824,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
824
1024
  })
825
1025
  .catch((error) => {
826
1026
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1027
+ // error.stack && this.log.debug(error.stack);
827
1028
  });
828
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
+ */
829
1035
  createMatterLogger() {
830
- 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 */ });
831
1037
  return (_level, formattedLog) => {
832
1038
  const logger = formattedLog.slice(44, 44 + 20).trim();
833
1039
  const message = formattedLog.slice(65);
834
1040
  matterLogger.logName = logger;
835
1041
  switch (_level) {
836
1042
  case MatterLogLevel.DEBUG:
837
- matterLogger.log("debug", message);
1043
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
838
1044
  break;
839
1045
  case MatterLogLevel.INFO:
840
- matterLogger.log("info", message);
1046
+ matterLogger.log("info" /* LogLevel.INFO */, message);
841
1047
  break;
842
1048
  case MatterLogLevel.NOTICE:
843
- matterLogger.log("notice", message);
1049
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
844
1050
  break;
845
1051
  case MatterLogLevel.WARN:
846
- matterLogger.log("warn", message);
1052
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
847
1053
  break;
848
1054
  case MatterLogLevel.ERROR:
849
- matterLogger.log("error", message);
1055
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
850
1056
  break;
851
1057
  case MatterLogLevel.FATAL:
852
- matterLogger.log("fatal", message);
1058
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
853
1059
  break;
854
1060
  default:
855
- matterLogger.log("debug", message);
1061
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
856
1062
  break;
857
1063
  }
858
1064
  };
859
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
+ */
860
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
861
1075
  let fileSize = 0;
862
1076
  if (unlink) {
863
1077
  try {
@@ -906,53 +1120,86 @@ export class Matterbridge extends EventEmitter {
906
1120
  }
907
1121
  };
908
1122
  }
1123
+ /**
1124
+ * Update matterbridge and cleanup.
1125
+ */
909
1126
  async updateProcess() {
910
1127
  await this.cleanup('updating...', false);
911
1128
  }
1129
+ /**
1130
+ * Restarts the process by spawning a new process and exiting the current process.
1131
+ */
912
1132
  async restartProcess() {
913
1133
  await this.cleanup('restarting...', true);
914
1134
  }
1135
+ /**
1136
+ * Shut down the process by exiting the current process.
1137
+ */
915
1138
  async shutdownProcess() {
916
1139
  await this.cleanup('shutting down...', false);
917
1140
  }
1141
+ /**
1142
+ * Shut down the process and reset.
1143
+ */
918
1144
  async unregisterAndShutdownProcess() {
919
1145
  this.log.info('Unregistering all devices and shutting down...');
920
- for (const plugin of this.plugins) {
921
- 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);
922
1151
  }
923
1152
  await this.cleanup('unregistered all devices and shutting down...', false);
924
1153
  }
1154
+ /**
1155
+ * Shut down the process and reset.
1156
+ */
925
1157
  async shutdownProcessAndReset() {
926
1158
  await this.cleanup('shutting down with reset...', false);
927
1159
  }
1160
+ /**
1161
+ * Shut down the process and factory reset.
1162
+ */
928
1163
  async shutdownProcessAndFactoryReset() {
929
1164
  await this.cleanup('shutting down with factory reset...', false);
930
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
+ */
931
1172
  async cleanup(message, restart = false) {
932
1173
  if (this.initialized && !this.hasCleanupStarted) {
933
1174
  this.hasCleanupStarted = true;
934
1175
  this.log.info(message);
1176
+ // Deregisters the process handlers
935
1177
  this.deregisterProcesslHandlers();
1178
+ // Clear the start matter interval
936
1179
  if (this.startMatterInterval) {
937
1180
  clearInterval(this.startMatterInterval);
938
1181
  this.startMatterInterval = undefined;
939
1182
  this.log.debug('Start matter interval cleared');
940
1183
  }
1184
+ // Clear the check update interval
941
1185
  if (this.checkUpdateInterval) {
942
1186
  clearInterval(this.checkUpdateInterval);
943
1187
  this.checkUpdateInterval = undefined;
944
1188
  this.log.debug('Check update interval cleared');
945
1189
  }
1190
+ // Clear the configure timeout
946
1191
  if (this.configureTimeout) {
947
1192
  clearTimeout(this.configureTimeout);
948
1193
  this.configureTimeout = undefined;
949
1194
  this.log.debug('Matterbridge configure timeout cleared');
950
1195
  }
1196
+ // Clear the reachability timeout
951
1197
  if (this.reachabilityTimeout) {
952
1198
  clearTimeout(this.reachabilityTimeout);
953
1199
  this.reachabilityTimeout = undefined;
954
1200
  this.log.debug('Matterbridge reachability timeout cleared');
955
1201
  }
1202
+ // Calling the shutdown method of each plugin and clear the reachability timeout
956
1203
  for (const plugin of this.plugins) {
957
1204
  if (!plugin.enabled || plugin.error)
958
1205
  continue;
@@ -963,24 +1210,42 @@ export class Matterbridge extends EventEmitter {
963
1210
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
964
1211
  }
965
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
966
1227
  if (this.httpServer) {
967
1228
  this.httpServer.close();
968
1229
  this.httpServer.removeAllListeners();
969
1230
  this.httpServer = undefined;
970
1231
  this.log.debug('Frontend http server closed successfully');
971
1232
  }
1233
+ // Close the https server
972
1234
  if (this.httpsServer) {
973
1235
  this.httpsServer.close();
974
1236
  this.httpsServer.removeAllListeners();
975
1237
  this.httpsServer = undefined;
976
1238
  this.log.debug('Frontend https server closed successfully');
977
1239
  }
1240
+ // Remove listeners from the express app
978
1241
  if (this.expressApp) {
979
1242
  this.expressApp.removeAllListeners();
980
1243
  this.expressApp = undefined;
981
1244
  this.log.debug('Frontend app closed successfully');
982
1245
  }
1246
+ // Close the WebSocket server
983
1247
  if (this.webSocketServer) {
1248
+ // Close all active connections
984
1249
  this.webSocketServer.clients.forEach((client) => {
985
1250
  if (client.readyState === WebSocket.OPEN) {
986
1251
  client.close();
@@ -996,29 +1261,35 @@ export class Matterbridge extends EventEmitter {
996
1261
  });
997
1262
  this.webSocketServer = undefined;
998
1263
  }
999
- if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
1000
- await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
1001
- }
1264
+ // Closing matter
1002
1265
  await this.stopMatterServer();
1266
+ // Closing matter storage
1003
1267
  await this.stopMatterStorage();
1268
+ // Remove the matterfilelogger
1004
1269
  try {
1005
1270
  Logger.removeLogger('matterfilelogger');
1271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1006
1272
  }
1007
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}`);
1008
1275
  }
1276
+ // Serialize registeredDevices
1009
1277
  if (this.nodeStorage && this.nodeContext) {
1010
1278
  this.log.info('Saving registered devices...');
1011
1279
  const serializedRegisteredDevices = [];
1012
1280
  this.devices.forEach(async (device) => {
1013
1281
  const serializedMatterbridgeDevice = device.serialize();
1282
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1014
1283
  if (serializedMatterbridgeDevice)
1015
1284
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1016
1285
  });
1017
1286
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1018
1287
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1288
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1019
1289
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1020
1290
  await this.nodeContext.close();
1021
1291
  this.nodeContext = undefined;
1292
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1022
1293
  for (const plugin of this.plugins) {
1023
1294
  if (plugin.nodeContext) {
1024
1295
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1048,17 +1319,37 @@ export class Matterbridge extends EventEmitter {
1048
1319
  }
1049
1320
  }
1050
1321
  else {
1051
- if (message === 'shutting down with reset...') {
1052
- this.log.info('Resetting Matterbridge commissioning information...');
1053
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1054
- 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.');
1055
1342
  }
1056
1343
  if (message === 'shutting down with factory reset...') {
1057
- this.log.info('Resetting Matterbridge commissioning information...');
1058
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1059
- this.log.info('Resetting Matterbridge storage...');
1060
- await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1061
- 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.');
1062
1353
  }
1063
1354
  this.log.notice('Cleanup completed. Shutting down...');
1064
1355
  Matterbridge.instance = undefined;
@@ -1068,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
1068
1359
  this.initialized = false;
1069
1360
  }
1070
1361
  }
1362
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1071
1363
  async addBridgedEndpoint(pluginName, device) {
1364
+ // Nothing to do here
1072
1365
  }
1366
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1073
1367
  async removeBridgedEndpoint(pluginName, device) {
1368
+ // Nothing to do here
1074
1369
  }
1370
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1075
1371
  async removeAllBridgedEndpoints(pluginName) {
1372
+ // Nothing to do here
1076
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
+ */
1077
1380
  async addBridgedDevice(pluginName, device) {
1078
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
1079
1383
  const plugin = this.plugins.get(pluginName);
1080
1384
  if (!plugin) {
1081
1385
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1082
1386
  return;
1083
1387
  }
1388
+ // Register and add the device to matterbridge aggregator in bridge mode
1084
1389
  if (this.bridgeMode === 'bridge') {
1085
1390
  if (!this.matterAggregator) {
1086
1391
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1088,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
1088
1393
  }
1089
1394
  this.matterAggregator.addBridgedDevice(device);
1090
1395
  }
1396
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1397
+ // Register and add the device in childbridge mode
1091
1398
  if (this.bridgeMode === 'childbridge') {
1092
1399
  if (plugin.type === 'AccessoryPlatform') {
1400
+ // Check if the plugin is locked with the commissioning server
1093
1401
  if (!plugin.locked) {
1094
1402
  plugin.locked = true;
1095
1403
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1103,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
1103
1411
  }
1104
1412
  }
1105
1413
  if (plugin.type === 'DynamicPlatform') {
1414
+ // Check if the plugin is locked with the commissioning server and the aggregator
1106
1415
  if (!plugin.locked) {
1107
1416
  plugin.locked = true;
1108
1417
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1123,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
1123
1432
  plugin.registeredDevices++;
1124
1433
  if (plugin.addedDevices !== undefined)
1125
1434
  plugin.addedDevices++;
1435
+ // Add the device to the DeviceManager
1126
1436
  this.devices.set(device);
1127
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}`);
1128
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
+ */
1129
1445
  async removeBridgedDevice(pluginName, device) {
1130
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
1131
1448
  const plugin = this.plugins.get(pluginName);
1132
1449
  if (!plugin) {
1133
1450
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1134
1451
  return;
1135
1452
  }
1453
+ // Remove the device from matterbridge aggregator in bridge mode
1136
1454
  if (this.bridgeMode === 'bridge') {
1137
1455
  if (!this.matterAggregator) {
1138
1456
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1142,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
1142
1460
  device.setBridgedDeviceReachability(false);
1143
1461
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1144
1462
  }
1463
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1464
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1145
1465
  this.matterAggregator?.removeBridgedDevice(device);
1146
1466
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1147
1467
  if (plugin.registeredDevices !== undefined)
@@ -1149,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
1149
1469
  if (plugin.addedDevices !== undefined)
1150
1470
  plugin.addedDevices--;
1151
1471
  }
1472
+ // Remove the device in childbridge mode
1152
1473
  if (this.bridgeMode === 'childbridge') {
1153
1474
  if (plugin.type === 'AccessoryPlatform') {
1154
1475
  if (!plugin.commissioningServer) {
@@ -1172,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
1172
1493
  plugin.registeredDevices--;
1173
1494
  if (plugin.addedDevices !== undefined)
1174
1495
  plugin.addedDevices--;
1496
+ // Remove the commissioning server
1175
1497
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1176
1498
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1177
1499
  plugin.commissioningServer = undefined;
1178
1500
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1179
1501
  }
1180
1502
  }
1503
+ // Remove the device from the DeviceManager
1181
1504
  this.devices.remove(device);
1182
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
+ */
1183
1512
  async removeAllBridgedDevices(pluginName) {
1184
1513
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1185
1514
  this.devices.forEach(async (device) => {
@@ -1188,12 +1517,18 @@ export class Matterbridge extends EventEmitter {
1188
1517
  }
1189
1518
  });
1190
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
+ */
1191
1525
  async startBridge() {
1526
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1192
1527
  if (!this.storageManager)
1193
1528
  throw new Error('No storage manager initialized');
1194
1529
  if (!this.matterbridgeContext)
1195
1530
  throw new Error('No storage context initialized');
1196
- this.matterServer = this.createMatterServer(this.storageManager);
1531
+ this.matterServer = await this.createMatterServer(this.storageManager);
1197
1532
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1198
1533
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1199
1534
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -1207,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
1207
1542
  let failCount = 0;
1208
1543
  this.startMatterInterval = setInterval(async () => {
1209
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
1210
1546
  if (!plugin.enabled)
1211
1547
  continue;
1212
1548
  if (plugin.error) {
@@ -1231,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
1231
1567
  clearInterval(this.startMatterInterval);
1232
1568
  this.startMatterInterval = undefined;
1233
1569
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1570
+ // Start the Matter server
1234
1571
  await this.startMatterServer();
1235
1572
  this.log.notice('Matter server started');
1573
+ // Show the QR code for commissioning or log the already commissioned message
1236
1574
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1575
+ // Configure the plugins
1237
1576
  this.configureTimeout = setTimeout(async () => {
1238
1577
  for (const plugin of this.plugins) {
1239
1578
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1240
1579
  continue;
1241
1580
  try {
1242
- await this.plugins.configure(plugin);
1581
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1243
1582
  }
1244
1583
  catch (error) {
1245
1584
  plugin.error = true;
@@ -1248,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
1248
1587
  }
1249
1588
  this.wssSendRefreshRequired();
1250
1589
  }, 30 * 1000);
1590
+ // Setting reachability to true
1251
1591
  this.reachabilityTimeout = setTimeout(() => {
1252
1592
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1253
1593
  if (this.commissioningServer)
@@ -1257,16 +1597,24 @@ export class Matterbridge extends EventEmitter {
1257
1597
  }, 60 * 1000);
1258
1598
  }, 1000);
1259
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
+ */
1260
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
1261
1608
  if (!this.storageManager)
1262
1609
  throw new Error('No storage manager initialized');
1263
- this.matterServer = this.createMatterServer(this.storageManager);
1610
+ this.matterServer = await this.createMatterServer(this.storageManager);
1264
1611
  await this.startPlugins();
1265
1612
  this.log.debug('Starting start matter interval in childbridge mode...');
1266
1613
  let failCount = 0;
1267
1614
  this.startMatterInterval = setInterval(async () => {
1268
1615
  let allStarted = true;
1269
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
1270
1618
  if (!plugin.enabled)
1271
1619
  continue;
1272
1620
  if (plugin.error) {
@@ -1294,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
1294
1642
  clearInterval(this.startMatterInterval);
1295
1643
  this.startMatterInterval = undefined;
1296
1644
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1645
+ // Start the Matter server
1297
1646
  await this.startMatterServer();
1298
1647
  this.log.notice('Matter server started');
1648
+ // Configure the plugins
1299
1649
  this.configureTimeout = setTimeout(async () => {
1300
1650
  for (const plugin of this.plugins) {
1301
1651
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1302
1652
  continue;
1303
1653
  try {
1304
- await this.plugins.configure(plugin);
1654
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1305
1655
  }
1306
1656
  catch (error) {
1307
1657
  plugin.error = true;
@@ -1330,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
1330
1680
  continue;
1331
1681
  }
1332
1682
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1683
+ // Setting reachability to true
1333
1684
  plugin.reachabilityTimeout = setTimeout(() => {
1334
1685
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1335
1686
  if (plugin.commissioningServer)
@@ -1342,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
1342
1693
  }
1343
1694
  }, 1000);
1344
1695
  }
1696
+ /**
1697
+ * Starts the Matterbridge controller.
1698
+ * @private
1699
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1700
+ */
1345
1701
  async startController() {
1346
1702
  if (!this.storageManager) {
1347
1703
  this.log.error('No storage manager initialized');
@@ -1356,7 +1712,7 @@ export class Matterbridge extends EventEmitter {
1356
1712
  return;
1357
1713
  }
1358
1714
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1359
- this.matterServer = this.createMatterServer(this.storageManager);
1715
+ this.matterServer = await this.createMatterServer(this.storageManager);
1360
1716
  this.log.info('Creating matter commissioning controller');
1361
1717
  this.commissioningController = new CommissioningController({
1362
1718
  autoConnect: false,
@@ -1404,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
1404
1760
  const nodeId = await this.commissioningController.commissionNode(options);
1405
1761
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1406
1762
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1407
- }
1763
+ } // (hasParameter('pairingcode'))
1408
1764
  if (hasParameter('unpairall')) {
1409
1765
  this.log.info('***Commissioning controller unpairing all nodes...');
1410
1766
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1415,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
1415
1771
  return;
1416
1772
  }
1417
1773
  if (hasParameter('discover')) {
1774
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
+ // console.log(discover);
1418
1776
  }
1419
1777
  if (!this.commissioningController.isCommissioned()) {
1420
1778
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1455,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
1455
1813
  },
1456
1814
  });
1457
1815
  node.logStructure();
1816
+ // Get the interaction client
1458
1817
  this.log.info('Getting the interaction client');
1459
1818
  const interactionClient = await node.getInteractionClient();
1460
1819
  let cluster;
1461
1820
  let attributes;
1821
+ // Log BasicInformationCluster
1462
1822
  cluster = BasicInformationCluster;
1463
1823
  attributes = await interactionClient.getMultipleAttributes({
1464
1824
  attributes: [{ clusterId: cluster.id }],
@@ -1468,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
1468
1828
  attributes.forEach((attribute) => {
1469
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}`);
1470
1830
  });
1831
+ // Log PowerSourceCluster
1471
1832
  cluster = PowerSourceCluster;
1472
1833
  attributes = await interactionClient.getMultipleAttributes({
1473
1834
  attributes: [{ clusterId: cluster.id }],
@@ -1477,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
1477
1838
  attributes.forEach((attribute) => {
1478
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}`);
1479
1840
  });
1841
+ // Log ThreadNetworkDiagnostics
1480
1842
  cluster = ThreadNetworkDiagnosticsCluster;
1481
1843
  attributes = await interactionClient.getMultipleAttributes({
1482
1844
  attributes: [{ clusterId: cluster.id }],
@@ -1486,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
1486
1848
  attributes.forEach((attribute) => {
1487
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}`);
1488
1850
  });
1851
+ // Log SwitchCluster
1489
1852
  cluster = SwitchCluster;
1490
1853
  attributes = await interactionClient.getMultipleAttributes({
1491
1854
  attributes: [{ clusterId: cluster.id }],
@@ -1506,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
1506
1869
  this.log.info('Subscribed to all attributes and events');
1507
1870
  }
1508
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
+ */
1509
1881
  async startMatterStorage(storageType, storageName) {
1510
1882
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1511
1883
  if (storageType === 'disk') {
@@ -1554,11 +1926,19 @@ export class Matterbridge extends EventEmitter {
1554
1926
  await this.matterbridgeContext.set('passcode', this.passcode);
1555
1927
  await this.matterbridgeContext.set('discriminator', this.discriminator);
1556
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
+ */
1557
1935
  async convertStorage(context, pluginName) {
1936
+ if (this.edge !== false)
1937
+ return;
1558
1938
  try {
1559
1939
  const storageService = Environment.default.get(StorageService);
1560
1940
  Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
1561
- const nodeStorage = await storageService.open('Matterbridge');
1941
+ const nodeStorage = await storageService.open(pluginName);
1562
1942
  if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
1563
1943
  this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
1564
1944
  return;
@@ -1566,13 +1946,19 @@ export class Matterbridge extends EventEmitter {
1566
1946
  else {
1567
1947
  this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1568
1948
  }
1949
+ // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1569
1950
  const fabricManagerContext = context.createContext('FabricManager');
1570
1951
  const fabrics = (await fabricManagerContext.get('fabrics', []));
1571
1952
  const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
1953
+ // Read EventHandler from the old storage
1572
1954
  const eventHandlerContext = context.createContext('EventHandler');
1955
+ // Read SessionManager from the old storage
1573
1956
  const sessionManagerContext = context.createContext('SessionManager');
1957
+ // Read EndpointStructure from the old storage
1574
1958
  const endpointStructureContext = context.createContext('EndpointStructure');
1959
+ // Read generalCommissioning from the old storage
1575
1960
  const generalCommissioningContext = context.createContext('Cluster-0-48');
1961
+ // Read basicInformation from the old storage
1576
1962
  const basicInformationContext = context.createContext('Cluster-0-40');
1577
1963
  const fabricInfo = {};
1578
1964
  const fabricInfoArray = [];
@@ -1581,7 +1967,7 @@ export class Matterbridge extends EventEmitter {
1581
1967
  const aclArray = [];
1582
1968
  this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
1583
1969
  if (fabrics.length === 0 || nextFabricIndex === 0) {
1584
- this.log.notice(`If you want to try out matterbridge edge (beta) add -edge to the command line and pair it to your controller(s).`);
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).`);
1585
1971
  return;
1586
1972
  }
1587
1973
  for (const fabric of fabrics) {
@@ -1603,7 +1989,7 @@ export class Matterbridge extends EventEmitter {
1603
1989
  label: fabric.label,
1604
1990
  });
1605
1991
  nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
1606
- trcArray.push('{\"__object__\":\"Uint8Array\",\"__value__\":\"' + Buffer.from(fabric.rootCert).toString('hex') + '\"}');
1992
+ trcArray.push(fabric.rootCert);
1607
1993
  this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
1608
1994
  const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
1609
1995
  if (acl && acl.value.length > 0) {
@@ -1621,14 +2007,18 @@ export class Matterbridge extends EventEmitter {
1621
2007
  await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
1622
2008
  await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
1623
2009
  await nodeStorage.createContext('root').set('__number__', 0);
1624
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
1625
2010
  await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
1626
2011
  await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
1627
2012
  await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
1628
2013
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
1629
2014
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
2015
+ // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
1630
2016
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
2017
+ // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
1631
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}]
1632
2022
  await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
1633
2023
  await nodeStorage
1634
2024
  .createContext('root')
@@ -1639,9 +2029,39 @@ export class Matterbridge extends EventEmitter {
1639
2029
  .createContext('basicInformation')
1640
2030
  .set('location', await basicInformationContext.get('location', 'XX'));
1641
2031
  await nodeStorage.createContext('root').createContext('network').set('ble', false);
1642
- await nodeStorage.createContext('root').createContext('network').set('operationalPort', 5540);
1643
- await nodeStorage.createContext('root').createContext('productDescription').set('productId', 0x8000);
1644
- 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}...`);
1645
2065
  for (const key of await endpointStructureContext.keys()) {
1646
2066
  if (key === 'nextEndpointId') {
1647
2067
  await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
@@ -1650,41 +2070,43 @@ export class Matterbridge extends EventEmitter {
1650
2070
  const parts = key.split('-');
1651
2071
  const number = await endpointStructureContext.get(key);
1652
2072
  if (parts.length === 2) {
1653
- this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.__number__:${number}`);
1654
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', number);
1655
- }
1656
- else if (parts.length === 3 && parts[2].startsWith('custom_')) {
1657
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${number}`);
1658
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).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);
1659
2075
  }
1660
2076
  else if (parts.length === 3 && parts[2].startsWith('unique_')) {
1661
2077
  const device = this.devices.get(parts[2].replace('unique_', ''));
1662
2078
  if (device && device.deviceName && device.maybeNumber) {
1663
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${device.maybeNumber}`);
1664
- 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);
1665
2081
  }
1666
2082
  }
1667
2083
  else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
1668
2084
  const device = this.devices.get(parts[2].replace('unique_', ''));
1669
2085
  if (device && device.deviceName && device.maybeNumber) {
1670
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${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}`);
1671
2089
  await nodeStorage
1672
2090
  .createContext('root')
1673
2091
  .createContext('parts')
1674
- .createContext('Matterbridge')
2092
+ .createContext(rootDeviceName)
1675
2093
  .createContext('parts')
1676
2094
  .createContext(device.deviceName.replace(/[ .]/g, ''))
1677
2095
  .createContext('parts')
1678
2096
  .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
1679
- .set('__number__', device.maybeNumber);
2097
+ .set('__number__', childEndpoint?.number);
1680
2098
  }
1681
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
+ }
1682
2104
  else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
1683
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${number}`);
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}`);
1684
2106
  await nodeStorage
1685
2107
  .createContext('root')
1686
2108
  .createContext('parts')
1687
- .createContext('Matterbridge')
2109
+ .createContext(rootDeviceName)
1688
2110
  .createContext('parts')
1689
2111
  .createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
1690
2112
  .createContext('parts')
@@ -1709,12 +2131,19 @@ export class Matterbridge extends EventEmitter {
1709
2131
  await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
1710
2132
  await context.set('converted', true);
1711
2133
  this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
1712
- 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.`);
1713
2136
  }
1714
2137
  catch (error) {
1715
2138
  this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
1716
2139
  }
1717
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
+ */
1718
2147
  async backupMatterStorage(storageName, backupName) {
1719
2148
  try {
1720
2149
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1735,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
1735
2164
  }
1736
2165
  }
1737
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
+ */
1738
2173
  async restoreMatterStorage(backupName, storageName) {
1739
2174
  try {
1740
2175
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1755,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
1755
2190
  }
1756
2191
  }
1757
2192
  }
2193
+ /**
2194
+ * Stops the matter storage.
2195
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2196
+ */
1758
2197
  async stopMatterStorage() {
1759
2198
  this.log.debug('Stopping storage');
1760
2199
  await this.storageManager?.close();
@@ -1763,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
1763
2202
  this.matterbridgeContext = undefined;
1764
2203
  this.mattercontrollerContext = undefined;
1765
2204
  }
1766
- 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) {
1767
2211
  this.log.debug('Creating matter server');
2212
+ // Validate mdnsInterface
1768
2213
  if (this.mdnsInterface) {
1769
2214
  const networkInterfaces = os.networkInterfaces();
1770
2215
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1780,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
1780
2225
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1781
2226
  return matterServer;
1782
2227
  }
2228
+ /**
2229
+ * Starts the Matter server.
2230
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
2231
+ */
1783
2232
  async startMatterServer() {
1784
2233
  if (!this.matterServer) {
1785
2234
  this.log.error('No matter server initialized');
@@ -1789,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
1789
2238
  this.log.debug('Starting matter server...');
1790
2239
  await this.matterServer.start();
1791
2240
  this.log.debug('Started matter server');
2241
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1792
2242
  }
2243
+ /**
2244
+ * Stops the Matter server, commissioningServer and commissioningController.
2245
+ */
1793
2246
  async stopMatterServer() {
1794
2247
  this.log.debug('Stopping matter commissioningServer');
1795
2248
  await this.commissioningServer?.close();
@@ -1803,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
1803
2256
  this.matterAggregator = undefined;
1804
2257
  this.matterServer = undefined;
1805
2258
  }
2259
+ /**
2260
+ * Creates a Matter Aggregator.
2261
+ * @param {StorageContext} context - The storage context.
2262
+ * @returns {Aggregator} - The created Matter Aggregator.
2263
+ */
1806
2264
  async createMatterAggregator(context, pluginName) {
1807
2265
  this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
1808
2266
  const matterAggregator = new Aggregator();
1809
2267
  return matterAggregator;
1810
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
+ */
1811
2276
  async createCommisioningServer(context, pluginName) {
1812
2277
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1813
2278
  const deviceName = await context.get('deviceName');
1814
2279
  const deviceType = await context.get('deviceType');
1815
2280
  const vendorId = await context.get('vendorId');
1816
- const vendorName = await context.get('vendorName');
2281
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1817
2282
  const productId = await context.get('productId');
1818
- const productName = await context.get('productName');
2283
+ const productName = await context.get('productName'); // Home app = Model
1819
2284
  const serialNumber = await context.get('serialNumber');
1820
2285
  const uniqueId = await context.get('uniqueId');
1821
2286
  const softwareVersion = await context.get('softwareVersion', 1);
1822
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2287
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1823
2288
  const hardwareVersion = await context.get('hardwareVersion', 1);
1824
2289
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1825
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')})`);
@@ -1827,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
1827
2292
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1828
2293
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1829
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
1830
2296
  if (this.ipv4address) {
1831
2297
  const networkInterfaces = os.networkInterfaces();
1832
2298
  const availableAddresses = Object.values(networkInterfaces)
@@ -1841,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
1841
2307
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1842
2308
  }
1843
2309
  }
2310
+ // Validate ipv6address
1844
2311
  if (this.ipv6address) {
1845
2312
  const networkInterfaces = os.networkInterfaces();
1846
2313
  const availableAddresses = Object.values(networkInterfaces)
@@ -1871,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
1871
2338
  nodeLabel: productName,
1872
2339
  productLabel: productName,
1873
2340
  softwareVersion,
1874
- softwareVersionString,
2341
+ softwareVersionString, // Home app = Firmware Revision
1875
2342
  hardwareVersion,
1876
2343
  hardwareVersionString,
1877
2344
  uniqueId,
@@ -1967,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
1967
2434
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1968
2435
  return commissioningServer;
1969
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
+ */
1970
2455
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1971
2456
  if (!this.storageManager)
1972
2457
  throw new Error('No storage manager initialized');
@@ -1994,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
1994
2479
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1995
2480
  return storageContext;
1996
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
+ */
1997
2489
  async importCommissioningServerContext(pluginName, device) {
1998
2490
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1999
2491
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -2028,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
2028
2520
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2029
2521
  return storageContext;
2030
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
+ */
2031
2531
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
2032
2532
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
2033
2533
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -2038,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
2038
2538
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2039
2539
  const QrCode = new QrCodeSchema();
2040
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`);
2041
- 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 */)
2042
2543
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
2043
2544
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2044
2545
  if (pluginName === 'Matterbridge') {
@@ -2085,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
2085
2586
  }
2086
2587
  this.wssSendRefreshRequired();
2087
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
+ */
2088
2595
  sanitizeFabricInformations(fabricInfo) {
2089
2596
  return fabricInfo.map((info) => {
2090
2597
  return {
@@ -2098,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
2098
2605
  };
2099
2606
  });
2100
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
+ */
2101
2614
  sanitizeSessionInformation(sessionInfo) {
2102
2615
  return sessionInfo
2103
2616
  .filter((session) => session.isPeerActive)
@@ -2125,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
2125
2638
  };
2126
2639
  });
2127
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
+ */
2128
2647
  setCommissioningServerReachability(commissioningServer, reachable) {
2129
2648
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2130
2649
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2132,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
2132
2651
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2133
2652
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2134
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
+ */
2135
2659
  setAggregatorReachability(matterAggregator, reachable) {
2136
2660
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2137
2661
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2144,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
2144
2668
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2145
2669
  });
2146
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
+ */
2147
2677
  setDeviceReachability(device, reachable) {
2148
2678
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2149
2679
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2192,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
2192
2722
  }
2193
2723
  return vendorName;
2194
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
+ */
2195
2729
  async getBaseRegisteredPlugins() {
2196
2730
  const baseRegisteredPlugins = [];
2197
2731
  for (const plugin of this.plugins) {
@@ -2223,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
2223
2757
  }
2224
2758
  return baseRegisteredPlugins;
2225
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
+ */
2226
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
+ */
2227
2780
  const cmdLine = command + ' ' + args.join(' ');
2228
2781
  if (process.platform === 'win32' && command === 'npm') {
2782
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2229
2783
  const argstring = 'npm ' + args.join(' ');
2230
2784
  args.splice(0, args.length, '/c', argstring);
2231
2785
  command = 'cmd.exe';
2232
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
2233
2790
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2234
2791
  args.unshift(command);
2235
2792
  command = 'sudo';
@@ -2287,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
2287
2844
  }
2288
2845
  });
2289
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
+ */
2290
2855
  wssSendMessage(level, time, name, message) {
2291
2856
  if (!level || !time || !name || !message)
2292
2857
  return;
2858
+ // Remove ANSI escape codes from the message
2859
+ // eslint-disable-next-line no-control-regex
2293
2860
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2861
+ // Remove leading asterisks from the message
2294
2862
  message = message.replace(/^\*+/, '');
2863
+ // Replace all occurrences of \t and \n
2295
2864
  message = message.replace(/[\t\n]/g, '');
2865
+ // Remove non-printable characters
2866
+ // eslint-disable-next-line no-control-regex
2296
2867
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2868
+ // Replace all occurrences of \" with "
2297
2869
  message = message.replace(/\\"/g, '"');
2870
+ // Define the maximum allowed length for continuous characters without a space
2298
2871
  const maxContinuousLength = 100;
2299
2872
  const keepStartLength = 20;
2300
2873
  const keepEndLength = 20;
2874
+ // Split the message into words
2301
2875
  message = message
2302
2876
  .split(' ')
2303
2877
  .map((word) => {
2878
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2304
2879
  if (word.length > maxContinuousLength) {
2305
2880
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2306
2881
  }
2307
2882
  return word;
2308
2883
  })
2309
2884
  .join(' ');
2885
+ // Send the message to all connected clients
2310
2886
  this.webSocketServer?.clients.forEach((client) => {
2311
2887
  if (client.readyState === WebSocket.OPEN) {
2312
2888
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2313
2889
  }
2314
2890
  });
2315
2891
  }
2892
+ /**
2893
+ * Sends a need to refresh WebSocket message to all connected clients.
2894
+ *
2895
+ */
2316
2896
  wssSendRefreshRequired() {
2317
2897
  this.matterbridgeInformation.refreshRequired = true;
2898
+ // Send the message to all connected clients
2318
2899
  this.webSocketServer?.clients.forEach((client) => {
2319
2900
  if (client.readyState === WebSocket.OPEN) {
2320
- 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: {} }));
2321
2902
  }
2322
2903
  });
2323
2904
  }
2905
+ /**
2906
+ * Sends a need to restart WebSocket message to all connected clients.
2907
+ *
2908
+ */
2324
2909
  wssSendRestartRequired() {
2325
2910
  this.matterbridgeInformation.restartRequired = true;
2911
+ // Send the message to all connected clients
2326
2912
  this.webSocketServer?.clients.forEach((client) => {
2327
2913
  if (client.readyState === WebSocket.OPEN) {
2328
- 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: {} }));
2329
2915
  }
2330
2916
  });
2331
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
+ */
2332
2923
  async initializeFrontend(port = 8283) {
2333
2924
  let initializeError = false;
2334
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
2335
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
2336
2938
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2337
2939
  if (!hasParameter('ssl')) {
2940
+ // Create an HTTP server and attach the express app
2338
2941
  this.httpServer = createServer(this.expressApp);
2942
+ // Listen on the specified port
2339
2943
  if (hasParameter('ingress')) {
2340
2944
  this.httpServer.listen(port, '0.0.0.0', () => {
2341
2945
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2349,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
2349
2953
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2350
2954
  });
2351
2955
  }
2956
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2352
2957
  this.httpServer.on('error', (error) => {
2353
2958
  this.log.error(`Frontend http server error listening on ${port}`);
2354
2959
  switch (error.code) {
@@ -2364,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
2364
2969
  });
2365
2970
  }
2366
2971
  else {
2972
+ // Load the SSL certificate, the private key and optionally the CA certificate
2367
2973
  let cert;
2368
2974
  try {
2369
2975
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2391,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
2391
2997
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2392
2998
  }
2393
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
2394
3001
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
3002
+ // Listen on the specified port
2395
3003
  if (hasParameter('ingress')) {
2396
3004
  this.httpsServer.listen(port, '0.0.0.0', () => {
2397
3005
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2405,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
2405
3013
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2406
3014
  });
2407
3015
  }
3016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2408
3017
  this.httpsServer.on('error', (error) => {
2409
3018
  this.log.error(`Frontend https server error listening on ${port}`);
2410
3019
  switch (error.code) {
@@ -2421,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
2421
3030
  }
2422
3031
  if (initializeError)
2423
3032
  return;
3033
+ // Createe a WebSocket server and attach it to the http or https server
2424
3034
  const wssPort = port;
2425
3035
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2426
3036
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2427
3037
  this.webSocketServer.on('connection', (ws, request) => {
2428
3038
  const clientIp = request.socket.remoteAddress;
2429
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
3039
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2430
3040
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2431
3041
  ws.on('message', (message) => {
2432
3042
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2459,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
2459
3069
  this.webSocketServer.on('error', (ws, error) => {
2460
3070
  this.log.error(`WebSocketServer error: ${error}`);
2461
3071
  });
3072
+ // Endpoint to validate login code
2462
3073
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2463
3074
  const { password } = req.body;
2464
3075
  this.log.debug('The frontend sent /api/login', password);
@@ -2477,12 +3088,14 @@ export class Matterbridge extends EventEmitter {
2477
3088
  this.log.warn('/api/login error wrong password');
2478
3089
  res.json({ valid: false });
2479
3090
  }
3091
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2480
3092
  }
2481
3093
  catch (error) {
2482
3094
  this.log.error('/api/login error getting password');
2483
3095
  res.json({ valid: false });
2484
3096
  }
2485
3097
  });
3098
+ // Endpoint to provide settings
2486
3099
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2487
3100
  this.log.debug('The frontend sent /api/settings');
2488
3101
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2502,14 +3115,18 @@ export class Matterbridge extends EventEmitter {
2502
3115
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2503
3116
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2504
3117
  this.matterbridgeInformation.profile = this.profile;
2505
- 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));
2506
3120
  res.json(response);
2507
3121
  });
3122
+ // Endpoint to provide plugins
2508
3123
  this.expressApp.get('/api/plugins', async (req, res) => {
2509
3124
  this.log.debug('The frontend sent /api/plugins');
2510
3125
  const response = await this.getBaseRegisteredPlugins();
3126
+ // this.log.debug('Response:', debugStringify(response));
2511
3127
  res.json(response);
2512
3128
  });
3129
+ // Endpoint to provide devices
2513
3130
  this.expressApp.get('/api/devices', (req, res) => {
2514
3131
  this.log.debug('The frontend sent /api/devices');
2515
3132
  const devices = [];
@@ -2542,8 +3159,10 @@ export class Matterbridge extends EventEmitter {
2542
3159
  cluster: cluster,
2543
3160
  });
2544
3161
  });
3162
+ // this.log.debug('Response:', debugStringify(data));
2545
3163
  res.json(devices);
2546
3164
  });
3165
+ // Endpoint to provide the cluster servers of the devices
2547
3166
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2548
3167
  const selectedPluginName = req.params.selectedPluginName;
2549
3168
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2563,6 +3182,7 @@ export class Matterbridge extends EventEmitter {
2563
3182
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2564
3183
  if (clusterServer.name === 'EveHistory')
2565
3184
  return;
3185
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2566
3186
  let attributeValue;
2567
3187
  try {
2568
3188
  if (typeof value.getLocal() === 'object')
@@ -2573,6 +3193,7 @@ export class Matterbridge extends EventEmitter {
2573
3193
  catch (error) {
2574
3194
  attributeValue = 'Fabric-Scoped';
2575
3195
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3196
+ // console.log(error);
2576
3197
  }
2577
3198
  data.push({
2578
3199
  endpoint: device.number ? device.number.toString() : '...',
@@ -2585,12 +3206,14 @@ export class Matterbridge extends EventEmitter {
2585
3206
  });
2586
3207
  });
2587
3208
  device.getChildEndpoints().forEach((childEndpoint) => {
3209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2588
3210
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2589
3211
  const clusterServers = childEndpoint.getAllClusterServers();
2590
3212
  clusterServers.forEach((clusterServer) => {
2591
3213
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2592
3214
  if (clusterServer.name === 'EveHistory')
2593
3215
  return;
3216
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2594
3217
  let attributeValue;
2595
3218
  try {
2596
3219
  if (typeof value.getLocal() === 'object')
@@ -2601,6 +3224,7 @@ export class Matterbridge extends EventEmitter {
2601
3224
  catch (error) {
2602
3225
  attributeValue = 'Unavailable';
2603
3226
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3227
+ // console.log(error);
2604
3228
  }
2605
3229
  data.push({
2606
3230
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2617,6 +3241,7 @@ export class Matterbridge extends EventEmitter {
2617
3241
  });
2618
3242
  res.json(data);
2619
3243
  });
3244
+ // Endpoint to view the log
2620
3245
  this.expressApp.get('/api/view-log', async (req, res) => {
2621
3246
  this.log.debug('The frontend sent /api/log');
2622
3247
  try {
@@ -2629,10 +3254,12 @@ export class Matterbridge extends EventEmitter {
2629
3254
  res.status(500).send('Error reading log file');
2630
3255
  }
2631
3256
  });
3257
+ // Endpoint to download the matterbridge log
2632
3258
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2633
3259
  this.log.debug('The frontend sent /api/download-mblog');
2634
3260
  try {
2635
3261
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3262
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2636
3263
  }
2637
3264
  catch (error) {
2638
3265
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2644,10 +3271,12 @@ export class Matterbridge extends EventEmitter {
2644
3271
  }
2645
3272
  });
2646
3273
  });
3274
+ // Endpoint to download the matter log
2647
3275
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2648
3276
  this.log.debug('The frontend sent /api/download-mjlog');
2649
3277
  try {
2650
3278
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3279
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2651
3280
  }
2652
3281
  catch (error) {
2653
3282
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2659,6 +3288,7 @@ export class Matterbridge extends EventEmitter {
2659
3288
  }
2660
3289
  });
2661
3290
  });
3291
+ // Endpoint to download the matter storage file
2662
3292
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2663
3293
  this.log.debug('The frontend sent /api/download-mjstorage');
2664
3294
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2668,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
2668
3298
  }
2669
3299
  });
2670
3300
  });
3301
+ // Endpoint to download the matterbridge storage directory
2671
3302
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2672
3303
  this.log.debug('The frontend sent /api/download-mbstorage');
2673
3304
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2678,6 +3309,7 @@ export class Matterbridge extends EventEmitter {
2678
3309
  }
2679
3310
  });
2680
3311
  });
3312
+ // Endpoint to download the matterbridge plugin directory
2681
3313
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2682
3314
  this.log.debug('The frontend sent /api/download-pluginstorage');
2683
3315
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2688,9 +3320,11 @@ export class Matterbridge extends EventEmitter {
2688
3320
  }
2689
3321
  });
2690
3322
  });
3323
+ // Endpoint to download the matterbridge plugin config files
2691
3324
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2692
3325
  this.log.debug('The frontend sent /api/download-pluginconfig');
2693
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')));
2694
3328
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2695
3329
  if (error) {
2696
3330
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2698,6 +3332,7 @@ export class Matterbridge extends EventEmitter {
2698
3332
  }
2699
3333
  });
2700
3334
  });
3335
+ // Endpoint to download the matterbridge plugin config files
2701
3336
  this.expressApp.get('/api/download-backup', async (req, res) => {
2702
3337
  this.log.debug('The frontend sent /api/download-backup');
2703
3338
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2707,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
2707
3342
  }
2708
3343
  });
2709
3344
  });
3345
+ // Endpoint to receive commands
2710
3346
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2711
3347
  const command = req.params.command;
2712
3348
  let param = req.params.param;
@@ -2716,13 +3352,15 @@ export class Matterbridge extends EventEmitter {
2716
3352
  return;
2717
3353
  }
2718
3354
  this.log.debug(`Received frontend command: ${command}:${param}`);
3355
+ // Handle the command setpassword from Settings
2719
3356
  if (command === 'setpassword') {
2720
- const password = param.slice(1, -1);
3357
+ const password = param.slice(1, -1); // Remove the first and last characters
2721
3358
  this.log.debug('setpassword', param, password);
2722
3359
  await this.nodeContext?.set('password', password);
2723
3360
  res.json({ message: 'Command received' });
2724
3361
  return;
2725
3362
  }
3363
+ // Handle the command setbridgemode from Settings
2726
3364
  if (command === 'setbridgemode') {
2727
3365
  this.log.debug(`setbridgemode: ${param}`);
2728
3366
  this.wssSendRestartRequired();
@@ -2730,6 +3368,7 @@ export class Matterbridge extends EventEmitter {
2730
3368
  res.json({ message: 'Command received' });
2731
3369
  return;
2732
3370
  }
3371
+ // Handle the command backup from Settings
2733
3372
  if (command === 'backup') {
2734
3373
  this.log.notice(`Prepairing the backup...`);
2735
3374
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2737,25 +3376,26 @@ export class Matterbridge extends EventEmitter {
2737
3376
  res.json({ message: 'Command received' });
2738
3377
  return;
2739
3378
  }
3379
+ // Handle the command setmbloglevel from Settings
2740
3380
  if (command === 'setmbloglevel') {
2741
3381
  this.log.debug('Matterbridge log level:', param);
2742
3382
  if (param === 'Debug') {
2743
- this.log.logLevel = "debug";
3383
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2744
3384
  }
2745
3385
  else if (param === 'Info') {
2746
- this.log.logLevel = "info";
3386
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2747
3387
  }
2748
3388
  else if (param === 'Notice') {
2749
- this.log.logLevel = "notice";
3389
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2750
3390
  }
2751
3391
  else if (param === 'Warn') {
2752
- this.log.logLevel = "warn";
3392
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2753
3393
  }
2754
3394
  else if (param === 'Error') {
2755
- this.log.logLevel = "error";
3395
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2756
3396
  }
2757
3397
  else if (param === 'Fatal') {
2758
- this.log.logLevel = "fatal";
3398
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2759
3399
  }
2760
3400
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2761
3401
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2763,12 +3403,13 @@ export class Matterbridge extends EventEmitter {
2763
3403
  for (const plugin of this.plugins) {
2764
3404
  if (!plugin.platform || !plugin.platform.config)
2765
3405
  continue;
2766
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2767
- 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);
2768
3408
  }
2769
3409
  res.json({ message: 'Command received' });
2770
3410
  return;
2771
3411
  }
3412
+ // Handle the command setmbloglevel from Settings
2772
3413
  if (command === 'setmjloglevel') {
2773
3414
  this.log.debug('Matter.js log level:', param);
2774
3415
  if (param === 'Debug') {
@@ -2793,30 +3434,34 @@ export class Matterbridge extends EventEmitter {
2793
3434
  res.json({ message: 'Command received' });
2794
3435
  return;
2795
3436
  }
3437
+ // Handle the command setmdnsinterface from Settings
2796
3438
  if (command === 'setmdnsinterface') {
2797
- param = param.slice(1, -1);
3439
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2798
3440
  this.matterbridgeInformation.mattermdnsinterface = param;
2799
3441
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2800
3442
  await this.nodeContext?.set('mattermdnsinterface', param);
2801
3443
  res.json({ message: 'Command received' });
2802
3444
  return;
2803
3445
  }
3446
+ // Handle the command setipv4address from Settings
2804
3447
  if (command === 'setipv4address') {
2805
- param = param.slice(1, -1);
3448
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2806
3449
  this.matterbridgeInformation.matteripv4address = param;
2807
3450
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2808
3451
  await this.nodeContext?.set('matteripv4address', param);
2809
3452
  res.json({ message: 'Command received' });
2810
3453
  return;
2811
3454
  }
3455
+ // Handle the command setipv6address from Settings
2812
3456
  if (command === 'setipv6address') {
2813
- param = param.slice(1, -1);
3457
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2814
3458
  this.matterbridgeInformation.matteripv6address = param;
2815
3459
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2816
3460
  await this.nodeContext?.set('matteripv6address', param);
2817
3461
  res.json({ message: 'Command received' });
2818
3462
  return;
2819
3463
  }
3464
+ // Handle the command setmatterport from Settings
2820
3465
  if (command === 'setmatterport') {
2821
3466
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2822
3467
  this.matterbridgeInformation.matterPort = port;
@@ -2825,6 +3470,7 @@ export class Matterbridge extends EventEmitter {
2825
3470
  res.json({ message: 'Command received' });
2826
3471
  return;
2827
3472
  }
3473
+ // Handle the command setmatterdiscriminator from Settings
2828
3474
  if (command === 'setmatterdiscriminator') {
2829
3475
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2830
3476
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2833,6 +3479,7 @@ export class Matterbridge extends EventEmitter {
2833
3479
  res.json({ message: 'Command received' });
2834
3480
  return;
2835
3481
  }
3482
+ // Handle the command setmatterpasscode from Settings
2836
3483
  if (command === 'setmatterpasscode') {
2837
3484
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2838
3485
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2841,17 +3488,20 @@ export class Matterbridge extends EventEmitter {
2841
3488
  res.json({ message: 'Command received' });
2842
3489
  return;
2843
3490
  }
3491
+ // Handle the command setmbloglevel from Settings
2844
3492
  if (command === 'setmblogfile') {
2845
3493
  this.log.debug('Matterbridge file log:', param);
2846
3494
  this.matterbridgeInformation.fileLogger = param === 'true';
2847
3495
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3496
+ // Create the file logger for matterbridge
2848
3497
  if (param === 'true')
2849
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3498
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2850
3499
  else
2851
3500
  AnsiLogger.setGlobalLogfile(undefined);
2852
3501
  res.json({ message: 'Command received' });
2853
3502
  return;
2854
3503
  }
3504
+ // Handle the command setmbloglevel from Settings
2855
3505
  if (command === 'setmjlogfile') {
2856
3506
  this.log.debug('Matter file log:', param);
2857
3507
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2878,36 +3528,43 @@ export class Matterbridge extends EventEmitter {
2878
3528
  res.json({ message: 'Command received' });
2879
3529
  return;
2880
3530
  }
3531
+ // Handle the command unregister from Settings
2881
3532
  if (command === 'unregister') {
2882
3533
  await this.unregisterAndShutdownProcess();
2883
3534
  res.json({ message: 'Command received' });
2884
3535
  return;
2885
3536
  }
3537
+ // Handle the command reset from Settings
2886
3538
  if (command === 'reset') {
2887
3539
  await this.shutdownProcessAndReset();
2888
3540
  res.json({ message: 'Command received' });
2889
3541
  return;
2890
3542
  }
3543
+ // Handle the command factoryreset from Settings
2891
3544
  if (command === 'factoryreset') {
2892
3545
  await this.shutdownProcessAndFactoryReset();
2893
3546
  res.json({ message: 'Command received' });
2894
3547
  return;
2895
3548
  }
3549
+ // Handle the command shutdown from Header
2896
3550
  if (command === 'shutdown') {
2897
3551
  await this.shutdownProcess();
2898
3552
  res.json({ message: 'Command received' });
2899
3553
  return;
2900
3554
  }
3555
+ // Handle the command restart from Header
2901
3556
  if (command === 'restart') {
2902
3557
  await this.restartProcess();
2903
3558
  res.json({ message: 'Command received' });
2904
3559
  return;
2905
3560
  }
3561
+ // Handle the command update from Header
2906
3562
  if (command === 'update') {
2907
3563
  this.log.info('Updating matterbridge...');
2908
3564
  try {
2909
3565
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2910
3566
  this.log.info('Matterbridge has been updated. Full restart required.');
3567
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2911
3568
  }
2912
3569
  catch (error) {
2913
3570
  this.log.error('Error updating matterbridge');
@@ -2917,9 +3574,11 @@ export class Matterbridge extends EventEmitter {
2917
3574
  res.json({ message: 'Command received' });
2918
3575
  return;
2919
3576
  }
3577
+ // Handle the command saveconfig from Home
2920
3578
  if (command === 'saveconfig') {
2921
3579
  param = param.replace(/\*/g, '\\');
2922
3580
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3581
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2923
3582
  if (!this.plugins.has(param)) {
2924
3583
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2925
3584
  }
@@ -2933,33 +3592,39 @@ export class Matterbridge extends EventEmitter {
2933
3592
  res.json({ message: 'Command received' });
2934
3593
  return;
2935
3594
  }
3595
+ // Handle the command installplugin from Home
2936
3596
  if (command === 'installplugin') {
2937
3597
  param = param.replace(/\*/g, '\\');
2938
3598
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2939
3599
  try {
2940
3600
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2941
3601
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3602
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2942
3603
  }
2943
3604
  catch (error) {
2944
3605
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2945
3606
  }
2946
3607
  this.wssSendRestartRequired();
2947
3608
  param = param.split('@')[0];
3609
+ // Also add the plugin to matterbridge so no return!
2948
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
2949
3612
  res.json({ message: 'Command received' });
2950
3613
  return;
2951
3614
  }
2952
3615
  }
3616
+ // Handle the command addplugin from Home
2953
3617
  if (command === 'addplugin' || command === 'installplugin') {
2954
3618
  param = param.replace(/\*/g, '\\');
2955
3619
  const plugin = await this.plugins.add(param);
2956
3620
  if (plugin) {
2957
- 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
2958
3622
  }
2959
3623
  res.json({ message: 'Command received' });
2960
3624
  this.wssSendRefreshRequired();
2961
3625
  return;
2962
3626
  }
3627
+ // Handle the command removeplugin from Home
2963
3628
  if (command === 'removeplugin') {
2964
3629
  if (!this.plugins.has(param)) {
2965
3630
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2973,6 +3638,7 @@ export class Matterbridge extends EventEmitter {
2973
3638
  this.wssSendRefreshRequired();
2974
3639
  return;
2975
3640
  }
3641
+ // Handle the command enableplugin from Home
2976
3642
  if (command === 'enableplugin') {
2977
3643
  if (!this.plugins.has(param)) {
2978
3644
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2990,13 +3656,14 @@ export class Matterbridge extends EventEmitter {
2990
3656
  plugin.registeredDevices = undefined;
2991
3657
  plugin.addedDevices = undefined;
2992
3658
  await this.plugins.enable(param);
2993
- 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
2994
3660
  }
2995
3661
  }
2996
3662
  res.json({ message: 'Command received' });
2997
3663
  this.wssSendRefreshRequired();
2998
3664
  return;
2999
3665
  }
3666
+ // Handle the command disableplugin from Home
3000
3667
  if (command === 'disableplugin') {
3001
3668
  if (!this.plugins.has(param)) {
3002
3669
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -3013,6 +3680,7 @@ export class Matterbridge extends EventEmitter {
3013
3680
  return;
3014
3681
  }
3015
3682
  });
3683
+ // Fallback for routing (must be the last route)
3016
3684
  this.expressApp.get('*', (req, res) => {
3017
3685
  this.log.debug('The frontend sent:', req.url);
3018
3686
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -3020,6 +3688,11 @@ export class Matterbridge extends EventEmitter {
3020
3688
  });
3021
3689
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3022
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
+ */
3023
3696
  getClusterTextFromDevice(device) {
3024
3697
  const stringifyUserLabel = (endpoint) => {
3025
3698
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -3042,9 +3715,11 @@ export class Matterbridge extends EventEmitter {
3042
3715
  return '';
3043
3716
  };
3044
3717
  let attributes = '';
3718
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3045
3719
  const clusterServers = device.getAllClusterServers();
3046
3720
  clusterServers.forEach((clusterServer) => {
3047
3721
  try {
3722
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3048
3723
  if (clusterServer.name === 'OnOff')
3049
3724
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
3050
3725
  if (clusterServer.name === 'Switch')
@@ -3095,18 +3770,30 @@ export class Matterbridge extends EventEmitter {
3095
3770
  attributes += `${stringifyFixedLabel(device)} `;
3096
3771
  if (clusterServer.name === 'UserLabel')
3097
3772
  attributes += `${stringifyUserLabel(device)} `;
3773
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3098
3774
  }
3099
3775
  catch (error) {
3100
3776
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3101
3777
  }
3102
3778
  });
3779
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3103
3780
  return attributes;
3104
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
+ */
3105
3788
  async startExtension(dataPath, extensionVersion, port = 5540) {
3789
+ // Set the bridge mode
3106
3790
  this.bridgeMode = 'bridge';
3791
+ // Set the first port to use
3107
3792
  this.port = port;
3108
- 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 */ });
3109
3795
  this.log.debug('Matterbridge extension is starting...');
3796
+ // Initialize NodeStorage
3110
3797
  this.matterbridgeDirectory = dataPath;
3111
3798
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3112
3799
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -3125,10 +3812,13 @@ export class Matterbridge extends EventEmitter {
3125
3812
  };
3126
3813
  this.plugins.set(plugin);
3127
3814
  this.plugins.saveToStorage();
3815
+ // Log system info and create .matterbridge directory
3128
3816
  await this.logNodeAndSystemInfo();
3129
3817
  this.matterbridgeDirectory = dataPath;
3818
+ // Set matter.js logger level and format
3130
3819
  Logger.defaultLogLevel = MatterLogLevel.INFO;
3131
3820
  Logger.format = MatterLogFormat.ANSI;
3821
+ // Start the storage and create matterbridgeContext
3132
3822
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3133
3823
  if (!this.storageManager)
3134
3824
  return false;
@@ -3138,8 +3828,8 @@ export class Matterbridge extends EventEmitter {
3138
3828
  await this.matterbridgeContext.set('softwareVersion', 1);
3139
3829
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3140
3830
  await this.matterbridgeContext.set('hardwareVersion', 1);
3141
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3142
- 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);
3143
3833
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3144
3834
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
3145
3835
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -3151,6 +3841,7 @@ export class Matterbridge extends EventEmitter {
3151
3841
  await this.startMatterServer();
3152
3842
  this.log.info('Matter server started');
3153
3843
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3844
+ // Set reachability to true and trigger event after 60 seconds
3154
3845
  setTimeout(() => {
3155
3846
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3156
3847
  if (this.commissioningServer)
@@ -3160,14 +3851,31 @@ export class Matterbridge extends EventEmitter {
3160
3851
  }, 60 * 1000);
3161
3852
  return this.commissioningServer.isCommissioned();
3162
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
+ */
3163
3860
  async stopExtension() {
3861
+ // Closing matter
3164
3862
  await this.stopMatterServer();
3863
+ // Clearing the session manager
3864
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3865
+ // Closing storage
3165
3866
  await this.stopMatterStorage();
3166
3867
  this.log.info('Matter server stopped');
3167
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
+ */
3168
3875
  isExtensionCommissioned() {
3169
3876
  if (!this.commissioningServer)
3170
3877
  return false;
3171
3878
  return this.commissioningServer.isCommissioned();
3172
3879
  }
3173
3880
  }
3881
+ //# sourceMappingURL=matterbridge.js.map