matterbridge 1.6.7-dev.3 → 1.6.7

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