matterbridge 1.6.6-dev.7 → 1.6.6

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 (102) hide show
  1. package/CHANGELOG.md +22 -10
  2. package/README-DEV.md +3 -3
  3. package/README.md +4 -0
  4. package/dist/cli.d.ts +25 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +26 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/cluster/export.d.ts +2 -0
  9. package/dist/cluster/export.d.ts.map +1 -0
  10. package/dist/cluster/export.js +2 -0
  11. package/dist/cluster/export.js.map +1 -0
  12. package/dist/defaultConfigSchema.d.ts +27 -0
  13. package/dist/defaultConfigSchema.d.ts.map +1 -0
  14. package/dist/defaultConfigSchema.js +23 -0
  15. package/dist/defaultConfigSchema.js.map +1 -0
  16. package/dist/deviceManager.d.ts +46 -0
  17. package/dist/deviceManager.d.ts.map +1 -0
  18. package/dist/deviceManager.js +26 -1
  19. package/dist/deviceManager.js.map +1 -0
  20. package/dist/index.d.ts +40 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +30 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/logger/export.d.ts +2 -0
  25. package/dist/logger/export.d.ts.map +1 -0
  26. package/dist/logger/export.js +1 -0
  27. package/dist/logger/export.js.map +1 -0
  28. package/dist/matter/export.d.ts +5 -0
  29. package/dist/matter/export.d.ts.map +1 -0
  30. package/dist/matter/export.js +1 -0
  31. package/dist/matter/export.js.map +1 -0
  32. package/dist/matterbridge.d.ts +466 -0
  33. package/dist/matterbridge.d.ts.map +1 -0
  34. package/dist/matterbridge.js +712 -65
  35. package/dist/matterbridge.js.map +1 -0
  36. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  37. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  38. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  39. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  40. package/dist/matterbridgeBehaviors.d.ts +942 -0
  41. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  42. package/dist/matterbridgeBehaviors.js +38 -1
  43. package/dist/matterbridgeBehaviors.js.map +1 -0
  44. package/dist/matterbridgeDevice.d.ts +6674 -0
  45. package/dist/matterbridgeDevice.d.ts.map +1 -0
  46. package/dist/matterbridgeDevice.js +1001 -26
  47. package/dist/matterbridgeDevice.js.map +1 -0
  48. package/dist/matterbridgeDeviceTypes.d.ts +82 -0
  49. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  50. package/dist/matterbridgeDeviceTypes.js +59 -13
  51. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  52. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  53. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  54. package/dist/matterbridgeDynamicPlatform.js +33 -0
  55. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  56. package/dist/matterbridgeEdge.d.ts +89 -0
  57. package/dist/matterbridgeEdge.d.ts.map +1 -0
  58. package/dist/matterbridgeEdge.js +529 -4
  59. package/dist/matterbridgeEdge.js.map +1 -0
  60. package/dist/matterbridgeEndpoint.d.ts +9774 -0
  61. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  62. package/dist/matterbridgeEndpoint.js +1113 -94
  63. package/dist/matterbridgeEndpoint.js.map +1 -0
  64. package/dist/matterbridgePlatform.d.ts +114 -0
  65. package/dist/matterbridgePlatform.d.ts.map +1 -0
  66. package/dist/matterbridgePlatform.js +122 -13
  67. package/dist/matterbridgePlatform.js.map +1 -0
  68. package/dist/matterbridgeTypes.d.ts +161 -0
  69. package/dist/matterbridgeTypes.d.ts.map +1 -0
  70. package/dist/matterbridgeTypes.js +24 -0
  71. package/dist/matterbridgeTypes.js.map +1 -0
  72. package/dist/matterbridgeWebsocket.d.ts +49 -0
  73. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  74. package/dist/matterbridgeWebsocket.js +50 -0
  75. package/dist/matterbridgeWebsocket.js.map +1 -0
  76. package/dist/pluginManager.d.ts +238 -0
  77. package/dist/pluginManager.d.ts.map +1 -0
  78. package/dist/pluginManager.js +238 -3
  79. package/dist/pluginManager.js.map +1 -0
  80. package/dist/storage/export.d.ts +2 -0
  81. package/dist/storage/export.d.ts.map +1 -0
  82. package/dist/storage/export.js +1 -0
  83. package/dist/storage/export.js.map +1 -0
  84. package/dist/utils/colorUtils.d.ts +61 -0
  85. package/dist/utils/colorUtils.d.ts.map +1 -0
  86. package/dist/utils/colorUtils.js +236 -89
  87. package/dist/utils/colorUtils.js.map +1 -0
  88. package/dist/utils/export.d.ts +3 -0
  89. package/dist/utils/export.d.ts.map +1 -0
  90. package/dist/utils/export.js +1 -0
  91. package/dist/utils/export.js.map +1 -0
  92. package/dist/utils/utils.d.ts +221 -0
  93. package/dist/utils/utils.d.ts.map +1 -0
  94. package/dist/utils/utils.js +252 -7
  95. package/dist/utils/utils.js.map +1 -0
  96. package/frontend/build/asset-manifest.json +3 -3
  97. package/frontend/build/index.html +1 -1
  98. package/frontend/build/static/js/{main.cb537856.js → main.a742de4e.js} +9 -9
  99. package/frontend/build/static/js/{main.cb537856.js.map → main.a742de4e.js.map} +1 -1
  100. package/npm-shrinkwrap.json +165 -84
  101. package/package.json +4 -4
  102. /package/frontend/build/static/js/{main.cb537856.js.LICENSE.txt → main.a742de4e.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,26 +29,35 @@ import EventEmitter from 'events';
6
29
  import os from 'os';
7
30
  import path from 'path';
8
31
  import { randomBytes } from 'crypto';
32
+ // Package modules
9
33
  import https from 'https';
10
34
  import express from 'express';
11
35
  import WebSocket, { WebSocketServer } from 'ws';
36
+ // NodeStorage and AnsiLogger modules
12
37
  import { NodeStorageManager } from 'node-persist-manager';
13
38
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
39
+ // Matterbridge
14
40
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
15
41
  import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
16
42
  import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
17
43
  import { PluginManager } from './pluginManager.js';
18
44
  import { DeviceManager } from './deviceManager.js';
19
45
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
46
+ // @matter
20
47
  import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer } from '@matter/main';
21
48
  import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
22
49
  import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
23
50
  import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
51
+ // @project-chip
24
52
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
25
53
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
54
+ // Default colors
26
55
  const plg = '\u001B[38;5;33m';
27
56
  const dev = '\u001B[38;5;79m';
28
57
  const typ = '\u001B[38;5;207m';
