matterbridge 1.6.8-dev.1 → 1.6.8-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README-SERVICE.md +12 -3
  3. package/README.md +2 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +26 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts.map +1 -0
  11. package/dist/defaultConfigSchema.js +23 -0
  12. package/dist/defaultConfigSchema.js.map +1 -0
  13. package/dist/deviceManager.d.ts +46 -0
  14. package/dist/deviceManager.d.ts.map +1 -0
  15. package/dist/deviceManager.js +26 -1
  16. package/dist/deviceManager.js.map +1 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +30 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/logger/export.d.ts.map +1 -0
  21. package/dist/logger/export.js +1 -0
  22. package/dist/logger/export.js.map +1 -0
  23. package/dist/matter/export.d.ts.map +1 -0
  24. package/dist/matter/export.js +1 -0
  25. package/dist/matter/export.js.map +1 -0
  26. package/dist/matterbridge.d.ts +466 -0
  27. package/dist/matterbridge.d.ts.map +1 -0
  28. package/dist/matterbridge.js +661 -62
  29. package/dist/matterbridge.js.map +1 -0
  30. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  31. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  32. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  33. package/dist/matterbridgeBehaviors.d.ts +116 -0
  34. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  35. package/dist/matterbridgeBehaviors.js +29 -1
  36. package/dist/matterbridgeBehaviors.js.map +1 -0
  37. package/dist/matterbridgeDevice.d.ts +1142 -0
  38. package/dist/matterbridgeDevice.d.ts.map +1 -0
  39. package/dist/matterbridgeDevice.js +1009 -8
  40. package/dist/matterbridgeDevice.js.map +1 -0
  41. package/dist/matterbridgeDeviceTypes.d.ts +109 -0
  42. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  43. package/dist/matterbridgeDeviceTypes.js +82 -11
  44. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  45. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  46. package/dist/matterbridgeDynamicPlatform.js +33 -0
  47. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  48. package/dist/matterbridgeEdge.d.ts +90 -0
  49. package/dist/matterbridgeEdge.d.ts.map +1 -0
  50. package/dist/matterbridgeEdge.js +529 -0
  51. package/dist/matterbridgeEdge.js.map +1 -0
  52. package/dist/matterbridgeEndpoint.d.ts +1134 -0
  53. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  54. package/dist/matterbridgeEndpoint.js +1134 -11
  55. package/dist/matterbridgeEndpoint.js.map +1 -0
  56. package/dist/matterbridgePlatform.d.ts +114 -0
  57. package/dist/matterbridgePlatform.d.ts.map +1 -0
  58. package/dist/matterbridgePlatform.js +91 -3
  59. package/dist/matterbridgePlatform.js.map +1 -0
  60. package/dist/matterbridgeTypes.d.ts.map +1 -0
  61. package/dist/matterbridgeTypes.js +24 -0
  62. package/dist/matterbridgeTypes.js.map +1 -0
  63. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  64. package/dist/matterbridgeWebsocket.js +45 -0
  65. package/dist/matterbridgeWebsocket.js.map +1 -0
  66. package/dist/pluginManager.d.ts +238 -0
  67. package/dist/pluginManager.d.ts.map +1 -0
  68. package/dist/pluginManager.js +238 -3
  69. package/dist/pluginManager.js.map +1 -0
  70. package/dist/storage/export.d.ts.map +1 -0
  71. package/dist/storage/export.js +1 -0
  72. package/dist/storage/export.js.map +1 -0
  73. package/dist/utils/colorUtils.d.ts.map +1 -0
  74. package/dist/utils/colorUtils.js +205 -2
  75. package/dist/utils/colorUtils.js.map +1 -0
  76. package/dist/utils/export.d.ts.map +1 -0
  77. package/dist/utils/export.js +1 -0
  78. package/dist/utils/export.js.map +1 -0
  79. package/dist/utils/utils.d.ts +221 -0
  80. package/dist/utils/utils.d.ts.map +1 -0
  81. package/dist/utils/utils.js +252 -7
  82. package/dist/utils/utils.js.map +1 -0
  83. package/npm-shrinkwrap.json +2 -2
  84. package/package.json +1 -1
@@ -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,36 @@ import EventEmitter from 'events';
6
29
  import os from 'os';
7
30
  import path from 'path';
8
31
  import { randomBytes } from 'crypto';
32
+ // Package modules
9
33
  import https from 'https';
10
34
  import express from 'express';
11
35
  import WebSocket, { WebSocketServer } from 'ws';
36
+ // NodeStorage and AnsiLogger modules
12
37
  import { NodeStorageManager } from 'node-persist-manager';
13
38
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
39
+ // Matterbridge
14
40
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
15
41
  import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
16
42
  import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
17
43
  import { PluginManager } from './pluginManager.js';
18
44
  import { DeviceManager } from './deviceManager.js';
19
45
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
46
+ // @matter
20
47
  import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer } 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
+ import { aggregator } from './matterbridgeDeviceTypes.js';
55
+ // Default colors
26
56
  const plg = '\u001B[38;5;33m';
27
57
  const dev = '\u001B[38;5;79m';
28
58
  const typ = '\u001B[38;5;207m';
