matterbridge 1.7.2-dev.7 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +3 -2
  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 +11 -0
  27. package/dist/matter/export.d.ts.map +1 -0
  28. package/dist/matter/export.js +4 -0
  29. package/dist/matter/export.js.map +1 -0
  30. package/dist/matterbridge.d.ts +483 -0
  31. package/dist/matterbridge.d.ts.map +1 -0
  32. package/dist/matterbridge.js +717 -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 +7077 -0
  43. package/dist/matterbridgeDevice.d.ts.map +1 -0
  44. package/dist/matterbridgeDevice.js +996 -9
  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 +82 -11
  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 +91 -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 +10156 -0
  59. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  60. package/dist/matterbridgeEndpoint.js +1120 -11
  61. package/dist/matterbridgeEndpoint.js.map +1 -0
  62. package/dist/matterbridgePlatform.d.ts +168 -0
  63. package/dist/matterbridgePlatform.d.ts.map +1 -0
  64. package/dist/matterbridgePlatform.js +124 -3
  65. package/dist/matterbridgePlatform.js.map +1 -0
  66. package/dist/matterbridgeTypes.d.ts +172 -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 +46 -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/frontend/build/asset-manifest.json +6 -6
  95. package/frontend/build/index.html +1 -1
  96. package/frontend/build/static/css/{main.8727aee4.css → main.cf25d33e.css} +2 -2
  97. package/frontend/build/static/css/main.cf25d33e.css.map +1 -0
  98. package/frontend/build/static/js/{main.c320863b.js → main.08241820.js} +12 -12
  99. package/frontend/build/static/js/main.08241820.js.map +1 -0
  100. package/npm-shrinkwrap.json +2 -2
  101. package/package.json +2 -1
  102. package/frontend/build/static/css/main.8727aee4.css.map +0 -1
  103. package/frontend/build/static/js/main.c320863b.js.map +0 -1
  104. /package/frontend/build/static/js/{main.c320863b.js.LICENSE.txt → main.08241820.js.LICENSE.txt} +0 -0
@@ -1,3 +1,26 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @date 2023-12-29
7
+ * @version 1.5.2
8
+ *
9
+ * Copyright 2023, 2024, 2025 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // Node.js modules
1
24
  import { fileURLToPath } from 'url';
2
25
  import { promises as fs } from 'fs';
3
26
  import { exec, spawn } from 'child_process';
@@ -6,27 +29,36 @@ import EventEmitter from 'events';
6
29
  import os from 'os';
7
30
  import path from 'path';
8
31
  import { randomBytes } from 'crypto';
32
+ // Package modules
9
33
  import https from 'https';
10
34
  import express from 'express';
11
35
  import WebSocket, { WebSocketServer } from 'ws';
36
+ // NodeStorage and AnsiLogger modules
12
37
  import { NodeStorageManager } from 'node-persist-manager';
13
38
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
39
+ // Matterbridge
14
40
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
15
41
  import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
16
42
  import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
17
43
  import { PluginManager } from './pluginManager.js';
18
44
  import { DeviceManager } from './deviceManager.js';
19
45
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
46
+ // @matter
20
47
  import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer, StorageService, Environment } from '@matter/main';
21
48
  import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
22
49
  import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
23
50
  import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
51
+ // @project-chip
24
52
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
25
53
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
26
54
  import { aggregator } from './matterbridgeDeviceTypes.js';
55
+ // Default colors
27
56
  const plg = '\u001B[38;5;33m';
28
57
  const dev = '\u001B[38;5;79m';
29
58
  const typ = '\u001B[38;5;207m';