58
+ /**
59
+ * Represents the Matterbridge application.
60
+ */
29
61
  export class Matterbridge extends EventEmitter {
30
62
  systemInformation = {
31
63
  interfaceName: '',
@@ -61,7 +93,7 @@ export class Matterbridge extends EventEmitter {
61
93
  restartMode: '',
62
94
  edge: hasParameter('edge'),
63
95
  profile: getParameter('profile'),
64
- loggerLevel: "info",
96
+ loggerLevel: "info" /* LogLevel.INFO */,
65
97
  fileLogger: false,
66
98
  matterLoggerLevel: MatterLogLevel.INFO,
67
99
  matterFileLogger: false,
@@ -100,6 +132,7 @@ export class Matterbridge extends EventEmitter {
100
132
  nodeContext;
101
133
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
102
134
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
135
+ // Cleanup
103
136
  hasCleanupStarted = false;
104
137
  initialized = false;
105
138
  execRunningCount = 0;
@@ -111,16 +144,18 @@ export class Matterbridge extends EventEmitter {
111
144
  sigtermHandler;
112
145
  exceptionHandler;
113
146
  rejectionHandler;
147
+ // Frontend
114
148
  expressApp;
115
149
  httpServer;
116
150
  httpsServer;
117
151
  webSocketServer;
118
- mdnsInterface;
119
- ipv4address;
120
- ipv6address;
121
- port = 5540;
122
- passcode;
123
- discriminator;
152
+ // Matter
153
+ mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
154
+ ipv4address; // matter commissioning server listeningAddressIpv4
155
+ ipv6address; // matter commissioning server listeningAddressIpv6
156
+ port = 5540; // first commissioning server port
157
+ passcode; // first commissioning server passcode
158
+ discriminator; // first commissioning server discriminator
124
159
  storageManager;
125
160
  matterbridgeContext;
126
161
  mattercontrollerContext;
@@ -131,13 +166,26 @@ export class Matterbridge extends EventEmitter {
131
166
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
132
167
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
133
168
  static instance;
169
+ // We load asyncronously so is private
134
170
  constructor() {
135
171
  super();
172
+ // Bind the handler to the instance
136
173
  this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
137
174
  }
138
175
  matterbridgeMessageHandler;
176
+ /** ***********************************************************************************************************************************/
177
+ /** loadInstance() and cleanup() methods */
178
+ /** ***********************************************************************************************************************************/
179
+ /**
180
+ * Loads an instance of the Matterbridge class.
181
+ * If an instance already exists, return that instance.
182
+ *
183
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
184
+ * @returns The loaded Matterbridge instance.
185
+ */
139
186
  static async loadInstance(initialize = false) {
140
187
  if (!Matterbridge.instance) {
188
+ // eslint-disable-next-line no-console
141
189
  if (hasParameter('debug'))
142
190
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
143
191
  Matterbridge.instance = new Matterbridge();
@@ -146,6 +194,11 @@ export class Matterbridge extends EventEmitter {
146
194
  }
147
195
  return Matterbridge.instance;
148
196
  }
197
+ /**
198
+ * Call cleanup().
199
+ * @deprecated This method is deprecated and is only used for jest tests.
200
+ *
201
+ */
149
202
  async destroyInstance() {
150
203
  await this.cleanup('destroying instance...', false);
151
204
  await waiter('destroying instance...', () => {
@@ -153,39 +206,60 @@ export class Matterbridge extends EventEmitter {
153
206
  }, false, 60000, 100, false);
154
207
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
155
208
  }
209
+ /**
210
+ * Initializes the Matterbridge application.
211
+ *
212
+ * @remarks
213
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
214
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
215
+ * node version, registers signal handlers, initializes storage, and parses the command line.
216
+ *
217
+ * @returns A Promise that resolves when the initialization is complete.
218
+ */
156
219
  async initialize() {
220
+ // Set the restart mode
157
221
  if (hasParameter('service'))
158
222
  this.restartMode = 'service';
159
223
  if (hasParameter('docker'))
160
224
  this.restartMode = 'docker';
225
+ // Set the matterbridge directory
161
226
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
162
227
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
163
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
228
+ // Create matterbridge logger
229
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
230
+ // Initialize nodeStorage and nodeContext
164
231
  try {
165
232
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
166
233
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
167
234
  this.log.debug('Creating node storage context for matterbridge');
168
235
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
236
+ // TODO: Remove this code when node-persist-manager is updated
237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
238
  const keys = (await this.nodeStorage?.storage.keys());
170
239
  for (const key of keys) {
171
240
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
242
  await this.nodeStorage?.storage.get(key);
173
243
  }
174
244
  const storages = await this.nodeStorage.getStorageNames();
175
245
  for (const storage of storages) {
176
246
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
177
247
  const nodeContext = await this.nodeStorage?.createStorage(storage);
248
+ // TODO: Remove this code when node-persist-manager is updated
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
250
  const keys = (await nodeContext?.storage.keys());
179
251
  keys.forEach(async (key) => {
180
252
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
181
253
  await nodeContext?.get(key);
182
254
  });
183
255
  }
256
+ // Creating a backup of the node storage since it is not corrupted
184
257
  this.log.debug('Creating node storage backup...');
185
258
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
186
259
  this.log.debug('Created node storage backup');
187
260
  }
188
261
  catch (error) {
262
+ // Restoring the backup of the node storage since it is corrupted
189
263
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
190
264
  if (hasParameter('norestore')) {
191
265
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -200,44 +274,50 @@ export class Matterbridge extends EventEmitter {
200
274
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
201
275
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
202
276
  }
277
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
203
278
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
279
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
204
280
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
281
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
205
282
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
283
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
206
284
  if (hasParameter('logger')) {
207
285
  const level = getParameter('logger');
208
286
  if (level === 'debug') {
209
- this.log.logLevel = "debug";
287
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
210
288
  }
211
289
  else if (level === 'info') {
212
- this.log.logLevel = "info";
290
+ this.log.logLevel = "info" /* LogLevel.INFO */;
213
291
  }
214
292
  else if (level === 'notice') {
215
- this.log.logLevel = "notice";
293
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
216
294
  }
217
295
  else if (level === 'warn') {
218
- this.log.logLevel = "warn";
296
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
219
297
  }
220
298
  else if (level === 'error') {
221
- this.log.logLevel = "error";
299
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
222
300
  }
223
301
  else if (level === 'fatal') {
224
- this.log.logLevel = "fatal";
302
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
225
303
  }
226
304
  else {
227
305
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
228
- this.log.logLevel = "info";
306
+ this.log.logLevel = "info" /* LogLevel.INFO */;
229
307
  }
230
308
  }
231
309
  else {
232
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
310
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
233
311
  }
234
312
  MatterbridgeDevice.logLevel = this.log.logLevel;
313
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
235
314
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
236
315
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
237
316
  this.matterbridgeInformation.fileLogger = true;
238
317
  }
239
318
  this.log.notice('Matterbridge is starting...');
240
319
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
320
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
241
321
  if (hasParameter('matterlogger')) {
242
322
  const level = getParameter('matterlogger');
243
323
  if (level === 'debug') {
@@ -268,6 +348,7 @@ export class Matterbridge extends EventEmitter {
268
348
  }
269
349
  Logger.format = MatterLogFormat.ANSI;
270
350
  Logger.setLogger('default', this.createMatterLogger());
351
+ // Create the file logger for matter.js (context: matterFileLog)
271
352
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
272
353
  this.matterbridgeInformation.matterFileLogger = true;
273
354
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -276,6 +357,7 @@ export class Matterbridge extends EventEmitter {
276
357
  });
277
358
  }
278
359
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
360
+ // Set the interface to use for the matter server mdnsInterface
279
361
  if (hasParameter('mdnsinterface')) {
280
362
  this.mdnsInterface = getParameter('mdnsinterface');
281
363
  }
@@ -284,6 +366,7 @@ export class Matterbridge extends EventEmitter {
284
366
  if (this.mdnsInterface === '')
285
367
  this.mdnsInterface = undefined;
286
368
  }
369
+ // Set the listeningAddressIpv4 for the matter commissioning server
287
370
  if (hasParameter('ipv4address')) {
288
371
  this.ipv4address = getParameter('ipv4address');
289
372
  }
@@ -292,6 +375,7 @@ export class Matterbridge extends EventEmitter {
292
375
  if (this.ipv4address === '')
293
376
  this.ipv4address = undefined;
294
377
  }
378
+ // Set the listeningAddressIpv6 for the matter commissioning server
295
379
  if (hasParameter('ipv6address')) {
296
380
  this.ipv6address = getParameter('ipv6address');
297
381
  }
@@ -300,17 +384,23 @@ export class Matterbridge extends EventEmitter {
300
384
  if (this.ipv6address === '')
301
385
  this.ipv6address = undefined;
302
386
  }
387
+ // Initialize PluginManager
303
388
  this.plugins = new PluginManager(this);
304
389
  await this.plugins.loadFromStorage();
390
+ // Initialize DeviceManager
305
391
  this.devices = new DeviceManager(this, this.nodeContext);
392
+ // Get the plugins from node storage and create the plugins node storage contexts
306
393
  for (const plugin of this.plugins) {
307
394
  const packageJson = await this.plugins.parse(plugin);
308
395
  if (packageJson === null && !hasParameter('add')) {
396
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
397
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
309
398
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
310
399
  try {
311
400
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
312
401
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
313
402
  plugin.error = false;
403
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
314
404
  }
315
405
  catch (error) {
316
406
  plugin.error = true;
@@ -327,6 +417,7 @@ export class Matterbridge extends EventEmitter {
327
417
  await plugin.nodeContext.set('description', plugin.description);
328
418
  await plugin.nodeContext.set('author', plugin.author);
329
419
  }
420
+ // Log system info and create .matterbridge directory
330
421
  await this.logNodeAndSystemInfo();
331
422
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
332
423
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -334,6 +425,7 @@ export class Matterbridge extends EventEmitter {
334
425
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
335
426
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
336
427
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
428
+ // Check node version and throw error
337
429
  const minNodeVersion = 18;
338
430
  const nodeVersion = process.versions.node;
339
431
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -341,10 +433,17 @@ export class Matterbridge extends EventEmitter {
341
433
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
342
434
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
343
435
  }
436
+ // Register process handlers
344
437
  this.registerProcessHandlers();
438
+ // Parse command line
345
439
  await this.parseCommandLine();
346
440
  this.initialized = true;
347
441
  }
442
+ /**
443
+ * Parses the command line arguments and performs the corresponding actions.
444
+ * @private
445
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
446
+ */
348
447
  async parseCommandLine() {
349
448
  if (hasParameter('help')) {
350
449
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -452,12 +551,14 @@ export class Matterbridge extends EventEmitter {
452
551
  }
453
552
  if (hasParameter('factoryreset')) {
454
553
  try {
554
+ // Delete matter storage file
455
555
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
456
556
  }
457
557
  catch (err) {
458
558
  this.log.error(`Error deleting storage: ${err}`);
459
559
  }
460
560
  try {
561
+ // Delete node storage directory with its subdirectories
461
562
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
462
563
  }
463
564
  catch (err) {
@@ -471,6 +572,7 @@ export class Matterbridge extends EventEmitter {
471
572
  this.emit('shutdown');
472
573
  return;
473
574
  }
575
+ // Start the matter storage and create the matterbridge context
474
576
  try {
475
577
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
476
578
  }
@@ -505,28 +607,34 @@ export class Matterbridge extends EventEmitter {
505
607
  this.emit('shutdown');
506
608
  return;
507
609
  }
610
+ // Initialize frontend
508
611
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
509
612
  await this.initializeFrontend(getIntParameter('frontend'));
613
+ // Check each 60 minutes the latest versions
510
614
  this.checkUpdateInterval = setInterval(() => {
511
615
  this.getMatterbridgeLatestVersion();
512
616
  for (const plugin of this.plugins) {
513
617
  this.getPluginLatestVersion(plugin);
514
618
  }
515
619
  }, 60 * 60 * 1000);
620
+ // Start the matterbridge in mode test
516
621
  if (hasParameter('test')) {
517
622
  this.bridgeMode = 'bridge';
518
623
  MatterbridgeDevice.bridgeMode = 'bridge';
519
624
  return;
520
625
  }
626
+ // Start the matterbridge in mode controller
521
627
  if (hasParameter('controller')) {
522
628
  this.bridgeMode = 'controller';
523
629
  await this.startController();
524
630
  return;
525
631
  }
632
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
526
633
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
527
634
  this.log.info('Setting default matterbridge start mode to bridge');
528
635
  await this.nodeContext?.set('bridgeMode', 'bridge');
529
636
  }
637
+ // Start matterbridge in bridge mode
530
638
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
531
639
  this.bridgeMode = 'bridge';
532
640
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -535,6 +643,7 @@ export class Matterbridge extends EventEmitter {
535
643
  await this.startBridge();
536
644
  return;
537
645
  }
646
+ // Start matterbridge in childbridge mode
538
647
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
539
648
  this.bridgeMode = 'childbridge';
540
649
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -544,17 +653,28 @@ export class Matterbridge extends EventEmitter {
544
653
  return;
545
654
  }
546
655
  }
656
+ /**
657
+ * Asynchronously loads and starts the registered plugins.
658
+ *
659
+ * This method is responsible for initializing and staarting all enabled plugins.
660
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
661
+ *
662
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
663
+ */
547
664
  async startPlugins() {
665
+ // Check, load and start the plugins
548
666
  for (const plugin of this.plugins) {
549
667
  plugin.configJson = await this.plugins.loadConfig(plugin);
550
668
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
669
+ // Check if the plugin is available
551
670
  if (!(await this.plugins.resolve(plugin.path))) {
552
671
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
553
672
  plugin.enabled = false;
554
673
  plugin.error = true;
555
674
  continue;
556
675
  }
557
- this.getPluginLatestVersion(plugin);
676
+ // Check if the plugin has a new version
677
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
558
678
  if (!plugin.enabled) {
559
679
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
560
680
  continue;
@@ -569,20 +689,26 @@ export class Matterbridge extends EventEmitter {
569
689
  plugin.addedDevices = undefined;
570
690
  plugin.qrPairingCode = undefined;
571
691
  plugin.manualPairingCode = undefined;
572
- this.plugins.load(plugin, true, 'Matterbridge is starting');
692
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
573
693
  }
574
694
  this.wssSendRefreshRequired();
575
695
  }
696
+ /**
697
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
698
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
699
+ */
576
700
  registerProcessHandlers() {
577
701
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
578
702
  process.removeAllListeners('uncaughtException');
579
703
  process.removeAllListeners('unhandledRejection');
580
704
  this.exceptionHandler = async (error) => {
581
705
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
706
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
582
707
  };
583
708
  process.on('uncaughtException', this.exceptionHandler);
584
709
  this.rejectionHandler = async (reason, promise) => {
585
710
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
711
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
586
712
  };
587
713
  process.on('unhandledRejection', this.rejectionHandler);
588
714
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -595,6 +721,9 @@ export class Matterbridge extends EventEmitter {
595
721
  };
596
722
  process.on('SIGTERM', this.sigtermHandler);
597
723
  }
724
+ /**
725
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
726
+ */
598
727
  deregisterProcesslHandlers() {
599
728
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
600
729
  if (this.exceptionHandler)
@@ -611,7 +740,11 @@ export class Matterbridge extends EventEmitter {
611
740
  process.off('SIGTERM', this.sigtermHandler);
612
741
  this.sigtermHandler = undefined;
613
742
  }
743
+ /**
744
+ * Logs the node and system information.
745
+ */
614
746
  async logNodeAndSystemInfo() {
747
+ // IP address information
615
748
  const networkInterfaces = os.networkInterfaces();
616
749
  this.systemInformation.ipv4Address = '';
617
750
  this.systemInformation.ipv6Address = '';
@@ -631,7 +764,7 @@ export class Matterbridge extends EventEmitter {
631
764
  this.systemInformation.macAddress = detail.mac;
632
765
  }
633
766
  }
634
- if (this.systemInformation.ipv4Address !== '') {
767
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
635
768
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
636
769
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
637
770
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -639,19 +772,22 @@ export class Matterbridge extends EventEmitter {
639
772
  break;
640
773
  }
641
774
  }
775
+ // Node information
642
776
  this.systemInformation.nodeVersion = process.versions.node;
643
777
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
644
778
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
645
779
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
780
+ // Host system information
646
781
  this.systemInformation.hostname = os.hostname();
647
782
  this.systemInformation.user = os.userInfo().username;
648
- this.systemInformation.osType = os.type();
649
- this.systemInformation.osRelease = os.release();
650
- this.systemInformation.osPlatform = os.platform();
651
- this.systemInformation.osArch = os.arch();
652
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
653
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
654
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
783
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
784
+ this.systemInformation.osRelease = os.release(); // Kernel version
785
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
786
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
787
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
788
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
789
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
790
+ // Log the system information
655
791
  this.log.debug('Host System Information:');
656
792
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
657
793
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -667,15 +803,19 @@ export class Matterbridge extends EventEmitter {
667
803
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
668
804
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
669
805
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
806
+ // Home directory
670
807
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
671
808
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
672
809
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
810
+ // Package root directory
673
811
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
674
812
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
675
813
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
676
814
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
815
+ // Global node_modules directory
677
816
  if (this.nodeContext)
678
817
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
818
+ // First run of Matterbridge so the node storage is empty
679
819
  if (this.globalModulesDirectory === '') {
680
820
  try {
681
821
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -699,6 +839,7 @@ export class Matterbridge extends EventEmitter {
699
839
  this.log.error(`Error getting global node_modules directory: ${error}`);
700
840
  });
701
841
  }
842
+ // Create the data directory .matterbridge in the home directory
702
843
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
703
844
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
704
845
  try {
@@ -722,6 +863,7 @@ export class Matterbridge extends EventEmitter {
722
863
  }
723
864
  }
724
865
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
866
+ // Create the plugin directory Matterbridge in the home directory
725
867
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
726
868
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
727
869
  try {
@@ -745,19 +887,28 @@ export class Matterbridge extends EventEmitter {
745
887
  }
746
888
  }
747
889
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
890
+ // Matterbridge version
748
891
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
749
892
  this.matterbridgeVersion = packageJson.version;
750
893
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
751
894
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
895
+ // Matterbridge latest version
752
896
  if (this.nodeContext)
753
897
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
754
898
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
755
899
  this.getMatterbridgeLatestVersion();
900
+ // Current working directory
756
901
  const currentDir = process.cwd();
757
902
  this.log.debug(`Current Working Directory: ${currentDir}`);
903
+ // Command line arguments (excluding 'node' and the script name)
758
904
  const cmdArgs = process.argv.slice(2).join(' ');
759
905
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
760
906
  }
907
+ /**
908
+ * Retrieves the latest version of a package from the npm registry.
909
+ * @param packageName - The name of the package.
910
+ * @returns A Promise that resolves to the latest version of the package.
911
+ */
761
912
  async getLatestVersion(packageName) {
762
913
  return new Promise((resolve, reject) => {
763
914
  this.execRunningCount++;
@@ -772,6 +923,10 @@ export class Matterbridge extends EventEmitter {
772
923
  });
773
924
  });
774
925
  }
926
+ /**
927
+ * Retrieves the path to the global Node.js modules directory.
928
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
929
+ */
775
930
  async getGlobalNodeModules() {
776
931
  return new Promise((resolve, reject) => {
777
932
  this.execRunningCount++;
@@ -786,6 +941,11 @@ export class Matterbridge extends EventEmitter {
786
941
  });
787
942
  });
788
943
  }
944
+ /**
945
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
946
+ * @private
947
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
948
+ */
789
949
  async getMatterbridgeLatestVersion() {
790
950
  this.getLatestVersion('matterbridge')
791
951
  .then(async (matterbridgeLatestVersion) => {
@@ -802,8 +962,19 @@ export class Matterbridge extends EventEmitter {
802
962
  })
803
963
  .catch((error) => {
804
964
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
965
+ // error.stack && this.log.debug(error.stack);
805
966
  });
806
967
  }
968
+ /**
969
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
970
+ * If the plugin's version is different from the latest version, logs a warning message.
971
+ * If the plugin's version is the same as the latest version, logs an info message.
972
+ * If there is an error retrieving the latest version, logs an error message.
973
+ *
974
+ * @private
975
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
976
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
977
+ */
807
978
  async getPluginLatestVersion(plugin) {
808
979
  this.getLatestVersion(plugin.name)
809
980
  .then(async (latestVersion) => {
@@ -815,40 +986,54 @@ export class Matterbridge extends EventEmitter {
815
986
  })
816
987
  .catch((error) => {
817
988
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
989
+ // error.stack && this.log.debug(error.stack);
818
990
  });
819
991
  }
992
+ /**
993
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
994
+ *
995
+ * @returns {Function} The MatterLogger function.
996
+ */
820
997
  createMatterLogger() {
821
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
998
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
822
999
  return (_level, formattedLog) => {
823
1000
  const logger = formattedLog.slice(44, 44 + 20).trim();
824
1001
  const message = formattedLog.slice(65);
825
1002
  matterLogger.logName = logger;
826
1003
  switch (_level) {
827
1004
  case MatterLogLevel.DEBUG:
828
- matterLogger.log("debug", message);
1005
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
829
1006
  break;
830
1007
  case MatterLogLevel.INFO:
831
- matterLogger.log("info", message);
1008
+ matterLogger.log("info" /* LogLevel.INFO */, message);
832
1009
  break;
833
1010
  case MatterLogLevel.NOTICE:
834
- matterLogger.log("notice", message);
1011
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
835
1012
  break;
836
1013
  case MatterLogLevel.WARN:
837
- matterLogger.log("warn", message);
1014
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
838
1015
  break;
839
1016
  case MatterLogLevel.ERROR:
840
- matterLogger.log("error", message);
1017
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
841
1018
  break;
842
1019
  case MatterLogLevel.FATAL:
843
- matterLogger.log("fatal", message);
1020
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
844
1021
  break;
845
1022
  default:
846
- matterLogger.log("debug", message);
1023
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
847
1024
  break;
848
1025
  }
849
1026
  };
850
1027
  }
1028
+ /**
1029
+ * Creates a Matter File Logger.
1030
+ *
1031
+ * @param {string} filePath - The path to the log file.
1032
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1033
+ * @returns {Function} - A function that logs formatted messages to the log file.
1034
+ */
851
1035
  async createMatterFileLogger(filePath, unlink = false) {
1036
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
852
1037
  let fileSize = 0;
853
1038
  if (unlink) {
854
1039
  try {
@@ -897,53 +1082,83 @@ export class Matterbridge extends EventEmitter {
897
1082
  }
898
1083
  };
899
1084
  }
1085
+ /**
1086
+ * Update matterbridge and cleanup.
1087
+ */
900
1088
  async updateProcess() {
901
1089
  await this.cleanup('updating...', false);
902
1090
  }
1091
+ /**
1092
+ * Restarts the process by spawning a new process and exiting the current process.
1093
+ */
903
1094
  async restartProcess() {
904
1095
  await this.cleanup('restarting...', true);
905
1096
  }
1097
+ /**
1098
+ * Shut down the process by exiting the current process.
1099
+ */
906
1100
  async shutdownProcess() {
907
1101
  await this.cleanup('shutting down...', false);
908
1102
  }
1103
+ /**
1104
+ * Shut down the process and reset.
1105
+ */
909
1106
  async unregisterAndShutdownProcess() {
910
1107
  this.log.info('Unregistering all devices and shutting down...');
911
- for (const plugin of this.plugins) {
1108
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
912
1109
  await this.removeAllBridgedDevices(plugin.name);
913
1110
  }
914
1111
  await this.cleanup('unregistered all devices and shutting down...', false);
915
1112
  }
1113
+ /**
1114
+ * Shut down the process and reset.
1115
+ */
916
1116
  async shutdownProcessAndReset() {
917
1117
  await this.cleanup('shutting down with reset...', false);
918
1118
  }
1119
+ /**
1120
+ * Shut down the process and factory reset.
1121
+ */
919
1122
  async shutdownProcessAndFactoryReset() {
920
1123
  await this.cleanup('shutting down with factory reset...', false);
921
1124
  }
1125
+ /**
1126
+ * Cleans up the Matterbridge instance.
1127
+ * @param message - The cleanup message.
1128
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1129
+ * @returns A promise that resolves when the cleanup is completed.
1130
+ */
922
1131
  async cleanup(message, restart = false) {
923
1132
  if (this.initialized && !this.hasCleanupStarted) {
924
1133
  this.hasCleanupStarted = true;
925
1134
  this.log.info(message);
1135
+ // Deregisters the process handlers
926
1136
  this.deregisterProcesslHandlers();
1137
+ // Clear the start matter interval
927
1138
  if (this.startMatterInterval) {
928
1139
  clearInterval(this.startMatterInterval);
929
1140
  this.startMatterInterval = undefined;
930
1141
  this.log.debug('Start matter interval cleared');
931
1142
  }
1143
+ // Clear the check update interval
932
1144
  if (this.checkUpdateInterval) {
933
1145
  clearInterval(this.checkUpdateInterval);
934
1146
  this.checkUpdateInterval = undefined;
935
1147
  this.log.debug('Check update interval cleared');
936
1148
  }
1149
+ // Clear the configure timeout
937
1150
  if (this.configureTimeout) {
938
1151
  clearTimeout(this.configureTimeout);
939
1152
  this.configureTimeout = undefined;
940
1153
  this.log.debug('Matterbridge configure timeout cleared');
941
1154
  }
1155
+ // Clear the reachability timeout
942
1156
  if (this.reachabilityTimeout) {
943
1157
  clearTimeout(this.reachabilityTimeout);
944
1158
  this.reachabilityTimeout = undefined;
945
1159
  this.log.debug('Matterbridge reachability timeout cleared');
946
1160
  }
1161
+ // Calling the shutdown method of each plugin and clear the reachability timeout
947
1162
  for (const plugin of this.plugins) {
948
1163
  if (!plugin.enabled || plugin.error)
949
1164
  continue;
@@ -954,24 +1169,29 @@ export class Matterbridge extends EventEmitter {
954
1169
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
955
1170
  }
956
1171
  }
1172
+ // Close the http server
957
1173
  if (this.httpServer) {
958
1174
  this.httpServer.close();
959
1175
  this.httpServer.removeAllListeners();
960
1176
  this.httpServer = undefined;
961
1177
  this.log.debug('Frontend http server closed successfully');
962
1178
  }
1179
+ // Close the https server
963
1180
  if (this.httpsServer) {
964
1181
  this.httpsServer.close();
965
1182
  this.httpsServer.removeAllListeners();
966
1183
  this.httpsServer = undefined;
967
1184
  this.log.debug('Frontend https server closed successfully');
968
1185
  }
1186
+ // Remove listeners from the express app
969
1187
  if (this.expressApp) {
970
1188
  this.expressApp.removeAllListeners();
971
1189
  this.expressApp = undefined;
972
1190
  this.log.debug('Frontend app closed successfully');
973
1191
  }
1192
+ // Close the WebSocket server
974
1193
  if (this.webSocketServer) {
1194
+ // Close all active connections
975
1195
  this.webSocketServer.clients.forEach((client) => {
976
1196
  if (client.readyState === WebSocket.OPEN) {
977
1197
  client.close();
@@ -987,26 +1207,35 @@ export class Matterbridge extends EventEmitter {
987
1207
  });
988
1208
  this.webSocketServer = undefined;
989
1209
  }
1210
+ // Closing matter
990
1211
  await this.stopMatterServer();
1212
+ // Closing matter storage
991
1213
  await this.stopMatterStorage();
1214
+ // Remove the matterfilelogger
992
1215
  try {
993
1216
  Logger.removeLogger('matterfilelogger');
1217
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
994
1218
  }
995
1219
  catch (error) {
1220
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
996
1221
  }
1222
+ // Serialize registeredDevices
997
1223
  if (this.nodeStorage && this.nodeContext) {
998
1224
  this.log.info('Saving registered devices...');
999
1225
  const serializedRegisteredDevices = [];
1000
1226
  this.devices.forEach(async (device) => {
1001
1227
  const serializedMatterbridgeDevice = device.serialize();
1228
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1002
1229
  if (serializedMatterbridgeDevice)
1003
1230
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1004
1231
  });
1005
1232
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1006
1233
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1234
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1007
1235
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1008
1236
  await this.nodeContext.close();
1009
1237
  this.nodeContext = undefined;
1238
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1010
1239
  for (const plugin of this.plugins) {
1011
1240
  if (plugin.nodeContext) {
1012
1241
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1037,13 +1266,16 @@ export class Matterbridge extends EventEmitter {
1037
1266
  }
1038
1267
  else {
1039
1268
  if (message === 'shutting down with reset...') {
1269
+ // Delete matter storage file
1040
1270
  this.log.info('Resetting Matterbridge commissioning information...');
1041
1271
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1042
1272
  this.log.info('Reset done! Remove all paired devices from the controllers.');
1043
1273
  }
1044
1274
  if (message === 'shutting down with factory reset...') {
1275
+ // Delete matter storage file
1045
1276
  this.log.info('Resetting Matterbridge commissioning information...');
1046
1277
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1278
+ // Delete node storage directory with its subdirectories
1047
1279
  this.log.info('Resetting Matterbridge storage...');
1048
1280
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1049
1281
  this.log.info('Factory reset done! Remove all paired devices from the controllers.');
@@ -1056,19 +1288,33 @@ export class Matterbridge extends EventEmitter {
1056
1288
  this.initialized = false;
1057
1289
  }
1058
1290
  }
1291
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1059
1292
  async addBridgedEndpoint(pluginName, device) {
1293
+ // Nothing to do here
1060
1294
  }
1295
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1061
1296
  async removeBridgedEndpoint(pluginName, device) {
1297
+ // Nothing to do here
1062
1298
  }
1299
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1063
1300
  async removeAllBridgedEndpoints(pluginName) {
1301
+ // Nothing to do here
1064
1302
  }
1303
+ /**
1304
+ * Adds a bridged device to the Matterbridge.
1305
+ * @param pluginName - The name of the plugin.
1306
+ * @param device - The bridged device to add.
1307
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1308
+ */
1065
1309
  async addBridgedDevice(pluginName, device) {
1066
1310
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1311
+ // Check if the plugin is registered
1067
1312
  const plugin = this.plugins.get(pluginName);
1068
1313
  if (!plugin) {
1069
1314
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1070
1315
  return;
1071
1316
  }
1317
+ // Register and add the device to matterbridge aggregator in bridge mode
1072
1318
  if (this.bridgeMode === 'bridge') {
1073
1319
  if (!this.matterAggregator) {
1074
1320
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1076,8 +1322,11 @@ export class Matterbridge extends EventEmitter {
1076
1322
  }
1077
1323
  this.matterAggregator.addBridgedDevice(device);
1078
1324
  }
1325
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1326
+ // Register and add the device in childbridge mode
1079
1327
  if (this.bridgeMode === 'childbridge') {
1080
1328
  if (plugin.type === 'AccessoryPlatform') {
1329
+ // Check if the plugin is locked with the commissioning server
1081
1330
  if (!plugin.locked) {
1082
1331
  plugin.locked = true;
1083
1332
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1091,6 +1340,7 @@ export class Matterbridge extends EventEmitter {
1091
1340
  }
1092
1341
  }
1093
1342
  if (plugin.type === 'DynamicPlatform') {
1343
+ // Check if the plugin is locked with the commissioning server and the aggregator
1094
1344
  if (!plugin.locked) {
1095
1345
  plugin.locked = true;
1096
1346
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1098,7 +1348,7 @@ export class Matterbridge extends EventEmitter {
1098
1348
  this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1099
1349
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1100
1350
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1101
- plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
1351
+ plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name); // Generate serialNumber and uniqueId
1102
1352
  this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1103
1353
  plugin.commissioningServer.addDevice(plugin.aggregator);
1104
1354
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
@@ -1111,16 +1361,25 @@ export class Matterbridge extends EventEmitter {
1111
1361
  plugin.registeredDevices++;
1112
1362
  if (plugin.addedDevices !== undefined)
1113
1363
  plugin.addedDevices++;
1364
+ // Add the device to the DeviceManager
1114
1365
  this.devices.set(device);
1115
1366
  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}`);
1116
1367
  }
1368
+ /**
1369
+ * Removes a bridged device from the Matterbridge.
1370
+ * @param pluginName - The name of the plugin.
1371
+ * @param device - The device to be removed.
1372
+ * @returns A Promise that resolves when the device is successfully removed.
1373
+ */
1117
1374
  async removeBridgedDevice(pluginName, device) {
1118
1375
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1376
+ // Check if the plugin is registered
1119
1377
  const plugin = this.plugins.get(pluginName);
1120
1378
  if (!plugin) {
1121
1379
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1122
1380
  return;
1123
1381
  }
1382
+ // Remove the device from matterbridge aggregator in bridge mode
1124
1383
  if (this.bridgeMode === 'bridge') {
1125
1384
  if (!this.matterAggregator) {
1126
1385
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1130,6 +1389,8 @@ export class Matterbridge extends EventEmitter {
1130
1389
  device.setBridgedDeviceReachability(false);
1131
1390
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1132
1391
  }
1392
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1393
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1133
1394
  this.matterAggregator?.removeBridgedDevice(device);
1134
1395
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1135
1396
  if (plugin.registeredDevices !== undefined)
@@ -1137,6 +1398,7 @@ export class Matterbridge extends EventEmitter {
1137
1398
  if (plugin.addedDevices !== undefined)
1138
1399
  plugin.addedDevices--;
1139
1400
  }
1401
+ // Remove the device in childbridge mode
1140
1402
  if (this.bridgeMode === 'childbridge') {
1141
1403
  if (plugin.type === 'AccessoryPlatform') {
1142
1404
  if (!plugin.commissioningServer) {
@@ -1160,14 +1422,22 @@ export class Matterbridge extends EventEmitter {
1160
1422
  plugin.registeredDevices--;
1161
1423
  if (plugin.addedDevices !== undefined)
1162
1424
  plugin.addedDevices--;
1425
+ // Remove the commissioning server
1163
1426
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1164
1427
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1165
1428
  plugin.commissioningServer = undefined;
1166
1429
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1167
1430
  }
1168
1431
  }
1432
+ // Remove the device from the DeviceManager
1169
1433
  this.devices.remove(device);
1170
1434
  }
1435
+ /**
1436
+ * Removes all bridged devices associated with a specific plugin.
1437
+ *
1438
+ * @param pluginName - The name of the plugin.
1439
+ * @returns A promise that resolves when all devices have been removed.
1440
+ */
1171
1441
  async removeAllBridgedDevices(pluginName) {
1172
1442
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1173
1443
  this.devices.forEach(async (device) => {
@@ -1176,7 +1446,13 @@ export class Matterbridge extends EventEmitter {
1176
1446
  }
1177
1447
  });
1178
1448
  }
1449
+ /**
1450
+ * Starts the Matterbridge in bridge mode.
1451
+ * @private
1452
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1453
+ */
1179
1454
  async startBridge() {
1455
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1180
1456
  if (!this.storageManager)
1181
1457
  throw new Error('No storage manager initialized');
1182
1458
  if (!this.matterbridgeContext)
@@ -1195,6 +1471,7 @@ export class Matterbridge extends EventEmitter {
1195
1471
  let failCount = 0;
1196
1472
  this.startMatterInterval = setInterval(async () => {
1197
1473
  for (const plugin of this.plugins) {
1474
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1198
1475
  if (!plugin.enabled)
1199
1476
  continue;
1200
1477
  if (plugin.error) {
@@ -1219,15 +1496,18 @@ export class Matterbridge extends EventEmitter {
1219
1496
  clearInterval(this.startMatterInterval);
1220
1497
  this.startMatterInterval = undefined;
1221
1498
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1499
+ // Start the Matter server
1222
1500
  await this.startMatterServer();
1223
1501
  this.log.notice('Matter server started');
1502
+ // Show the QR code for commissioning or log the already commissioned message
1224
1503
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1504
+ // Configure the plugins
1225
1505
  this.configureTimeout = setTimeout(async () => {
1226
1506
  for (const plugin of this.plugins) {
1227
1507
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1228
1508
  continue;
1229
1509
  try {
1230
- await this.plugins.configure(plugin);
1510
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1231
1511
  }
1232
1512
  catch (error) {
1233
1513
  plugin.error = true;
@@ -1236,6 +1516,7 @@ export class Matterbridge extends EventEmitter {
1236
1516
  }
1237
1517
  this.wssSendRefreshRequired();
1238
1518
  }, 30 * 1000);
1519
+ // Setting reachability to true
1239
1520
  this.reachabilityTimeout = setTimeout(() => {
1240
1521
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1241
1522
  if (this.commissioningServer)
@@ -1245,7 +1526,14 @@ export class Matterbridge extends EventEmitter {
1245
1526
  }, 60 * 1000);
1246
1527
  }, 1000);
1247
1528
  }
1529
+ /**
1530
+ * Starts the Matterbridge in childbridge mode.
1531
+ * @private
1532
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1533
+ */
1248
1534
  async startChildbridge() {
1535
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1536
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1249
1537
  if (!this.storageManager)
1250
1538
  throw new Error('No storage manager initialized');
1251
1539
  this.matterServer = this.createMatterServer(this.storageManager);
@@ -1255,6 +1543,7 @@ export class Matterbridge extends EventEmitter {
1255
1543
  this.startMatterInterval = setInterval(async () => {
1256
1544
  let allStarted = true;
1257
1545
  for (const plugin of this.plugins) {
1546
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1258
1547
  if (!plugin.enabled)
1259
1548
  continue;
1260
1549
  if (plugin.error) {
@@ -1282,14 +1571,16 @@ export class Matterbridge extends EventEmitter {
1282
1571
  clearInterval(this.startMatterInterval);
1283
1572
  this.startMatterInterval = undefined;
1284
1573
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1574
+ // Start the Matter server
1285
1575
  await this.startMatterServer();
1286
1576
  this.log.notice('Matter server started');
1577
+ // Configure the plugins
1287
1578
  this.configureTimeout = setTimeout(async () => {
1288
1579
  for (const plugin of this.plugins) {
1289
1580
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1290
1581
  continue;
1291
1582
  try {
1292
- await this.plugins.configure(plugin);
1583
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1293
1584
  }
1294
1585
  catch (error) {
1295
1586
  plugin.error = true;
@@ -1318,6 +1609,7 @@ export class Matterbridge extends EventEmitter {
1318
1609
  continue;
1319
1610
  }
1320
1611
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1612
+ // Setting reachability to true
1321
1613
  plugin.reachabilityTimeout = setTimeout(() => {
1322
1614
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1323
1615
  if (plugin.commissioningServer)
@@ -1330,6 +1622,11 @@ export class Matterbridge extends EventEmitter {
1330
1622
  }
1331
1623
  }, 1000);
1332
1624
  }
1625
+ /**
1626
+ * Starts the Matterbridge controller.
1627
+ * @private
1628
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1629
+ */
1333
1630
  async startController() {
1334
1631
  if (!this.storageManager) {
1335
1632
  this.log.error('No storage manager initialized');
@@ -1392,7 +1689,7 @@ export class Matterbridge extends EventEmitter {
1392
1689
  const nodeId = await this.commissioningController.commissionNode(options);
1393
1690
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1394
1691
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1395
- }
1692
+ } // (hasParameter('pairingcode'))
1396
1693
  if (hasParameter('unpairall')) {
1397
1694
  this.log.info('***Commissioning controller unpairing all nodes...');
1398
1695
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1403,6 +1700,8 @@ export class Matterbridge extends EventEmitter {
1403
1700
  return;
1404
1701
  }
1405
1702
  if (hasParameter('discover')) {
1703
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1704
+ // console.log(discover);
1406
1705
  }
1407
1706
  if (!this.commissioningController.isCommissioned()) {
1408
1707
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1443,10 +1742,12 @@ export class Matterbridge extends EventEmitter {
1443
1742
  },
1444
1743
  });
1445
1744
  node.logStructure();
1745
+ // Get the interaction client
1446
1746
  this.log.info('Getting the interaction client');
1447
1747
  const interactionClient = await node.getInteractionClient();
1448
1748
  let cluster;
1449
1749
  let attributes;
1750
+ // Log BasicInformationCluster
1450
1751
  cluster = BasicInformationCluster;
1451
1752
  attributes = await interactionClient.getMultipleAttributes({
1452
1753
  attributes: [{ clusterId: cluster.id }],
@@ -1456,6 +1757,7 @@ export class Matterbridge extends EventEmitter {
1456
1757
  attributes.forEach((attribute) => {
1457
1758
  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}`);
1458
1759
  });
1760
+ // Log PowerSourceCluster
1459
1761
  cluster = PowerSourceCluster;
1460
1762
  attributes = await interactionClient.getMultipleAttributes({
1461
1763
  attributes: [{ clusterId: cluster.id }],
@@ -1465,6 +1767,7 @@ export class Matterbridge extends EventEmitter {
1465
1767
  attributes.forEach((attribute) => {
1466
1768
  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}`);
1467
1769
  });
1770
+ // Log ThreadNetworkDiagnostics
1468
1771
  cluster = ThreadNetworkDiagnosticsCluster;
1469
1772
  attributes = await interactionClient.getMultipleAttributes({
1470
1773
  attributes: [{ clusterId: cluster.id }],
@@ -1474,6 +1777,7 @@ export class Matterbridge extends EventEmitter {
1474
1777
  attributes.forEach((attribute) => {
1475
1778
  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}`);
1476
1779
  });
1780
+ // Log SwitchCluster
1477
1781
  cluster = SwitchCluster;
1478
1782
  attributes = await interactionClient.getMultipleAttributes({
1479
1783
  attributes: [{ clusterId: cluster.id }],
@@ -1494,6 +1798,15 @@ export class Matterbridge extends EventEmitter {
1494
1798
  this.log.info('Subscribed to all attributes and events');
1495
1799
  }
1496
1800
  }
1801
+ /** ***********************************************************************************************************************************/
1802
+ /** Matter.js methods */
1803
+ /** ***********************************************************************************************************************************/
1804
+ /**
1805
+ * Starts the matter storage process based on the specified storage type and name.
1806
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1807
+ * @param {string} storageName - The name of the storage file.
1808
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1809
+ */
1497
1810
  async startMatterStorage(storageType, storageName) {
1498
1811
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1499
1812
  if (storageType === 'disk') {
@@ -1539,6 +1852,12 @@ export class Matterbridge extends EventEmitter {
1539
1852
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1540
1853
  this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1541
1854
  }
1855
+ /**
1856
+ * Makes a backup copy of the specified matter JSON storage file.
1857
+ *
1858
+ * @param storageName - The name of the JSON storage file to be backed up.
1859
+ * @param backupName - The name of the backup file to be created.
1860
+ */
1542
1861
  async backupMatterStorage(storageName, backupName) {
1543
1862
  try {
1544
1863
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1559,6 +1878,12 @@ export class Matterbridge extends EventEmitter {
1559
1878
  }
1560
1879
  }
1561
1880
  }
1881
+ /**
1882
+ * Restore the specified matter JSON storage file.
1883
+ *
1884
+ * @param backupName - The name of the backup file to restore from.
1885
+ * @param storageName - The name of the JSON storage file to restored.
1886
+ */
1562
1887
  async restoreMatterStorage(backupName, storageName) {
1563
1888
  try {
1564
1889
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1579,6 +1904,10 @@ export class Matterbridge extends EventEmitter {
1579
1904
  }
1580
1905
  }
1581
1906
  }
1907
+ /**
1908
+ * Stops the matter storage.
1909
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1910
+ */
1582
1911
  async stopMatterStorage() {
1583
1912
  this.log.debug('Stopping storage');
1584
1913
  await this.storageManager?.close();
@@ -1587,8 +1916,14 @@ export class Matterbridge extends EventEmitter {
1587
1916
  this.matterbridgeContext = undefined;
1588
1917
  this.mattercontrollerContext = undefined;
1589
1918
  }
1919
+ /**
1920
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
1921
+ * @param storageManager The storage manager to be used by the Matter server.
1922
+ *
1923
+ */
1590
1924
  createMatterServer(storageManager) {
1591
1925
  this.log.debug('Creating matter server');
1926
+ // Validate mdnsInterface
1592
1927
  if (this.mdnsInterface) {
1593
1928
  const networkInterfaces = os.networkInterfaces();
1594
1929
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1604,6 +1939,10 @@ export class Matterbridge extends EventEmitter {
1604
1939
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1605
1940
  return matterServer;
1606
1941
  }
1942
+ /**
1943
+ * Starts the Matter server.
1944
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1945
+ */
1607
1946
  async startMatterServer() {
1608
1947
  if (!this.matterServer) {
1609
1948
  this.log.error('No matter server initialized');
@@ -1613,7 +1952,11 @@ export class Matterbridge extends EventEmitter {
1613
1952
  this.log.debug('Starting matter server...');
1614
1953
  await this.matterServer.start();
1615
1954
  this.log.debug('Started matter server');
1955
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1616
1956
  }
1957
+ /**
1958
+ * Stops the Matter server, commissioningServer and commissioningController.
1959
+ */
1617
1960
  async stopMatterServer() {
1618
1961
  this.log.debug('Stopping matter commissioningServer');
1619
1962
  await this.commissioningServer?.close();
@@ -1627,22 +1970,78 @@ export class Matterbridge extends EventEmitter {
1627
1970
  this.matterAggregator = undefined;
1628
1971
  this.matterServer = undefined;
1629
1972
  }
1973
+ /**
1974
+ * Creates a Matter Aggregator.
1975
+ * @param {StorageContext} context - The storage context.
1976
+ * @returns {Aggregator} - The created Matter Aggregator.
1977
+ */
1978
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1630
1979
  async createMatterAggregator(context, pluginName) {
1980
+ /*
1981
+ const random = randomBytes(8).toString('hex');
1982
+ await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
1983
+ await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
1984
+
1985
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with uniqueId ${await context.get<string>('aggregatorUniqueId')} serialNumber ${await context.get<string>('aggregatorSerialNumber')}`);
1986
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with softwareVersion ${await context.get<number>('softwareVersion', 1)} softwareVersionString ${await context.get<string>('softwareVersionString', '1.0.0')}`);
1987
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with hardwareVersion ${await context.get<number>('hardwareVersion', 1)} hardwareVersionString ${await context.get<string>('hardwareVersionString', '1.0.0')}`);
1988
+ */
1631
1989
  const matterAggregator = new Aggregator();
1990
+ /*
1991
+ matterAggregator.addClusterServer(
1992
+ ClusterServer(
1993
+ BasicInformationCluster,
1994
+ {
1995
+ dataModelRevision: 1,
1996
+ location: 'FR',
1997
+ vendorId: VendorId(0xfff1),
1998
+ vendorName: 'Matterbridge',
1999
+ productId: 0x8000,
2000
+ productName: 'Matterbridge aggregator',
2001
+ productLabel: 'Matterbridge aggregator',
2002
+ nodeLabel: 'Matterbridge aggregator',
2003
+ serialNumber: await context.get<string>('aggregatorSerialNumber'),
2004
+ uniqueId: await context.get<string>('aggregatorUniqueId'),
2005
+ softwareVersion: await context.get<number>('softwareVersion', 1),
2006
+ softwareVersionString: await context.get<string>('softwareVersionString', '1.0.0'),
2007
+ hardwareVersion: await context.get<number>('hardwareVersion', 1),
2008
+ hardwareVersionString: await context.get<string>('hardwareVersionString', '1.0.0'),
2009
+ reachable: true,
2010
+ capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 },
2011
+ specificationVersion: Specification.SPECIFICATION_VERSION,
2012
+ maxPathsPerInvoke: 1,
2013
+ },
2014
+ {},
2015
+ {
2016
+ startUp: true,
2017
+ shutDown: true,
2018
+ leave: true,
2019
+ reachableChanged: true,
2020
+ },
2021
+ ),
2022
+ );
2023
+ */
1632
2024
  return matterAggregator;
1633
2025
  }
2026
+ /**
2027
+ * Creates a matter commissioning server.
2028
+ *
2029
+ * @param {StorageContext} context - The storage context.
2030
+ * @param {string} pluginName - The name of the commissioning server.
2031
+ * @returns {CommissioningServer} The created commissioning server.
2032
+ */
1634
2033
  async createCommisioningServer(context, pluginName) {
1635
2034
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1636
2035
  const deviceName = await context.get('deviceName');
1637
2036
  const deviceType = await context.get('deviceType');
1638
2037
  const vendorId = await context.get('vendorId');
1639
- const vendorName = await context.get('vendorName');
2038
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1640
2039
  const productId = await context.get('productId');
1641
- const productName = await context.get('productName');
2040
+ const productName = await context.get('productName'); // Home app = Model
1642
2041
  const serialNumber = await context.get('serialNumber');
1643
2042
  const uniqueId = await context.get('uniqueId');
1644
2043
  const softwareVersion = await context.get('softwareVersion', 1);
1645
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2044
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1646
2045
  const hardwareVersion = await context.get('hardwareVersion', 1);
1647
2046
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1648
2047
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1650,6 +2049,7 @@ export class Matterbridge extends EventEmitter {
1650
2049
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1651
2050
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1652
2051
  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} `);
2052
+ // Validate ipv4address
1653
2053
  if (this.ipv4address) {
1654
2054
  const networkInterfaces = os.networkInterfaces();
1655
2055
  const availableAddresses = Object.values(networkInterfaces)
@@ -1664,6 +2064,7 @@ export class Matterbridge extends EventEmitter {
1664
2064
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1665
2065
  }
1666
2066
  }
2067
+ // Validate ipv6address
1667
2068
  if (this.ipv6address) {
1668
2069
  const networkInterfaces = os.networkInterfaces();
1669
2070
  const availableAddresses = Object.values(networkInterfaces)
@@ -1694,7 +2095,7 @@ export class Matterbridge extends EventEmitter {
1694
2095
  nodeLabel: productName,
1695
2096
  productLabel: productName,
1696
2097
  softwareVersion,
1697
- softwareVersionString,
2098
+ softwareVersionString, // Home app = Firmware Revision
1698
2099
  hardwareVersion,
1699
2100
  hardwareVersionString,
1700
2101
  uniqueId,
@@ -1790,6 +2191,24 @@ export class Matterbridge extends EventEmitter {
1790
2191
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1791
2192
  return commissioningServer;
1792
2193
  }
2194
+ /**
2195
+ * Creates a commissioning server storage context.
2196
+ *
2197
+ * @param pluginName - The name of the plugin.
2198
+ * @param deviceName - The name of the device.
2199
+ * @param deviceType - The type of the device.
2200
+ * @param vendorId - The vendor ID.
2201
+ * @param vendorName - The vendor name.
2202
+ * @param productId - The product ID.
2203
+ * @param productName - The product name.
2204
+ * @param serialNumber - The serial number of the device (optional).
2205
+ * @param uniqueId - The unique ID of the device (optional).
2206
+ * @param softwareVersion - The software version of the device (optional).
2207
+ * @param softwareVersionString - The software version string of the device (optional).
2208
+ * @param hardwareVersion - The hardware version of the device (optional).
2209
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2210
+ * @returns The storage context for the commissioning server.
2211
+ */
1793
2212
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1794
2213
  if (!this.storageManager)
1795
2214
  throw new Error('No storage manager initialized');
@@ -1817,6 +2236,13 @@ export class Matterbridge extends EventEmitter {
1817
2236
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1818
2237
  return storageContext;
1819
2238
  }
2239
+ /**
2240
+ * Imports the commissioning server context for a specific plugin and device.
2241
+ * @param pluginName - The name of the plugin.
2242
+ * @param device - The MatterbridgeDevice object representing the device.
2243
+ * @returns The commissioning server context.
2244
+ * @throws Error if the BasicInformationCluster is not found.
2245
+ */
1820
2246
  async importCommissioningServerContext(pluginName, device) {
1821
2247
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1822
2248
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -1851,6 +2277,14 @@ export class Matterbridge extends EventEmitter {
1851
2277
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1852
2278
  return storageContext;
1853
2279
  }
2280
+ /**
2281
+ * Shows the commissioning server QR code for a given plugin.
2282
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2283
+ * @param {StorageContext} storageContext - The storage context instance.
2284
+ * @param {NodeStorage} nodeContext - The node storage instance.
2285
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2286
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2287
+ */
1854
2288
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1855
2289
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
1856
2290
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -1861,7 +2295,8 @@ export class Matterbridge extends EventEmitter {
1861
2295
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1862
2296
  const QrCode = new QrCodeSchema();
1863
2297
  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`);
1864
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2298
+ // eslint-disable-next-line no-console
2299
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
1865
2300
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
1866
2301
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
1867
2302
  if (pluginName === 'Matterbridge') {
@@ -1908,6 +2343,12 @@ export class Matterbridge extends EventEmitter {
1908
2343
  }
1909
2344
  this.wssSendRefreshRequired();
1910
2345
  }
2346
+ /**
2347
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2348
+ *
2349
+ * @param fabricInfo - The array of exposed fabric information objects.
2350
+ * @returns An array of sanitized exposed fabric information objects.
2351
+ */
1911
2352
  sanitizeFabricInformations(fabricInfo) {
1912
2353
  return fabricInfo.map((info) => {
1913
2354
  return {
@@ -1921,6 +2362,12 @@ export class Matterbridge extends EventEmitter {
1921
2362
  };
1922
2363
  });
1923
2364
  }
2365
+ /**
2366
+ * Sanitizes the session information by converting bigint properties to string.
2367
+ *
2368
+ * @param sessionInfo - The array of session information objects.
2369
+ * @returns An array of sanitized session information objects.
2370
+ */
1924
2371
  sanitizeSessionInformation(sessionInfo) {
1925
2372
  return sessionInfo
1926
2373
  .filter((session) => session.isPeerActive)
@@ -1948,6 +2395,12 @@ export class Matterbridge extends EventEmitter {
1948
2395
  };
1949
2396
  });
1950
2397
  }
2398
+ /**
2399
+ * Sets the reachability of a commissioning server and trigger.
2400
+ *
2401
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2402
+ * @param {boolean} reachable - The new reachability status.
2403
+ */
1951
2404
  setCommissioningServerReachability(commissioningServer, reachable) {
1952
2405
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
1953
2406
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1955,6 +2408,11 @@ export class Matterbridge extends EventEmitter {
1955
2408
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1956
2409
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1957
2410
  }
2411
+ /**
2412
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2413
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2414
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2415
+ */
1958
2416
  setAggregatorReachability(matterAggregator, reachable) {
1959
2417
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
1960
2418
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1967,6 +2425,12 @@ export class Matterbridge extends EventEmitter {
1967
2425
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
1968
2426
  });
1969
2427
  }
2428
+ /**
2429
+ * Sets the reachability of a device and trigger.
2430
+ *
2431
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2432
+ * @param {boolean} reachable - The new reachability status of the device.
2433
+ */
1970
2434
  setDeviceReachability(device, reachable) {
1971
2435
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
1972
2436
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2015,6 +2479,10 @@ export class Matterbridge extends EventEmitter {
2015
2479
  }
2016
2480
  return vendorName;
2017
2481
  };
2482
+ /**
2483
+ * Retrieves the base registered plugins sanitized for res.json().
2484
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2485
+ */
2018
2486
  async getBaseRegisteredPlugins() {
2019
2487
  const baseRegisteredPlugins = [];
2020
2488
  for (const plugin of this.plugins) {
@@ -2046,13 +2514,36 @@ export class Matterbridge extends EventEmitter {
2046
2514
  }
2047
2515
  return baseRegisteredPlugins;
2048
2516
  }
2517
+ /**
2518
+ * Spawns a child process with the given command and arguments.
2519
+ * @param {string} command - The command to execute.
2520
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2521
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2522
+ */
2049
2523
  async spawnCommand(command, args = []) {
2524
+ /*
2525
+ npm > npm.cmd on windows
2526
+ cmd.exe ['dir'] on windows
2527
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2528
+ process.on('unhandledRejection', (reason, promise) => {
2529
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2530
+ });
2531
+
2532
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2533
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2534
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2535
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2536
+ */
2050
2537
  const cmdLine = command + ' ' + args.join(' ');
2051
2538
  if (process.platform === 'win32' && command === 'npm') {
2539
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2052
2540
  const argstring = 'npm ' + args.join(' ');
2053
2541
  args.splice(0, args.length, '/c', argstring);
2054
2542
  command = 'cmd.exe';
2055
2543
  }
2544
+ // Decide when using sudo on linux
2545
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2546
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2056
2547
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2057
2548
  args.unshift(command);
2058
2549
  command = 'sudo';
@@ -2110,55 +2601,102 @@ export class Matterbridge extends EventEmitter {
2110
2601
  }
2111
2602
  });
2112
2603
  }
2604
+ /**
2605
+ * Sends a WebSocket message to all connected clients.
2606
+ *
2607
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2608
+ * @param {string} time - The time string of the message
2609
+ * @param {string} name - The logger name of the message
2610
+ * @param {string} message - The content of the message.
2611
+ */
2113
2612
  wssSendMessage(level, time, name, message) {
2114
2613
  if (!level || !time || !name || !message)
2115
2614
  return;
2615
+ // Remove ANSI escape codes from the message
2616
+ // eslint-disable-next-line no-control-regex
2116
2617
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2618
+ // Remove leading asterisks from the message
2117
2619
  message = message.replace(/^\*+/, '');
2620
+ // Replace all occurrences of \t and \n
2118
2621
  message = message.replace(/[\t\n]/g, '');
2622
+ // Remove non-printable characters
2623
+ // eslint-disable-next-line no-control-regex
2119
2624
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2625
+ // Replace all occurrences of \" with "
2120
2626
  message = message.replace(/\\"/g, '"');
2627
+ // Define the maximum allowed length for continuous characters without a space
2121
2628
  const maxContinuousLength = 100;
2122
2629
  const keepStartLength = 20;
2123
2630
  const keepEndLength = 20;
2631
+ // Split the message into words
2124
2632
  message = message
2125
2633
  .split(' ')
2126
2634
  .map((word) => {
2635
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2127
2636
  if (word.length > maxContinuousLength) {
2128
2637
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2129
2638
  }
2130
2639
  return word;
2131
2640
  })
2132
2641
  .join(' ');
2642
+ // Send the message to all connected clients
2133
2643
  this.webSocketServer?.clients.forEach((client) => {
2134
2644
  if (client.readyState === WebSocket.OPEN) {
2135
2645
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2136
2646
  }
2137
2647
  });
2138
2648
  }
2649
+ /**
2650
+ * Sends a need to refresh WebSocket message to all connected clients.
2651
+ *
2652
+ */
2139
2653
  wssSendRefreshRequired() {
2140
2654
  this.matterbridgeInformation.refreshRequired = true;
2655
+ // Send the message to all connected clients
2141
2656
  this.webSocketServer?.clients.forEach((client) => {
2142
2657
  if (client.readyState === WebSocket.OPEN) {
2143
2658
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2144
2659
  }
2145
2660
  });
2146
2661
  }
2662
+ /**
2663
+ * Sends a need to restart WebSocket message to all connected clients.
2664
+ *
2665
+ */
2147
2666
  wssSendRestartRequired() {
2148
2667
  this.matterbridgeInformation.restartRequired = true;
2668
+ // Send the message to all connected clients
2149
2669
  this.webSocketServer?.clients.forEach((client) => {
2150
2670
  if (client.readyState === WebSocket.OPEN) {
2151
2671
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2152
2672
  }
2153
2673
  });
2154
2674
  }
2675
+ /**
2676
+ * Initializes the frontend of Matterbridge.
2677
+ *
2678
+ * @param port The port number to run the frontend server on. Default is 8283.
2679
+ */
2155
2680
  async initializeFrontend(port = 8283) {
2156
2681
  let initializeError = false;
2157
2682
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2683
+ // Create the express app that serves the frontend
2158
2684
  this.expressApp = express();
2685
+ // Log all requests to the server for debugging
2686
+ /*
2687
+ if (hasParameter('homedir')) {
2688
+ this.expressApp.use((req, res, next) => {
2689
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2690
+ next();
2691
+ });
2692
+ }
2693
+ */
2694
+ // Serve static files from '/static' endpoint
2159
2695
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2160
2696
  if (!hasParameter('ssl')) {
2697
+ // Create an HTTP server and attach the express app
2161
2698
  this.httpServer = createServer(this.expressApp);
2699
+ // Listen on the specified port
2162
2700
  if (hasParameter('ingress')) {
2163
2701
  this.httpServer.listen(port, '0.0.0.0', () => {
2164
2702
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2172,6 +2710,7 @@ export class Matterbridge extends EventEmitter {
2172
2710
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2173
2711
  });
2174
2712
  }
2713
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2175
2714
  this.httpServer.on('error', (error) => {
2176
2715
  this.log.error(`Frontend http server error listening on ${port}`);
2177
2716
  switch (error.code) {
@@ -2187,6 +2726,7 @@ export class Matterbridge extends EventEmitter {
2187
2726
  });
2188
2727
  }
2189
2728
  else {
2729
+ // Load the SSL certificate, the private key and optionally the CA certificate
2190
2730
  let cert;
2191
2731
  try {
2192
2732
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2214,7 +2754,9 @@ export class Matterbridge extends EventEmitter {
2214
2754
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2215
2755
  }
2216
2756
  const serverOptions = { cert, key, ca };
2757
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2217
2758
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
2759
+ // Listen on the specified port
2218
2760
  if (hasParameter('ingress')) {
2219
2761
  this.httpsServer.listen(port, '0.0.0.0', () => {
2220
2762
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2228,6 +2770,7 @@ export class Matterbridge extends EventEmitter {
2228
2770
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2229
2771
  });
2230
2772
  }
2773
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2231
2774
  this.httpsServer.on('error', (error) => {
2232
2775
  this.log.error(`Frontend https server error listening on ${port}`);
2233
2776
  switch (error.code) {
@@ -2244,12 +2787,13 @@ export class Matterbridge extends EventEmitter {
2244
2787
  }
2245
2788
  if (initializeError)
2246
2789
  return;
2790
+ // Createe a WebSocket server and attach it to the http or https server
2247
2791
  const wssPort = port;
2248
2792
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2249
2793
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2250
2794
  this.webSocketServer.on('connection', (ws, request) => {
2251
2795
  const clientIp = request.socket.remoteAddress;
2252
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
2796
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2253
2797
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2254
2798
  ws.on('message', (message) => {
2255
2799
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2282,6 +2826,7 @@ export class Matterbridge extends EventEmitter {
2282
2826
  this.webSocketServer.on('error', (ws, error) => {
2283
2827
  this.log.error(`WebSocketServer error: ${error}`);
2284
2828
  });
2829
+ // Endpoint to validate login code
2285
2830
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2286
2831
  const { password } = req.body;
2287
2832
  this.log.debug('The frontend sent /api/login', password);
@@ -2300,12 +2845,14 @@ export class Matterbridge extends EventEmitter {
2300
2845
  this.log.warn('/api/login error wrong password');
2301
2846
  res.json({ valid: false });
2302
2847
  }
2848
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2303
2849
  }
2304
2850
  catch (error) {
2305
2851
  this.log.error('/api/login error getting password');
2306
2852
  res.json({ valid: false });
2307
2853
  }
2308
2854
  });
2855
+ // Endpoint to provide settings
2309
2856
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2310
2857
  this.log.debug('The frontend sent /api/settings');
2311
2858
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2326,16 +2873,20 @@ export class Matterbridge extends EventEmitter {
2326
2873
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2327
2874
  this.matterbridgeInformation.profile = this.profile;
2328
2875
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2876
+ // this.log.debug('Response:', debugStringify(response));
2329
2877
  res.json(response);
2330
2878
  });
2879
+ // Endpoint to provide plugins
2331
2880
  this.expressApp.get('/api/plugins', async (req, res) => {
2332
2881
  this.log.debug('The frontend sent /api/plugins');
2333
2882
  const response = await this.getBaseRegisteredPlugins();
2883
+ // this.log.debug('Response:', debugStringify(response));
2334
2884
  res.json(response);
2335
2885
  });
2886
+ // Endpoint to provide devices
2336
2887
  this.expressApp.get('/api/devices', (req, res) => {
2337
2888
  this.log.debug('The frontend sent /api/devices');
2338
- const data = [];
2889
+ const devices = [];
2339
2890
  this.devices.forEach(async (device) => {
2340
2891
  const pluginName = device.plugin ?? 'Unknown';
2341
2892
  if (this.edge)
@@ -2346,22 +2897,29 @@ export class Matterbridge extends EventEmitter {
2346
2897
  let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
2347
2898
  if (!serial)
2348
2899
  serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
2900
+ let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
2901
+ if (!productUrl)
2902
+ productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
2349
2903
  let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
2350
2904
  if (!uniqueId)
2351
2905
  uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
2352
2906
  const cluster = this.getClusterTextFromDevice(device);
2353
- data.push({
2907
+ devices.push({
2354
2908
  pluginName,
2355
2909
  type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
2356
2910
  endpoint: device.number,
2357
2911
  name,
2358
2912
  serial,
2913
+ productUrl,
2914
+ configUrl: device.configUrl,
2359
2915
  uniqueId,
2360
2916
  cluster: cluster,
2361
2917
  });
2362
2918
  });
2363
- res.json(data);
2919
+ // this.log.debug('Response:', debugStringify(data));
2920
+ res.json(devices);
2364
2921
  });
2922
+ // Endpoint to provide the cluster servers of the devices
2365
2923
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2366
2924
  const selectedPluginName = req.params.selectedPluginName;
2367
2925
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2381,6 +2939,7 @@ export class Matterbridge extends EventEmitter {
2381
2939
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2382
2940
  if (clusterServer.name === 'EveHistory')
2383
2941
  return;
2942
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2384
2943
  let attributeValue;
2385
2944
  try {
2386
2945
  if (typeof value.getLocal() === 'object')
@@ -2391,6 +2950,7 @@ export class Matterbridge extends EventEmitter {
2391
2950
  catch (error) {
2392
2951
  attributeValue = 'Fabric-Scoped';
2393
2952
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2953
+ // console.log(error);
2394
2954
  }
2395
2955
  data.push({
2396
2956
  endpoint: device.number ? device.number.toString() : '...',
@@ -2403,12 +2963,14 @@ export class Matterbridge extends EventEmitter {
2403
2963
  });
2404
2964
  });
2405
2965
  device.getChildEndpoints().forEach((childEndpoint) => {
2966
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2406
2967
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2407
2968
  const clusterServers = childEndpoint.getAllClusterServers();
2408
2969
  clusterServers.forEach((clusterServer) => {
2409
2970
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2410
2971
  if (clusterServer.name === 'EveHistory')
2411
2972
  return;
2973
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2412
2974
  let attributeValue;
2413
2975
  try {
2414
2976
  if (typeof value.getLocal() === 'object')
@@ -2419,6 +2981,7 @@ export class Matterbridge extends EventEmitter {
2419
2981
  catch (error) {
2420
2982
  attributeValue = 'Unavailable';
2421
2983
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2984
+ // console.log(error);
2422
2985
  }
2423
2986
  data.push({
2424
2987
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2435,6 +2998,7 @@ export class Matterbridge extends EventEmitter {
2435
2998
  });
2436
2999
  res.json(data);
2437
3000
  });
3001
+ // Endpoint to view the log
2438
3002
  this.expressApp.get('/api/view-log', async (req, res) => {
2439
3003
  this.log.debug('The frontend sent /api/log');
2440
3004
  try {
@@ -2447,10 +3011,12 @@ export class Matterbridge extends EventEmitter {
2447
3011
  res.status(500).send('Error reading log file');
2448
3012
  }
2449
3013
  });
3014
+ // Endpoint to download the matterbridge log
2450
3015
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2451
3016
  this.log.debug('The frontend sent /api/download-mblog');
2452
3017
  try {
2453
3018
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3019
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2454
3020
  }
2455
3021
  catch (error) {
2456
3022
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2462,10 +3028,12 @@ export class Matterbridge extends EventEmitter {
2462
3028
  }
2463
3029
  });
2464
3030
  });
3031
+ // Endpoint to download the matter log
2465
3032
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2466
3033
  this.log.debug('The frontend sent /api/download-mjlog');
2467
3034
  try {
2468
3035
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3036
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2469
3037
  }
2470
3038
  catch (error) {
2471
3039
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2477,6 +3045,7 @@ export class Matterbridge extends EventEmitter {
2477
3045
  }
2478
3046
  });
2479
3047
  });
3048
+ // Endpoint to download the matter storage file
2480
3049
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2481
3050
  this.log.debug('The frontend sent /api/download-mjstorage');
2482
3051
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2486,6 +3055,7 @@ export class Matterbridge extends EventEmitter {
2486
3055
  }
2487
3056
  });
2488
3057
  });
3058
+ // Endpoint to download the matterbridge storage directory
2489
3059
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2490
3060
  this.log.debug('The frontend sent /api/download-mbstorage');
2491
3061
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2496,6 +3066,7 @@ export class Matterbridge extends EventEmitter {
2496
3066
  }
2497
3067
  });
2498
3068
  });
3069
+ // Endpoint to download the matterbridge plugin directory
2499
3070
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2500
3071
  this.log.debug('The frontend sent /api/download-pluginstorage');
2501
3072
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2506,9 +3077,11 @@ export class Matterbridge extends EventEmitter {
2506
3077
  }
2507
3078
  });
2508
3079
  });
3080
+ // Endpoint to download the matterbridge plugin config files
2509
3081
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2510
3082
  this.log.debug('The frontend sent /api/download-pluginconfig');
2511
3083
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3084
+ // 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')));
2512
3085
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2513
3086
  if (error) {
2514
3087
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2516,6 +3089,7 @@ export class Matterbridge extends EventEmitter {
2516
3089
  }
2517
3090
  });
2518
3091
  });
3092
+ // Endpoint to download the matterbridge plugin config files
2519
3093
  this.expressApp.get('/api/download-backup', async (req, res) => {
2520
3094
  this.log.debug('The frontend sent /api/download-backup');
2521
3095
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2525,6 +3099,7 @@ export class Matterbridge extends EventEmitter {
2525
3099
  }
2526
3100
  });
2527
3101
  });
3102
+ // Endpoint to receive commands
2528
3103
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2529
3104
  const command = req.params.command;
2530
3105
  let param = req.params.param;
@@ -2534,13 +3109,15 @@ export class Matterbridge extends EventEmitter {
2534
3109
  return;
2535
3110
  }
2536
3111
  this.log.debug(`Received frontend command: ${command}:${param}`);
3112
+ // Handle the command setpassword from Settings
2537
3113
  if (command === 'setpassword') {
2538
- const password = param.slice(1, -1);
3114
+ const password = param.slice(1, -1); // Remove the first and last characters
2539
3115
  this.log.debug('setpassword', param, password);
2540
3116
  await this.nodeContext?.set('password', password);
2541
3117
  res.json({ message: 'Command received' });
2542
3118
  return;
2543
3119
  }
3120
+ // Handle the command setbridgemode from Settings
2544
3121
  if (command === 'setbridgemode') {
2545
3122
  this.log.debug(`setbridgemode: ${param}`);
2546
3123
  this.wssSendRestartRequired();
@@ -2548,6 +3125,7 @@ export class Matterbridge extends EventEmitter {
2548
3125
  res.json({ message: 'Command received' });
2549
3126
  return;
2550
3127
  }
3128
+ // Handle the command backup from Settings
2551
3129
  if (command === 'backup') {
2552
3130
  this.log.notice(`Prepairing the backup...`);
2553
3131
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2555,25 +3133,26 @@ export class Matterbridge extends EventEmitter {
2555
3133
  res.json({ message: 'Command received' });
2556
3134
  return;
2557
3135
  }
3136
+ // Handle the command setmbloglevel from Settings
2558
3137
  if (command === 'setmbloglevel') {
2559
3138
  this.log.debug('Matterbridge log level:', param);
2560
3139
  if (param === 'Debug') {
2561
- this.log.logLevel = "debug";
3140
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2562
3141
  }
2563
3142
  else if (param === 'Info') {
2564
- this.log.logLevel = "info";
3143
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2565
3144
  }
2566
3145
  else if (param === 'Notice') {
2567
- this.log.logLevel = "notice";
3146
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2568
3147
  }
2569
3148
  else if (param === 'Warn') {
2570
- this.log.logLevel = "warn";
3149
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2571
3150
  }
2572
3151
  else if (param === 'Error') {
2573
- this.log.logLevel = "error";
3152
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2574
3153
  }
2575
3154
  else if (param === 'Fatal') {
2576
- this.log.logLevel = "fatal";
3155
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2577
3156
  }
2578
3157
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2579
3158
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2581,12 +3160,13 @@ export class Matterbridge extends EventEmitter {
2581
3160
  for (const plugin of this.plugins) {
2582
3161
  if (!plugin.platform || !plugin.platform.config)
2583
3162
  continue;
2584
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2585
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3163
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3164
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2586
3165
  }
2587
3166
  res.json({ message: 'Command received' });
2588
3167
  return;
2589
3168
  }
3169
+ // Handle the command setmbloglevel from Settings
2590
3170
  if (command === 'setmjloglevel') {
2591
3171
  this.log.debug('Matter.js log level:', param);
2592
3172
  if (param === 'Debug') {
@@ -2611,30 +3191,34 @@ export class Matterbridge extends EventEmitter {
2611
3191
  res.json({ message: 'Command received' });
2612
3192
  return;
2613
3193
  }
3194
+ // Handle the command setmdnsinterface from Settings
2614
3195
  if (command === 'setmdnsinterface') {
2615
- param = param.slice(1, -1);
3196
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2616
3197
  this.matterbridgeInformation.mattermdnsinterface = param;
2617
3198
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2618
3199
  await this.nodeContext?.set('mattermdnsinterface', param);
2619
3200
  res.json({ message: 'Command received' });
2620
3201
  return;
2621
3202
  }
3203
+ // Handle the command setipv4address from Settings
2622
3204
  if (command === 'setipv4address') {
2623
- param = param.slice(1, -1);
3205
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2624
3206
  this.matterbridgeInformation.matteripv4address = param;
2625
3207
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2626
3208
  await this.nodeContext?.set('matteripv4address', param);
2627
3209
  res.json({ message: 'Command received' });
2628
3210
  return;
2629
3211
  }
3212
+ // Handle the command setipv6address from Settings
2630
3213
  if (command === 'setipv6address') {
2631
- param = param.slice(1, -1);
3214
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2632
3215
  this.matterbridgeInformation.matteripv6address = param;
2633
3216
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2634
3217
  await this.nodeContext?.set('matteripv6address', param);
2635
3218
  res.json({ message: 'Command received' });
2636
3219
  return;
2637
3220
  }
3221
+ // Handle the command setmatterport from Settings
2638
3222
  if (command === 'setmatterport') {
2639
3223
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2640
3224
  this.matterbridgeInformation.matterPort = port;
@@ -2643,6 +3227,7 @@ export class Matterbridge extends EventEmitter {
2643
3227
  res.json({ message: 'Command received' });
2644
3228
  return;
2645
3229
  }
3230
+ // Handle the command setmatterdiscriminator from Settings
2646
3231
  if (command === 'setmatterdiscriminator') {
2647
3232
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2648
3233
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2651,6 +3236,7 @@ export class Matterbridge extends EventEmitter {
2651
3236
  res.json({ message: 'Command received' });
2652
3237
  return;
2653
3238
  }
3239
+ // Handle the command setmatterpasscode from Settings
2654
3240
  if (command === 'setmatterpasscode') {
2655
3241
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2656
3242
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2659,17 +3245,20 @@ export class Matterbridge extends EventEmitter {
2659
3245
  res.json({ message: 'Command received' });
2660
3246
  return;
2661
3247
  }
3248
+ // Handle the command setmbloglevel from Settings
2662
3249
  if (command === 'setmblogfile') {
2663
3250
  this.log.debug('Matterbridge file log:', param);
2664
3251
  this.matterbridgeInformation.fileLogger = param === 'true';
2665
3252
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3253
+ // Create the file logger for matterbridge
2666
3254
  if (param === 'true')
2667
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3255
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2668
3256
  else
2669
3257
  AnsiLogger.setGlobalLogfile(undefined);
2670
3258
  res.json({ message: 'Command received' });
2671
3259
  return;
2672
3260
  }
3261
+ // Handle the command setmbloglevel from Settings
2673
3262
  if (command === 'setmjlogfile') {
2674
3263
  this.log.debug('Matter file log:', param);
2675
3264
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2696,36 +3285,43 @@ export class Matterbridge extends EventEmitter {
2696
3285
  res.json({ message: 'Command received' });
2697
3286
  return;
2698
3287
  }
3288
+ // Handle the command unregister from Settings
2699
3289
  if (command === 'unregister') {
2700
3290
  await this.unregisterAndShutdownProcess();
2701
3291
  res.json({ message: 'Command received' });
2702
3292
  return;
2703
3293
  }
3294
+ // Handle the command reset from Settings
2704
3295
  if (command === 'reset') {
2705
3296
  await this.shutdownProcessAndReset();
2706
3297
  res.json({ message: 'Command received' });
2707
3298
  return;
2708
3299
  }
3300
+ // Handle the command factoryreset from Settings
2709
3301
  if (command === 'factoryreset') {
2710
3302
  await this.shutdownProcessAndFactoryReset();
2711
3303
  res.json({ message: 'Command received' });
2712
3304
  return;
2713
3305
  }
3306
+ // Handle the command shutdown from Header
2714
3307
  if (command === 'shutdown') {
2715
3308
  await this.shutdownProcess();
2716
3309
  res.json({ message: 'Command received' });
2717
3310
  return;
2718
3311
  }
3312
+ // Handle the command restart from Header
2719
3313
  if (command === 'restart') {
2720
3314
  await this.restartProcess();
2721
3315
  res.json({ message: 'Command received' });
2722
3316
  return;
2723
3317
  }
3318
+ // Handle the command update from Header
2724
3319
  if (command === 'update') {
2725
3320
  this.log.info('Updating matterbridge...');
2726
3321
  try {
2727
3322
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2728
3323
  this.log.info('Matterbridge has been updated. Full restart required.');
3324
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2729
3325
  }
2730
3326
  catch (error) {
2731
3327
  this.log.error('Error updating matterbridge');
@@ -2735,9 +3331,11 @@ export class Matterbridge extends EventEmitter {
2735
3331
  res.json({ message: 'Command received' });
2736
3332
  return;
2737
3333
  }
3334
+ // Handle the command saveconfig from Home
2738
3335
  if (command === 'saveconfig') {
2739
3336
  param = param.replace(/\*/g, '\\');
2740
3337
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3338
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2741
3339
  if (!this.plugins.has(param)) {
2742
3340
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2743
3341
  }
@@ -2751,33 +3349,39 @@ export class Matterbridge extends EventEmitter {
2751
3349
  res.json({ message: 'Command received' });
2752
3350
  return;
2753
3351
  }
3352
+ // Handle the command installplugin from Home
2754
3353
  if (command === 'installplugin') {
2755
3354
  param = param.replace(/\*/g, '\\');
2756
3355
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2757
3356
  try {
2758
3357
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2759
3358
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3359
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2760
3360
  }
2761
3361
  catch (error) {
2762
3362
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2763
3363
  }
2764
3364
  this.wssSendRestartRequired();
2765
3365
  param = param.split('@')[0];
3366
+ // Also add the plugin to matterbridge so no return!
2766
3367
  if (param === 'matterbridge') {
3368
+ // 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
2767
3369
  res.json({ message: 'Command received' });
2768
3370
  return;
2769
3371
  }
2770
3372
  }
3373
+ // Handle the command addplugin from Home
2771
3374
  if (command === 'addplugin' || command === 'installplugin') {
2772
3375
  param = param.replace(/\*/g, '\\');
2773
3376
  const plugin = await this.plugins.add(param);
2774
3377
  if (plugin) {
2775
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3378
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2776
3379
  }
2777
3380
  res.json({ message: 'Command received' });
2778
3381
  this.wssSendRefreshRequired();
2779
3382
  return;
2780
3383
  }
3384
+ // Handle the command removeplugin from Home
2781
3385
  if (command === 'removeplugin') {
2782
3386
  if (!this.plugins.has(param)) {
2783
3387
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2791,6 +3395,7 @@ export class Matterbridge extends EventEmitter {
2791
3395
  this.wssSendRefreshRequired();
2792
3396
  return;
2793
3397
  }
3398
+ // Handle the command enableplugin from Home
2794
3399
  if (command === 'enableplugin') {
2795
3400
  if (!this.plugins.has(param)) {
2796
3401
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2808,13 +3413,14 @@ export class Matterbridge extends EventEmitter {
2808
3413
  plugin.registeredDevices = undefined;
2809
3414
  plugin.addedDevices = undefined;
2810
3415
  await this.plugins.enable(param);
2811
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3416
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
2812
3417
  }
2813
3418
  }
2814
3419
  res.json({ message: 'Command received' });
2815
3420
  this.wssSendRefreshRequired();
2816
3421
  return;
2817
3422
  }
3423
+ // Handle the command disableplugin from Home
2818
3424
  if (command === 'disableplugin') {
2819
3425
  if (!this.plugins.has(param)) {
2820
3426
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2831,6 +3437,7 @@ export class Matterbridge extends EventEmitter {
2831
3437
  return;
2832
3438
  }
2833
3439
  });
3440
+ // Fallback for routing (must be the last route)
2834
3441
  this.expressApp.get('*', (req, res) => {
2835
3442
  this.log.debug('The frontend sent:', req.url);
2836
3443
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -2838,6 +3445,11 @@ export class Matterbridge extends EventEmitter {
2838
3445
  });
2839
3446
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2840
3447
  }
3448
+ /**
3449
+ * Retrieves the cluster text description from a given device.
3450
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3451
+ * @returns {string} The attributes description of the cluster servers in the device.
3452
+ */
2841
3453
  getClusterTextFromDevice(device) {
2842
3454
  const stringifyUserLabel = (endpoint) => {
2843
3455
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -2860,9 +3472,11 @@ export class Matterbridge extends EventEmitter {
2860
3472
  return '';
2861
3473
  };
2862
3474
  let attributes = '';
3475
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2863
3476
  const clusterServers = device.getAllClusterServers();
2864
3477
  clusterServers.forEach((clusterServer) => {
2865
3478
  try {
3479
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2866
3480
  if (clusterServer.name === 'OnOff')
2867
3481
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
2868
3482
  if (clusterServer.name === 'Switch')
@@ -2913,18 +3527,30 @@ export class Matterbridge extends EventEmitter {
2913
3527
  attributes += `${stringifyFixedLabel(device)} `;
2914
3528
  if (clusterServer.name === 'UserLabel')
2915
3529
  attributes += `${stringifyUserLabel(device)} `;
3530
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2916
3531
  }
2917
3532
  catch (error) {
2918
3533
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
2919
3534
  }
2920
3535
  });
3536
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2921
3537
  return attributes;
2922
3538
  }
3539
+ /**
3540
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3541
+ * @deprecated This method is deprecated and will be removed in a future version.
3542
+ *
3543
+ * @returns A Promise that resolves when the initialization is complete.
3544
+ */
2923
3545
  async startExtension(dataPath, extensionVersion, port = 5540) {
3546
+ // Set the bridge mode
2924
3547
  this.bridgeMode = 'bridge';
3548
+ // Set the first port to use
2925
3549
  this.port = port;
2926
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3550
+ // Set Matterbridge logger
3551
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
2927
3552
  this.log.debug('Matterbridge extension is starting...');
3553
+ // Initialize NodeStorage
2928
3554
  this.matterbridgeDirectory = dataPath;
2929
3555
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
2930
3556
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -2943,10 +3569,13 @@ export class Matterbridge extends EventEmitter {
2943
3569
  };
2944
3570
  this.plugins.set(plugin);
2945
3571
  this.plugins.saveToStorage();
3572
+ // Log system info and create .matterbridge directory
2946
3573
  await this.logNodeAndSystemInfo();
2947
3574
  this.matterbridgeDirectory = dataPath;
3575
+ // Set matter.js logger level and format
2948
3576
  Logger.defaultLogLevel = MatterLogLevel.INFO;
2949
3577
  Logger.format = MatterLogFormat.ANSI;
3578
+ // Start the storage and create matterbridgeContext
2950
3579
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
2951
3580
  if (!this.storageManager)
2952
3581
  return false;
@@ -2956,7 +3585,7 @@ export class Matterbridge extends EventEmitter {
2956
3585
  await this.matterbridgeContext.set('softwareVersion', 1);
2957
3586
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
2958
3587
  await this.matterbridgeContext.set('hardwareVersion', 1);
2959
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3588
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
2960
3589
  this.matterServer = this.createMatterServer(this.storageManager);
2961
3590
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
2962
3591
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
@@ -2969,6 +3598,7 @@ export class Matterbridge extends EventEmitter {
2969
3598
  await this.startMatterServer();
2970
3599
  this.log.info('Matter server started');
2971
3600
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3601
+ // Set reachability to true and trigger event after 60 seconds
2972
3602
  setTimeout(() => {
2973
3603
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
2974
3604
  if (this.commissioningServer)
@@ -2978,14 +3608,31 @@ export class Matterbridge extends EventEmitter {
2978
3608
  }, 60 * 1000);
2979
3609
  return this.commissioningServer.isCommissioned();
2980
3610
  }
3611
+ /**
3612
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3613
+ * @deprecated This method is deprecated and will be removed in a future version.
3614
+ *
3615
+ * @returns A Promise that resolves when the initialization is complete.
3616
+ */
2981
3617
  async stopExtension() {
3618
+ // Closing matter
2982
3619
  await this.stopMatterServer();
3620
+ // Clearing the session manager
3621
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3622
+ // Closing storage
2983
3623
  await this.stopMatterStorage();
2984
3624
  this.log.info('Matter server stopped');
2985
3625
  }
3626
+ /**
3627
+ * Checks if the extension is commissioned.
3628
+ * @deprecated This method is deprecated and will be removed in a future version.
3629
+ *
3630
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3631
+ */
2986
3632
  isExtensionCommissioned() {
2987
3633
  if (!this.commissioningServer)
2988
3634
  return false;
2989
3635
  return this.commissioningServer.isCommissioned();
2990
3636
  }
2991
3637
  }
3638
+ //# sourceMappingURL=matterbridge.js.map