59
+ /**
60
+ * Represents the Matterbridge application.
61
+ */
29
62
  export class Matterbridge extends EventEmitter {
30
63
  systemInformation = {
31
64
  interfaceName: '',
@@ -62,7 +95,7 @@ export class Matterbridge extends EventEmitter {
62
95
  edge: hasParameter('edge'),
63
96
  readOnly: hasParameter('readonly'),
64
97
  profile: getParameter('profile'),
65
- loggerLevel: "info",
98
+ loggerLevel: "info" /* LogLevel.INFO */,
66
99
  fileLogger: false,
67
100
  matterLoggerLevel: MatterLogLevel.INFO,
68
101
  matterFileLogger: false,
@@ -101,6 +134,7 @@ export class Matterbridge extends EventEmitter {
101
134
  nodeContext;
102
135
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
103
136
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
137
+ // Cleanup
104
138
  hasCleanupStarted = false;
105
139
  initialized = false;
106
140
  execRunningCount = 0;
@@ -112,16 +146,18 @@ export class Matterbridge extends EventEmitter {
112
146
  sigtermHandler;
113
147
  exceptionHandler;
114
148
  rejectionHandler;
149
+ // Frontend
115
150
  expressApp;
116
151
  httpServer;
117
152
  httpsServer;
118
153
  webSocketServer;
119
- mdnsInterface;
120
- ipv4address;
121
- ipv6address;
122
- port = 5540;
123
- passcode;
124
- discriminator;
154
+ // Matter
155
+ mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
156
+ ipv4address; // matter commissioning server listeningAddressIpv4
157
+ ipv6address; // matter commissioning server listeningAddressIpv6
158
+ port = 5540; // first commissioning server port
159
+ passcode; // first commissioning server passcode
160
+ discriminator; // first commissioning server discriminator
125
161
  storageManager;
126
162
  matterbridgeContext;
127
163
  mattercontrollerContext;
@@ -132,13 +168,26 @@ export class Matterbridge extends EventEmitter {
132
168
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
133
169
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
134
170
  static instance;
171
+ // We load asyncronously so is private
135
172
  constructor() {
136
173
  super();
174
+ // Bind the handler to the instance
137
175
  this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
138
176
  }
139
177
  matterbridgeMessageHandler;
178
+ /** ***********************************************************************************************************************************/
179
+ /** loadInstance() and cleanup() methods */
180
+ /** ***********************************************************************************************************************************/
181
+ /**
182
+ * Loads an instance of the Matterbridge class.
183
+ * If an instance already exists, return that instance.
184
+ *
185
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
186
+ * @returns The loaded Matterbridge instance.
187
+ */
140
188
  static async loadInstance(initialize = false) {
141
189
  if (!Matterbridge.instance) {
190
+ // eslint-disable-next-line no-console
142
191
  if (hasParameter('debug'))
143
192
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
144
193
  Matterbridge.instance = new Matterbridge();
@@ -147,6 +196,11 @@ export class Matterbridge extends EventEmitter {
147
196
  }
148
197
  return Matterbridge.instance;
149
198
  }
199
+ /**
200
+ * Call cleanup().
201
+ * @deprecated This method is deprecated and is only used for jest tests.
202
+ *
203
+ */
150
204
  async destroyInstance() {
151
205
  await this.cleanup('destroying instance...', false);
152
206
  await waiter('destroying instance...', () => {
@@ -154,39 +208,60 @@ export class Matterbridge extends EventEmitter {
154
208
  }, false, 60000, 100, false);
155
209
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
156
210
  }
211
+ /**
212
+ * Initializes the Matterbridge application.
213
+ *
214
+ * @remarks
215
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
216
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
217
+ * node version, registers signal handlers, initializes storage, and parses the command line.
218
+ *
219
+ * @returns A Promise that resolves when the initialization is complete.
220
+ */
157
221
  async initialize() {
222
+ // Set the restart mode
158
223
  if (hasParameter('service'))
159
224
  this.restartMode = 'service';
160
225
  if (hasParameter('docker'))
161
226
  this.restartMode = 'docker';
227
+ // Set the matterbridge directory
162
228
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
163
229
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
164
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
230
+ // Create matterbridge logger
231
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
232
+ // Initialize nodeStorage and nodeContext
165
233
  try {
166
234
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
167
235
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
168
236
  this.log.debug('Creating node storage context for matterbridge');
169
237
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
238
+ // TODO: Remove this code when node-persist-manager is updated
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
240
  const keys = (await this.nodeStorage?.storage.keys());
171
241
  for (const key of keys) {
172
242
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
244
  await this.nodeStorage?.storage.get(key);
174
245
  }
175
246
  const storages = await this.nodeStorage.getStorageNames();
176
247
  for (const storage of storages) {
177
248
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
178
249
  const nodeContext = await this.nodeStorage?.createStorage(storage);
250
+ // TODO: Remove this code when node-persist-manager is updated
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
252
  const keys = (await nodeContext?.storage.keys());
180
253
  keys.forEach(async (key) => {
181
254
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
182
255
  await nodeContext?.get(key);
183
256
  });
184
257
  }
258
+ // Creating a backup of the node storage since it is not corrupted
185
259
  this.log.debug('Creating node storage backup...');
186
260
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
187
261
  this.log.debug('Created node storage backup');
188
262
  }
189
263
  catch (error) {
264
+ // Restoring the backup of the node storage since it is corrupted
190
265
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
191
266
  if (hasParameter('norestore')) {
192
267
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -201,45 +276,51 @@ export class Matterbridge extends EventEmitter {
201
276
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
202
277
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
203
278
  }
279
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
204
280
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
281
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
205
282
  this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
283
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
206
284
  this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
207
285
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
286
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
208
287
  if (hasParameter('logger')) {
209
288
  const level = getParameter('logger');
210
289
  if (level === 'debug') {
211
- this.log.logLevel = "debug";
290
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
212
291
  }
213
292
  else if (level === 'info') {
214
- this.log.logLevel = "info";
293
+ this.log.logLevel = "info" /* LogLevel.INFO */;
215
294
  }
216
295
  else if (level === 'notice') {
217
- this.log.logLevel = "notice";
296
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
218
297
  }
219
298
  else if (level === 'warn') {
220
- this.log.logLevel = "warn";
299
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
221
300
  }
222
301
  else if (level === 'error') {
223
- this.log.logLevel = "error";
302
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
224
303
  }
225
304
  else if (level === 'fatal') {
226
- this.log.logLevel = "fatal";
305
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
227
306
  }
228
307
  else {
229
308
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
230
- this.log.logLevel = "info";
309
+ this.log.logLevel = "info" /* LogLevel.INFO */;
231
310
  }
232
311
  }
233
312
  else {
234
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
313
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
235
314
  }
236
315
  MatterbridgeDevice.logLevel = this.log.logLevel;
316
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
237
317
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
238
318
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
239
319
  this.matterbridgeInformation.fileLogger = true;
240
320
  }
241
321
  this.log.notice('Matterbridge is starting...');
242
322
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
323
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
243
324
  if (hasParameter('matterlogger')) {
244
325
  const level = getParameter('matterlogger');
245
326
  if (level === 'debug') {
@@ -270,6 +351,7 @@ export class Matterbridge extends EventEmitter {
270
351
  }
271
352
  Logger.format = MatterLogFormat.ANSI;
272
353
  Logger.setLogger('default', this.createMatterLogger());
354
+ // Create the file logger for matter.js (context: matterFileLog)
273
355
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
274
356
  this.matterbridgeInformation.matterFileLogger = true;
275
357
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -278,6 +360,7 @@ export class Matterbridge extends EventEmitter {
278
360
  });
279
361
  }
280
362
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
363
+ // Set the interface to use for the matter server mdnsInterface
281
364
  if (hasParameter('mdnsinterface')) {
282
365
  this.mdnsInterface = getParameter('mdnsinterface');
283
366
  }
@@ -286,6 +369,7 @@ export class Matterbridge extends EventEmitter {
286
369
  if (this.mdnsInterface === '')
287
370
  this.mdnsInterface = undefined;
288
371
  }
372
+ // Set the listeningAddressIpv4 for the matter commissioning server
289
373
  if (hasParameter('ipv4address')) {
290
374
  this.ipv4address = getParameter('ipv4address');
291
375
  }
@@ -294,6 +378,7 @@ export class Matterbridge extends EventEmitter {
294
378
  if (this.ipv4address === '')
295
379
  this.ipv4address = undefined;
296
380
  }
381
+ // Set the listeningAddressIpv6 for the matter commissioning server
297
382
  if (hasParameter('ipv6address')) {
298
383
  this.ipv6address = getParameter('ipv6address');
299
384
  }
@@ -302,17 +387,23 @@ export class Matterbridge extends EventEmitter {
302
387
  if (this.ipv6address === '')
303
388
  this.ipv6address = undefined;
304
389
  }
390
+ // Initialize PluginManager
305
391
  this.plugins = new PluginManager(this);
306
392
  await this.plugins.loadFromStorage();
393
+ // Initialize DeviceManager
307
394
  this.devices = new DeviceManager(this, this.nodeContext);
395
+ // Get the plugins from node storage and create the plugins node storage contexts
308
396
  for (const plugin of this.plugins) {
309
397
  const packageJson = await this.plugins.parse(plugin);
310
398
  if (packageJson === null && !hasParameter('add')) {
399
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
400
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
311
401
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
312
402
  try {
313
403
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
314
404
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
315
405
  plugin.error = false;
406
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
316
407
  }
317
408
  catch (error) {
318
409
  plugin.error = true;
@@ -329,6 +420,7 @@ export class Matterbridge extends EventEmitter {
329
420
  await plugin.nodeContext.set('description', plugin.description);
330
421
  await plugin.nodeContext.set('author', plugin.author);
331
422
  }
423
+ // Log system info and create .matterbridge directory
332
424
  await this.logNodeAndSystemInfo();
333
425
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
334
426
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -336,6 +428,7 @@ export class Matterbridge extends EventEmitter {
336
428
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
337
429
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
338
430
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
431
+ // Check node version and throw error
339
432
  const minNodeVersion = 18;
340
433
  const nodeVersion = process.versions.node;
341
434
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -343,10 +436,17 @@ export class Matterbridge extends EventEmitter {
343
436
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
344
437
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
345
438
  }
439
+ // Register process handlers
346
440
  this.registerProcessHandlers();
441
+ // Parse command line
347
442
  await this.parseCommandLine();
348
443
  this.initialized = true;
349
444
  }
445
+ /**
446
+ * Parses the command line arguments and performs the corresponding actions.
447
+ * @private
448
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
449
+ */
350
450
  async parseCommandLine() {
351
451
  if (hasParameter('help')) {
352
452
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -454,12 +554,14 @@ export class Matterbridge extends EventEmitter {
454
554
  }
455
555
  if (hasParameter('factoryreset')) {
456
556
  try {
557
+ // Delete matter storage file
457
558
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
458
559
  }
459
560
  catch (err) {
460
561
  this.log.error(`Error deleting storage: ${err}`);
461
562
  }
462
563
  try {
564
+ // Delete node storage directory with its subdirectories
463
565
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
464
566
  }
465
567
  catch (err) {
@@ -473,6 +575,7 @@ export class Matterbridge extends EventEmitter {
473
575
  this.emit('shutdown');
474
576
  return;
475
577
  }
578
+ // Start the matter storage and create the matterbridge context
476
579
  try {
477
580
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
478
581
  }
@@ -507,28 +610,34 @@ export class Matterbridge extends EventEmitter {
507
610
  this.emit('shutdown');
508
611
  return;
509
612
  }
613
+ // Initialize frontend
510
614
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
511
615
  await this.initializeFrontend(getIntParameter('frontend'));
616
+ // Check each 60 minutes the latest versions
512
617
  this.checkUpdateInterval = setInterval(() => {
513
618
  this.getMatterbridgeLatestVersion();
514
619
  for (const plugin of this.plugins) {
515
620
  this.getPluginLatestVersion(plugin);
516
621
  }
517
622
  }, 60 * 60 * 1000);
623
+ // Start the matterbridge in mode test
518
624
  if (hasParameter('test')) {
519
625
  this.bridgeMode = 'bridge';
520
626
  MatterbridgeDevice.bridgeMode = 'bridge';
521
627
  return;
522
628
  }
629
+ // Start the matterbridge in mode controller
523
630
  if (hasParameter('controller')) {
524
631
  this.bridgeMode = 'controller';
525
632
  await this.startController();
526
633
  return;
527
634
  }
635
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
528
636
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
529
637
  this.log.info('Setting default matterbridge start mode to bridge');
530
638
  await this.nodeContext?.set('bridgeMode', 'bridge');
531
639
  }
640
+ // Start matterbridge in bridge mode
532
641
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
533
642
  this.bridgeMode = 'bridge';
534
643
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -537,6 +646,7 @@ export class Matterbridge extends EventEmitter {
537
646
  await this.startBridge();
538
647
  return;
539
648
  }
649
+ // Start matterbridge in childbridge mode
540
650
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
541
651
  this.bridgeMode = 'childbridge';
542
652
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -546,17 +656,28 @@ export class Matterbridge extends EventEmitter {
546
656
  return;
547
657
  }
548
658
  }
659
+ /**
660
+ * Asynchronously loads and starts the registered plugins.
661
+ *
662
+ * This method is responsible for initializing and staarting all enabled plugins.
663
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
664
+ *
665
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
666
+ */
549
667
  async startPlugins() {
668
+ // Check, load and start the plugins
550
669
  for (const plugin of this.plugins) {
551
670
  plugin.configJson = await this.plugins.loadConfig(plugin);
552
671
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
672
+ // Check if the plugin is available
553
673
  if (!(await this.plugins.resolve(plugin.path))) {
554
674
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
555
675
  plugin.enabled = false;
556
676
  plugin.error = true;
557
677
  continue;
558
678
  }
559
- this.getPluginLatestVersion(plugin);
679
+ // Check if the plugin has a new version
680
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
560
681
  if (!plugin.enabled) {
561
682
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
562
683
  continue;
@@ -571,20 +692,26 @@ export class Matterbridge extends EventEmitter {
571
692
  plugin.addedDevices = undefined;
572
693
  plugin.qrPairingCode = undefined;
573
694
  plugin.manualPairingCode = undefined;
574
- this.plugins.load(plugin, true, 'Matterbridge is starting');
695
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
575
696
  }
576
697
  this.wssSendRefreshRequired();
577
698
  }
699
+ /**
700
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
701
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
702
+ */
578
703
  registerProcessHandlers() {
579
704
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
580
705
  process.removeAllListeners('uncaughtException');
581
706
  process.removeAllListeners('unhandledRejection');
582
707
  this.exceptionHandler = async (error) => {
583
708
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
709
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
584
710
  };
585
711
  process.on('uncaughtException', this.exceptionHandler);
586
712
  this.rejectionHandler = async (reason, promise) => {
587
713
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
714
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
588
715
  };
589
716
  process.on('unhandledRejection', this.rejectionHandler);
590
717
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -597,6 +724,9 @@ export class Matterbridge extends EventEmitter {
597
724
  };
598
725
  process.on('SIGTERM', this.sigtermHandler);
599
726
  }
727
+ /**
728
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
729
+ */
600
730
  deregisterProcesslHandlers() {
601
731
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
602
732
  if (this.exceptionHandler)
@@ -613,7 +743,11 @@ export class Matterbridge extends EventEmitter {
613
743
  process.off('SIGTERM', this.sigtermHandler);
614
744
  this.sigtermHandler = undefined;
615
745
  }
746
+ /**
747
+ * Logs the node and system information.
748
+ */
616
749
  async logNodeAndSystemInfo() {
750
+ // IP address information
617
751
  const networkInterfaces = os.networkInterfaces();
618
752
  this.systemInformation.ipv4Address = '';
619
753
  this.systemInformation.ipv6Address = '';
@@ -633,7 +767,7 @@ export class Matterbridge extends EventEmitter {
633
767
  this.systemInformation.macAddress = detail.mac;
634
768
  }
635
769
  }
636
- if (this.systemInformation.ipv4Address !== '') {
770
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
637
771
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
638
772
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
639
773
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -641,19 +775,22 @@ export class Matterbridge extends EventEmitter {
641
775
  break;
642
776
  }
643
777
  }
778
+ // Node information
644
779
  this.systemInformation.nodeVersion = process.versions.node;
645
780
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
646
781
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
647
782
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
783
+ // Host system information
648
784
  this.systemInformation.hostname = os.hostname();
649
785
  this.systemInformation.user = os.userInfo().username;
650
- this.systemInformation.osType = os.type();
651
- this.systemInformation.osRelease = os.release();
652
- this.systemInformation.osPlatform = os.platform();
653
- this.systemInformation.osArch = os.arch();
654
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
655
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
656
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
786
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
787
+ this.systemInformation.osRelease = os.release(); // Kernel version
788
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
789
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
790
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
791
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
792
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
793
+ // Log the system information
657
794
  this.log.debug('Host System Information:');
658
795
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
659
796
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -669,15 +806,19 @@ export class Matterbridge extends EventEmitter {
669
806
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
670
807
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
671
808
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
809
+ // Home directory
672
810
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
673
811
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
674
812
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
813
+ // Package root directory
675
814
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
676
815
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
677
816
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
678
817
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
818
+ // Global node_modules directory
679
819
  if (this.nodeContext)
680
820
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
821
+ // First run of Matterbridge so the node storage is empty
681
822
  if (this.globalModulesDirectory === '') {
682
823
  try {
683
824
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -701,6 +842,7 @@ export class Matterbridge extends EventEmitter {
701
842
  this.log.error(`Error getting global node_modules directory: ${error}`);
702
843
  });
703
844
  }
845
+ // Create the data directory .matterbridge in the home directory
704
846
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
705
847
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
706
848
  try {
@@ -724,6 +866,7 @@ export class Matterbridge extends EventEmitter {
724
866
  }
725
867
  }
726
868
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
869
+ // Create the plugin directory Matterbridge in the home directory
727
870
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
728
871
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
729
872
  try {
@@ -747,19 +890,28 @@ export class Matterbridge extends EventEmitter {
747
890
  }
748
891
  }
749
892
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
893
+ // Matterbridge version
750
894
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
751
895
  this.matterbridgeVersion = packageJson.version;
752
896
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
753
897
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
898
+ // Matterbridge latest version
754
899
  if (this.nodeContext)
755
900
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
756
901
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
757
902
  this.getMatterbridgeLatestVersion();
903
+ // Current working directory
758
904
  const currentDir = process.cwd();
759
905
  this.log.debug(`Current Working Directory: ${currentDir}`);
906
+ // Command line arguments (excluding 'node' and the script name)
760
907
  const cmdArgs = process.argv.slice(2).join(' ');
761
908
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
762
909
  }
910
+ /**
911
+ * Retrieves the latest version of a package from the npm registry.
912
+ * @param packageName - The name of the package.
913
+ * @returns A Promise that resolves to the latest version of the package.
914
+ */
763
915
  async getLatestVersion(packageName) {
764
916
  return new Promise((resolve, reject) => {
765
917
  this.execRunningCount++;
@@ -774,6 +926,10 @@ export class Matterbridge extends EventEmitter {
774
926
  });
775
927
  });
776
928
  }
929
+ /**
930
+ * Retrieves the path to the global Node.js modules directory.
931
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
932
+ */
777
933
  async getGlobalNodeModules() {
778
934
  return new Promise((resolve, reject) => {
779
935
  this.execRunningCount++;
@@ -788,6 +944,11 @@ export class Matterbridge extends EventEmitter {
788
944
  });
789
945
  });
790
946
  }
947
+ /**
948
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
949
+ * @private
950
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
951
+ */
791
952
  async getMatterbridgeLatestVersion() {
792
953
  this.getLatestVersion('matterbridge')
793
954
  .then(async (matterbridgeLatestVersion) => {
@@ -804,8 +965,19 @@ export class Matterbridge extends EventEmitter {
804
965
  })
805
966
  .catch((error) => {
806
967
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
968
+ // error.stack && this.log.debug(error.stack);
807
969
  });
808
970
  }
971
+ /**
972
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
973
+ * If the plugin's version is different from the latest version, logs a warning message.
974
+ * If the plugin's version is the same as the latest version, logs an info message.
975
+ * If there is an error retrieving the latest version, logs an error message.
976
+ *
977
+ * @private
978
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
979
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
980
+ */
809
981
  async getPluginLatestVersion(plugin) {
810
982
  this.getLatestVersion(plugin.name)
811
983
  .then(async (latestVersion) => {
@@ -817,40 +989,54 @@ export class Matterbridge extends EventEmitter {
817
989
  })
818
990
  .catch((error) => {
819
991
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
992
+ // error.stack && this.log.debug(error.stack);
820
993
  });
821
994
  }
995
+ /**
996
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
997
+ *
998
+ * @returns {Function} The MatterLogger function.
999
+ */
822
1000
  createMatterLogger() {
823
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1001
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
824
1002
  return (_level, formattedLog) => {
825
1003
  const logger = formattedLog.slice(44, 44 + 20).trim();
826
1004
  const message = formattedLog.slice(65);
827
1005
  matterLogger.logName = logger;
828
1006
  switch (_level) {
829
1007
  case MatterLogLevel.DEBUG:
830
- matterLogger.log("debug", message);
1008
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
831
1009
  break;
832
1010
  case MatterLogLevel.INFO:
833
- matterLogger.log("info", message);
1011
+ matterLogger.log("info" /* LogLevel.INFO */, message);
834
1012
  break;
835
1013
  case MatterLogLevel.NOTICE:
836
- matterLogger.log("notice", message);
1014
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
837
1015
  break;
838
1016
  case MatterLogLevel.WARN:
839
- matterLogger.log("warn", message);
1017
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
840
1018
  break;
841
1019
  case MatterLogLevel.ERROR:
842
- matterLogger.log("error", message);
1020
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
843
1021
  break;
844
1022
  case MatterLogLevel.FATAL:
845
- matterLogger.log("fatal", message);
1023
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
846
1024
  break;
847
1025
  default:
848
- matterLogger.log("debug", message);
1026
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
849
1027
  break;
850
1028
  }
851
1029
  };
852
1030
  }
1031
+ /**
1032
+ * Creates a Matter File Logger.
1033
+ *
1034
+ * @param {string} filePath - The path to the log file.
1035
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1036
+ * @returns {Function} - A function that logs formatted messages to the log file.
1037
+ */
853
1038
  async createMatterFileLogger(filePath, unlink = false) {
1039
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
854
1040
  let fileSize = 0;
855
1041
  if (unlink) {
856
1042
  try {
@@ -899,53 +1085,83 @@ export class Matterbridge extends EventEmitter {
899
1085
  }
900
1086
  };
901
1087
  }
1088
+ /**
1089
+ * Update matterbridge and cleanup.
1090
+ */
902
1091
  async updateProcess() {
903
1092
  await this.cleanup('updating...', false);
904
1093
  }
1094
+ /**
1095
+ * Restarts the process by spawning a new process and exiting the current process.
1096
+ */
905
1097
  async restartProcess() {
906
1098
  await this.cleanup('restarting...', true);
907
1099
  }
1100
+ /**
1101
+ * Shut down the process by exiting the current process.
1102
+ */
908
1103
  async shutdownProcess() {
909
1104
  await this.cleanup('shutting down...', false);
910
1105
  }
1106
+ /**
1107
+ * Shut down the process and reset.
1108
+ */
911
1109
  async unregisterAndShutdownProcess() {
912
1110
  this.log.info('Unregistering all devices and shutting down...');
913
- for (const plugin of this.plugins) {
1111
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
914
1112
  await this.removeAllBridgedDevices(plugin.name);
915
1113
  }
916
1114
  await this.cleanup('unregistered all devices and shutting down...', false);
917
1115
  }
1116
+ /**
1117
+ * Shut down the process and reset.
1118
+ */
918
1119
  async shutdownProcessAndReset() {
919
1120
  await this.cleanup('shutting down with reset...', false);
920
1121
  }
1122
+ /**
1123
+ * Shut down the process and factory reset.
1124
+ */
921
1125
  async shutdownProcessAndFactoryReset() {
922
1126
  await this.cleanup('shutting down with factory reset...', false);
923
1127
  }
1128
+ /**
1129
+ * Cleans up the Matterbridge instance.
1130
+ * @param message - The cleanup message.
1131
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1132
+ * @returns A promise that resolves when the cleanup is completed.
1133
+ */
924
1134
  async cleanup(message, restart = false) {
925
1135
  if (this.initialized && !this.hasCleanupStarted) {
926
1136
  this.hasCleanupStarted = true;
927
1137
  this.log.info(message);
1138
+ // Deregisters the process handlers
928
1139
  this.deregisterProcesslHandlers();
1140
+ // Clear the start matter interval
929
1141
  if (this.startMatterInterval) {
930
1142
  clearInterval(this.startMatterInterval);
931
1143
  this.startMatterInterval = undefined;
932
1144
  this.log.debug('Start matter interval cleared');
933
1145
  }
1146
+ // Clear the check update interval
934
1147
  if (this.checkUpdateInterval) {
935
1148
  clearInterval(this.checkUpdateInterval);
936
1149
  this.checkUpdateInterval = undefined;
937
1150
  this.log.debug('Check update interval cleared');
938
1151
  }
1152
+ // Clear the configure timeout
939
1153
  if (this.configureTimeout) {
940
1154
  clearTimeout(this.configureTimeout);
941
1155
  this.configureTimeout = undefined;
942
1156
  this.log.debug('Matterbridge configure timeout cleared');
943
1157
  }
1158
+ // Clear the reachability timeout
944
1159
  if (this.reachabilityTimeout) {
945
1160
  clearTimeout(this.reachabilityTimeout);
946
1161
  this.reachabilityTimeout = undefined;
947
1162
  this.log.debug('Matterbridge reachability timeout cleared');
948
1163
  }
1164
+ // Calling the shutdown method of each plugin and clear the reachability timeout
949
1165
  for (const plugin of this.plugins) {
950
1166
  if (!plugin.enabled || plugin.error)
951
1167
  continue;
@@ -956,24 +1172,29 @@ export class Matterbridge extends EventEmitter {
956
1172
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
957
1173
  }
958
1174
  }
1175
+ // Close the http server
959
1176
  if (this.httpServer) {
960
1177
  this.httpServer.close();
961
1178
  this.httpServer.removeAllListeners();
962
1179
  this.httpServer = undefined;
963
1180
  this.log.debug('Frontend http server closed successfully');
964
1181
  }
1182
+ // Close the https server
965
1183
  if (this.httpsServer) {
966
1184
  this.httpsServer.close();
967
1185
  this.httpsServer.removeAllListeners();
968
1186
  this.httpsServer = undefined;
969
1187
  this.log.debug('Frontend https server closed successfully');
970
1188
  }
1189
+ // Remove listeners from the express app
971
1190
  if (this.expressApp) {
972
1191
  this.expressApp.removeAllListeners();
973
1192
  this.expressApp = undefined;
974
1193
  this.log.debug('Frontend app closed successfully');
975
1194
  }
1195
+ // Close the WebSocket server
976
1196
  if (this.webSocketServer) {
1197
+ // Close all active connections
977
1198
  this.webSocketServer.clients.forEach((client) => {
978
1199
  if (client.readyState === WebSocket.OPEN) {
979
1200
  client.close();
@@ -989,26 +1210,35 @@ export class Matterbridge extends EventEmitter {
989
1210
  });
990
1211
  this.webSocketServer = undefined;
991
1212
  }
1213
+ // Closing matter
992
1214
  await this.stopMatterServer();
1215
+ // Closing matter storage
993
1216
  await this.stopMatterStorage();
1217
+ // Remove the matterfilelogger
994
1218
  try {
995
1219
  Logger.removeLogger('matterfilelogger');
1220
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
996
1221
  }
997
1222
  catch (error) {
1223
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
998
1224
  }
1225
+ // Serialize registeredDevices
999
1226
  if (this.nodeStorage && this.nodeContext) {
1000
1227
  this.log.info('Saving registered devices...');
1001
1228
  const serializedRegisteredDevices = [];
1002
1229
  this.devices.forEach(async (device) => {
1003
1230
  const serializedMatterbridgeDevice = device.serialize();
1231
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1004
1232
  if (serializedMatterbridgeDevice)
1005
1233
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1006
1234
  });
1007
1235
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1008
1236
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1237
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1009
1238
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1010
1239
  await this.nodeContext.close();
1011
1240
  this.nodeContext = undefined;
1241
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1012
1242
  for (const plugin of this.plugins) {
1013
1243
  if (plugin.nodeContext) {
1014
1244
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1039,13 +1269,16 @@ export class Matterbridge extends EventEmitter {
1039
1269
  }
1040
1270
  else {
1041
1271
  if (message === 'shutting down with reset...') {
1272
+ // Delete matter storage file
1042
1273
  this.log.info('Resetting Matterbridge commissioning information...');
1043
1274
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1044
1275
  this.log.info('Reset done! Remove all paired devices from the controllers.');
1045
1276
  }
1046
1277
  if (message === 'shutting down with factory reset...') {
1278
+ // Delete matter storage file
1047
1279
  this.log.info('Resetting Matterbridge commissioning information...');
1048
1280
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1281
+ // Delete node storage directory with its subdirectories
1049
1282
  this.log.info('Resetting Matterbridge storage...');
1050
1283
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1051
1284
  this.log.info('Factory reset done! Remove all paired devices from the controllers.');
@@ -1058,19 +1291,33 @@ export class Matterbridge extends EventEmitter {
1058
1291
  this.initialized = false;
1059
1292
  }
1060
1293
  }
1294
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1061
1295
  async addBridgedEndpoint(pluginName, device) {
1296
+ // Nothing to do here
1062
1297
  }
1298
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1063
1299
  async removeBridgedEndpoint(pluginName, device) {
1300
+ // Nothing to do here
1064
1301
  }
1302
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1065
1303
  async removeAllBridgedEndpoints(pluginName) {
1304
+ // Nothing to do here
1066
1305
  }
1306
+ /**
1307
+ * Adds a bridged device to the Matterbridge.
1308
+ * @param pluginName - The name of the plugin.
1309
+ * @param device - The bridged device to add.
1310
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1311
+ */
1067
1312
  async addBridgedDevice(pluginName, device) {
1068
1313
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1314
+ // Check if the plugin is registered
1069
1315
  const plugin = this.plugins.get(pluginName);
1070
1316
  if (!plugin) {
1071
1317
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1072
1318
  return;
1073
1319
  }
1320
+ // Register and add the device to matterbridge aggregator in bridge mode
1074
1321
  if (this.bridgeMode === 'bridge') {
1075
1322
  if (!this.matterAggregator) {
1076
1323
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1078,8 +1325,11 @@ export class Matterbridge extends EventEmitter {
1078
1325
  }
1079
1326
  this.matterAggregator.addBridgedDevice(device);
1080
1327
  }
1328
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1329
+ // Register and add the device in childbridge mode
1081
1330
  if (this.bridgeMode === 'childbridge') {
1082
1331
  if (plugin.type === 'AccessoryPlatform') {
1332
+ // Check if the plugin is locked with the commissioning server
1083
1333
  if (!plugin.locked) {
1084
1334
  plugin.locked = true;
1085
1335
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1093,6 +1343,7 @@ export class Matterbridge extends EventEmitter {
1093
1343
  }
1094
1344
  }
1095
1345
  if (plugin.type === 'DynamicPlatform') {
1346
+ // Check if the plugin is locked with the commissioning server and the aggregator
1096
1347
  if (!plugin.locked) {
1097
1348
  plugin.locked = true;
1098
1349
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1113,16 +1364,25 @@ export class Matterbridge extends EventEmitter {
1113
1364
  plugin.registeredDevices++;
1114
1365
  if (plugin.addedDevices !== undefined)
1115
1366
  plugin.addedDevices++;
1367
+ // Add the device to the DeviceManager
1116
1368
  this.devices.set(device);
1117
1369
  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}`);
1118
1370
  }
1371
+ /**
1372
+ * Removes a bridged device from the Matterbridge.
1373
+ * @param pluginName - The name of the plugin.
1374
+ * @param device - The device to be removed.
1375
+ * @returns A Promise that resolves when the device is successfully removed.
1376
+ */
1119
1377
  async removeBridgedDevice(pluginName, device) {
1120
1378
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1379
+ // Check if the plugin is registered
1121
1380
  const plugin = this.plugins.get(pluginName);
1122
1381
  if (!plugin) {
1123
1382
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1124
1383
  return;
1125
1384
  }
1385
+ // Remove the device from matterbridge aggregator in bridge mode
1126
1386
  if (this.bridgeMode === 'bridge') {
1127
1387
  if (!this.matterAggregator) {
1128
1388
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1132,6 +1392,8 @@ export class Matterbridge extends EventEmitter {
1132
1392
  device.setBridgedDeviceReachability(false);
1133
1393
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1134
1394
  }
1395
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1396
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1135
1397
  this.matterAggregator?.removeBridgedDevice(device);
1136
1398
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1137
1399
  if (plugin.registeredDevices !== undefined)
@@ -1139,6 +1401,7 @@ export class Matterbridge extends EventEmitter {
1139
1401
  if (plugin.addedDevices !== undefined)
1140
1402
  plugin.addedDevices--;
1141
1403
  }
1404
+ // Remove the device in childbridge mode
1142
1405
  if (this.bridgeMode === 'childbridge') {
1143
1406
  if (plugin.type === 'AccessoryPlatform') {
1144
1407
  if (!plugin.commissioningServer) {
@@ -1162,14 +1425,22 @@ export class Matterbridge extends EventEmitter {
1162
1425
  plugin.registeredDevices--;
1163
1426
  if (plugin.addedDevices !== undefined)
1164
1427
  plugin.addedDevices--;
1428
+ // Remove the commissioning server
1165
1429
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1166
1430
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1167
1431
  plugin.commissioningServer = undefined;
1168
1432
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1169
1433
  }
1170
1434
  }
1435
+ // Remove the device from the DeviceManager
1171
1436
  this.devices.remove(device);
1172
1437
  }
1438
+ /**
1439
+ * Removes all bridged devices associated with a specific plugin.
1440
+ *
1441
+ * @param pluginName - The name of the plugin.
1442
+ * @returns A promise that resolves when all devices have been removed.
1443
+ */
1173
1444
  async removeAllBridgedDevices(pluginName) {
1174
1445
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1175
1446
  this.devices.forEach(async (device) => {
@@ -1178,7 +1449,13 @@ export class Matterbridge extends EventEmitter {
1178
1449
  }
1179
1450
  });
1180
1451
  }
1452
+ /**
1453
+ * Starts the Matterbridge in bridge mode.
1454
+ * @private
1455
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1456
+ */
1181
1457
  async startBridge() {
1458
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1182
1459
  if (!this.storageManager)
1183
1460
  throw new Error('No storage manager initialized');
1184
1461
  if (!this.matterbridgeContext)
@@ -1197,6 +1474,7 @@ export class Matterbridge extends EventEmitter {
1197
1474
  let failCount = 0;
1198
1475
  this.startMatterInterval = setInterval(async () => {
1199
1476
  for (const plugin of this.plugins) {
1477
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1200
1478
  if (!plugin.enabled)
1201
1479
  continue;
1202
1480
  if (plugin.error) {
@@ -1221,15 +1499,18 @@ export class Matterbridge extends EventEmitter {
1221
1499
  clearInterval(this.startMatterInterval);
1222
1500
  this.startMatterInterval = undefined;
1223
1501
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1502
+ // Start the Matter server
1224
1503
  await this.startMatterServer();
1225
1504
  this.log.notice('Matter server started');
1505
+ // Show the QR code for commissioning or log the already commissioned message
1226
1506
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1507
+ // Configure the plugins
1227
1508
  this.configureTimeout = setTimeout(async () => {
1228
1509
  for (const plugin of this.plugins) {
1229
1510
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1230
1511
  continue;
1231
1512
  try {
1232
- await this.plugins.configure(plugin);
1513
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1233
1514
  }
1234
1515
  catch (error) {
1235
1516
  plugin.error = true;
@@ -1238,6 +1519,7 @@ export class Matterbridge extends EventEmitter {
1238
1519
  }
1239
1520
  this.wssSendRefreshRequired();
1240
1521
  }, 30 * 1000);
1522
+ // Setting reachability to true
1241
1523
  this.reachabilityTimeout = setTimeout(() => {
1242
1524
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1243
1525
  if (this.commissioningServer)
@@ -1247,7 +1529,14 @@ export class Matterbridge extends EventEmitter {
1247
1529
  }, 60 * 1000);
1248
1530
  }, 1000);
1249
1531
  }
1532
+ /**
1533
+ * Starts the Matterbridge in childbridge mode.
1534
+ * @private
1535
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1536
+ */
1250
1537
  async startChildbridge() {
1538
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1539
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1251
1540
  if (!this.storageManager)
1252
1541
  throw new Error('No storage manager initialized');
1253
1542
  this.matterServer = this.createMatterServer(this.storageManager);
@@ -1257,6 +1546,7 @@ export class Matterbridge extends EventEmitter {
1257
1546
  this.startMatterInterval = setInterval(async () => {
1258
1547
  let allStarted = true;
1259
1548
  for (const plugin of this.plugins) {
1549
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1260
1550
  if (!plugin.enabled)
1261
1551
  continue;
1262
1552
  if (plugin.error) {
@@ -1284,14 +1574,16 @@ export class Matterbridge extends EventEmitter {
1284
1574
  clearInterval(this.startMatterInterval);
1285
1575
  this.startMatterInterval = undefined;
1286
1576
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1577
+ // Start the Matter server
1287
1578
  await this.startMatterServer();
1288
1579
  this.log.notice('Matter server started');
1580
+ // Configure the plugins
1289
1581
  this.configureTimeout = setTimeout(async () => {
1290
1582
  for (const plugin of this.plugins) {
1291
1583
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1292
1584
  continue;
1293
1585
  try {
1294
- await this.plugins.configure(plugin);
1586
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1295
1587
  }
1296
1588
  catch (error) {
1297
1589
  plugin.error = true;
@@ -1320,6 +1612,7 @@ export class Matterbridge extends EventEmitter {
1320
1612
  continue;
1321
1613
  }
1322
1614
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1615
+ // Setting reachability to true
1323
1616
  plugin.reachabilityTimeout = setTimeout(() => {
1324
1617
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1325
1618
  if (plugin.commissioningServer)
@@ -1332,6 +1625,11 @@ export class Matterbridge extends EventEmitter {
1332
1625
  }
1333
1626
  }, 1000);
1334
1627
  }
1628
+ /**
1629
+ * Starts the Matterbridge controller.
1630
+ * @private
1631
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1632
+ */
1335
1633
  async startController() {
1336
1634
  if (!this.storageManager) {
1337
1635
  this.log.error('No storage manager initialized');
@@ -1394,7 +1692,7 @@ export class Matterbridge extends EventEmitter {
1394
1692
  const nodeId = await this.commissioningController.commissionNode(options);
1395
1693
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1396
1694
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1397
- }
1695
+ } // (hasParameter('pairingcode'))
1398
1696
  if (hasParameter('unpairall')) {
1399
1697
  this.log.info('***Commissioning controller unpairing all nodes...');
1400
1698
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1405,6 +1703,8 @@ export class Matterbridge extends EventEmitter {
1405
1703
  return;
1406
1704
  }
1407
1705
  if (hasParameter('discover')) {
1706
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1707
+ // console.log(discover);
1408
1708
  }
1409
1709
  if (!this.commissioningController.isCommissioned()) {
1410
1710
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1445,10 +1745,12 @@ export class Matterbridge extends EventEmitter {
1445
1745
  },
1446
1746
  });
1447
1747
  node.logStructure();
1748
+ // Get the interaction client
1448
1749
  this.log.info('Getting the interaction client');
1449
1750
  const interactionClient = await node.getInteractionClient();
1450
1751
  let cluster;
1451
1752
  let attributes;
1753
+ // Log BasicInformationCluster
1452
1754
  cluster = BasicInformationCluster;
1453
1755
  attributes = await interactionClient.getMultipleAttributes({
1454
1756
  attributes: [{ clusterId: cluster.id }],
@@ -1458,6 +1760,7 @@ export class Matterbridge extends EventEmitter {
1458
1760
  attributes.forEach((attribute) => {
1459
1761
  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}`);
1460
1762
  });
1763
+ // Log PowerSourceCluster
1461
1764
  cluster = PowerSourceCluster;
1462
1765
  attributes = await interactionClient.getMultipleAttributes({
1463
1766
  attributes: [{ clusterId: cluster.id }],
@@ -1467,6 +1770,7 @@ export class Matterbridge extends EventEmitter {
1467
1770
  attributes.forEach((attribute) => {
1468
1771
  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}`);
1469
1772
  });
1773
+ // Log ThreadNetworkDiagnostics
1470
1774
  cluster = ThreadNetworkDiagnosticsCluster;
1471
1775
  attributes = await interactionClient.getMultipleAttributes({
1472
1776
  attributes: [{ clusterId: cluster.id }],
@@ -1476,6 +1780,7 @@ export class Matterbridge extends EventEmitter {
1476
1780
  attributes.forEach((attribute) => {
1477
1781
  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}`);
1478
1782
  });
1783
+ // Log SwitchCluster
1479
1784
  cluster = SwitchCluster;
1480
1785
  attributes = await interactionClient.getMultipleAttributes({
1481
1786
  attributes: [{ clusterId: cluster.id }],
@@ -1496,6 +1801,15 @@ export class Matterbridge extends EventEmitter {
1496
1801
  this.log.info('Subscribed to all attributes and events');
1497
1802
  }
1498
1803
  }
1804
+ /** ***********************************************************************************************************************************/
1805
+ /** Matter.js methods */
1806
+ /** ***********************************************************************************************************************************/
1807
+ /**
1808
+ * Starts the matter storage process based on the specified storage type and name.
1809
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1810
+ * @param {string} storageName - The name of the storage file.
1811
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1812
+ */
1499
1813
  async startMatterStorage(storageType, storageName) {
1500
1814
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1501
1815
  if (storageType === 'disk') {
@@ -1539,8 +1853,14 @@ export class Matterbridge extends EventEmitter {
1539
1853
  }
1540
1854
  }
1541
1855
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1542
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1856
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', aggregator.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1543
1857
  }
1858
+ /**
1859
+ * Makes a backup copy of the specified matter JSON storage file.
1860
+ *
1861
+ * @param storageName - The name of the JSON storage file to be backed up.
1862
+ * @param backupName - The name of the backup file to be created.
1863
+ */
1544
1864
  async backupMatterStorage(storageName, backupName) {
1545
1865
  try {
1546
1866
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1561,6 +1881,12 @@ export class Matterbridge extends EventEmitter {
1561
1881
  }
1562
1882
  }
1563
1883
  }
1884
+ /**
1885
+ * Restore the specified matter JSON storage file.
1886
+ *
1887
+ * @param backupName - The name of the backup file to restore from.
1888
+ * @param storageName - The name of the JSON storage file to restored.
1889
+ */
1564
1890
  async restoreMatterStorage(backupName, storageName) {
1565
1891
  try {
1566
1892
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1581,6 +1907,10 @@ export class Matterbridge extends EventEmitter {
1581
1907
  }
1582
1908
  }
1583
1909
  }
1910
+ /**
1911
+ * Stops the matter storage.
1912
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1913
+ */
1584
1914
  async stopMatterStorage() {
1585
1915
  this.log.debug('Stopping storage');
1586
1916
  await this.storageManager?.close();
@@ -1589,8 +1919,14 @@ export class Matterbridge extends EventEmitter {
1589
1919
  this.matterbridgeContext = undefined;
1590
1920
  this.mattercontrollerContext = undefined;
1591
1921
  }
1922
+ /**
1923
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
1924
+ * @param storageManager The storage manager to be used by the Matter server.
1925
+ *
1926
+ */
1592
1927
  createMatterServer(storageManager) {
1593
1928
  this.log.debug('Creating matter server');
1929
+ // Validate mdnsInterface
1594
1930
  if (this.mdnsInterface) {
1595
1931
  const networkInterfaces = os.networkInterfaces();
1596
1932
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1606,6 +1942,10 @@ export class Matterbridge extends EventEmitter {
1606
1942
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1607
1943
  return matterServer;
1608
1944
  }
1945
+ /**
1946
+ * Starts the Matter server.
1947
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1948
+ */
1609
1949
  async startMatterServer() {
1610
1950
  if (!this.matterServer) {
1611
1951
  this.log.error('No matter server initialized');
@@ -1615,7 +1955,11 @@ export class Matterbridge extends EventEmitter {
1615
1955
  this.log.debug('Starting matter server...');
1616
1956
  await this.matterServer.start();
1617
1957
  this.log.debug('Started matter server');
1958
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1618
1959
  }
1960
+ /**
1961
+ * Stops the Matter server, commissioningServer and commissioningController.
1962
+ */
1619
1963
  async stopMatterServer() {
1620
1964
  this.log.debug('Stopping matter commissioningServer');
1621
1965
  await this.commissioningServer?.close();
@@ -1629,23 +1973,35 @@ export class Matterbridge extends EventEmitter {
1629
1973
  this.matterAggregator = undefined;
1630
1974
  this.matterServer = undefined;
1631
1975
  }
1976
+ /**
1977
+ * Creates a Matter Aggregator.
1978
+ * @param {StorageContext} context - The storage context.
1979
+ * @returns {Aggregator} - The created Matter Aggregator.
1980
+ */
1632
1981
  async createMatterAggregator(context, pluginName) {
1633
1982
  this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
1634
1983
  const matterAggregator = new Aggregator();
1635
1984
  return matterAggregator;
1636
1985
  }
1986
+ /**
1987
+ * Creates a matter commissioning server.
1988
+ *
1989
+ * @param {StorageContext} context - The storage context.
1990
+ * @param {string} pluginName - The name of the commissioning server.
1991
+ * @returns {CommissioningServer} The created commissioning server.
1992
+ */
1637
1993
  async createCommisioningServer(context, pluginName) {
1638
1994
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1639
1995
  const deviceName = await context.get('deviceName');
1640
1996
  const deviceType = await context.get('deviceType');
1641
1997
  const vendorId = await context.get('vendorId');
1642
- const vendorName = await context.get('vendorName');
1998
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1643
1999
  const productId = await context.get('productId');
1644
- const productName = await context.get('productName');
2000
+ const productName = await context.get('productName'); // Home app = Model
1645
2001
  const serialNumber = await context.get('serialNumber');
1646
2002
  const uniqueId = await context.get('uniqueId');
1647
2003
  const softwareVersion = await context.get('softwareVersion', 1);
1648
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2004
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1649
2005
  const hardwareVersion = await context.get('hardwareVersion', 1);
1650
2006
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1651
2007
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1653,6 +2009,7 @@ export class Matterbridge extends EventEmitter {
1653
2009
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1654
2010
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1655
2011
  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} `);
2012
+ // Validate ipv4address
1656
2013
  if (this.ipv4address) {
1657
2014
  const networkInterfaces = os.networkInterfaces();
1658
2015
  const availableAddresses = Object.values(networkInterfaces)
@@ -1667,6 +2024,7 @@ export class Matterbridge extends EventEmitter {
1667
2024
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1668
2025
  }
1669
2026
  }
2027
+ // Validate ipv6address
1670
2028
  if (this.ipv6address) {
1671
2029
  const networkInterfaces = os.networkInterfaces();
1672
2030
  const availableAddresses = Object.values(networkInterfaces)
@@ -1697,7 +2055,7 @@ export class Matterbridge extends EventEmitter {
1697
2055
  nodeLabel: productName,
1698
2056
  productLabel: productName,
1699
2057
  softwareVersion,
1700
- softwareVersionString,
2058
+ softwareVersionString, // Home app = Firmware Revision
1701
2059
  hardwareVersion,
1702
2060
  hardwareVersionString,
1703
2061
  uniqueId,
@@ -1793,6 +2151,24 @@ export class Matterbridge extends EventEmitter {
1793
2151
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1794
2152
  return commissioningServer;
1795
2153
  }
2154
+ /**
2155
+ * Creates a commissioning server storage context.
2156
+ *
2157
+ * @param pluginName - The name of the plugin.
2158
+ * @param deviceName - The name of the device.
2159
+ * @param deviceType - The type of the device.
2160
+ * @param vendorId - The vendor ID.
2161
+ * @param vendorName - The vendor name.
2162
+ * @param productId - The product ID.
2163
+ * @param productName - The product name.
2164
+ * @param serialNumber - The serial number of the device (optional).
2165
+ * @param uniqueId - The unique ID of the device (optional).
2166
+ * @param softwareVersion - The software version of the device (optional).
2167
+ * @param softwareVersionString - The software version string of the device (optional).
2168
+ * @param hardwareVersion - The hardware version of the device (optional).
2169
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2170
+ * @returns The storage context for the commissioning server.
2171
+ */
1796
2172
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1797
2173
  if (!this.storageManager)
1798
2174
  throw new Error('No storage manager initialized');
@@ -1820,6 +2196,13 @@ export class Matterbridge extends EventEmitter {
1820
2196
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1821
2197
  return storageContext;
1822
2198
  }
2199
+ /**
2200
+ * Imports the commissioning server context for a specific plugin and device.
2201
+ * @param pluginName - The name of the plugin.
2202
+ * @param device - The MatterbridgeDevice object representing the device.
2203
+ * @returns The commissioning server context.
2204
+ * @throws Error if the BasicInformationCluster is not found.
2205
+ */
1823
2206
  async importCommissioningServerContext(pluginName, device) {
1824
2207
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1825
2208
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -1854,6 +2237,14 @@ export class Matterbridge extends EventEmitter {
1854
2237
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1855
2238
  return storageContext;
1856
2239
  }
2240
+ /**
2241
+ * Shows the commissioning server QR code for a given plugin.
2242
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2243
+ * @param {StorageContext} storageContext - The storage context instance.
2244
+ * @param {NodeStorage} nodeContext - The node storage instance.
2245
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2246
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2247
+ */
1857
2248
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1858
2249
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
1859
2250
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -1864,7 +2255,8 @@ export class Matterbridge extends EventEmitter {
1864
2255
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1865
2256
  const QrCode = new QrCodeSchema();
1866
2257
  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`);
1867
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2258
+ // eslint-disable-next-line no-console
2259
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
1868
2260
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
1869
2261
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
1870
2262
  if (pluginName === 'Matterbridge') {
@@ -1911,6 +2303,12 @@ export class Matterbridge extends EventEmitter {
1911
2303
  }
1912
2304
  this.wssSendRefreshRequired();
1913
2305
  }
2306
+ /**
2307
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2308
+ *
2309
+ * @param fabricInfo - The array of exposed fabric information objects.
2310
+ * @returns An array of sanitized exposed fabric information objects.
2311
+ */
1914
2312
  sanitizeFabricInformations(fabricInfo) {
1915
2313
  return fabricInfo.map((info) => {
1916
2314
  return {
@@ -1924,6 +2322,12 @@ export class Matterbridge extends EventEmitter {
1924
2322
  };
1925
2323
  });
1926
2324
  }
2325
+ /**
2326
+ * Sanitizes the session information by converting bigint properties to string.
2327
+ *
2328
+ * @param sessionInfo - The array of session information objects.
2329
+ * @returns An array of sanitized session information objects.
2330
+ */
1927
2331
  sanitizeSessionInformation(sessionInfo) {
1928
2332
  return sessionInfo
1929
2333
  .filter((session) => session.isPeerActive)
@@ -1951,6 +2355,12 @@ export class Matterbridge extends EventEmitter {
1951
2355
  };
1952
2356
  });
1953
2357
  }
2358
+ /**
2359
+ * Sets the reachability of a commissioning server and trigger.
2360
+ *
2361
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2362
+ * @param {boolean} reachable - The new reachability status.
2363
+ */
1954
2364
  setCommissioningServerReachability(commissioningServer, reachable) {
1955
2365
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
1956
2366
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1958,6 +2368,11 @@ export class Matterbridge extends EventEmitter {
1958
2368
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1959
2369
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1960
2370
  }
2371
+ /**
2372
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2373
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2374
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2375
+ */
1961
2376
  setAggregatorReachability(matterAggregator, reachable) {
1962
2377
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
1963
2378
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -1970,6 +2385,12 @@ export class Matterbridge extends EventEmitter {
1970
2385
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
1971
2386
  });
1972
2387
  }
2388
+ /**
2389
+ * Sets the reachability of a device and trigger.
2390
+ *
2391
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2392
+ * @param {boolean} reachable - The new reachability status of the device.
2393
+ */
1973
2394
  setDeviceReachability(device, reachable) {
1974
2395
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
1975
2396
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2018,6 +2439,10 @@ export class Matterbridge extends EventEmitter {
2018
2439
  }
2019
2440
  return vendorName;
2020
2441
  };
2442
+ /**
2443
+ * Retrieves the base registered plugins sanitized for res.json().
2444
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2445
+ */
2021
2446
  async getBaseRegisteredPlugins() {
2022
2447
  const baseRegisteredPlugins = [];
2023
2448
  for (const plugin of this.plugins) {
@@ -2049,13 +2474,36 @@ export class Matterbridge extends EventEmitter {
2049
2474
  }
2050
2475
  return baseRegisteredPlugins;
2051
2476
  }
2477
+ /**
2478
+ * Spawns a child process with the given command and arguments.
2479
+ * @param {string} command - The command to execute.
2480
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2481
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2482
+ */
2052
2483
  async spawnCommand(command, args = []) {
2484
+ /*
2485
+ npm > npm.cmd on windows
2486
+ cmd.exe ['dir'] on windows
2487
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2488
+ process.on('unhandledRejection', (reason, promise) => {
2489
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2490
+ });
2491
+
2492
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2493
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2494
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2495
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2496
+ */
2053
2497
  const cmdLine = command + ' ' + args.join(' ');
2054
2498
  if (process.platform === 'win32' && command === 'npm') {
2499
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2055
2500
  const argstring = 'npm ' + args.join(' ');
2056
2501
  args.splice(0, args.length, '/c', argstring);
2057
2502
  command = 'cmd.exe';
2058
2503
  }
2504
+ // Decide when using sudo on linux
2505
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2506
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2059
2507
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2060
2508
  args.unshift(command);
2061
2509
  command = 'sudo';
@@ -2113,55 +2561,102 @@ export class Matterbridge extends EventEmitter {
2113
2561
  }
2114
2562
  });
2115
2563
  }
2564
+ /**
2565
+ * Sends a WebSocket message to all connected clients.
2566
+ *
2567
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2568
+ * @param {string} time - The time string of the message
2569
+ * @param {string} name - The logger name of the message
2570
+ * @param {string} message - The content of the message.
2571
+ */
2116
2572
  wssSendMessage(level, time, name, message) {
2117
2573
  if (!level || !time || !name || !message)
2118
2574
  return;
2575
+ // Remove ANSI escape codes from the message
2576
+ // eslint-disable-next-line no-control-regex
2119
2577
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2578
+ // Remove leading asterisks from the message
2120
2579
  message = message.replace(/^\*+/, '');
2580
+ // Replace all occurrences of \t and \n
2121
2581
  message = message.replace(/[\t\n]/g, '');
2582
+ // Remove non-printable characters
2583
+ // eslint-disable-next-line no-control-regex
2122
2584
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2585
+ // Replace all occurrences of \" with "
2123
2586
  message = message.replace(/\\"/g, '"');
2587
+ // Define the maximum allowed length for continuous characters without a space
2124
2588
  const maxContinuousLength = 100;
2125
2589
  const keepStartLength = 20;
2126
2590
  const keepEndLength = 20;
2591
+ // Split the message into words
2127
2592
  message = message
2128
2593
  .split(' ')
2129
2594
  .map((word) => {
2595
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2130
2596
  if (word.length > maxContinuousLength) {
2131
2597
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2132
2598
  }
2133
2599
  return word;
2134
2600
  })
2135
2601
  .join(' ');
2602
+ // Send the message to all connected clients
2136
2603
  this.webSocketServer?.clients.forEach((client) => {
2137
2604
  if (client.readyState === WebSocket.OPEN) {
2138
2605
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2139
2606
  }
2140
2607
  });
2141
2608
  }
2609
+ /**
2610
+ * Sends a need to refresh WebSocket message to all connected clients.
2611
+ *
2612
+ */
2142
2613
  wssSendRefreshRequired() {
2143
2614
  this.matterbridgeInformation.refreshRequired = true;
2615
+ // Send the message to all connected clients
2144
2616
  this.webSocketServer?.clients.forEach((client) => {
2145
2617
  if (client.readyState === WebSocket.OPEN) {
2146
2618
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2147
2619
  }
2148
2620
  });
2149
2621
  }
2622
+ /**
2623
+ * Sends a need to restart WebSocket message to all connected clients.
2624
+ *
2625
+ */
2150
2626
  wssSendRestartRequired() {
2151
2627
  this.matterbridgeInformation.restartRequired = true;
2628
+ // Send the message to all connected clients
2152
2629
  this.webSocketServer?.clients.forEach((client) => {
2153
2630
  if (client.readyState === WebSocket.OPEN) {
2154
2631
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2155
2632
  }
2156
2633
  });
2157
2634
  }
2635
+ /**
2636
+ * Initializes the frontend of Matterbridge.
2637
+ *
2638
+ * @param port The port number to run the frontend server on. Default is 8283.
2639
+ */
2158
2640
  async initializeFrontend(port = 8283) {
2159
2641
  let initializeError = false;
2160
2642
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2643
+ // Create the express app that serves the frontend
2161
2644
  this.expressApp = express();
2645
+ // Log all requests to the server for debugging
2646
+ /*
2647
+ if (hasParameter('homedir')) {
2648
+ this.expressApp.use((req, res, next) => {
2649
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2650
+ next();
2651
+ });
2652
+ }
2653
+ */
2654
+ // Serve static files from '/static' endpoint
2162
2655
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2163
2656
  if (!hasParameter('ssl')) {
2657
+ // Create an HTTP server and attach the express app
2164
2658
  this.httpServer = createServer(this.expressApp);
2659
+ // Listen on the specified port
2165
2660
  if (hasParameter('ingress')) {
2166
2661
  this.httpServer.listen(port, '0.0.0.0', () => {
2167
2662
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2175,6 +2670,7 @@ export class Matterbridge extends EventEmitter {
2175
2670
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2176
2671
  });
2177
2672
  }
2673
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2178
2674
  this.httpServer.on('error', (error) => {
2179
2675
  this.log.error(`Frontend http server error listening on ${port}`);
2180
2676
  switch (error.code) {
@@ -2190,6 +2686,7 @@ export class Matterbridge extends EventEmitter {
2190
2686
  });
2191
2687
  }
2192
2688
  else {
2689
+ // Load the SSL certificate, the private key and optionally the CA certificate
2193
2690
  let cert;
2194
2691
  try {
2195
2692
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2217,7 +2714,9 @@ export class Matterbridge extends EventEmitter {
2217
2714
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2218
2715
  }
2219
2716
  const serverOptions = { cert, key, ca };
2717
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2220
2718
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
2719
+ // Listen on the specified port
2221
2720
  if (hasParameter('ingress')) {
2222
2721
  this.httpsServer.listen(port, '0.0.0.0', () => {
2223
2722
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2231,6 +2730,7 @@ export class Matterbridge extends EventEmitter {
2231
2730
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2232
2731
  });
2233
2732
  }
2733
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2234
2734
  this.httpsServer.on('error', (error) => {
2235
2735
  this.log.error(`Frontend https server error listening on ${port}`);
2236
2736
  switch (error.code) {
@@ -2247,12 +2747,13 @@ export class Matterbridge extends EventEmitter {
2247
2747
  }
2248
2748
  if (initializeError)
2249
2749
  return;
2750
+ // Createe a WebSocket server and attach it to the http or https server
2250
2751
  const wssPort = port;
2251
2752
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2252
2753
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2253
2754
  this.webSocketServer.on('connection', (ws, request) => {
2254
2755
  const clientIp = request.socket.remoteAddress;
2255
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
2756
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2256
2757
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2257
2758
  ws.on('message', (message) => {
2258
2759
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2285,6 +2786,7 @@ export class Matterbridge extends EventEmitter {
2285
2786
  this.webSocketServer.on('error', (ws, error) => {
2286
2787
  this.log.error(`WebSocketServer error: ${error}`);
2287
2788
  });
2789
+ // Endpoint to validate login code
2288
2790
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2289
2791
  const { password } = req.body;
2290
2792
  this.log.debug('The frontend sent /api/login', password);
@@ -2303,12 +2805,14 @@ export class Matterbridge extends EventEmitter {
2303
2805
  this.log.warn('/api/login error wrong password');
2304
2806
  res.json({ valid: false });
2305
2807
  }
2808
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2306
2809
  }
2307
2810
  catch (error) {
2308
2811
  this.log.error('/api/login error getting password');
2309
2812
  res.json({ valid: false });
2310
2813
  }
2311
2814
  });
2815
+ // Endpoint to provide settings
2312
2816
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2313
2817
  this.log.debug('The frontend sent /api/settings');
2314
2818
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2329,13 +2833,17 @@ export class Matterbridge extends EventEmitter {
2329
2833
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2330
2834
  this.matterbridgeInformation.profile = this.profile;
2331
2835
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2836
+ // this.log.debug('Response:', debugStringify(response));
2332
2837
  res.json(response);
2333
2838
  });
2839
+ // Endpoint to provide plugins
2334
2840
  this.expressApp.get('/api/plugins', async (req, res) => {
2335
2841
  this.log.debug('The frontend sent /api/plugins');
2336
2842
  const response = await this.getBaseRegisteredPlugins();
2843
+ // this.log.debug('Response:', debugStringify(response));
2337
2844
  res.json(response);
2338
2845
  });
2846
+ // Endpoint to provide devices
2339
2847
  this.expressApp.get('/api/devices', (req, res) => {
2340
2848
  this.log.debug('The frontend sent /api/devices');
2341
2849
  const devices = [];
@@ -2368,8 +2876,10 @@ export class Matterbridge extends EventEmitter {
2368
2876
  cluster: cluster,
2369
2877
  });
2370
2878
  });
2879
+ // this.log.debug('Response:', debugStringify(data));
2371
2880
  res.json(devices);
2372
2881
  });
2882
+ // Endpoint to provide the cluster servers of the devices
2373
2883
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2374
2884
  const selectedPluginName = req.params.selectedPluginName;
2375
2885
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2389,6 +2899,7 @@ export class Matterbridge extends EventEmitter {
2389
2899
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2390
2900
  if (clusterServer.name === 'EveHistory')
2391
2901
  return;
2902
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2392
2903
  let attributeValue;
2393
2904
  try {
2394
2905
  if (typeof value.getLocal() === 'object')
@@ -2399,6 +2910,7 @@ export class Matterbridge extends EventEmitter {
2399
2910
  catch (error) {
2400
2911
  attributeValue = 'Fabric-Scoped';
2401
2912
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2913
+ // console.log(error);
2402
2914
  }
2403
2915
  data.push({
2404
2916
  endpoint: device.number ? device.number.toString() : '...',
@@ -2411,12 +2923,14 @@ export class Matterbridge extends EventEmitter {
2411
2923
  });
2412
2924
  });
2413
2925
  device.getChildEndpoints().forEach((childEndpoint) => {
2926
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2414
2927
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2415
2928
  const clusterServers = childEndpoint.getAllClusterServers();
2416
2929
  clusterServers.forEach((clusterServer) => {
2417
2930
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2418
2931
  if (clusterServer.name === 'EveHistory')
2419
2932
  return;
2933
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2420
2934
  let attributeValue;
2421
2935
  try {
2422
2936
  if (typeof value.getLocal() === 'object')
@@ -2427,6 +2941,7 @@ export class Matterbridge extends EventEmitter {
2427
2941
  catch (error) {
2428
2942
  attributeValue = 'Unavailable';
2429
2943
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2944
+ // console.log(error);
2430
2945
  }
2431
2946
  data.push({
2432
2947
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2443,6 +2958,7 @@ export class Matterbridge extends EventEmitter {
2443
2958
  });
2444
2959
  res.json(data);
2445
2960
  });
2961
+ // Endpoint to view the log
2446
2962
  this.expressApp.get('/api/view-log', async (req, res) => {
2447
2963
  this.log.debug('The frontend sent /api/log');
2448
2964
  try {
@@ -2455,10 +2971,12 @@ export class Matterbridge extends EventEmitter {
2455
2971
  res.status(500).send('Error reading log file');
2456
2972
  }
2457
2973
  });
2974
+ // Endpoint to download the matterbridge log
2458
2975
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2459
2976
  this.log.debug('The frontend sent /api/download-mblog');
2460
2977
  try {
2461
2978
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
2979
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2462
2980
  }
2463
2981
  catch (error) {
2464
2982
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2470,10 +2988,12 @@ export class Matterbridge extends EventEmitter {
2470
2988
  }
2471
2989
  });
2472
2990
  });
2991
+ // Endpoint to download the matter log
2473
2992
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2474
2993
  this.log.debug('The frontend sent /api/download-mjlog');
2475
2994
  try {
2476
2995
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
2996
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2477
2997
  }
2478
2998
  catch (error) {
2479
2999
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2485,6 +3005,7 @@ export class Matterbridge extends EventEmitter {
2485
3005
  }
2486
3006
  });
2487
3007
  });
3008
+ // Endpoint to download the matter storage file
2488
3009
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2489
3010
  this.log.debug('The frontend sent /api/download-mjstorage');
2490
3011
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2494,6 +3015,7 @@ export class Matterbridge extends EventEmitter {
2494
3015
  }
2495
3016
  });
2496
3017
  });
3018
+ // Endpoint to download the matterbridge storage directory
2497
3019
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2498
3020
  this.log.debug('The frontend sent /api/download-mbstorage');
2499
3021
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2504,6 +3026,7 @@ export class Matterbridge extends EventEmitter {
2504
3026
  }
2505
3027
  });
2506
3028
  });
3029
+ // Endpoint to download the matterbridge plugin directory
2507
3030
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2508
3031
  this.log.debug('The frontend sent /api/download-pluginstorage');
2509
3032
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2514,9 +3037,11 @@ export class Matterbridge extends EventEmitter {
2514
3037
  }
2515
3038
  });
2516
3039
  });
3040
+ // Endpoint to download the matterbridge plugin config files
2517
3041
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2518
3042
  this.log.debug('The frontend sent /api/download-pluginconfig');
2519
3043
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3044
+ // 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')));
2520
3045
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2521
3046
  if (error) {
2522
3047
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2524,6 +3049,7 @@ export class Matterbridge extends EventEmitter {
2524
3049
  }
2525
3050
  });
2526
3051
  });
3052
+ // Endpoint to download the matterbridge plugin config files
2527
3053
  this.expressApp.get('/api/download-backup', async (req, res) => {
2528
3054
  this.log.debug('The frontend sent /api/download-backup');
2529
3055
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2533,6 +3059,7 @@ export class Matterbridge extends EventEmitter {
2533
3059
  }
2534
3060
  });
2535
3061
  });
3062
+ // Endpoint to receive commands
2536
3063
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2537
3064
  const command = req.params.command;
2538
3065
  let param = req.params.param;
@@ -2542,13 +3069,15 @@ export class Matterbridge extends EventEmitter {
2542
3069
  return;
2543
3070
  }
2544
3071
  this.log.debug(`Received frontend command: ${command}:${param}`);
3072
+ // Handle the command setpassword from Settings
2545
3073
  if (command === 'setpassword') {
2546
- const password = param.slice(1, -1);
3074
+ const password = param.slice(1, -1); // Remove the first and last characters
2547
3075
  this.log.debug('setpassword', param, password);
2548
3076
  await this.nodeContext?.set('password', password);
2549
3077
  res.json({ message: 'Command received' });
2550
3078
  return;
2551
3079
  }
3080
+ // Handle the command setbridgemode from Settings
2552
3081
  if (command === 'setbridgemode') {
2553
3082
  this.log.debug(`setbridgemode: ${param}`);
2554
3083
  this.wssSendRestartRequired();
@@ -2556,6 +3085,7 @@ export class Matterbridge extends EventEmitter {
2556
3085
  res.json({ message: 'Command received' });
2557
3086
  return;
2558
3087
  }
3088
+ // Handle the command backup from Settings
2559
3089
  if (command === 'backup') {
2560
3090
  this.log.notice(`Prepairing the backup...`);
2561
3091
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2563,25 +3093,26 @@ export class Matterbridge extends EventEmitter {
2563
3093
  res.json({ message: 'Command received' });
2564
3094
  return;
2565
3095
  }
3096
+ // Handle the command setmbloglevel from Settings
2566
3097
  if (command === 'setmbloglevel') {
2567
3098
  this.log.debug('Matterbridge log level:', param);
2568
3099
  if (param === 'Debug') {
2569
- this.log.logLevel = "debug";
3100
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2570
3101
  }
2571
3102
  else if (param === 'Info') {
2572
- this.log.logLevel = "info";
3103
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2573
3104
  }
2574
3105
  else if (param === 'Notice') {
2575
- this.log.logLevel = "notice";
3106
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2576
3107
  }
2577
3108
  else if (param === 'Warn') {
2578
- this.log.logLevel = "warn";
3109
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2579
3110
  }
2580
3111
  else if (param === 'Error') {
2581
- this.log.logLevel = "error";
3112
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2582
3113
  }
2583
3114
  else if (param === 'Fatal') {
2584
- this.log.logLevel = "fatal";
3115
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2585
3116
  }
2586
3117
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2587
3118
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2589,12 +3120,13 @@ export class Matterbridge extends EventEmitter {
2589
3120
  for (const plugin of this.plugins) {
2590
3121
  if (!plugin.platform || !plugin.platform.config)
2591
3122
  continue;
2592
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2593
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3123
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3124
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2594
3125
  }
2595
3126
  res.json({ message: 'Command received' });
2596
3127
  return;
2597
3128
  }
3129
+ // Handle the command setmbloglevel from Settings
2598
3130
  if (command === 'setmjloglevel') {
2599
3131
  this.log.debug('Matter.js log level:', param);
2600
3132
  if (param === 'Debug') {
@@ -2619,30 +3151,34 @@ export class Matterbridge extends EventEmitter {
2619
3151
  res.json({ message: 'Command received' });
2620
3152
  return;
2621
3153
  }
3154
+ // Handle the command setmdnsinterface from Settings
2622
3155
  if (command === 'setmdnsinterface') {
2623
- param = param.slice(1, -1);
3156
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2624
3157
  this.matterbridgeInformation.mattermdnsinterface = param;
2625
3158
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2626
3159
  await this.nodeContext?.set('mattermdnsinterface', param);
2627
3160
  res.json({ message: 'Command received' });
2628
3161
  return;
2629
3162
  }
3163
+ // Handle the command setipv4address from Settings
2630
3164
  if (command === 'setipv4address') {
2631
- param = param.slice(1, -1);
3165
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2632
3166
  this.matterbridgeInformation.matteripv4address = param;
2633
3167
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2634
3168
  await this.nodeContext?.set('matteripv4address', param);
2635
3169
  res.json({ message: 'Command received' });
2636
3170
  return;
2637
3171
  }
3172
+ // Handle the command setipv6address from Settings
2638
3173
  if (command === 'setipv6address') {
2639
- param = param.slice(1, -1);
3174
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2640
3175
  this.matterbridgeInformation.matteripv6address = param;
2641
3176
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2642
3177
  await this.nodeContext?.set('matteripv6address', param);
2643
3178
  res.json({ message: 'Command received' });
2644
3179
  return;
2645
3180
  }
3181
+ // Handle the command setmatterport from Settings
2646
3182
  if (command === 'setmatterport') {
2647
3183
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2648
3184
  this.matterbridgeInformation.matterPort = port;
@@ -2651,6 +3187,7 @@ export class Matterbridge extends EventEmitter {
2651
3187
  res.json({ message: 'Command received' });
2652
3188
  return;
2653
3189
  }
3190
+ // Handle the command setmatterdiscriminator from Settings
2654
3191
  if (command === 'setmatterdiscriminator') {
2655
3192
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2656
3193
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2659,6 +3196,7 @@ export class Matterbridge extends EventEmitter {
2659
3196
  res.json({ message: 'Command received' });
2660
3197
  return;
2661
3198
  }
3199
+ // Handle the command setmatterpasscode from Settings
2662
3200
  if (command === 'setmatterpasscode') {
2663
3201
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2664
3202
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2667,17 +3205,20 @@ export class Matterbridge extends EventEmitter {
2667
3205
  res.json({ message: 'Command received' });
2668
3206
  return;
2669
3207
  }
3208
+ // Handle the command setmbloglevel from Settings
2670
3209
  if (command === 'setmblogfile') {
2671
3210
  this.log.debug('Matterbridge file log:', param);
2672
3211
  this.matterbridgeInformation.fileLogger = param === 'true';
2673
3212
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3213
+ // Create the file logger for matterbridge
2674
3214
  if (param === 'true')
2675
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3215
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2676
3216
  else
2677
3217
  AnsiLogger.setGlobalLogfile(undefined);
2678
3218
  res.json({ message: 'Command received' });
2679
3219
  return;
2680
3220
  }
3221
+ // Handle the command setmbloglevel from Settings
2681
3222
  if (command === 'setmjlogfile') {
2682
3223
  this.log.debug('Matter file log:', param);
2683
3224
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2704,36 +3245,43 @@ export class Matterbridge extends EventEmitter {
2704
3245
  res.json({ message: 'Command received' });
2705
3246
  return;
2706
3247
  }
3248
+ // Handle the command unregister from Settings
2707
3249
  if (command === 'unregister') {
2708
3250
  await this.unregisterAndShutdownProcess();
2709
3251
  res.json({ message: 'Command received' });
2710
3252
  return;
2711
3253
  }
3254
+ // Handle the command reset from Settings
2712
3255
  if (command === 'reset') {
2713
3256
  await this.shutdownProcessAndReset();
2714
3257
  res.json({ message: 'Command received' });
2715
3258
  return;
2716
3259
  }
3260
+ // Handle the command factoryreset from Settings
2717
3261
  if (command === 'factoryreset') {
2718
3262
  await this.shutdownProcessAndFactoryReset();
2719
3263
  res.json({ message: 'Command received' });
2720
3264
  return;
2721
3265
  }
3266
+ // Handle the command shutdown from Header
2722
3267
  if (command === 'shutdown') {
2723
3268
  await this.shutdownProcess();
2724
3269
  res.json({ message: 'Command received' });
2725
3270
  return;
2726
3271
  }
3272
+ // Handle the command restart from Header
2727
3273
  if (command === 'restart') {
2728
3274
  await this.restartProcess();
2729
3275
  res.json({ message: 'Command received' });
2730
3276
  return;
2731
3277
  }
3278
+ // Handle the command update from Header
2732
3279
  if (command === 'update') {
2733
3280
  this.log.info('Updating matterbridge...');
2734
3281
  try {
2735
3282
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2736
3283
  this.log.info('Matterbridge has been updated. Full restart required.');
3284
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2737
3285
  }
2738
3286
  catch (error) {
2739
3287
  this.log.error('Error updating matterbridge');
@@ -2743,9 +3291,11 @@ export class Matterbridge extends EventEmitter {
2743
3291
  res.json({ message: 'Command received' });
2744
3292
  return;
2745
3293
  }
3294
+ // Handle the command saveconfig from Home
2746
3295
  if (command === 'saveconfig') {
2747
3296
  param = param.replace(/\*/g, '\\');
2748
3297
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3298
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2749
3299
  if (!this.plugins.has(param)) {
2750
3300
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2751
3301
  }
@@ -2759,33 +3309,39 @@ export class Matterbridge extends EventEmitter {
2759
3309
  res.json({ message: 'Command received' });
2760
3310
  return;
2761
3311
  }
3312
+ // Handle the command installplugin from Home
2762
3313
  if (command === 'installplugin') {
2763
3314
  param = param.replace(/\*/g, '\\');
2764
3315
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2765
3316
  try {
2766
3317
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2767
3318
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3319
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2768
3320
  }
2769
3321
  catch (error) {
2770
3322
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2771
3323
  }
2772
3324
  this.wssSendRestartRequired();
2773
3325
  param = param.split('@')[0];
3326
+ // Also add the plugin to matterbridge so no return!
2774
3327
  if (param === 'matterbridge') {
3328
+ // 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
2775
3329
  res.json({ message: 'Command received' });
2776
3330
  return;
2777
3331
  }
2778
3332
  }
3333
+ // Handle the command addplugin from Home
2779
3334
  if (command === 'addplugin' || command === 'installplugin') {
2780
3335
  param = param.replace(/\*/g, '\\');
2781
3336
  const plugin = await this.plugins.add(param);
2782
3337
  if (plugin) {
2783
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3338
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2784
3339
  }
2785
3340
  res.json({ message: 'Command received' });
2786
3341
  this.wssSendRefreshRequired();
2787
3342
  return;
2788
3343
  }
3344
+ // Handle the command removeplugin from Home
2789
3345
  if (command === 'removeplugin') {
2790
3346
  if (!this.plugins.has(param)) {
2791
3347
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2799,6 +3355,7 @@ export class Matterbridge extends EventEmitter {
2799
3355
  this.wssSendRefreshRequired();
2800
3356
  return;
2801
3357
  }
3358
+ // Handle the command enableplugin from Home
2802
3359
  if (command === 'enableplugin') {
2803
3360
  if (!this.plugins.has(param)) {
2804
3361
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2816,13 +3373,14 @@ export class Matterbridge extends EventEmitter {
2816
3373
  plugin.registeredDevices = undefined;
2817
3374
  plugin.addedDevices = undefined;
2818
3375
  await this.plugins.enable(param);
2819
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3376
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
2820
3377
  }
2821
3378
  }
2822
3379
  res.json({ message: 'Command received' });
2823
3380
  this.wssSendRefreshRequired();
2824
3381
  return;
2825
3382
  }
3383
+ // Handle the command disableplugin from Home
2826
3384
  if (command === 'disableplugin') {
2827
3385
  if (!this.plugins.has(param)) {
2828
3386
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2839,6 +3397,7 @@ export class Matterbridge extends EventEmitter {
2839
3397
  return;
2840
3398
  }
2841
3399
  });
3400
+ // Fallback for routing (must be the last route)
2842
3401
  this.expressApp.get('*', (req, res) => {
2843
3402
  this.log.debug('The frontend sent:', req.url);
2844
3403
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -2846,6 +3405,11 @@ export class Matterbridge extends EventEmitter {
2846
3405
  });
2847
3406
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2848
3407
  }
3408
+ /**
3409
+ * Retrieves the cluster text description from a given device.
3410
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3411
+ * @returns {string} The attributes description of the cluster servers in the device.
3412
+ */
2849
3413
  getClusterTextFromDevice(device) {
2850
3414
  const stringifyUserLabel = (endpoint) => {
2851
3415
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -2868,9 +3432,11 @@ export class Matterbridge extends EventEmitter {
2868
3432
  return '';
2869
3433
  };
2870
3434
  let attributes = '';
3435
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2871
3436
  const clusterServers = device.getAllClusterServers();
2872
3437
  clusterServers.forEach((clusterServer) => {
2873
3438
  try {
3439
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2874
3440
  if (clusterServer.name === 'OnOff')
2875
3441
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
2876
3442
  if (clusterServer.name === 'Switch')
@@ -2921,18 +3487,30 @@ export class Matterbridge extends EventEmitter {
2921
3487
  attributes += `${stringifyFixedLabel(device)} `;
2922
3488
  if (clusterServer.name === 'UserLabel')
2923
3489
  attributes += `${stringifyUserLabel(device)} `;
3490
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2924
3491
  }
2925
3492
  catch (error) {
2926
3493
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
2927
3494
  }
2928
3495
  });
3496
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2929
3497
  return attributes;
2930
3498
  }
3499
+ /**
3500
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3501
+ * @deprecated This method is deprecated and will be removed in a future version.
3502
+ *
3503
+ * @returns A Promise that resolves when the initialization is complete.
3504
+ */
2931
3505
  async startExtension(dataPath, extensionVersion, port = 5540) {
3506
+ // Set the bridge mode
2932
3507
  this.bridgeMode = 'bridge';
3508
+ // Set the first port to use
2933
3509
  this.port = port;
2934
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3510
+ // Set Matterbridge logger
3511
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
2935
3512
  this.log.debug('Matterbridge extension is starting...');
3513
+ // Initialize NodeStorage
2936
3514
  this.matterbridgeDirectory = dataPath;
2937
3515
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
2938
3516
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -2951,10 +3529,13 @@ export class Matterbridge extends EventEmitter {
2951
3529
  };
2952
3530
  this.plugins.set(plugin);
2953
3531
  this.plugins.saveToStorage();
3532
+ // Log system info and create .matterbridge directory
2954
3533
  await this.logNodeAndSystemInfo();
2955
3534
  this.matterbridgeDirectory = dataPath;
3535
+ // Set matter.js logger level and format
2956
3536
  Logger.defaultLogLevel = MatterLogLevel.INFO;
2957
3537
  Logger.format = MatterLogFormat.ANSI;
3538
+ // Start the storage and create matterbridgeContext
2958
3539
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
2959
3540
  if (!this.storageManager)
2960
3541
  return false;
@@ -2964,7 +3545,7 @@ export class Matterbridge extends EventEmitter {
2964
3545
  await this.matterbridgeContext.set('softwareVersion', 1);
2965
3546
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
2966
3547
  await this.matterbridgeContext.set('hardwareVersion', 1);
2967
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3548
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
2968
3549
  this.matterServer = this.createMatterServer(this.storageManager);
2969
3550
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
2970
3551
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
@@ -2977,6 +3558,7 @@ export class Matterbridge extends EventEmitter {
2977
3558
  await this.startMatterServer();
2978
3559
  this.log.info('Matter server started');
2979
3560
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3561
+ // Set reachability to true and trigger event after 60 seconds
2980
3562
  setTimeout(() => {
2981
3563
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
2982
3564
  if (this.commissioningServer)
@@ -2986,14 +3568,31 @@ export class Matterbridge extends EventEmitter {
2986
3568
  }, 60 * 1000);
2987
3569
  return this.commissioningServer.isCommissioned();
2988
3570
  }
3571
+ /**
3572
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3573
+ * @deprecated This method is deprecated and will be removed in a future version.
3574
+ *
3575
+ * @returns A Promise that resolves when the initialization is complete.
3576
+ */
2989
3577
  async stopExtension() {
3578
+ // Closing matter
2990
3579
  await this.stopMatterServer();
3580
+ // Clearing the session manager
3581
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3582
+ // Closing storage
2991
3583
  await this.stopMatterStorage();
2992
3584
  this.log.info('Matter server stopped');
2993
3585
  }
3586
+ /**
3587
+ * Checks if the extension is commissioned.
3588
+ * @deprecated This method is deprecated and will be removed in a future version.
3589
+ *
3590
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3591
+ */
2994
3592
  isExtensionCommissioned() {
2995
3593
  if (!this.commissioningServer)
2996
3594
  return false;
2997
3595
  return this.commissioningServer.isCommissioned();
2998
3596
  }
2999
3597
  }
3598
+ //# sourceMappingURL=matterbridge.js.map