59
+ /**
60
+ * Represents the Matterbridge application.
61
+ */
30
62
  export class Matterbridge extends EventEmitter {
31
63
  systemInformation = {
32
64
  interfaceName: '',
@@ -63,7 +95,7 @@ export class Matterbridge extends EventEmitter {
63
95
  edge: hasParameter('edge'),
64
96
  readOnly: hasParameter('readonly'),
65
97
  profile: getParameter('profile'),
66
- loggerLevel: "info",
98
+ loggerLevel: "info" /* LogLevel.INFO */,
67
99
  fileLogger: false,
68
100
  matterLoggerLevel: MatterLogLevel.INFO,
69
101
  matterFileLogger: false,
@@ -102,6 +134,7 @@ export class Matterbridge extends EventEmitter {
102
134
  nodeContext;
103
135
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
104
136
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
137
+ // Cleanup
105
138
  hasCleanupStarted = false;
106
139
  initialized = false;
107
140
  execRunningCount = 0;
@@ -113,16 +146,18 @@ export class Matterbridge extends EventEmitter {
113
146
  sigtermHandler;
114
147
  exceptionHandler;
115
148
  rejectionHandler;
149
+ // Frontend
116
150
  expressApp;
117
151
  httpServer;
118
152
  httpsServer;
119
153
  webSocketServer;
120
- mdnsInterface;
121
- ipv4address;
122
- ipv6address;
123
- port = 5540;
124
- passcode;
125
- discriminator;
154
+ // Matter
155
+ mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
156
+ ipv4address; // matter commissioning server listeningAddressIpv4
157
+ ipv6address; // matter commissioning server listeningAddressIpv6
158
+ port = 5540; // first commissioning server port
159
+ passcode; // first commissioning server passcode
160
+ discriminator; // first commissioning server discriminator
126
161
  storageManager;
127
162
  matterbridgeContext;
128
163
  mattercontrollerContext;
@@ -133,19 +168,40 @@ export class Matterbridge extends EventEmitter {
133
168
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
134
169
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
135
170
  static instance;
171
+ // We load asyncronously so is private
136
172
  constructor() {
137
173
  super();
174
+ // Bind the handler to the instance
138
175
  this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
139
176
  }
177
+ /**
178
+ * Retrieves the list of Matterbridge devices.
179
+ * @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
180
+ */
140
181
  getDevices() {
141
182
  return this.devices.array();
142
183
  }
184
+ /**
185
+ * Retrieves the list of registered plugins.
186
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
187
+ */
143
188
  getPlugins() {
144
189
  return this.plugins.array();
145
190
  }
146
191
  matterbridgeMessageHandler;
192
+ /** ***********************************************************************************************************************************/
193
+ /** loadInstance() and cleanup() methods */
194
+ /** ***********************************************************************************************************************************/
195
+ /**
196
+ * Loads an instance of the Matterbridge class.
197
+ * If an instance already exists, return that instance.
198
+ *
199
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
200
+ * @returns The loaded Matterbridge instance.
201
+ */
147
202
  static async loadInstance(initialize = false) {
148
203
  if (!Matterbridge.instance) {
204
+ // eslint-disable-next-line no-console
149
205
  if (hasParameter('debug'))
150
206
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
151
207
  Matterbridge.instance = new Matterbridge();
@@ -154,6 +210,11 @@ export class Matterbridge extends EventEmitter {
154
210
  }
155
211
  return Matterbridge.instance;
156
212
  }
213
+ /**
214
+ * Call cleanup().
215
+ * @deprecated This method is deprecated and is only used for jest tests.
216
+ *
217
+ */
157
218
  async destroyInstance() {
158
219
  await this.cleanup('destroying instance...', false);
159
220
  await waiter('destroying instance...', () => {
@@ -161,39 +222,60 @@ export class Matterbridge extends EventEmitter {
161
222
  }, false, 60000, 100, false);
162
223
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
163
224
  }
225
+ /**
226
+ * Initializes the Matterbridge application.
227
+ *
228
+ * @remarks
229
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
230
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
231
+ * node version, registers signal handlers, initializes storage, and parses the command line.
232
+ *
233
+ * @returns A Promise that resolves when the initialization is complete.
234
+ */
164
235
  async initialize() {
236
+ // Set the restart mode
165
237
  if (hasParameter('service'))
166
238
  this.restartMode = 'service';
167
239
  if (hasParameter('docker'))
168
240
  this.restartMode = 'docker';
241
+ // Set the matterbridge directory
169
242
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
170
243
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
171
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
244
+ // Create matterbridge logger
245
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
246
+ // Initialize nodeStorage and nodeContext
172
247
  try {
173
248
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
174
249
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
175
250
  this.log.debug('Creating node storage context for matterbridge');
176
251
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
252
+ // TODO: Remove this code when node-persist-manager is updated
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
254
  const keys = (await this.nodeStorage?.storage.keys());
178
255
  for (const key of keys) {
179
256
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
258
  await this.nodeStorage?.storage.get(key);
181
259
  }
182
260
  const storages = await this.nodeStorage.getStorageNames();
183
261
  for (const storage of storages) {
184
262
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
185
263
  const nodeContext = await this.nodeStorage?.createStorage(storage);
264
+ // TODO: Remove this code when node-persist-manager is updated
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
266
  const keys = (await nodeContext?.storage.keys());
187
267
  keys.forEach(async (key) => {
188
268
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
189
269
  await nodeContext?.get(key);
190
270
  });
191
271
  }
272
+ // Creating a backup of the node storage since it is not corrupted
192
273
  this.log.debug('Creating node storage backup...');
193
274
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
194
275
  this.log.debug('Created node storage backup');
195
276
  }
196
277
  catch (error) {
278
+ // Restoring the backup of the node storage since it is corrupted
197
279
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
198
280
  if (hasParameter('norestore')) {
199
281
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -208,45 +290,51 @@ export class Matterbridge extends EventEmitter {
208
290
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
209
291
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
210
292
  }
293
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
211
294
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
295
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
212
296
  this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
297
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
213
298
  this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
214
299
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
300
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
215
301
  if (hasParameter('logger')) {
216
302
  const level = getParameter('logger');
217
303
  if (level === 'debug') {
218
- this.log.logLevel = "debug";
304
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
219
305
  }
220
306
  else if (level === 'info') {
221
- this.log.logLevel = "info";
307
+ this.log.logLevel = "info" /* LogLevel.INFO */;
222
308
  }
223
309
  else if (level === 'notice') {
224
- this.log.logLevel = "notice";
310
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
225
311
  }
226
312
  else if (level === 'warn') {
227
- this.log.logLevel = "warn";
313
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
228
314
  }
229
315
  else if (level === 'error') {
230
- this.log.logLevel = "error";
316
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
231
317
  }
232
318
  else if (level === 'fatal') {
233
- this.log.logLevel = "fatal";
319
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
234
320
  }
235
321
  else {
236
322
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
237
- this.log.logLevel = "info";
323
+ this.log.logLevel = "info" /* LogLevel.INFO */;
238
324
  }
239
325
  }
240
326
  else {
241
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
327
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
242
328
  }
243
329
  MatterbridgeDevice.logLevel = this.log.logLevel;
330
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
244
331
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
245
332
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
246
333
  this.matterbridgeInformation.fileLogger = true;
247
334
  }
248
335
  this.log.notice('Matterbridge is starting...');
249
336
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
337
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
250
338
  if (hasParameter('matterlogger')) {
251
339
  const level = getParameter('matterlogger');
252
340
  if (level === 'debug') {
@@ -277,6 +365,7 @@ export class Matterbridge extends EventEmitter {
277
365
  }
278
366
  Logger.format = MatterLogFormat.ANSI;
279
367
  Logger.setLogger('default', this.createMatterLogger());
368
+ // Create the file logger for matter.js (context: matterFileLog)
280
369
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
281
370
  this.matterbridgeInformation.matterFileLogger = true;
282
371
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -285,6 +374,7 @@ export class Matterbridge extends EventEmitter {
285
374
  });
286
375
  }
287
376
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
+ // Set the interface to use for the matter server mdnsInterface
288
378
  if (hasParameter('mdnsinterface')) {
289
379
  this.mdnsInterface = getParameter('mdnsinterface');
290
380
  }
@@ -293,6 +383,7 @@ export class Matterbridge extends EventEmitter {
293
383
  if (this.mdnsInterface === '')
294
384
  this.mdnsInterface = undefined;
295
385
  }
386
+ // Set the listeningAddressIpv4 for the matter commissioning server
296
387
  if (hasParameter('ipv4address')) {
297
388
  this.ipv4address = getParameter('ipv4address');
298
389
  }
@@ -301,6 +392,7 @@ export class Matterbridge extends EventEmitter {
301
392
  if (this.ipv4address === '')
302
393
  this.ipv4address = undefined;
303
394
  }
395
+ // Set the listeningAddressIpv6 for the matter commissioning server
304
396
  if (hasParameter('ipv6address')) {
305
397
  this.ipv6address = getParameter('ipv6address');
306
398
  }
@@ -309,17 +401,23 @@ export class Matterbridge extends EventEmitter {
309
401
  if (this.ipv6address === '')
310
402
  this.ipv6address = undefined;
311
403
  }
404
+ // Initialize PluginManager
312
405
  this.plugins = new PluginManager(this);
313
406
  await this.plugins.loadFromStorage();
407
+ // Initialize DeviceManager
314
408
  this.devices = new DeviceManager(this, this.nodeContext);
409
+ // Get the plugins from node storage and create the plugins node storage contexts
315
410
  for (const plugin of this.plugins) {
316
411
  const packageJson = await this.plugins.parse(plugin);
317
412
  if (packageJson === null && !hasParameter('add')) {
413
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
414
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
318
415
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
319
416
  try {
320
417
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
321
418
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
322
419
  plugin.error = false;
420
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
323
421
  }
324
422
  catch (error) {
325
423
  plugin.error = true;
@@ -336,6 +434,7 @@ export class Matterbridge extends EventEmitter {
336
434
  await plugin.nodeContext.set('description', plugin.description);
337
435
  await plugin.nodeContext.set('author', plugin.author);
338
436
  }
437
+ // Log system info and create .matterbridge directory
339
438
  await this.logNodeAndSystemInfo();
340
439
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
341
440
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -343,6 +442,7 @@ export class Matterbridge extends EventEmitter {
343
442
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
344
443
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
345
444
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
445
+ // Check node version and throw error
346
446
  const minNodeVersion = 18;
347
447
  const nodeVersion = process.versions.node;
348
448
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -350,10 +450,17 @@ export class Matterbridge extends EventEmitter {
350
450
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
351
451
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
352
452
  }
453
+ // Register process handlers
353
454
  this.registerProcessHandlers();
455
+ // Parse command line
354
456
  await this.parseCommandLine();
355
457
  this.initialized = true;
356
458
  }
459
+ /**
460
+ * Parses the command line arguments and performs the corresponding actions.
461
+ * @private
462
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
463
+ */
357
464
  async parseCommandLine() {
358
465
  if (hasParameter('help')) {
359
466
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -461,6 +568,7 @@ export class Matterbridge extends EventEmitter {
461
568
  }
462
569
  if (hasParameter('factoryreset')) {
463
570
  try {
571
+ // Delete old matter storage file
464
572
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
465
573
  this.log.info(`Unlinking old matter storage file: ${file}`);
466
574
  await fs.unlink(file);
@@ -471,6 +579,7 @@ export class Matterbridge extends EventEmitter {
471
579
  }
472
580
  }
473
581
  try {
582
+ // Delete matter node storage directory with its subdirectories
474
583
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
475
584
  this.log.info(`Removing matter node storage directory: ${dir}`);
476
585
  await fs.rm(dir, { recursive: true });
@@ -481,6 +590,7 @@ export class Matterbridge extends EventEmitter {
481
590
  }
482
591
  }
483
592
  try {
593
+ // Delete node storage directory with its subdirectories
484
594
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
485
595
  this.log.info(`Removing storage directory: ${dir}`);
486
596
  await fs.rm(dir, { recursive: true });
@@ -498,6 +608,7 @@ export class Matterbridge extends EventEmitter {
498
608
  this.emit('shutdown');
499
609
  return;
500
610
  }
611
+ // Start the matter storage and create the matterbridge context
501
612
  try {
502
613
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
503
614
  }
@@ -505,6 +616,7 @@ export class Matterbridge extends EventEmitter {
505
616
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
506
617
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
507
618
  }
619
+ // Clear the matterbridge context if the reset parameter is set
508
620
  if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
509
621
  this.log.info('Resetting Matterbridge commissioning information...');
510
622
  await this.matterbridgeContext?.clearAll();
@@ -513,6 +625,7 @@ export class Matterbridge extends EventEmitter {
513
625
  this.emit('shutdown');
514
626
  return;
515
627
  }
628
+ // Clear matterbridge plugin context if the reset parameter is set
516
629
  if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
517
630
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
518
631
  const plugin = this.plugins.get(getParameter('reset'));
@@ -532,28 +645,34 @@ export class Matterbridge extends EventEmitter {
532
645
  this.emit('shutdown');
533
646
  return;
534
647
  }
648
+ // Initialize frontend
535
649
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
536
650
  await this.initializeFrontend(getIntParameter('frontend'));
651
+ // Check each 60 minutes the latest versions
537
652
  this.checkUpdateInterval = setInterval(() => {
538
653
  this.getMatterbridgeLatestVersion();
539
654
  for (const plugin of this.plugins) {
540
655
  this.getPluginLatestVersion(plugin);
541
656
  }
542
657
  }, 60 * 60 * 1000);
658
+ // Start the matterbridge in mode test
543
659
  if (hasParameter('test')) {
544
660
  this.bridgeMode = 'bridge';
545
661
  MatterbridgeDevice.bridgeMode = 'bridge';
546
662
  return;
547
663
  }
664
+ // Start the matterbridge in mode controller
548
665
  if (hasParameter('controller')) {
549
666
  this.bridgeMode = 'controller';
550
667
  await this.startController();
551
668
  return;
552
669
  }
670
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
553
671
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
554
672
  this.log.info('Setting default matterbridge start mode to bridge');
555
673
  await this.nodeContext?.set('bridgeMode', 'bridge');
556
674
  }
675
+ // Start matterbridge in bridge mode
557
676
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
558
677
  this.bridgeMode = 'bridge';
559
678
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -562,6 +681,7 @@ export class Matterbridge extends EventEmitter {
562
681
  await this.startBridge();
563
682
  return;
564
683
  }
684
+ // Start matterbridge in childbridge mode
565
685
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
566
686
  this.bridgeMode = 'childbridge';
567
687
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -571,17 +691,28 @@ export class Matterbridge extends EventEmitter {
571
691
  return;
572
692
  }
573
693
  }
694
+ /**
695
+ * Asynchronously loads and starts the registered plugins.
696
+ *
697
+ * This method is responsible for initializing and staarting all enabled plugins.
698
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
699
+ *
700
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
701
+ */
574
702
  async startPlugins() {
703
+ // Check, load and start the plugins
575
704
  for (const plugin of this.plugins) {
576
705
  plugin.configJson = await this.plugins.loadConfig(plugin);
577
706
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
707
+ // Check if the plugin is available
578
708
  if (!(await this.plugins.resolve(plugin.path))) {
579
709
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
580
710
  plugin.enabled = false;
581
711
  plugin.error = true;
582
712
  continue;
583
713
  }
584
- this.getPluginLatestVersion(plugin);
714
+ // Check if the plugin has a new version
715
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
585
716
  if (!plugin.enabled) {
586
717
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
587
718
  continue;
@@ -596,20 +727,26 @@ export class Matterbridge extends EventEmitter {
596
727
  plugin.addedDevices = undefined;
597
728
  plugin.qrPairingCode = undefined;
598
729
  plugin.manualPairingCode = undefined;
599
- this.plugins.load(plugin, true, 'Matterbridge is starting');
730
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
600
731
  }
601
732
  this.wssSendRefreshRequired();
602
733
  }
734
+ /**
735
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
736
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
737
+ */
603
738
  registerProcessHandlers() {
604
739
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
605
740
  process.removeAllListeners('uncaughtException');
606
741
  process.removeAllListeners('unhandledRejection');
607
742
  this.exceptionHandler = async (error) => {
608
743
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
744
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
609
745
  };
610
746
  process.on('uncaughtException', this.exceptionHandler);
611
747
  this.rejectionHandler = async (reason, promise) => {
612
748
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
749
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
613
750
  };
614
751
  process.on('unhandledRejection', this.rejectionHandler);
615
752
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -622,6 +759,9 @@ export class Matterbridge extends EventEmitter {
622
759
  };
623
760
  process.on('SIGTERM', this.sigtermHandler);
624
761
  }
762
+ /**
763
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
764
+ */
625
765
  deregisterProcesslHandlers() {
626
766
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
627
767
  if (this.exceptionHandler)
@@ -638,7 +778,11 @@ export class Matterbridge extends EventEmitter {
638
778
  process.off('SIGTERM', this.sigtermHandler);
639
779
  this.sigtermHandler = undefined;
640
780
  }
781
+ /**
782
+ * Logs the node and system information.
783
+ */
641
784
  async logNodeAndSystemInfo() {
785
+ // IP address information
642
786
  const networkInterfaces = os.networkInterfaces();
643
787
  this.systemInformation.ipv4Address = '';
644
788
  this.systemInformation.ipv6Address = '';
@@ -658,7 +802,7 @@ export class Matterbridge extends EventEmitter {
658
802
  this.systemInformation.macAddress = detail.mac;
659
803
  }
660
804
  }
661
- if (this.systemInformation.ipv4Address !== '') {
805
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
662
806
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
663
807
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
664
808
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -666,19 +810,22 @@ export class Matterbridge extends EventEmitter {
666
810
  break;
667
811
  }
668
812
  }
813
+ // Node information
669
814
  this.systemInformation.nodeVersion = process.versions.node;
670
815
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
671
816
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
672
817
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
818
+ // Host system information
673
819
  this.systemInformation.hostname = os.hostname();
674
820
  this.systemInformation.user = os.userInfo().username;
675
- this.systemInformation.osType = os.type();
676
- this.systemInformation.osRelease = os.release();
677
- this.systemInformation.osPlatform = os.platform();
678
- this.systemInformation.osArch = os.arch();
679
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
680
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
681
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
821
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
822
+ this.systemInformation.osRelease = os.release(); // Kernel version
823
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
824
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
825
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
826
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
827
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
828
+ // Log the system information
682
829
  this.log.debug('Host System Information:');
683
830
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
684
831
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -694,15 +841,19 @@ export class Matterbridge extends EventEmitter {
694
841
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
695
842
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
696
843
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
844
+ // Home directory
697
845
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
698
846
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
699
847
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
848
+ // Package root directory
700
849
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
701
850
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
702
851
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
703
852
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
853
+ // Global node_modules directory
704
854
  if (this.nodeContext)
705
855
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
856
+ // First run of Matterbridge so the node storage is empty
706
857
  if (this.globalModulesDirectory === '') {
707
858
  try {
708
859
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -726,6 +877,7 @@ export class Matterbridge extends EventEmitter {
726
877
  this.log.error(`Error getting global node_modules directory: ${error}`);
727
878
  });
728
879
  }
880
+ // Create the data directory .matterbridge in the home directory
729
881
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
730
882
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
731
883
  try {
@@ -749,6 +901,7 @@ export class Matterbridge extends EventEmitter {
749
901
  }
750
902
  }
751
903
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
904
+ // Create the plugin directory Matterbridge in the home directory
752
905
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
753
906
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
754
907
  try {
@@ -772,19 +925,28 @@ export class Matterbridge extends EventEmitter {
772
925
  }
773
926
  }
774
927
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
928
+ // Matterbridge version
775
929
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
776
930
  this.matterbridgeVersion = packageJson.version;
777
931
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
778
932
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
933
+ // Matterbridge latest version
779
934
  if (this.nodeContext)
780
935
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
781
936
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
782
937
  this.getMatterbridgeLatestVersion();
938
+ // Current working directory
783
939
  const currentDir = process.cwd();
784
940
  this.log.debug(`Current Working Directory: ${currentDir}`);
941
+ // Command line arguments (excluding 'node' and the script name)
785
942
  const cmdArgs = process.argv.slice(2).join(' ');
786
943
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
787
944
  }
945
+ /**
946
+ * Retrieves the latest version of a package from the npm registry.
947
+ * @param packageName - The name of the package.
948
+ * @returns A Promise that resolves to the latest version of the package.
949
+ */
788
950
  async getLatestVersion(packageName) {
789
951
  return new Promise((resolve, reject) => {
790
952
  this.execRunningCount++;
@@ -799,6 +961,10 @@ export class Matterbridge extends EventEmitter {
799
961
  });
800
962
  });
801
963
  }
964
+ /**
965
+ * Retrieves the path to the global Node.js modules directory.
966
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
967
+ */
802
968
  async getGlobalNodeModules() {
803
969
  return new Promise((resolve, reject) => {
804
970
  this.execRunningCount++;
@@ -813,6 +979,11 @@ export class Matterbridge extends EventEmitter {
813
979
  });
814
980
  });
815
981
  }
982
+ /**
983
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
984
+ * @private
985
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
986
+ */
816
987
  async getMatterbridgeLatestVersion() {
817
988
  this.getLatestVersion('matterbridge')
818
989
  .then(async (matterbridgeLatestVersion) => {
@@ -829,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
829
1000
  })
830
1001
  .catch((error) => {
831
1002
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
1003
+ // error.stack && this.log.debug(error.stack);
832
1004
  });
833
1005
  }
1006
+ /**
1007
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
1008
+ * If the plugin's version is different from the latest version, logs a warning message.
1009
+ * If the plugin's version is the same as the latest version, logs an info message.
1010
+ * If there is an error retrieving the latest version, logs an error message.
1011
+ *
1012
+ * @private
1013
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
1014
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
1015
+ */
834
1016
  async getPluginLatestVersion(plugin) {
835
1017
  this.getLatestVersion(plugin.name)
836
1018
  .then(async (latestVersion) => {
@@ -842,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
842
1024
  })
843
1025
  .catch((error) => {
844
1026
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1027
+ // error.stack && this.log.debug(error.stack);
845
1028
  });
846
1029
  }
1030
+ /**
1031
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1032
+ *
1033
+ * @returns {Function} The MatterLogger function.
1034
+ */
847
1035
  createMatterLogger() {
848
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1036
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
849
1037
  return (_level, formattedLog) => {
850
1038
  const logger = formattedLog.slice(44, 44 + 20).trim();
851
1039
  const message = formattedLog.slice(65);
852
1040
  matterLogger.logName = logger;
853
1041
  switch (_level) {
854
1042
  case MatterLogLevel.DEBUG:
855
- matterLogger.log("debug", message);
1043
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
856
1044
  break;
857
1045
  case MatterLogLevel.INFO:
858
- matterLogger.log("info", message);
1046
+ matterLogger.log("info" /* LogLevel.INFO */, message);
859
1047
  break;
860
1048
  case MatterLogLevel.NOTICE:
861
- matterLogger.log("notice", message);
1049
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
862
1050
  break;
863
1051
  case MatterLogLevel.WARN:
864
- matterLogger.log("warn", message);
1052
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
865
1053
  break;
866
1054
  case MatterLogLevel.ERROR:
867
- matterLogger.log("error", message);
1055
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
868
1056
  break;
869
1057
  case MatterLogLevel.FATAL:
870
- matterLogger.log("fatal", message);
1058
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
871
1059
  break;
872
1060
  default:
873
- matterLogger.log("debug", message);
1061
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
874
1062
  break;
875
1063
  }
876
1064
  };
877
1065
  }
1066
+ /**
1067
+ * Creates a Matter File Logger.
1068
+ *
1069
+ * @param {string} filePath - The path to the log file.
1070
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1071
+ * @returns {Function} - A function that logs formatted messages to the log file.
1072
+ */
878
1073
  async createMatterFileLogger(filePath, unlink = false) {
1074
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
879
1075
  let fileSize = 0;
880
1076
  if (unlink) {
881
1077
  try {
@@ -924,18 +1120,30 @@ export class Matterbridge extends EventEmitter {
924
1120
  }
925
1121
  };
926
1122
  }
1123
+ /**
1124
+ * Update matterbridge and cleanup.
1125
+ */
927
1126
  async updateProcess() {
928
1127
  await this.cleanup('updating...', false);
929
1128
  }
1129
+ /**
1130
+ * Restarts the process by spawning a new process and exiting the current process.
1131
+ */
930
1132
  async restartProcess() {
931
1133
  await this.cleanup('restarting...', true);
932
1134
  }
1135
+ /**
1136
+ * Shut down the process by exiting the current process.
1137
+ */
933
1138
  async shutdownProcess() {
934
1139
  await this.cleanup('shutting down...', false);
935
1140
  }
1141
+ /**
1142
+ * Shut down the process and reset.
1143
+ */
936
1144
  async unregisterAndShutdownProcess() {
937
1145
  this.log.info('Unregistering all devices and shutting down...');
938
- for (const plugin of this.plugins) {
1146
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
939
1147
  if (this.edge)
940
1148
  await this.removeAllBridgedEndpoints(plugin.name);
941
1149
  else
@@ -943,37 +1151,55 @@ export class Matterbridge extends EventEmitter {
943
1151
  }
944
1152
  await this.cleanup('unregistered all devices and shutting down...', false);
945
1153
  }
1154
+ /**
1155
+ * Shut down the process and reset.
1156
+ */
946
1157
  async shutdownProcessAndReset() {
947
1158
  await this.cleanup('shutting down with reset...', false);
948
1159
  }
1160
+ /**
1161
+ * Shut down the process and factory reset.
1162
+ */
949
1163
  async shutdownProcessAndFactoryReset() {
950
1164
  await this.cleanup('shutting down with factory reset...', false);
951
1165
  }
1166
+ /**
1167
+ * Cleans up the Matterbridge instance.
1168
+ * @param message - The cleanup message.
1169
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1170
+ * @returns A promise that resolves when the cleanup is completed.
1171
+ */
952
1172
  async cleanup(message, restart = false) {
953
1173
  if (this.initialized && !this.hasCleanupStarted) {
954
1174
  this.hasCleanupStarted = true;
955
1175
  this.log.info(message);
1176
+ // Deregisters the process handlers
956
1177
  this.deregisterProcesslHandlers();
1178
+ // Clear the start matter interval
957
1179
  if (this.startMatterInterval) {
958
1180
  clearInterval(this.startMatterInterval);
959
1181
  this.startMatterInterval = undefined;
960
1182
  this.log.debug('Start matter interval cleared');
961
1183
  }
1184
+ // Clear the check update interval
962
1185
  if (this.checkUpdateInterval) {
963
1186
  clearInterval(this.checkUpdateInterval);
964
1187
  this.checkUpdateInterval = undefined;
965
1188
  this.log.debug('Check update interval cleared');
966
1189
  }
1190
+ // Clear the configure timeout
967
1191
  if (this.configureTimeout) {
968
1192
  clearTimeout(this.configureTimeout);
969
1193
  this.configureTimeout = undefined;
970
1194
  this.log.debug('Matterbridge configure timeout cleared');
971
1195
  }
1196
+ // Clear the reachability timeout
972
1197
  if (this.reachabilityTimeout) {
973
1198
  clearTimeout(this.reachabilityTimeout);
974
1199
  this.reachabilityTimeout = undefined;
975
1200
  this.log.debug('Matterbridge reachability timeout cleared');
976
1201
  }
1202
+ // Calling the shutdown method of each plugin and clear the reachability timeout
977
1203
  for (const plugin of this.plugins) {
978
1204
  if (!plugin.enabled || plugin.error)
979
1205
  continue;
@@ -984,6 +1210,7 @@ export class Matterbridge extends EventEmitter {
984
1210
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
985
1211
  }
986
1212
  }
1213
+ // Convert the matter storage to the new format
987
1214
  if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
988
1215
  if (this.bridgeMode === 'bridge') {
989
1216
  await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
@@ -996,24 +1223,29 @@ export class Matterbridge extends EventEmitter {
996
1223
  }
997
1224
  }
998
1225
  }
1226
+ // Close the http server
999
1227
  if (this.httpServer) {
1000
1228
  this.httpServer.close();
1001
1229
  this.httpServer.removeAllListeners();
1002
1230
  this.httpServer = undefined;
1003
1231
  this.log.debug('Frontend http server closed successfully');
1004
1232
  }
1233
+ // Close the https server
1005
1234
  if (this.httpsServer) {
1006
1235
  this.httpsServer.close();
1007
1236
  this.httpsServer.removeAllListeners();
1008
1237
  this.httpsServer = undefined;
1009
1238
  this.log.debug('Frontend https server closed successfully');
1010
1239
  }
1240
+ // Remove listeners from the express app
1011
1241
  if (this.expressApp) {
1012
1242
  this.expressApp.removeAllListeners();
1013
1243
  this.expressApp = undefined;
1014
1244
  this.log.debug('Frontend app closed successfully');
1015
1245
  }
1246
+ // Close the WebSocket server
1016
1247
  if (this.webSocketServer) {
1248
+ // Close all active connections
1017
1249
  this.webSocketServer.clients.forEach((client) => {
1018
1250
  if (client.readyState === WebSocket.OPEN) {
1019
1251
  client.close();
@@ -1029,26 +1261,35 @@ export class Matterbridge extends EventEmitter {
1029
1261
  });
1030
1262
  this.webSocketServer = undefined;
1031
1263
  }
1264
+ // Closing matter
1032
1265
  await this.stopMatterServer();
1266
+ // Closing matter storage
1033
1267
  await this.stopMatterStorage();
1268
+ // Remove the matterfilelogger
1034
1269
  try {
1035
1270
  Logger.removeLogger('matterfilelogger');
1271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1036
1272
  }
1037
1273
  catch (error) {
1274
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1038
1275
  }
1276
+ // Serialize registeredDevices
1039
1277
  if (this.nodeStorage && this.nodeContext) {
1040
1278
  this.log.info('Saving registered devices...');
1041
1279
  const serializedRegisteredDevices = [];
1042
1280
  this.devices.forEach(async (device) => {
1043
1281
  const serializedMatterbridgeDevice = device.serialize();
1282
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1044
1283
  if (serializedMatterbridgeDevice)
1045
1284
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1046
1285
  });
1047
1286
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1048
1287
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1288
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1049
1289
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1050
1290
  await this.nodeContext.close();
1051
1291
  this.nodeContext = undefined;
1292
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1052
1293
  for (const plugin of this.plugins) {
1053
1294
  if (plugin.nodeContext) {
1054
1295
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1080,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
1080
1321
  else {
1081
1322
  if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
1082
1323
  try {
1324
+ // Delete old matter storage file
1083
1325
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1084
1326
  this.log.info(`Unlinking old matter storage file: ${file}`);
1085
1327
  await fs.unlink(file);
@@ -1088,6 +1330,7 @@ export class Matterbridge extends EventEmitter {
1088
1330
  this.log.debug(`Error resetting old matter storage file: ${error}`);
1089
1331
  }
1090
1332
  try {
1333
+ // Delete matter node storage directory with its subdirectories
1091
1334
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1092
1335
  this.log.info(`Removing matter node storage directory: ${dir}`);
1093
1336
  await fs.rm(dir, { recursive: true });
@@ -1099,6 +1342,7 @@ export class Matterbridge extends EventEmitter {
1099
1342
  }
1100
1343
  if (message === 'shutting down with factory reset...') {
1101
1344
  try {
1345
+ // Delete node storage directory with its subdirectories
1102
1346
  this.log.info('Resetting Matterbridge storage...');
1103
1347
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1104
1348
  }
@@ -1115,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
1115
1359
  this.initialized = false;
1116
1360
  }
1117
1361
  }
1362
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1118
1363
  async addBridgedEndpoint(pluginName, device) {
1364
+ // Nothing to do here
1119
1365
  }
1366
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1120
1367
  async removeBridgedEndpoint(pluginName, device) {
1368
+ // Nothing to do here
1121
1369
  }
1370
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1122
1371
  async removeAllBridgedEndpoints(pluginName) {
1372
+ // Nothing to do here
1123
1373
  }
1374
+ /**
1375
+ * Adds a bridged device to the Matterbridge.
1376
+ * @param pluginName - The name of the plugin.
1377
+ * @param device - The bridged device to add.
1378
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1379
+ */
1124
1380
  async addBridgedDevice(pluginName, device) {
1125
1381
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1382
+ // Check if the plugin is registered
1126
1383
  const plugin = this.plugins.get(pluginName);
1127
1384
  if (!plugin) {
1128
1385
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1129
1386
  return;
1130
1387
  }
1388
+ // Register and add the device to matterbridge aggregator in bridge mode
1131
1389
  if (this.bridgeMode === 'bridge') {
1132
1390
  if (!this.matterAggregator) {
1133
1391
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1135,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
1135
1393
  }
1136
1394
  this.matterAggregator.addBridgedDevice(device);
1137
1395
  }
1396
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1397
+ // Register and add the device in childbridge mode
1138
1398
  if (this.bridgeMode === 'childbridge') {
1139
1399
  if (plugin.type === 'AccessoryPlatform') {
1400
+ // Check if the plugin is locked with the commissioning server
1140
1401
  if (!plugin.locked) {
1141
1402
  plugin.locked = true;
1142
1403
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1150,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
1150
1411
  }
1151
1412
  }
1152
1413
  if (plugin.type === 'DynamicPlatform') {
1414
+ // Check if the plugin is locked with the commissioning server and the aggregator
1153
1415
  if (!plugin.locked) {
1154
1416
  plugin.locked = true;
1155
1417
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1170,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
1170
1432
  plugin.registeredDevices++;
1171
1433
  if (plugin.addedDevices !== undefined)
1172
1434
  plugin.addedDevices++;
1435
+ // Add the device to the DeviceManager
1173
1436
  this.devices.set(device);
1174
1437
  this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1175
1438
  }
1439
+ /**
1440
+ * Removes a bridged device from the Matterbridge.
1441
+ * @param pluginName - The name of the plugin.
1442
+ * @param device - The device to be removed.
1443
+ * @returns A Promise that resolves when the device is successfully removed.
1444
+ */
1176
1445
  async removeBridgedDevice(pluginName, device) {
1177
1446
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1447
+ // Check if the plugin is registered
1178
1448
  const plugin = this.plugins.get(pluginName);
1179
1449
  if (!plugin) {
1180
1450
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1181
1451
  return;
1182
1452
  }
1453
+ // Remove the device from matterbridge aggregator in bridge mode
1183
1454
  if (this.bridgeMode === 'bridge') {
1184
1455
  if (!this.matterAggregator) {
1185
1456
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1189,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
1189
1460
  device.setBridgedDeviceReachability(false);
1190
1461
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1191
1462
  }
1463
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1464
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1192
1465
  this.matterAggregator?.removeBridgedDevice(device);
1193
1466
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1194
1467
  if (plugin.registeredDevices !== undefined)
@@ -1196,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
1196
1469
  if (plugin.addedDevices !== undefined)
1197
1470
  plugin.addedDevices--;
1198
1471
  }
1472
+ // Remove the device in childbridge mode
1199
1473
  if (this.bridgeMode === 'childbridge') {
1200
1474
  if (plugin.type === 'AccessoryPlatform') {
1201
1475
  if (!plugin.commissioningServer) {
@@ -1219,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
1219
1493
  plugin.registeredDevices--;
1220
1494
  if (plugin.addedDevices !== undefined)
1221
1495
  plugin.addedDevices--;
1496
+ // Remove the commissioning server
1222
1497
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1223
1498
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1224
1499
  plugin.commissioningServer = undefined;
1225
1500
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1226
1501
  }
1227
1502
  }
1503
+ // Remove the device from the DeviceManager
1228
1504
  this.devices.remove(device);
1229
1505
  }
1506
+ /**
1507
+ * Removes all bridged devices associated with a specific plugin.
1508
+ *
1509
+ * @param pluginName - The name of the plugin.
1510
+ * @returns A promise that resolves when all devices have been removed.
1511
+ */
1230
1512
  async removeAllBridgedDevices(pluginName) {
1231
1513
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1232
1514
  this.devices.forEach(async (device) => {
@@ -1235,7 +1517,13 @@ export class Matterbridge extends EventEmitter {
1235
1517
  }
1236
1518
  });
1237
1519
  }
1520
+ /**
1521
+ * Starts the Matterbridge in bridge mode.
1522
+ * @private
1523
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1524
+ */
1238
1525
  async startBridge() {
1526
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1239
1527
  if (!this.storageManager)
1240
1528
  throw new Error('No storage manager initialized');
1241
1529
  if (!this.matterbridgeContext)
@@ -1254,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
1254
1542
  let failCount = 0;
1255
1543
  this.startMatterInterval = setInterval(async () => {
1256
1544
  for (const plugin of this.plugins) {
1545
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1257
1546
  if (!plugin.enabled)
1258
1547
  continue;
1259
1548
  if (plugin.error) {
@@ -1278,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
1278
1567
  clearInterval(this.startMatterInterval);
1279
1568
  this.startMatterInterval = undefined;
1280
1569
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1570
+ // Start the Matter server
1281
1571
  await this.startMatterServer();
1282
1572
  this.log.notice('Matter server started');
1573
+ // Show the QR code for commissioning or log the already commissioned message
1283
1574
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1575
+ // Configure the plugins
1284
1576
  this.configureTimeout = setTimeout(async () => {
1285
1577
  for (const plugin of this.plugins) {
1286
1578
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1287
1579
  continue;
1288
1580
  try {
1289
- await this.plugins.configure(plugin);
1581
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1290
1582
  }
1291
1583
  catch (error) {
1292
1584
  plugin.error = true;
@@ -1295,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
1295
1587
  }
1296
1588
  this.wssSendRefreshRequired();
1297
1589
  }, 30 * 1000);
1590
+ // Setting reachability to true
1298
1591
  this.reachabilityTimeout = setTimeout(() => {
1299
1592
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1300
1593
  if (this.commissioningServer)
@@ -1304,7 +1597,14 @@ export class Matterbridge extends EventEmitter {
1304
1597
  }, 60 * 1000);
1305
1598
  }, 1000);
1306
1599
  }
1600
+ /**
1601
+ * Starts the Matterbridge in childbridge mode.
1602
+ * @private
1603
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1604
+ */
1307
1605
  async startChildbridge() {
1606
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1607
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1308
1608
  if (!this.storageManager)
1309
1609
  throw new Error('No storage manager initialized');
1310
1610
  this.matterServer = await this.createMatterServer(this.storageManager);
@@ -1314,6 +1614,7 @@ export class Matterbridge extends EventEmitter {
1314
1614
  this.startMatterInterval = setInterval(async () => {
1315
1615
  let allStarted = true;
1316
1616
  for (const plugin of this.plugins) {
1617
+ // Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1317
1618
  if (!plugin.enabled)
1318
1619
  continue;
1319
1620
  if (plugin.error) {
@@ -1341,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
1341
1642
  clearInterval(this.startMatterInterval);
1342
1643
  this.startMatterInterval = undefined;
1343
1644
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1645
+ // Start the Matter server
1344
1646
  await this.startMatterServer();
1345
1647
  this.log.notice('Matter server started');
1648
+ // Configure the plugins
1346
1649
  this.configureTimeout = setTimeout(async () => {
1347
1650
  for (const plugin of this.plugins) {
1348
1651
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1349
1652
  continue;
1350
1653
  try {
1351
- await this.plugins.configure(plugin);
1654
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1352
1655
  }
1353
1656
  catch (error) {
1354
1657
  plugin.error = true;
@@ -1377,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
1377
1680
  continue;
1378
1681
  }
1379
1682
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1683
+ // Setting reachability to true
1380
1684
  plugin.reachabilityTimeout = setTimeout(() => {
1381
1685
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1382
1686
  if (plugin.commissioningServer)
@@ -1389,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
1389
1693
  }
1390
1694
  }, 1000);
1391
1695
  }
1696
+ /**
1697
+ * Starts the Matterbridge controller.
1698
+ * @private
1699
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1700
+ */
1392
1701
  async startController() {
1393
1702
  if (!this.storageManager) {
1394
1703
  this.log.error('No storage manager initialized');
@@ -1451,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
1451
1760
  const nodeId = await this.commissioningController.commissionNode(options);
1452
1761
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1453
1762
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1454
- }
1763
+ } // (hasParameter('pairingcode'))
1455
1764
  if (hasParameter('unpairall')) {
1456
1765
  this.log.info('***Commissioning controller unpairing all nodes...');
1457
1766
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1462,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
1462
1771
  return;
1463
1772
  }
1464
1773
  if (hasParameter('discover')) {
1774
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
+ // console.log(discover);
1465
1776
  }
1466
1777
  if (!this.commissioningController.isCommissioned()) {
1467
1778
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1502,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
1502
1813
  },
1503
1814
  });
1504
1815
  node.logStructure();
1816
+ // Get the interaction client
1505
1817
  this.log.info('Getting the interaction client');
1506
1818
  const interactionClient = await node.getInteractionClient();
1507
1819
  let cluster;
1508
1820
  let attributes;
1821
+ // Log BasicInformationCluster
1509
1822
  cluster = BasicInformationCluster;
1510
1823
  attributes = await interactionClient.getMultipleAttributes({
1511
1824
  attributes: [{ clusterId: cluster.id }],
@@ -1515,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
1515
1828
  attributes.forEach((attribute) => {
1516
1829
  this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1517
1830
  });
1831
+ // Log PowerSourceCluster
1518
1832
  cluster = PowerSourceCluster;
1519
1833
  attributes = await interactionClient.getMultipleAttributes({
1520
1834
  attributes: [{ clusterId: cluster.id }],
@@ -1524,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
1524
1838
  attributes.forEach((attribute) => {
1525
1839
  this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1526
1840
  });
1841
+ // Log ThreadNetworkDiagnostics
1527
1842
  cluster = ThreadNetworkDiagnosticsCluster;
1528
1843
  attributes = await interactionClient.getMultipleAttributes({
1529
1844
  attributes: [{ clusterId: cluster.id }],
@@ -1533,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
1533
1848
  attributes.forEach((attribute) => {
1534
1849
  this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1535
1850
  });
1851
+ // Log SwitchCluster
1536
1852
  cluster = SwitchCluster;
1537
1853
  attributes = await interactionClient.getMultipleAttributes({
1538
1854
  attributes: [{ clusterId: cluster.id }],
@@ -1553,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
1553
1869
  this.log.info('Subscribed to all attributes and events');
1554
1870
  }
1555
1871
  }
1872
+ /** ***********************************************************************************************************************************/
1873
+ /** Matter.js methods */
1874
+ /** ***********************************************************************************************************************************/
1875
+ /**
1876
+ * Starts the matter storage process based on the specified storage type and name.
1877
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1878
+ * @param {string} storageName - The name of the storage file.
1879
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1880
+ */
1556
1881
  async startMatterStorage(storageType, storageName) {
1557
1882
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1558
1883
  if (storageType === 'disk') {
@@ -1601,6 +1926,12 @@ export class Matterbridge extends EventEmitter {
1601
1926
  await this.matterbridgeContext.set('passcode', this.passcode);
1602
1927
  await this.matterbridgeContext.set('discriminator', this.discriminator);
1603
1928
  }
1929
+ /**
1930
+ * Convert the old API matter storage to the new API format.
1931
+ * @param {StorageContext} context - The context of Matterbridge or of the plugin.
1932
+ * @param {string} pluginName - The name of the plugin or Matterbridge.
1933
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1934
+ */
1604
1935
  async convertStorage(context, pluginName) {
1605
1936
  if (this.edge !== false)
1606
1937
  return;
@@ -1615,13 +1946,19 @@ export class Matterbridge extends EventEmitter {
1615
1946
  else {
1616
1947
  this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1617
1948
  }
1949
+ // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1618
1950
  const fabricManagerContext = context.createContext('FabricManager');
1619
1951
  const fabrics = (await fabricManagerContext.get('fabrics', []));
1620
1952
  const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
1953
+ // Read EventHandler from the old storage
1621
1954
  const eventHandlerContext = context.createContext('EventHandler');
1955
+ // Read SessionManager from the old storage
1622
1956
  const sessionManagerContext = context.createContext('SessionManager');
1957
+ // Read EndpointStructure from the old storage
1623
1958
  const endpointStructureContext = context.createContext('EndpointStructure');
1959
+ // Read generalCommissioning from the old storage
1624
1960
  const generalCommissioningContext = context.createContext('Cluster-0-48');
1961
+ // Read basicInformation from the old storage
1625
1962
  const basicInformationContext = context.createContext('Cluster-0-40');
1626
1963
  const fabricInfo = {};
1627
1964
  const fabricInfoArray = [];
@@ -1675,8 +2012,13 @@ export class Matterbridge extends EventEmitter {
1675
2012
  await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
1676
2013
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
1677
2014
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
2015
+ // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
1678
2016
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
2017
+ // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
1679
2018
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
2019
+ // ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
2020
+ // From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
2021
+ // [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
1680
2022
  await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
1681
2023
  await nodeStorage
1682
2024
  .createContext('root')
@@ -1699,6 +2041,25 @@ export class Matterbridge extends EventEmitter {
1699
2041
  .createContext('root')
1700
2042
  .createContext('productDescription')
1701
2043
  .set('vendorId', await context.get('vendorId', 0xfff1));
2044
+ /*
2045
+ "Matterbridge.EndpointStructure": {
2046
+ "unique_d60ca095a002f160-index_0": 1,
2047
+ "unique_d60ca095a002f160-index_0-custom_Switch0": 2,
2048
+ "unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
2049
+ "unique_d60ca095a002f160-index_0-custom_Light0": 4,
2050
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
2051
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
2052
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
2053
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
2054
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
2055
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
2056
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
2057
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
2058
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
2059
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
2060
+ "nextEndpointId": 5
2061
+ },
2062
+ */
1702
2063
  const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
1703
2064
  this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
1704
2065
  for (const key of await endpointStructureContext.keys()) {
@@ -1777,6 +2138,12 @@ export class Matterbridge extends EventEmitter {
1777
2138
  this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
1778
2139
  }
1779
2140
  }
2141
+ /**
2142
+ * Makes a backup copy of the specified matter JSON storage file.
2143
+ *
2144
+ * @param storageName - The name of the JSON storage file to be backed up.
2145
+ * @param backupName - The name of the backup file to be created.
2146
+ */
1780
2147
  async backupMatterStorage(storageName, backupName) {
1781
2148
  try {
1782
2149
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1797,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
1797
2164
  }
1798
2165
  }
1799
2166
  }
2167
+ /**
2168
+ * Restore the specified matter JSON storage file.
2169
+ *
2170
+ * @param backupName - The name of the backup file to restore from.
2171
+ * @param storageName - The name of the JSON storage file to restored.
2172
+ */
1800
2173
  async restoreMatterStorage(backupName, storageName) {
1801
2174
  try {
1802
2175
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1817,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
1817
2190
  }
1818
2191
  }
1819
2192
  }
2193
+ /**
2194
+ * Stops the matter storage.
2195
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2196
+ */
1820
2197
  async stopMatterStorage() {
1821
2198
  this.log.debug('Stopping storage');
1822
2199
  await this.storageManager?.close();
@@ -1825,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
1825
2202
  this.matterbridgeContext = undefined;
1826
2203
  this.mattercontrollerContext = undefined;
1827
2204
  }
2205
+ /**
2206
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
2207
+ * @param storageManager The storage manager to be used by the Matter server.
2208
+ *
2209
+ */
1828
2210
  async createMatterServer(storageManager) {
1829
2211
  this.log.debug('Creating matter server');
2212
+ // Validate mdnsInterface
1830
2213
  if (this.mdnsInterface) {
1831
2214
  const networkInterfaces = os.networkInterfaces();
1832
2215
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1842,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
1842
2225
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1843
2226
  return matterServer;
1844
2227
  }
2228
+ /**
2229
+ * Starts the Matter server.
2230
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
2231
+ */
1845
2232
  async startMatterServer() {
1846
2233
  if (!this.matterServer) {
1847
2234
  this.log.error('No matter server initialized');
@@ -1851,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
1851
2238
  this.log.debug('Starting matter server...');
1852
2239
  await this.matterServer.start();
1853
2240
  this.log.debug('Started matter server');
2241
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1854
2242
  }
2243
+ /**
2244
+ * Stops the Matter server, commissioningServer and commissioningController.
2245
+ */
1855
2246
  async stopMatterServer() {
1856
2247
  this.log.debug('Stopping matter commissioningServer');
1857
2248
  await this.commissioningServer?.close();
@@ -1865,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
1865
2256
  this.matterAggregator = undefined;
1866
2257
  this.matterServer = undefined;
1867
2258
  }
2259
+ /**
2260
+ * Creates a Matter Aggregator.
2261
+ * @param {StorageContext} context - The storage context.
2262
+ * @returns {Aggregator} - The created Matter Aggregator.
2263
+ */
1868
2264
  async createMatterAggregator(context, pluginName) {
1869
2265
  this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
1870
2266
  const matterAggregator = new Aggregator();
1871
2267
  return matterAggregator;
1872
2268
  }
2269
+ /**
2270
+ * Creates a matter commissioning server.
2271
+ *
2272
+ * @param {StorageContext} context - The storage context.
2273
+ * @param {string} pluginName - The name of the commissioning server.
2274
+ * @returns {CommissioningServer} The created commissioning server.
2275
+ */
1873
2276
  async createCommisioningServer(context, pluginName) {
1874
2277
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1875
2278
  const deviceName = await context.get('deviceName');
1876
2279
  const deviceType = await context.get('deviceType');
1877
2280
  const vendorId = await context.get('vendorId');
1878
- const vendorName = await context.get('vendorName');
2281
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1879
2282
  const productId = await context.get('productId');
1880
- const productName = await context.get('productName');
2283
+ const productName = await context.get('productName'); // Home app = Model
1881
2284
  const serialNumber = await context.get('serialNumber');
1882
2285
  const uniqueId = await context.get('uniqueId');
1883
2286
  const softwareVersion = await context.get('softwareVersion', 1);
1884
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2287
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1885
2288
  const hardwareVersion = await context.get('hardwareVersion', 1);
1886
2289
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1887
2290
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1889,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
1889
2292
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1890
2293
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1891
2294
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
2295
+ // Validate ipv4address
1892
2296
  if (this.ipv4address) {
1893
2297
  const networkInterfaces = os.networkInterfaces();
1894
2298
  const availableAddresses = Object.values(networkInterfaces)
@@ -1903,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
1903
2307
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1904
2308
  }
1905
2309
  }
2310
+ // Validate ipv6address
1906
2311
  if (this.ipv6address) {
1907
2312
  const networkInterfaces = os.networkInterfaces();
1908
2313
  const availableAddresses = Object.values(networkInterfaces)
@@ -1933,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
1933
2338
  nodeLabel: productName,
1934
2339
  productLabel: productName,
1935
2340
  softwareVersion,
1936
- softwareVersionString,
2341
+ softwareVersionString, // Home app = Firmware Revision
1937
2342
  hardwareVersion,
1938
2343
  hardwareVersionString,
1939
2344
  uniqueId,
@@ -2029,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
2029
2434
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
2030
2435
  return commissioningServer;
2031
2436
  }
2437
+ /**
2438
+ * Creates a commissioning server storage context.
2439
+ *
2440
+ * @param pluginName - The name of the plugin.
2441
+ * @param deviceName - The name of the device.
2442
+ * @param deviceType - The type of the device.
2443
+ * @param vendorId - The vendor ID.
2444
+ * @param vendorName - The vendor name.
2445
+ * @param productId - The product ID.
2446
+ * @param productName - The product name.
2447
+ * @param serialNumber - The serial number of the device (optional).
2448
+ * @param uniqueId - The unique ID of the device (optional).
2449
+ * @param softwareVersion - The software version of the device (optional).
2450
+ * @param softwareVersionString - The software version string of the device (optional).
2451
+ * @param hardwareVersion - The hardware version of the device (optional).
2452
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2453
+ * @returns The storage context for the commissioning server.
2454
+ */
2032
2455
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
2033
2456
  if (!this.storageManager)
2034
2457
  throw new Error('No storage manager initialized');
@@ -2056,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
2056
2479
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2057
2480
  return storageContext;
2058
2481
  }
2482
+ /**
2483
+ * Imports the commissioning server context for a specific plugin and device.
2484
+ * @param pluginName - The name of the plugin.
2485
+ * @param device - The MatterbridgeDevice object representing the device.
2486
+ * @returns The commissioning server context.
2487
+ * @throws Error if the BasicInformationCluster is not found.
2488
+ */
2059
2489
  async importCommissioningServerContext(pluginName, device) {
2060
2490
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
2061
2491
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -2090,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
2090
2520
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2091
2521
  return storageContext;
2092
2522
  }
2523
+ /**
2524
+ * Shows the commissioning server QR code for a given plugin.
2525
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2526
+ * @param {StorageContext} storageContext - The storage context instance.
2527
+ * @param {NodeStorage} nodeContext - The node storage instance.
2528
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2529
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2530
+ */
2093
2531
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
2094
2532
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
2095
2533
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -2100,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
2100
2538
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2101
2539
  const QrCode = new QrCodeSchema();
2102
2540
  this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
2103
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2541
+ // eslint-disable-next-line no-console
2542
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
2104
2543
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
2105
2544
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2106
2545
  if (pluginName === 'Matterbridge') {
@@ -2147,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
2147
2586
  }
2148
2587
  this.wssSendRefreshRequired();
2149
2588
  }
2589
+ /**
2590
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2591
+ *
2592
+ * @param fabricInfo - The array of exposed fabric information objects.
2593
+ * @returns An array of sanitized exposed fabric information objects.
2594
+ */
2150
2595
  sanitizeFabricInformations(fabricInfo) {
2151
2596
  return fabricInfo.map((info) => {
2152
2597
  return {
@@ -2160,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
2160
2605
  };
2161
2606
  });
2162
2607
  }
2608
+ /**
2609
+ * Sanitizes the session information by converting bigint properties to string.
2610
+ *
2611
+ * @param sessionInfo - The array of session information objects.
2612
+ * @returns An array of sanitized session information objects.
2613
+ */
2163
2614
  sanitizeSessionInformation(sessionInfo) {
2164
2615
  return sessionInfo
2165
2616
  .filter((session) => session.isPeerActive)
@@ -2187,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
2187
2638
  };
2188
2639
  });
2189
2640
  }
2641
+ /**
2642
+ * Sets the reachability of a commissioning server and trigger.
2643
+ *
2644
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2645
+ * @param {boolean} reachable - The new reachability status.
2646
+ */
2190
2647
  setCommissioningServerReachability(commissioningServer, reachable) {
2191
2648
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2192
2649
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2194,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
2194
2651
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2195
2652
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2196
2653
  }
2654
+ /**
2655
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2656
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2657
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2658
+ */
2197
2659
  setAggregatorReachability(matterAggregator, reachable) {
2198
2660
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2199
2661
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2206,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
2206
2668
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2207
2669
  });
2208
2670
  }
2671
+ /**
2672
+ * Sets the reachability of a device and trigger.
2673
+ *
2674
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2675
+ * @param {boolean} reachable - The new reachability status of the device.
2676
+ */
2209
2677
  setDeviceReachability(device, reachable) {
2210
2678
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2211
2679
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2254,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
2254
2722
  }
2255
2723
  return vendorName;
2256
2724
  };
2725
+ /**
2726
+ * Retrieves the base registered plugins sanitized for res.json().
2727
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2728
+ */
2257
2729
  async getBaseRegisteredPlugins() {
2258
2730
  const baseRegisteredPlugins = [];
2259
2731
  for (const plugin of this.plugins) {
@@ -2285,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
2285
2757
  }
2286
2758
  return baseRegisteredPlugins;
2287
2759
  }
2760
+ /**
2761
+ * Spawns a child process with the given command and arguments.
2762
+ * @param {string} command - The command to execute.
2763
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2764
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2765
+ */
2288
2766
  async spawnCommand(command, args = []) {
2767
+ /*
2768
+ npm > npm.cmd on windows
2769
+ cmd.exe ['dir'] on windows
2770
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2771
+ process.on('unhandledRejection', (reason, promise) => {
2772
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2773
+ });
2774
+
2775
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2776
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2777
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2778
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2779
+ */
2289
2780
  const cmdLine = command + ' ' + args.join(' ');
2290
2781
  if (process.platform === 'win32' && command === 'npm') {
2782
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2291
2783
  const argstring = 'npm ' + args.join(' ');
2292
2784
  args.splice(0, args.length, '/c', argstring);
2293
2785
  command = 'cmd.exe';
2294
2786
  }
2787
+ // Decide when using sudo on linux
2788
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2789
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2295
2790
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2296
2791
  args.unshift(command);
2297
2792
  command = 'sudo';
@@ -2349,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
2349
2844
  }
2350
2845
  });
2351
2846
  }
2847
+ /**
2848
+ * Sends a WebSocket message to all connected clients.
2849
+ *
2850
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2851
+ * @param {string} time - The time string of the message
2852
+ * @param {string} name - The logger name of the message
2853
+ * @param {string} message - The content of the message.
2854
+ */
2352
2855
  wssSendMessage(level, time, name, message) {
2353
2856
  if (!level || !time || !name || !message)
2354
2857
  return;
2858
+ // Remove ANSI escape codes from the message
2859
+ // eslint-disable-next-line no-control-regex
2355
2860
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2861
+ // Remove leading asterisks from the message
2356
2862
  message = message.replace(/^\*+/, '');
2863
+ // Replace all occurrences of \t and \n
2357
2864
  message = message.replace(/[\t\n]/g, '');
2865
+ // Remove non-printable characters
2866
+ // eslint-disable-next-line no-control-regex
2358
2867
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2868
+ // Replace all occurrences of \" with "
2359
2869
  message = message.replace(/\\"/g, '"');
2870
+ // Define the maximum allowed length for continuous characters without a space
2360
2871
  const maxContinuousLength = 100;
2361
2872
  const keepStartLength = 20;
2362
2873
  const keepEndLength = 20;
2874
+ // Split the message into words
2363
2875
  message = message
2364
2876
  .split(' ')
2365
2877
  .map((word) => {
2878
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2366
2879
  if (word.length > maxContinuousLength) {
2367
2880
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2368
2881
  }
2369
2882
  return word;
2370
2883
  })
2371
2884
  .join(' ');
2885
+ // Send the message to all connected clients
2372
2886
  this.webSocketServer?.clients.forEach((client) => {
2373
2887
  if (client.readyState === WebSocket.OPEN) {
2374
2888
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2375
2889
  }
2376
2890
  });
2377
2891
  }
2892
+ /**
2893
+ * Sends a need to refresh WebSocket message to all connected clients.
2894
+ *
2895
+ */
2378
2896
  wssSendRefreshRequired() {
2379
2897
  this.matterbridgeInformation.refreshRequired = true;
2898
+ // Send the message to all connected clients
2380
2899
  this.webSocketServer?.clients.forEach((client) => {
2381
2900
  if (client.readyState === WebSocket.OPEN) {
2382
2901
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
2383
2902
  }
2384
2903
  });
2385
2904
  }
2905
+ /**
2906
+ * Sends a need to restart WebSocket message to all connected clients.
2907
+ *
2908
+ */
2386
2909
  wssSendRestartRequired() {
2387
2910
  this.matterbridgeInformation.restartRequired = true;
2911
+ // Send the message to all connected clients
2388
2912
  this.webSocketServer?.clients.forEach((client) => {
2389
2913
  if (client.readyState === WebSocket.OPEN) {
2390
2914
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
2391
2915
  }
2392
2916
  });
2393
2917
  }
2918
+ /**
2919
+ * Initializes the frontend of Matterbridge.
2920
+ *
2921
+ * @param port The port number to run the frontend server on. Default is 8283.
2922
+ */
2394
2923
  async initializeFrontend(port = 8283) {
2395
2924
  let initializeError = false;
2396
2925
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2926
+ // Create the express app that serves the frontend
2397
2927
  this.expressApp = express();
2928
+ // Log all requests to the server for debugging
2929
+ /*
2930
+ if (hasParameter('homedir')) {
2931
+ this.expressApp.use((req, res, next) => {
2932
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2933
+ next();
2934
+ });
2935
+ }
2936
+ */
2937
+ // Serve static files from '/static' endpoint
2398
2938
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2399
2939
  if (!hasParameter('ssl')) {
2940
+ // Create an HTTP server and attach the express app
2400
2941
  this.httpServer = createServer(this.expressApp);
2942
+ // Listen on the specified port
2401
2943
  if (hasParameter('ingress')) {
2402
2944
  this.httpServer.listen(port, '0.0.0.0', () => {
2403
2945
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2411,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
2411
2953
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2412
2954
  });
2413
2955
  }
2956
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2414
2957
  this.httpServer.on('error', (error) => {
2415
2958
  this.log.error(`Frontend http server error listening on ${port}`);
2416
2959
  switch (error.code) {
@@ -2426,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
2426
2969
  });
2427
2970
  }
2428
2971
  else {
2972
+ // Load the SSL certificate, the private key and optionally the CA certificate
2429
2973
  let cert;
2430
2974
  try {
2431
2975
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2453,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
2453
2997
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2454
2998
  }
2455
2999
  const serverOptions = { cert, key, ca };
3000
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2456
3001
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
3002
+ // Listen on the specified port
2457
3003
  if (hasParameter('ingress')) {
2458
3004
  this.httpsServer.listen(port, '0.0.0.0', () => {
2459
3005
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2467,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
2467
3013
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2468
3014
  });
2469
3015
  }
3016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2470
3017
  this.httpsServer.on('error', (error) => {
2471
3018
  this.log.error(`Frontend https server error listening on ${port}`);
2472
3019
  switch (error.code) {
@@ -2483,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
2483
3030
  }
2484
3031
  if (initializeError)
2485
3032
  return;
3033
+ // Createe a WebSocket server and attach it to the http or https server
2486
3034
  const wssPort = port;
2487
3035
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2488
3036
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2489
3037
  this.webSocketServer.on('connection', (ws, request) => {
2490
3038
  const clientIp = request.socket.remoteAddress;
2491
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
3039
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2492
3040
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2493
3041
  ws.on('message', (message) => {
2494
3042
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2521,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
2521
3069
  this.webSocketServer.on('error', (ws, error) => {
2522
3070
  this.log.error(`WebSocketServer error: ${error}`);
2523
3071
  });
3072
+ // Endpoint to validate login code
2524
3073
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2525
3074
  const { password } = req.body;
2526
3075
  this.log.debug('The frontend sent /api/login', password);
@@ -2539,12 +3088,24 @@ export class Matterbridge extends EventEmitter {
2539
3088
  this.log.warn('/api/login error wrong password');
2540
3089
  res.json({ valid: false });
2541
3090
  }
3091
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2542
3092
  }
2543
3093
  catch (error) {
2544
3094
  this.log.error('/api/login error getting password');
2545
3095
  res.json({ valid: false });
2546
3096
  }
2547
3097
  });
3098
+ // Endpoint to provide health check
3099
+ this.expressApp.get('/health', (req, res) => {
3100
+ this.log.debug('Express received /health');
3101
+ const healthStatus = {
3102
+ status: 'ok', // Indicate service is healthy
3103
+ uptime: process.uptime(), // Server uptime in seconds
3104
+ timestamp: new Date().toISOString(), // Current timestamp
3105
+ };
3106
+ res.status(200).json(healthStatus);
3107
+ });
3108
+ // Endpoint to provide settings
2548
3109
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2549
3110
  this.log.debug('The frontend sent /api/settings');
2550
3111
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2565,13 +3126,17 @@ export class Matterbridge extends EventEmitter {
2565
3126
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2566
3127
  this.matterbridgeInformation.profile = this.profile;
2567
3128
  const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
3129
+ // this.log.debug('Response:', debugStringify(response));
2568
3130
  res.json(response);
2569
3131
  });
3132
+ // Endpoint to provide plugins
2570
3133
  this.expressApp.get('/api/plugins', async (req, res) => {
2571
3134
  this.log.debug('The frontend sent /api/plugins');
2572
3135
  const response = await this.getBaseRegisteredPlugins();
3136
+ // this.log.debug('Response:', debugStringify(response));
2573
3137
  res.json(response);
2574
3138
  });
3139
+ // Endpoint to provide devices
2575
3140
  this.expressApp.get('/api/devices', (req, res) => {
2576
3141
  this.log.debug('The frontend sent /api/devices');
2577
3142
  const devices = [];
@@ -2604,8 +3169,10 @@ export class Matterbridge extends EventEmitter {
2604
3169
  cluster: cluster,
2605
3170
  });
2606
3171
  });
3172
+ // this.log.debug('Response:', debugStringify(data));
2607
3173
  res.json(devices);
2608
3174
  });
3175
+ // Endpoint to provide the cluster servers of the devices
2609
3176
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2610
3177
  const selectedPluginName = req.params.selectedPluginName;
2611
3178
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2625,6 +3192,7 @@ export class Matterbridge extends EventEmitter {
2625
3192
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2626
3193
  if (clusterServer.name === 'EveHistory')
2627
3194
  return;
3195
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2628
3196
  let attributeValue;
2629
3197
  try {
2630
3198
  if (typeof value.getLocal() === 'object')
@@ -2635,6 +3203,7 @@ export class Matterbridge extends EventEmitter {
2635
3203
  catch (error) {
2636
3204
  attributeValue = 'Fabric-Scoped';
2637
3205
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3206
+ // console.log(error);
2638
3207
  }
2639
3208
  data.push({
2640
3209
  endpoint: device.number ? device.number.toString() : '...',
@@ -2647,12 +3216,14 @@ export class Matterbridge extends EventEmitter {
2647
3216
  });
2648
3217
  });
2649
3218
  device.getChildEndpoints().forEach((childEndpoint) => {
3219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2650
3220
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2651
3221
  const clusterServers = childEndpoint.getAllClusterServers();
2652
3222
  clusterServers.forEach((clusterServer) => {
2653
3223
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2654
3224
  if (clusterServer.name === 'EveHistory')
2655
3225
  return;
3226
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2656
3227
  let attributeValue;
2657
3228
  try {
2658
3229
  if (typeof value.getLocal() === 'object')
@@ -2663,6 +3234,7 @@ export class Matterbridge extends EventEmitter {
2663
3234
  catch (error) {
2664
3235
  attributeValue = 'Unavailable';
2665
3236
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3237
+ // console.log(error);
2666
3238
  }
2667
3239
  data.push({
2668
3240
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2679,6 +3251,7 @@ export class Matterbridge extends EventEmitter {
2679
3251
  });
2680
3252
  res.json(data);
2681
3253
  });
3254
+ // Endpoint to view the log
2682
3255
  this.expressApp.get('/api/view-log', async (req, res) => {
2683
3256
  this.log.debug('The frontend sent /api/log');
2684
3257
  try {
@@ -2691,10 +3264,12 @@ export class Matterbridge extends EventEmitter {
2691
3264
  res.status(500).send('Error reading log file');
2692
3265
  }
2693
3266
  });
3267
+ // Endpoint to download the matterbridge log
2694
3268
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2695
3269
  this.log.debug('The frontend sent /api/download-mblog');
2696
3270
  try {
2697
3271
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3272
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2698
3273
  }
2699
3274
  catch (error) {
2700
3275
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2706,10 +3281,12 @@ export class Matterbridge extends EventEmitter {
2706
3281
  }
2707
3282
  });
2708
3283
  });
3284
+ // Endpoint to download the matter log
2709
3285
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2710
3286
  this.log.debug('The frontend sent /api/download-mjlog');
2711
3287
  try {
2712
3288
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3289
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2713
3290
  }
2714
3291
  catch (error) {
2715
3292
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2721,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
2721
3298
  }
2722
3299
  });
2723
3300
  });
3301
+ // Endpoint to download the matter storage file
2724
3302
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2725
3303
  this.log.debug('The frontend sent /api/download-mjstorage');
2726
3304
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2730,6 +3308,7 @@ export class Matterbridge extends EventEmitter {
2730
3308
  }
2731
3309
  });
2732
3310
  });
3311
+ // Endpoint to download the matterbridge storage directory
2733
3312
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2734
3313
  this.log.debug('The frontend sent /api/download-mbstorage');
2735
3314
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2740,6 +3319,7 @@ export class Matterbridge extends EventEmitter {
2740
3319
  }
2741
3320
  });
2742
3321
  });
3322
+ // Endpoint to download the matterbridge plugin directory
2743
3323
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2744
3324
  this.log.debug('The frontend sent /api/download-pluginstorage');
2745
3325
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2750,9 +3330,11 @@ export class Matterbridge extends EventEmitter {
2750
3330
  }
2751
3331
  });
2752
3332
  });
3333
+ // Endpoint to download the matterbridge plugin config files
2753
3334
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2754
3335
  this.log.debug('The frontend sent /api/download-pluginconfig');
2755
3336
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3337
+ // 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')));
2756
3338
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2757
3339
  if (error) {
2758
3340
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2760,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
2760
3342
  }
2761
3343
  });
2762
3344
  });
3345
+ // Endpoint to download the matterbridge plugin config files
2763
3346
  this.expressApp.get('/api/download-backup', async (req, res) => {
2764
3347
  this.log.debug('The frontend sent /api/download-backup');
2765
3348
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2769,6 +3352,7 @@ export class Matterbridge extends EventEmitter {
2769
3352
  }
2770
3353
  });
2771
3354
  });
3355
+ // Endpoint to receive commands
2772
3356
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2773
3357
  const command = req.params.command;
2774
3358
  let param = req.params.param;
@@ -2778,13 +3362,15 @@ export class Matterbridge extends EventEmitter {
2778
3362
  return;
2779
3363
  }
2780
3364
  this.log.debug(`Received frontend command: ${command}:${param}`);
3365
+ // Handle the command setpassword from Settings
2781
3366
  if (command === 'setpassword') {
2782
- const password = param.slice(1, -1);
3367
+ const password = param.slice(1, -1); // Remove the first and last characters
2783
3368
  this.log.debug('setpassword', param, password);
2784
3369
  await this.nodeContext?.set('password', password);
2785
3370
  res.json({ message: 'Command received' });
2786
3371
  return;
2787
3372
  }
3373
+ // Handle the command setbridgemode from Settings
2788
3374
  if (command === 'setbridgemode') {
2789
3375
  this.log.debug(`setbridgemode: ${param}`);
2790
3376
  this.wssSendRestartRequired();
@@ -2792,6 +3378,7 @@ export class Matterbridge extends EventEmitter {
2792
3378
  res.json({ message: 'Command received' });
2793
3379
  return;
2794
3380
  }
3381
+ // Handle the command backup from Settings
2795
3382
  if (command === 'backup') {
2796
3383
  this.log.notice(`Prepairing the backup...`);
2797
3384
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2799,25 +3386,26 @@ export class Matterbridge extends EventEmitter {
2799
3386
  res.json({ message: 'Command received' });
2800
3387
  return;
2801
3388
  }
3389
+ // Handle the command setmbloglevel from Settings
2802
3390
  if (command === 'setmbloglevel') {
2803
3391
  this.log.debug('Matterbridge log level:', param);
2804
3392
  if (param === 'Debug') {
2805
- this.log.logLevel = "debug";
3393
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2806
3394
  }
2807
3395
  else if (param === 'Info') {
2808
- this.log.logLevel = "info";
3396
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2809
3397
  }
2810
3398
  else if (param === 'Notice') {
2811
- this.log.logLevel = "notice";
3399
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2812
3400
  }
2813
3401
  else if (param === 'Warn') {
2814
- this.log.logLevel = "warn";
3402
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2815
3403
  }
2816
3404
  else if (param === 'Error') {
2817
- this.log.logLevel = "error";
3405
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2818
3406
  }
2819
3407
  else if (param === 'Fatal') {
2820
- this.log.logLevel = "fatal";
3408
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2821
3409
  }
2822
3410
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2823
3411
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2825,12 +3413,13 @@ export class Matterbridge extends EventEmitter {
2825
3413
  for (const plugin of this.plugins) {
2826
3414
  if (!plugin.platform || !plugin.platform.config)
2827
3415
  continue;
2828
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2829
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3416
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3417
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2830
3418
  }
2831
3419
  res.json({ message: 'Command received' });
2832
3420
  return;
2833
3421
  }
3422
+ // Handle the command setmbloglevel from Settings
2834
3423
  if (command === 'setmjloglevel') {
2835
3424
  this.log.debug('Matter.js log level:', param);
2836
3425
  if (param === 'Debug') {
@@ -2855,30 +3444,34 @@ export class Matterbridge extends EventEmitter {
2855
3444
  res.json({ message: 'Command received' });
2856
3445
  return;
2857
3446
  }
3447
+ // Handle the command setmdnsinterface from Settings
2858
3448
  if (command === 'setmdnsinterface') {
2859
- param = param.slice(1, -1);
3449
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2860
3450
  this.matterbridgeInformation.mattermdnsinterface = param;
2861
3451
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2862
3452
  await this.nodeContext?.set('mattermdnsinterface', param);
2863
3453
  res.json({ message: 'Command received' });
2864
3454
  return;
2865
3455
  }
3456
+ // Handle the command setipv4address from Settings
2866
3457
  if (command === 'setipv4address') {
2867
- param = param.slice(1, -1);
3458
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2868
3459
  this.matterbridgeInformation.matteripv4address = param;
2869
3460
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2870
3461
  await this.nodeContext?.set('matteripv4address', param);
2871
3462
  res.json({ message: 'Command received' });
2872
3463
  return;
2873
3464
  }
3465
+ // Handle the command setipv6address from Settings
2874
3466
  if (command === 'setipv6address') {
2875
- param = param.slice(1, -1);
3467
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2876
3468
  this.matterbridgeInformation.matteripv6address = param;
2877
3469
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2878
3470
  await this.nodeContext?.set('matteripv6address', param);
2879
3471
  res.json({ message: 'Command received' });
2880
3472
  return;
2881
3473
  }
3474
+ // Handle the command setmatterport from Settings
2882
3475
  if (command === 'setmatterport') {
2883
3476
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2884
3477
  this.matterbridgeInformation.matterPort = port;
@@ -2887,6 +3480,7 @@ export class Matterbridge extends EventEmitter {
2887
3480
  res.json({ message: 'Command received' });
2888
3481
  return;
2889
3482
  }
3483
+ // Handle the command setmatterdiscriminator from Settings
2890
3484
  if (command === 'setmatterdiscriminator') {
2891
3485
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2892
3486
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2895,6 +3489,7 @@ export class Matterbridge extends EventEmitter {
2895
3489
  res.json({ message: 'Command received' });
2896
3490
  return;
2897
3491
  }
3492
+ // Handle the command setmatterpasscode from Settings
2898
3493
  if (command === 'setmatterpasscode') {
2899
3494
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2900
3495
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2903,17 +3498,20 @@ export class Matterbridge extends EventEmitter {
2903
3498
  res.json({ message: 'Command received' });
2904
3499
  return;
2905
3500
  }
3501
+ // Handle the command setmbloglevel from Settings
2906
3502
  if (command === 'setmblogfile') {
2907
3503
  this.log.debug('Matterbridge file log:', param);
2908
3504
  this.matterbridgeInformation.fileLogger = param === 'true';
2909
3505
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3506
+ // Create the file logger for matterbridge
2910
3507
  if (param === 'true')
2911
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3508
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2912
3509
  else
2913
3510
  AnsiLogger.setGlobalLogfile(undefined);
2914
3511
  res.json({ message: 'Command received' });
2915
3512
  return;
2916
3513
  }
3514
+ // Handle the command setmbloglevel from Settings
2917
3515
  if (command === 'setmjlogfile') {
2918
3516
  this.log.debug('Matter file log:', param);
2919
3517
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2940,36 +3538,43 @@ export class Matterbridge extends EventEmitter {
2940
3538
  res.json({ message: 'Command received' });
2941
3539
  return;
2942
3540
  }
3541
+ // Handle the command unregister from Settings
2943
3542
  if (command === 'unregister') {
2944
3543
  await this.unregisterAndShutdownProcess();
2945
3544
  res.json({ message: 'Command received' });
2946
3545
  return;
2947
3546
  }
3547
+ // Handle the command reset from Settings
2948
3548
  if (command === 'reset') {
2949
3549
  await this.shutdownProcessAndReset();
2950
3550
  res.json({ message: 'Command received' });
2951
3551
  return;
2952
3552
  }
3553
+ // Handle the command factoryreset from Settings
2953
3554
  if (command === 'factoryreset') {
2954
3555
  await this.shutdownProcessAndFactoryReset();
2955
3556
  res.json({ message: 'Command received' });
2956
3557
  return;
2957
3558
  }
3559
+ // Handle the command shutdown from Header
2958
3560
  if (command === 'shutdown') {
2959
3561
  await this.shutdownProcess();
2960
3562
  res.json({ message: 'Command received' });
2961
3563
  return;
2962
3564
  }
3565
+ // Handle the command restart from Header
2963
3566
  if (command === 'restart') {
2964
3567
  await this.restartProcess();
2965
3568
  res.json({ message: 'Command received' });
2966
3569
  return;
2967
3570
  }
3571
+ // Handle the command update from Header
2968
3572
  if (command === 'update') {
2969
3573
  this.log.info('Updating matterbridge...');
2970
3574
  try {
2971
3575
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2972
3576
  this.log.info('Matterbridge has been updated. Full restart required.');
3577
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2973
3578
  }
2974
3579
  catch (error) {
2975
3580
  this.log.error('Error updating matterbridge');
@@ -2979,9 +3584,11 @@ export class Matterbridge extends EventEmitter {
2979
3584
  res.json({ message: 'Command received' });
2980
3585
  return;
2981
3586
  }
3587
+ // Handle the command saveconfig from Home
2982
3588
  if (command === 'saveconfig') {
2983
3589
  param = param.replace(/\*/g, '\\');
2984
3590
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3591
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2985
3592
  if (!this.plugins.has(param)) {
2986
3593
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2987
3594
  }
@@ -2995,33 +3602,39 @@ export class Matterbridge extends EventEmitter {
2995
3602
  res.json({ message: 'Command received' });
2996
3603
  return;
2997
3604
  }
3605
+ // Handle the command installplugin from Home
2998
3606
  if (command === 'installplugin') {
2999
3607
  param = param.replace(/\*/g, '\\');
3000
3608
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
3001
3609
  try {
3002
3610
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
3003
3611
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3612
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3004
3613
  }
3005
3614
  catch (error) {
3006
3615
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
3007
3616
  }
3008
3617
  this.wssSendRestartRequired();
3009
3618
  param = param.split('@')[0];
3619
+ // Also add the plugin to matterbridge so no return!
3010
3620
  if (param === 'matterbridge') {
3621
+ // 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
3011
3622
  res.json({ message: 'Command received' });
3012
3623
  return;
3013
3624
  }
3014
3625
  }
3626
+ // Handle the command addplugin from Home
3015
3627
  if (command === 'addplugin' || command === 'installplugin') {
3016
3628
  param = param.replace(/\*/g, '\\');
3017
3629
  const plugin = await this.plugins.add(param);
3018
3630
  if (plugin) {
3019
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3631
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
3020
3632
  }
3021
3633
  res.json({ message: 'Command received' });
3022
3634
  this.wssSendRefreshRequired();
3023
3635
  return;
3024
3636
  }
3637
+ // Handle the command removeplugin from Home
3025
3638
  if (command === 'removeplugin') {
3026
3639
  if (!this.plugins.has(param)) {
3027
3640
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -3035,6 +3648,7 @@ export class Matterbridge extends EventEmitter {
3035
3648
  this.wssSendRefreshRequired();
3036
3649
  return;
3037
3650
  }
3651
+ // Handle the command enableplugin from Home
3038
3652
  if (command === 'enableplugin') {
3039
3653
  if (!this.plugins.has(param)) {
3040
3654
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -3052,13 +3666,14 @@ export class Matterbridge extends EventEmitter {
3052
3666
  plugin.registeredDevices = undefined;
3053
3667
  plugin.addedDevices = undefined;
3054
3668
  await this.plugins.enable(param);
3055
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3669
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
3056
3670
  }
3057
3671
  }
3058
3672
  res.json({ message: 'Command received' });
3059
3673
  this.wssSendRefreshRequired();
3060
3674
  return;
3061
3675
  }
3676
+ // Handle the command disableplugin from Home
3062
3677
  if (command === 'disableplugin') {
3063
3678
  if (!this.plugins.has(param)) {
3064
3679
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -3075,6 +3690,7 @@ export class Matterbridge extends EventEmitter {
3075
3690
  return;
3076
3691
  }
3077
3692
  });
3693
+ // Fallback for routing (must be the last route)
3078
3694
  this.expressApp.get('*', (req, res) => {
3079
3695
  this.log.debug('The frontend sent:', req.url);
3080
3696
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -3082,6 +3698,11 @@ export class Matterbridge extends EventEmitter {
3082
3698
  });
3083
3699
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3084
3700
  }
3701
+ /**
3702
+ * Retrieves the cluster text description from a given device.
3703
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3704
+ * @returns {string} The attributes description of the cluster servers in the device.
3705
+ */
3085
3706
  getClusterTextFromDevice(device) {
3086
3707
  const stringifyUserLabel = (endpoint) => {
3087
3708
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -3104,9 +3725,11 @@ export class Matterbridge extends EventEmitter {
3104
3725
  return '';
3105
3726
  };
3106
3727
  let attributes = '';
3728
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3107
3729
  const clusterServers = device.getAllClusterServers();
3108
3730
  clusterServers.forEach((clusterServer) => {
3109
3731
  try {
3732
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3110
3733
  if (clusterServer.name === 'OnOff')
3111
3734
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
3112
3735
  if (clusterServer.name === 'Switch')
@@ -3157,18 +3780,30 @@ export class Matterbridge extends EventEmitter {
3157
3780
  attributes += `${stringifyFixedLabel(device)} `;
3158
3781
  if (clusterServer.name === 'UserLabel')
3159
3782
  attributes += `${stringifyUserLabel(device)} `;
3783
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3160
3784
  }
3161
3785
  catch (error) {
3162
3786
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3163
3787
  }
3164
3788
  });
3789
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3165
3790
  return attributes;
3166
3791
  }
3792
+ /**
3793
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3794
+ * @deprecated This method is deprecated and will be removed in a future version.
3795
+ *
3796
+ * @returns A Promise that resolves when the initialization is complete.
3797
+ */
3167
3798
  async startExtension(dataPath, extensionVersion, port = 5540) {
3799
+ // Set the bridge mode
3168
3800
  this.bridgeMode = 'bridge';
3801
+ // Set the first port to use
3169
3802
  this.port = port;
3170
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3803
+ // Set Matterbridge logger
3804
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3171
3805
  this.log.debug('Matterbridge extension is starting...');
3806
+ // Initialize NodeStorage
3172
3807
  this.matterbridgeDirectory = dataPath;
3173
3808
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3174
3809
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -3187,10 +3822,13 @@ export class Matterbridge extends EventEmitter {
3187
3822
  };
3188
3823
  this.plugins.set(plugin);
3189
3824
  this.plugins.saveToStorage();
3825
+ // Log system info and create .matterbridge directory
3190
3826
  await this.logNodeAndSystemInfo();
3191
3827
  this.matterbridgeDirectory = dataPath;
3828
+ // Set matter.js logger level and format
3192
3829
  Logger.defaultLogLevel = MatterLogLevel.INFO;
3193
3830
  Logger.format = MatterLogFormat.ANSI;
3831
+ // Start the storage and create matterbridgeContext
3194
3832
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3195
3833
  if (!this.storageManager)
3196
3834
  return false;
@@ -3200,7 +3838,7 @@ export class Matterbridge extends EventEmitter {
3200
3838
  await this.matterbridgeContext.set('softwareVersion', 1);
3201
3839
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3202
3840
  await this.matterbridgeContext.set('hardwareVersion', 1);
3203
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3841
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
3204
3842
  this.matterServer = await this.createMatterServer(this.storageManager);
3205
3843
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3206
3844
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
@@ -3213,6 +3851,7 @@ export class Matterbridge extends EventEmitter {
3213
3851
  await this.startMatterServer();
3214
3852
  this.log.info('Matter server started');
3215
3853
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3854
+ // Set reachability to true and trigger event after 60 seconds
3216
3855
  setTimeout(() => {
3217
3856
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3218
3857
  if (this.commissioningServer)
@@ -3222,14 +3861,31 @@ export class Matterbridge extends EventEmitter {
3222
3861
  }, 60 * 1000);
3223
3862
  return this.commissioningServer.isCommissioned();
3224
3863
  }
3864
+ /**
3865
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3866
+ * @deprecated This method is deprecated and will be removed in a future version.
3867
+ *
3868
+ * @returns A Promise that resolves when the initialization is complete.
3869
+ */
3225
3870
  async stopExtension() {
3871
+ // Closing matter
3226
3872
  await this.stopMatterServer();
3873
+ // Clearing the session manager
3874
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3875
+ // Closing storage
3227
3876
  await this.stopMatterStorage();
3228
3877
  this.log.info('Matter server stopped');
3229
3878
  }
3879
+ /**
3880
+ * Checks if the extension is commissioned.
3881
+ * @deprecated This method is deprecated and will be removed in a future version.
3882
+ *
3883
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3884
+ */
3230
3885
  isExtensionCommissioned() {
3231
3886
  if (!this.commissioningServer)
3232
3887
  return false;
3233
3888
  return this.commissioningServer.isCommissioned();
3234
3889
  }
3235
3890
  }
3891
+ //# sourceMappingURL=matterbridge.js.map