matterbridge 1.6.8-dev.5 → 1.6.8-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +26 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cluster/export.d.ts.map +1 -0
  6. package/dist/cluster/export.js +2 -0
  7. package/dist/cluster/export.js.map +1 -0
  8. package/dist/defaultConfigSchema.d.ts.map +1 -0
  9. package/dist/defaultConfigSchema.js +23 -0
  10. package/dist/defaultConfigSchema.js.map +1 -0
  11. package/dist/deviceManager.d.ts +46 -0
  12. package/dist/deviceManager.d.ts.map +1 -0
  13. package/dist/deviceManager.js +26 -1
  14. package/dist/deviceManager.js.map +1 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +30 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/logger/export.d.ts.map +1 -0
  19. package/dist/logger/export.js +1 -0
  20. package/dist/logger/export.js.map +1 -0
  21. package/dist/matter/export.d.ts.map +1 -0
  22. package/dist/matter/export.js +1 -0
  23. package/dist/matter/export.js.map +1 -0
  24. package/dist/matterbridge.d.ts +473 -0
  25. package/dist/matterbridge.d.ts.map +1 -0
  26. package/dist/matterbridge.js +720 -66
  27. package/dist/matterbridge.js.map +1 -0
  28. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  29. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  30. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  31. package/dist/matterbridgeBehaviors.d.ts +116 -0
  32. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  33. package/dist/matterbridgeBehaviors.js +29 -1
  34. package/dist/matterbridgeBehaviors.js.map +1 -0
  35. package/dist/matterbridgeDevice.d.ts +1142 -0
  36. package/dist/matterbridgeDevice.d.ts.map +1 -0
  37. package/dist/matterbridgeDevice.js +997 -10
  38. package/dist/matterbridgeDevice.js.map +1 -0
  39. package/dist/matterbridgeDeviceTypes.d.ts +109 -0
  40. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  41. package/dist/matterbridgeDeviceTypes.js +82 -11
  42. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  43. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  44. package/dist/matterbridgeDynamicPlatform.js +33 -0
  45. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  46. package/dist/matterbridgeEdge.d.ts +90 -0
  47. package/dist/matterbridgeEdge.d.ts.map +1 -0
  48. package/dist/matterbridgeEdge.js +529 -0
  49. package/dist/matterbridgeEdge.js.map +1 -0
  50. package/dist/matterbridgeEndpoint.d.ts +1134 -0
  51. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  52. package/dist/matterbridgeEndpoint.js +1121 -12
  53. package/dist/matterbridgeEndpoint.js.map +1 -0
  54. package/dist/matterbridgePlatform.d.ts +123 -0
  55. package/dist/matterbridgePlatform.d.ts.map +1 -0
  56. package/dist/matterbridgePlatform.js +99 -3
  57. package/dist/matterbridgePlatform.js.map +1 -0
  58. package/dist/matterbridgeTypes.d.ts.map +1 -0
  59. package/dist/matterbridgeTypes.js +24 -0
  60. package/dist/matterbridgeTypes.js.map +1 -0
  61. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  62. package/dist/matterbridgeWebsocket.js +45 -0
  63. package/dist/matterbridgeWebsocket.js.map +1 -0
  64. package/dist/pluginManager.d.ts +238 -0
  65. package/dist/pluginManager.d.ts.map +1 -0
  66. package/dist/pluginManager.js +238 -3
  67. package/dist/pluginManager.js.map +1 -0
  68. package/dist/storage/export.d.ts.map +1 -0
  69. package/dist/storage/export.js +1 -0
  70. package/dist/storage/export.js.map +1 -0
  71. package/dist/utils/colorUtils.d.ts.map +1 -0
  72. package/dist/utils/colorUtils.js +205 -2
  73. package/dist/utils/colorUtils.js.map +1 -0
  74. package/dist/utils/export.d.ts.map +1 -0
  75. package/dist/utils/export.js +1 -0
  76. package/dist/utils/export.js.map +1 -0
  77. package/dist/utils/utils.d.ts +221 -0
  78. package/dist/utils/utils.d.ts.map +1 -0
  79. package/dist/utils/utils.js +253 -8
  80. package/dist/utils/utils.js.map +1 -0
  81. package/npm-shrinkwrap.json +2 -2
  82. package/package.json +1 -1
@@ -1,3 +1,26 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @date 2023-12-29
7
+ * @version 1.5.2
8
+ *
9
+ * Copyright 2023, 2024, 2025 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // Node.js modules
1
24
  import { fileURLToPath } from 'url';
2
25
  import { promises as fs } from 'fs';
3
26
  import { exec, spawn } from 'child_process';
@@ -6,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,13 +168,26 @@ 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
  }
140
177
  matterbridgeMessageHandler;
178
+ /** ***********************************************************************************************************************************/
179
+ /** loadInstance() and cleanup() methods */
180
+ /** ***********************************************************************************************************************************/
181
+ /**
182
+ * Loads an instance of the Matterbridge class.
183
+ * If an instance already exists, return that instance.
184
+ *
185
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
186
+ * @returns The loaded Matterbridge instance.
187
+ */
141
188
  static async loadInstance(initialize = false) {
142
189
  if (!Matterbridge.instance) {
190
+ // eslint-disable-next-line no-console
143
191
  if (hasParameter('debug'))
144
192
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
145
193
  Matterbridge.instance = new Matterbridge();
@@ -148,6 +196,11 @@ export class Matterbridge extends EventEmitter {
148
196
  }
149
197
  return Matterbridge.instance;
150
198
  }
199
+ /**
200
+ * Call cleanup().
201
+ * @deprecated This method is deprecated and is only used for jest tests.
202
+ *
203
+ */
151
204
  async destroyInstance() {
152
205
  await this.cleanup('destroying instance...', false);
153
206
  await waiter('destroying instance...', () => {
@@ -155,39 +208,60 @@ export class Matterbridge extends EventEmitter {
155
208
  }, false, 60000, 100, false);
156
209
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
157
210
  }
211
+ /**
212
+ * Initializes the Matterbridge application.
213
+ *
214
+ * @remarks
215
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
216
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
217
+ * node version, registers signal handlers, initializes storage, and parses the command line.
218
+ *
219
+ * @returns A Promise that resolves when the initialization is complete.
220
+ */
158
221
  async initialize() {
222
+ // Set the restart mode
159
223
  if (hasParameter('service'))
160
224
  this.restartMode = 'service';
161
225
  if (hasParameter('docker'))
162
226
  this.restartMode = 'docker';
227
+ // Set the matterbridge directory
163
228
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
164
229
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
165
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
230
+ // Create matterbridge logger
231
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
232
+ // Initialize nodeStorage and nodeContext
166
233
  try {
167
234
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
168
235
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
169
236
  this.log.debug('Creating node storage context for matterbridge');
170
237
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
238
+ // TODO: Remove this code when node-persist-manager is updated
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
240
  const keys = (await this.nodeStorage?.storage.keys());
172
241
  for (const key of keys) {
173
242
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
244
  await this.nodeStorage?.storage.get(key);
175
245
  }
176
246
  const storages = await this.nodeStorage.getStorageNames();
177
247
  for (const storage of storages) {
178
248
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
179
249
  const nodeContext = await this.nodeStorage?.createStorage(storage);
250
+ // TODO: Remove this code when node-persist-manager is updated
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
252
  const keys = (await nodeContext?.storage.keys());
181
253
  keys.forEach(async (key) => {
182
254
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
183
255
  await nodeContext?.get(key);
184
256
  });
185
257
  }
258
+ // Creating a backup of the node storage since it is not corrupted
186
259
  this.log.debug('Creating node storage backup...');
187
260
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
188
261
  this.log.debug('Created node storage backup');
189
262
  }
190
263
  catch (error) {
264
+ // Restoring the backup of the node storage since it is corrupted
191
265
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
192
266
  if (hasParameter('norestore')) {
193
267
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -202,45 +276,51 @@ export class Matterbridge extends EventEmitter {
202
276
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
203
277
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
204
278
  }
279
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
205
280
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
281
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
206
282
  this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
283
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
207
284
  this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
208
285
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
286
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
209
287
  if (hasParameter('logger')) {
210
288
  const level = getParameter('logger');
211
289
  if (level === 'debug') {
212
- this.log.logLevel = "debug";
290
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
213
291
  }
214
292
  else if (level === 'info') {
215
- this.log.logLevel = "info";
293
+ this.log.logLevel = "info" /* LogLevel.INFO */;
216
294
  }
217
295
  else if (level === 'notice') {
218
- this.log.logLevel = "notice";
296
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
219
297
  }
220
298
  else if (level === 'warn') {
221
- this.log.logLevel = "warn";
299
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
222
300
  }
223
301
  else if (level === 'error') {
224
- this.log.logLevel = "error";
302
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
225
303
  }
226
304
  else if (level === 'fatal') {
227
- this.log.logLevel = "fatal";
305
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
228
306
  }
229
307
  else {
230
308
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
231
- this.log.logLevel = "info";
309
+ this.log.logLevel = "info" /* LogLevel.INFO */;
232
310
  }
233
311
  }
234
312
  else {
235
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
313
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
236
314
  }
237
315
  MatterbridgeDevice.logLevel = this.log.logLevel;
316
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
238
317
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
239
318
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
240
319
  this.matterbridgeInformation.fileLogger = true;
241
320
  }
242
321
  this.log.notice('Matterbridge is starting...');
243
322
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
323
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
244
324
  if (hasParameter('matterlogger')) {
245
325
  const level = getParameter('matterlogger');
246
326
  if (level === 'debug') {
@@ -271,6 +351,7 @@ export class Matterbridge extends EventEmitter {
271
351
  }
272
352
  Logger.format = MatterLogFormat.ANSI;
273
353
  Logger.setLogger('default', this.createMatterLogger());
354
+ // Create the file logger for matter.js (context: matterFileLog)
274
355
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
275
356
  this.matterbridgeInformation.matterFileLogger = true;
276
357
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -279,6 +360,7 @@ export class Matterbridge extends EventEmitter {
279
360
  });
280
361
  }
281
362
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
363
+ // Set the interface to use for the matter server mdnsInterface
282
364
  if (hasParameter('mdnsinterface')) {
283
365
  this.mdnsInterface = getParameter('mdnsinterface');
284
366
  }
@@ -287,6 +369,7 @@ export class Matterbridge extends EventEmitter {
287
369
  if (this.mdnsInterface === '')
288
370
  this.mdnsInterface = undefined;
289
371
  }
372
+ // Set the listeningAddressIpv4 for the matter commissioning server
290
373
  if (hasParameter('ipv4address')) {
291
374
  this.ipv4address = getParameter('ipv4address');
292
375
  }
@@ -295,6 +378,7 @@ export class Matterbridge extends EventEmitter {
295
378
  if (this.ipv4address === '')
296
379
  this.ipv4address = undefined;
297
380
  }
381
+ // Set the listeningAddressIpv6 for the matter commissioning server
298
382
  if (hasParameter('ipv6address')) {
299
383
  this.ipv6address = getParameter('ipv6address');
300
384
  }
@@ -303,17 +387,23 @@ export class Matterbridge extends EventEmitter {
303
387
  if (this.ipv6address === '')
304
388
  this.ipv6address = undefined;
305
389
  }
390
+ // Initialize PluginManager
306
391
  this.plugins = new PluginManager(this);
307
392
  await this.plugins.loadFromStorage();
393
+ // Initialize DeviceManager
308
394
  this.devices = new DeviceManager(this, this.nodeContext);
395
+ // Get the plugins from node storage and create the plugins node storage contexts
309
396
  for (const plugin of this.plugins) {
310
397
  const packageJson = await this.plugins.parse(plugin);
311
398
  if (packageJson === null && !hasParameter('add')) {
399
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
400
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
312
401
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
313
402
  try {
314
403
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
315
404
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
316
405
  plugin.error = false;
406
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
317
407
  }
318
408
  catch (error) {
319
409
  plugin.error = true;
@@ -330,6 +420,7 @@ export class Matterbridge extends EventEmitter {
330
420
  await plugin.nodeContext.set('description', plugin.description);
331
421
  await plugin.nodeContext.set('author', plugin.author);
332
422
  }
423
+ // Log system info and create .matterbridge directory
333
424
  await this.logNodeAndSystemInfo();
334
425
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
335
426
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -337,6 +428,7 @@ export class Matterbridge extends EventEmitter {
337
428
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
338
429
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
339
430
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
431
+ // Check node version and throw error
340
432
  const minNodeVersion = 18;
341
433
  const nodeVersion = process.versions.node;
342
434
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -344,10 +436,17 @@ export class Matterbridge extends EventEmitter {
344
436
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
345
437
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
346
438
  }
439
+ // Register process handlers
347
440
  this.registerProcessHandlers();
441
+ // Parse command line
348
442
  await this.parseCommandLine();
349
443
  this.initialized = true;
350
444
  }
445
+ /**
446
+ * Parses the command line arguments and performs the corresponding actions.
447
+ * @private
448
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
449
+ */
351
450
  async parseCommandLine() {
352
451
  if (hasParameter('help')) {
353
452
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -455,12 +554,14 @@ export class Matterbridge extends EventEmitter {
455
554
  }
456
555
  if (hasParameter('factoryreset')) {
457
556
  try {
557
+ // Delete matter storage file
458
558
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
459
559
  }
460
560
  catch (err) {
461
561
  this.log.error(`Error deleting storage: ${err}`);
462
562
  }
463
563
  try {
564
+ // Delete node storage directory with its subdirectories
464
565
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
465
566
  }
466
567
  catch (err) {
@@ -474,6 +575,7 @@ export class Matterbridge extends EventEmitter {
474
575
  this.emit('shutdown');
475
576
  return;
476
577
  }
578
+ // Start the matter storage and create the matterbridge context
477
579
  try {
478
580
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
479
581
  }
@@ -508,28 +610,34 @@ export class Matterbridge extends EventEmitter {
508
610
  this.emit('shutdown');
509
611
  return;
510
612
  }
613
+ // Initialize frontend
511
614
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
512
615
  await this.initializeFrontend(getIntParameter('frontend'));
616
+ // Check each 60 minutes the latest versions
513
617
  this.checkUpdateInterval = setInterval(() => {
514
618
  this.getMatterbridgeLatestVersion();
515
619
  for (const plugin of this.plugins) {
516
620
  this.getPluginLatestVersion(plugin);
517
621
  }
518
622
  }, 60 * 60 * 1000);
623
+ // Start the matterbridge in mode test
519
624
  if (hasParameter('test')) {
520
625
  this.bridgeMode = 'bridge';
521
626
  MatterbridgeDevice.bridgeMode = 'bridge';
522
627
  return;
523
628
  }
629
+ // Start the matterbridge in mode controller
524
630
  if (hasParameter('controller')) {
525
631
  this.bridgeMode = 'controller';
526
632
  await this.startController();
527
633
  return;
528
634
  }
635
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
529
636
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
530
637
  this.log.info('Setting default matterbridge start mode to bridge');
531
638
  await this.nodeContext?.set('bridgeMode', 'bridge');
532
639
  }
640
+ // Start matterbridge in bridge mode
533
641
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
534
642
  this.bridgeMode = 'bridge';
535
643
  MatterbridgeDevice.bridgeMode = 'bridge';
@@ -538,6 +646,7 @@ export class Matterbridge extends EventEmitter {
538
646
  await this.startBridge();
539
647
  return;
540
648
  }
649
+ // Start matterbridge in childbridge mode
541
650
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
542
651
  this.bridgeMode = 'childbridge';
543
652
  MatterbridgeDevice.bridgeMode = 'childbridge';
@@ -547,17 +656,28 @@ export class Matterbridge extends EventEmitter {
547
656
  return;
548
657
  }
549
658
  }
659
+ /**
660
+ * Asynchronously loads and starts the registered plugins.
661
+ *
662
+ * This method is responsible for initializing and staarting all enabled plugins.
663
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
664
+ *
665
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
666
+ */
550
667
  async startPlugins() {
668
+ // Check, load and start the plugins
551
669
  for (const plugin of this.plugins) {
552
670
  plugin.configJson = await this.plugins.loadConfig(plugin);
553
671
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
672
+ // Check if the plugin is available
554
673
  if (!(await this.plugins.resolve(plugin.path))) {
555
674
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
556
675
  plugin.enabled = false;
557
676
  plugin.error = true;
558
677
  continue;
559
678
  }
560
- this.getPluginLatestVersion(plugin);
679
+ // Check if the plugin has a new version
680
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
561
681
  if (!plugin.enabled) {
562
682
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
563
683
  continue;
@@ -572,20 +692,26 @@ export class Matterbridge extends EventEmitter {
572
692
  plugin.addedDevices = undefined;
573
693
  plugin.qrPairingCode = undefined;
574
694
  plugin.manualPairingCode = undefined;
575
- this.plugins.load(plugin, true, 'Matterbridge is starting');
695
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
576
696
  }
577
697
  this.wssSendRefreshRequired();
578
698
  }
699
+ /**
700
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
701
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
702
+ */
579
703
  registerProcessHandlers() {
580
704
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
581
705
  process.removeAllListeners('uncaughtException');
582
706
  process.removeAllListeners('unhandledRejection');
583
707
  this.exceptionHandler = async (error) => {
584
708
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
709
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
585
710
  };
586
711
  process.on('uncaughtException', this.exceptionHandler);
587
712
  this.rejectionHandler = async (reason, promise) => {
588
713
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
714
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
589
715
  };
590
716
  process.on('unhandledRejection', this.rejectionHandler);
591
717
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -598,6 +724,9 @@ export class Matterbridge extends EventEmitter {
598
724
  };
599
725
  process.on('SIGTERM', this.sigtermHandler);
600
726
  }
727
+ /**
728
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
729
+ */
601
730
  deregisterProcesslHandlers() {
602
731
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
603
732
  if (this.exceptionHandler)
@@ -614,7 +743,11 @@ export class Matterbridge extends EventEmitter {
614
743
  process.off('SIGTERM', this.sigtermHandler);
615
744
  this.sigtermHandler = undefined;
616
745
  }
746
+ /**
747
+ * Logs the node and system information.
748
+ */
617
749
  async logNodeAndSystemInfo() {
750
+ // IP address information
618
751
  const networkInterfaces = os.networkInterfaces();
619
752
  this.systemInformation.ipv4Address = '';
620
753
  this.systemInformation.ipv6Address = '';
@@ -634,7 +767,7 @@ export class Matterbridge extends EventEmitter {
634
767
  this.systemInformation.macAddress = detail.mac;
635
768
  }
636
769
  }
637
- if (this.systemInformation.ipv4Address !== '') {
770
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
638
771
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
639
772
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
640
773
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -642,19 +775,22 @@ export class Matterbridge extends EventEmitter {
642
775
  break;
643
776
  }
644
777
  }
778
+ // Node information
645
779
  this.systemInformation.nodeVersion = process.versions.node;
646
780
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
647
781
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
648
782
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
783
+ // Host system information
649
784
  this.systemInformation.hostname = os.hostname();
650
785
  this.systemInformation.user = os.userInfo().username;
651
- this.systemInformation.osType = os.type();
652
- this.systemInformation.osRelease = os.release();
653
- this.systemInformation.osPlatform = os.platform();
654
- this.systemInformation.osArch = os.arch();
655
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
656
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
657
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
786
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
787
+ this.systemInformation.osRelease = os.release(); // Kernel version
788
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
789
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
790
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
791
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
792
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
793
+ // Log the system information
658
794
  this.log.debug('Host System Information:');
659
795
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
660
796
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -670,15 +806,19 @@ export class Matterbridge extends EventEmitter {
670
806
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
671
807
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
672
808
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
809
+ // Home directory
673
810
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
674
811
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
675
812
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
813
+ // Package root directory
676
814
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
677
815
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
678
816
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
679
817
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
818
+ // Global node_modules directory
680
819
  if (this.nodeContext)
681
820
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
821
+ // First run of Matterbridge so the node storage is empty
682
822
  if (this.globalModulesDirectory === '') {
683
823
  try {
684
824
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -702,6 +842,7 @@ export class Matterbridge extends EventEmitter {
702
842
  this.log.error(`Error getting global node_modules directory: ${error}`);
703
843
  });
704
844
  }
845
+ // Create the data directory .matterbridge in the home directory
705
846
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
706
847
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
707
848
  try {
@@ -725,6 +866,7 @@ export class Matterbridge extends EventEmitter {
725
866
  }
726
867
  }
727
868
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
869
+ // Create the plugin directory Matterbridge in the home directory
728
870
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
729
871
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
730
872
  try {
@@ -748,19 +890,28 @@ export class Matterbridge extends EventEmitter {
748
890
  }
749
891
  }
750
892
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
893
+ // Matterbridge version
751
894
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
752
895
  this.matterbridgeVersion = packageJson.version;
753
896
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
754
897
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
898
+ // Matterbridge latest version
755
899
  if (this.nodeContext)
756
900
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
757
901
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
758
902
  this.getMatterbridgeLatestVersion();
903
+ // Current working directory
759
904
  const currentDir = process.cwd();
760
905
  this.log.debug(`Current Working Directory: ${currentDir}`);
906
+ // Command line arguments (excluding 'node' and the script name)
761
907
  const cmdArgs = process.argv.slice(2).join(' ');
762
908
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
763
909
  }
910
+ /**
911
+ * Retrieves the latest version of a package from the npm registry.
912
+ * @param packageName - The name of the package.
913
+ * @returns A Promise that resolves to the latest version of the package.
914
+ */
764
915
  async getLatestVersion(packageName) {
765
916
  return new Promise((resolve, reject) => {
766
917
  this.execRunningCount++;
@@ -775,6 +926,10 @@ export class Matterbridge extends EventEmitter {
775
926
  });
776
927
  });
777
928
  }
929
+ /**
930
+ * Retrieves the path to the global Node.js modules directory.
931
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
932
+ */
778
933
  async getGlobalNodeModules() {
779
934
  return new Promise((resolve, reject) => {
780
935
  this.execRunningCount++;
@@ -789,6 +944,11 @@ export class Matterbridge extends EventEmitter {
789
944
  });
790
945
  });
791
946
  }
947
+ /**
948
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
949
+ * @private
950
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
951
+ */
792
952
  async getMatterbridgeLatestVersion() {
793
953
  this.getLatestVersion('matterbridge')
794
954
  .then(async (matterbridgeLatestVersion) => {
@@ -805,8 +965,19 @@ export class Matterbridge extends EventEmitter {
805
965
  })
806
966
  .catch((error) => {
807
967
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
968
+ // error.stack && this.log.debug(error.stack);
808
969
  });
809
970
  }
971
+ /**
972
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
973
+ * If the plugin's version is different from the latest version, logs a warning message.
974
+ * If the plugin's version is the same as the latest version, logs an info message.
975
+ * If there is an error retrieving the latest version, logs an error message.
976
+ *
977
+ * @private
978
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
979
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
980
+ */
810
981
  async getPluginLatestVersion(plugin) {
811
982
  this.getLatestVersion(plugin.name)
812
983
  .then(async (latestVersion) => {
@@ -818,40 +989,54 @@ export class Matterbridge extends EventEmitter {
818
989
  })
819
990
  .catch((error) => {
820
991
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
992
+ // error.stack && this.log.debug(error.stack);
821
993
  });
822
994
  }
995
+ /**
996
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
997
+ *
998
+ * @returns {Function} The MatterLogger function.
999
+ */
823
1000
  createMatterLogger() {
824
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1001
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
825
1002
  return (_level, formattedLog) => {
826
1003
  const logger = formattedLog.slice(44, 44 + 20).trim();
827
1004
  const message = formattedLog.slice(65);
828
1005
  matterLogger.logName = logger;
829
1006
  switch (_level) {
830
1007
  case MatterLogLevel.DEBUG:
831
- matterLogger.log("debug", message);
1008
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
832
1009
  break;
833
1010
  case MatterLogLevel.INFO:
834
- matterLogger.log("info", message);
1011
+ matterLogger.log("info" /* LogLevel.INFO */, message);
835
1012
  break;
836
1013
  case MatterLogLevel.NOTICE:
837
- matterLogger.log("notice", message);
1014
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
838
1015
  break;
839
1016
  case MatterLogLevel.WARN:
840
- matterLogger.log("warn", message);
1017
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
841
1018
  break;
842
1019
  case MatterLogLevel.ERROR:
843
- matterLogger.log("error", message);
1020
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
844
1021
  break;
845
1022
  case MatterLogLevel.FATAL:
846
- matterLogger.log("fatal", message);
1023
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
847
1024
  break;
848
1025
  default:
849
- matterLogger.log("debug", message);
1026
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
850
1027
  break;
851
1028
  }
852
1029
  };
853
1030
  }
1031
+ /**
1032
+ * Creates a Matter File Logger.
1033
+ *
1034
+ * @param {string} filePath - The path to the log file.
1035
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1036
+ * @returns {Function} - A function that logs formatted messages to the log file.
1037
+ */
854
1038
  async createMatterFileLogger(filePath, unlink = false) {
1039
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
855
1040
  let fileSize = 0;
856
1041
  if (unlink) {
857
1042
  try {
@@ -900,53 +1085,83 @@ export class Matterbridge extends EventEmitter {
900
1085
  }
901
1086
  };
902
1087
  }
1088
+ /**
1089
+ * Update matterbridge and cleanup.
1090
+ */
903
1091
  async updateProcess() {
904
1092
  await this.cleanup('updating...', false);
905
1093
  }
1094
+ /**
1095
+ * Restarts the process by spawning a new process and exiting the current process.
1096
+ */
906
1097
  async restartProcess() {
907
1098
  await this.cleanup('restarting...', true);
908
1099
  }
1100
+ /**
1101
+ * Shut down the process by exiting the current process.
1102
+ */
909
1103
  async shutdownProcess() {
910
1104
  await this.cleanup('shutting down...', false);
911
1105
  }
1106
+ /**
1107
+ * Shut down the process and reset.
1108
+ */
912
1109
  async unregisterAndShutdownProcess() {
913
1110
  this.log.info('Unregistering all devices and shutting down...');
914
- for (const plugin of this.plugins) {
1111
+ for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
915
1112
  await this.removeAllBridgedDevices(plugin.name);
916
1113
  }
917
1114
  await this.cleanup('unregistered all devices and shutting down...', false);
918
1115
  }
1116
+ /**
1117
+ * Shut down the process and reset.
1118
+ */
919
1119
  async shutdownProcessAndReset() {
920
1120
  await this.cleanup('shutting down with reset...', false);
921
1121
  }
1122
+ /**
1123
+ * Shut down the process and factory reset.
1124
+ */
922
1125
  async shutdownProcessAndFactoryReset() {
923
1126
  await this.cleanup('shutting down with factory reset...', false);
924
1127
  }
1128
+ /**
1129
+ * Cleans up the Matterbridge instance.
1130
+ * @param message - The cleanup message.
1131
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1132
+ * @returns A promise that resolves when the cleanup is completed.
1133
+ */
925
1134
  async cleanup(message, restart = false) {
926
1135
  if (this.initialized && !this.hasCleanupStarted) {
927
1136
  this.hasCleanupStarted = true;
928
1137
  this.log.info(message);
1138
+ // Deregisters the process handlers
929
1139
  this.deregisterProcesslHandlers();
1140
+ // Clear the start matter interval
930
1141
  if (this.startMatterInterval) {
931
1142
  clearInterval(this.startMatterInterval);
932
1143
  this.startMatterInterval = undefined;
933
1144
  this.log.debug('Start matter interval cleared');
934
1145
  }
1146
+ // Clear the check update interval
935
1147
  if (this.checkUpdateInterval) {
936
1148
  clearInterval(this.checkUpdateInterval);
937
1149
  this.checkUpdateInterval = undefined;
938
1150
  this.log.debug('Check update interval cleared');
939
1151
  }
1152
+ // Clear the configure timeout
940
1153
  if (this.configureTimeout) {
941
1154
  clearTimeout(this.configureTimeout);
942
1155
  this.configureTimeout = undefined;
943
1156
  this.log.debug('Matterbridge configure timeout cleared');
944
1157
  }
1158
+ // Clear the reachability timeout
945
1159
  if (this.reachabilityTimeout) {
946
1160
  clearTimeout(this.reachabilityTimeout);
947
1161
  this.reachabilityTimeout = undefined;
948
1162
  this.log.debug('Matterbridge reachability timeout cleared');
949
1163
  }
1164
+ // Calling the shutdown method of each plugin and clear the reachability timeout
950
1165
  for (const plugin of this.plugins) {
951
1166
  if (!plugin.enabled || plugin.error)
952
1167
  continue;
@@ -957,24 +1172,29 @@ export class Matterbridge extends EventEmitter {
957
1172
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
958
1173
  }
959
1174
  }
1175
+ // Close the http server
960
1176
  if (this.httpServer) {
961
1177
  this.httpServer.close();
962
1178
  this.httpServer.removeAllListeners();
963
1179
  this.httpServer = undefined;
964
1180
  this.log.debug('Frontend http server closed successfully');
965
1181
  }
1182
+ // Close the https server
966
1183
  if (this.httpsServer) {
967
1184
  this.httpsServer.close();
968
1185
  this.httpsServer.removeAllListeners();
969
1186
  this.httpsServer = undefined;
970
1187
  this.log.debug('Frontend https server closed successfully');
971
1188
  }
1189
+ // Remove listeners from the express app
972
1190
  if (this.expressApp) {
973
1191
  this.expressApp.removeAllListeners();
974
1192
  this.expressApp = undefined;
975
1193
  this.log.debug('Frontend app closed successfully');
976
1194
  }
1195
+ // Close the WebSocket server
977
1196
  if (this.webSocketServer) {
1197
+ // Close all active connections
978
1198
  this.webSocketServer.clients.forEach((client) => {
979
1199
  if (client.readyState === WebSocket.OPEN) {
980
1200
  client.close();
@@ -990,29 +1210,39 @@ export class Matterbridge extends EventEmitter {
990
1210
  });
991
1211
  this.webSocketServer = undefined;
992
1212
  }
993
- if (this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
994
- this.convertStorage(this.matterbridgeContext, 'Mattebridge');
1213
+ // Convert the matter storage to the new format
1214
+ if (hasParameter('convert') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
1215
+ await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
995
1216
  }
1217
+ // Closing matter
996
1218
  await this.stopMatterServer();
1219
+ // Closing matter storage
997
1220
  await this.stopMatterStorage();
1221
+ // Remove the matterfilelogger
998
1222
  try {
999
1223
  Logger.removeLogger('matterfilelogger');
1224
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1000
1225
  }
1001
1226
  catch (error) {
1227
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1002
1228
  }
1229
+ // Serialize registeredDevices
1003
1230
  if (this.nodeStorage && this.nodeContext) {
1004
1231
  this.log.info('Saving registered devices...');
1005
1232
  const serializedRegisteredDevices = [];
1006
1233
  this.devices.forEach(async (device) => {
1007
1234
  const serializedMatterbridgeDevice = device.serialize();
1235
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1008
1236
  if (serializedMatterbridgeDevice)
1009
1237
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1010
1238
  });
1011
1239
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1012
1240
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1241
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1013
1242
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1014
1243
  await this.nodeContext.close();
1015
1244
  this.nodeContext = undefined;
1245
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1016
1246
  for (const plugin of this.plugins) {
1017
1247
  if (plugin.nodeContext) {
1018
1248
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1043,13 +1273,16 @@ export class Matterbridge extends EventEmitter {
1043
1273
  }
1044
1274
  else {
1045
1275
  if (message === 'shutting down with reset...') {
1276
+ // Delete matter storage file
1046
1277
  this.log.info('Resetting Matterbridge commissioning information...');
1047
1278
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1048
1279
  this.log.info('Reset done! Remove all paired devices from the controllers.');
1049
1280
  }
1050
1281
  if (message === 'shutting down with factory reset...') {
1282
+ // Delete matter storage file
1051
1283
  this.log.info('Resetting Matterbridge commissioning information...');
1052
1284
  await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
1285
+ // Delete node storage directory with its subdirectories
1053
1286
  this.log.info('Resetting Matterbridge storage...');
1054
1287
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1055
1288
  this.log.info('Factory reset done! Remove all paired devices from the controllers.');
@@ -1062,19 +1295,33 @@ export class Matterbridge extends EventEmitter {
1062
1295
  this.initialized = false;
1063
1296
  }
1064
1297
  }
1298
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1065
1299
  async addBridgedEndpoint(pluginName, device) {
1300
+ // Nothing to do here
1066
1301
  }
1302
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1067
1303
  async removeBridgedEndpoint(pluginName, device) {
1304
+ // Nothing to do here
1068
1305
  }
1306
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1069
1307
  async removeAllBridgedEndpoints(pluginName) {
1308
+ // Nothing to do here
1070
1309
  }
1310
+ /**
1311
+ * Adds a bridged device to the Matterbridge.
1312
+ * @param pluginName - The name of the plugin.
1313
+ * @param device - The bridged device to add.
1314
+ * @returns {Promise<void>} - A promise that resolves when the device is added.
1315
+ */
1071
1316
  async addBridgedDevice(pluginName, device) {
1072
1317
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1318
+ // Check if the plugin is registered
1073
1319
  const plugin = this.plugins.get(pluginName);
1074
1320
  if (!plugin) {
1075
1321
  this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1076
1322
  return;
1077
1323
  }
1324
+ // Register and add the device to matterbridge aggregator in bridge mode
1078
1325
  if (this.bridgeMode === 'bridge') {
1079
1326
  if (!this.matterAggregator) {
1080
1327
  this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -1082,8 +1329,11 @@ export class Matterbridge extends EventEmitter {
1082
1329
  }
1083
1330
  this.matterAggregator.addBridgedDevice(device);
1084
1331
  }
1332
+ // The first time create the commissioning server and the aggregator for DynamicPlatform
1333
+ // Register and add the device in childbridge mode
1085
1334
  if (this.bridgeMode === 'childbridge') {
1086
1335
  if (plugin.type === 'AccessoryPlatform') {
1336
+ // Check if the plugin is locked with the commissioning server
1087
1337
  if (!plugin.locked) {
1088
1338
  plugin.locked = true;
1089
1339
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1097,6 +1347,7 @@ export class Matterbridge extends EventEmitter {
1097
1347
  }
1098
1348
  }
1099
1349
  if (plugin.type === 'DynamicPlatform') {
1350
+ // Check if the plugin is locked with the commissioning server and the aggregator
1100
1351
  if (!plugin.locked) {
1101
1352
  plugin.locked = true;
1102
1353
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
@@ -1117,16 +1368,25 @@ export class Matterbridge extends EventEmitter {
1117
1368
  plugin.registeredDevices++;
1118
1369
  if (plugin.addedDevices !== undefined)
1119
1370
  plugin.addedDevices++;
1371
+ // Add the device to the DeviceManager
1120
1372
  this.devices.set(device);
1121
1373
  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}`);
1122
1374
  }
1375
+ /**
1376
+ * Removes a bridged device from the Matterbridge.
1377
+ * @param pluginName - The name of the plugin.
1378
+ * @param device - The device to be removed.
1379
+ * @returns A Promise that resolves when the device is successfully removed.
1380
+ */
1123
1381
  async removeBridgedDevice(pluginName, device) {
1124
1382
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1383
+ // Check if the plugin is registered
1125
1384
  const plugin = this.plugins.get(pluginName);
1126
1385
  if (!plugin) {
1127
1386
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1128
1387
  return;
1129
1388
  }
1389
+ // Remove the device from matterbridge aggregator in bridge mode
1130
1390
  if (this.bridgeMode === 'bridge') {
1131
1391
  if (!this.matterAggregator) {
1132
1392
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
@@ -1136,6 +1396,8 @@ export class Matterbridge extends EventEmitter {
1136
1396
  device.setBridgedDeviceReachability(false);
1137
1397
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1138
1398
  }
1399
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1400
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1139
1401
  this.matterAggregator?.removeBridgedDevice(device);
1140
1402
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1141
1403
  if (plugin.registeredDevices !== undefined)
@@ -1143,6 +1405,7 @@ export class Matterbridge extends EventEmitter {
1143
1405
  if (plugin.addedDevices !== undefined)
1144
1406
  plugin.addedDevices--;
1145
1407
  }
1408
+ // Remove the device in childbridge mode
1146
1409
  if (this.bridgeMode === 'childbridge') {
1147
1410
  if (plugin.type === 'AccessoryPlatform') {
1148
1411
  if (!plugin.commissioningServer) {
@@ -1166,14 +1429,22 @@ export class Matterbridge extends EventEmitter {
1166
1429
  plugin.registeredDevices--;
1167
1430
  if (plugin.addedDevices !== undefined)
1168
1431
  plugin.addedDevices--;
1432
+ // Remove the commissioning server
1169
1433
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1170
1434
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1171
1435
  plugin.commissioningServer = undefined;
1172
1436
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1173
1437
  }
1174
1438
  }
1439
+ // Remove the device from the DeviceManager
1175
1440
  this.devices.remove(device);
1176
1441
  }
1442
+ /**
1443
+ * Removes all bridged devices associated with a specific plugin.
1444
+ *
1445
+ * @param pluginName - The name of the plugin.
1446
+ * @returns A promise that resolves when all devices have been removed.
1447
+ */
1177
1448
  async removeAllBridgedDevices(pluginName) {
1178
1449
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1179
1450
  this.devices.forEach(async (device) => {
@@ -1182,7 +1453,13 @@ export class Matterbridge extends EventEmitter {
1182
1453
  }
1183
1454
  });
1184
1455
  }
1456
+ /**
1457
+ * Starts the Matterbridge in bridge mode.
1458
+ * @private
1459
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1460
+ */
1185
1461
  async startBridge() {
1462
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1186
1463
  if (!this.storageManager)
1187
1464
  throw new Error('No storage manager initialized');
1188
1465
  if (!this.matterbridgeContext)
@@ -1201,6 +1478,7 @@ export class Matterbridge extends EventEmitter {
1201
1478
  let failCount = 0;
1202
1479
  this.startMatterInterval = setInterval(async () => {
1203
1480
  for (const plugin of this.plugins) {
1481
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1204
1482
  if (!plugin.enabled)
1205
1483
  continue;
1206
1484
  if (plugin.error) {
@@ -1225,15 +1503,18 @@ export class Matterbridge extends EventEmitter {
1225
1503
  clearInterval(this.startMatterInterval);
1226
1504
  this.startMatterInterval = undefined;
1227
1505
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1506
+ // Start the Matter server
1228
1507
  await this.startMatterServer();
1229
1508
  this.log.notice('Matter server started');
1509
+ // Show the QR code for commissioning or log the already commissioned message
1230
1510
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1511
+ // Configure the plugins
1231
1512
  this.configureTimeout = setTimeout(async () => {
1232
1513
  for (const plugin of this.plugins) {
1233
1514
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1234
1515
  continue;
1235
1516
  try {
1236
- await this.plugins.configure(plugin);
1517
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1237
1518
  }
1238
1519
  catch (error) {
1239
1520
  plugin.error = true;
@@ -1242,6 +1523,7 @@ export class Matterbridge extends EventEmitter {
1242
1523
  }
1243
1524
  this.wssSendRefreshRequired();
1244
1525
  }, 30 * 1000);
1526
+ // Setting reachability to true
1245
1527
  this.reachabilityTimeout = setTimeout(() => {
1246
1528
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1247
1529
  if (this.commissioningServer)
@@ -1251,7 +1533,14 @@ export class Matterbridge extends EventEmitter {
1251
1533
  }, 60 * 1000);
1252
1534
  }, 1000);
1253
1535
  }
1536
+ /**
1537
+ * Starts the Matterbridge in childbridge mode.
1538
+ * @private
1539
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1540
+ */
1254
1541
  async startChildbridge() {
1542
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1543
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1255
1544
  if (!this.storageManager)
1256
1545
  throw new Error('No storage manager initialized');
1257
1546
  this.matterServer = this.createMatterServer(this.storageManager);
@@ -1261,6 +1550,7 @@ export class Matterbridge extends EventEmitter {
1261
1550
  this.startMatterInterval = setInterval(async () => {
1262
1551
  let allStarted = true;
1263
1552
  for (const plugin of this.plugins) {
1553
+ // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1264
1554
  if (!plugin.enabled)
1265
1555
  continue;
1266
1556
  if (plugin.error) {
@@ -1288,14 +1578,16 @@ export class Matterbridge extends EventEmitter {
1288
1578
  clearInterval(this.startMatterInterval);
1289
1579
  this.startMatterInterval = undefined;
1290
1580
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1581
+ // Start the Matter server
1291
1582
  await this.startMatterServer();
1292
1583
  this.log.notice('Matter server started');
1584
+ // Configure the plugins
1293
1585
  this.configureTimeout = setTimeout(async () => {
1294
1586
  for (const plugin of this.plugins) {
1295
1587
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1296
1588
  continue;
1297
1589
  try {
1298
- await this.plugins.configure(plugin);
1590
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1299
1591
  }
1300
1592
  catch (error) {
1301
1593
  plugin.error = true;
@@ -1324,6 +1616,7 @@ export class Matterbridge extends EventEmitter {
1324
1616
  continue;
1325
1617
  }
1326
1618
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1619
+ // Setting reachability to true
1327
1620
  plugin.reachabilityTimeout = setTimeout(() => {
1328
1621
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1329
1622
  if (plugin.commissioningServer)
@@ -1336,6 +1629,11 @@ export class Matterbridge extends EventEmitter {
1336
1629
  }
1337
1630
  }, 1000);
1338
1631
  }
1632
+ /**
1633
+ * Starts the Matterbridge controller.
1634
+ * @private
1635
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1636
+ */
1339
1637
  async startController() {
1340
1638
  if (!this.storageManager) {
1341
1639
  this.log.error('No storage manager initialized');
@@ -1398,7 +1696,7 @@ export class Matterbridge extends EventEmitter {
1398
1696
  const nodeId = await this.commissioningController.commissionNode(options);
1399
1697
  this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1400
1698
  this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1401
- }
1699
+ } // (hasParameter('pairingcode'))
1402
1700
  if (hasParameter('unpairall')) {
1403
1701
  this.log.info('***Commissioning controller unpairing all nodes...');
1404
1702
  const nodeIds = this.commissioningController.getCommissionedNodes();
@@ -1409,6 +1707,8 @@ export class Matterbridge extends EventEmitter {
1409
1707
  return;
1410
1708
  }
1411
1709
  if (hasParameter('discover')) {
1710
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1711
+ // console.log(discover);
1412
1712
  }
1413
1713
  if (!this.commissioningController.isCommissioned()) {
1414
1714
  this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
@@ -1449,10 +1749,12 @@ export class Matterbridge extends EventEmitter {
1449
1749
  },
1450
1750
  });
1451
1751
  node.logStructure();
1752
+ // Get the interaction client
1452
1753
  this.log.info('Getting the interaction client');
1453
1754
  const interactionClient = await node.getInteractionClient();
1454
1755
  let cluster;
1455
1756
  let attributes;
1757
+ // Log BasicInformationCluster
1456
1758
  cluster = BasicInformationCluster;
1457
1759
  attributes = await interactionClient.getMultipleAttributes({
1458
1760
  attributes: [{ clusterId: cluster.id }],
@@ -1462,6 +1764,7 @@ export class Matterbridge extends EventEmitter {
1462
1764
  attributes.forEach((attribute) => {
1463
1765
  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}`);
1464
1766
  });
1767
+ // Log PowerSourceCluster
1465
1768
  cluster = PowerSourceCluster;
1466
1769
  attributes = await interactionClient.getMultipleAttributes({
1467
1770
  attributes: [{ clusterId: cluster.id }],
@@ -1471,6 +1774,7 @@ export class Matterbridge extends EventEmitter {
1471
1774
  attributes.forEach((attribute) => {
1472
1775
  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}`);
1473
1776
  });
1777
+ // Log ThreadNetworkDiagnostics
1474
1778
  cluster = ThreadNetworkDiagnosticsCluster;
1475
1779
  attributes = await interactionClient.getMultipleAttributes({
1476
1780
  attributes: [{ clusterId: cluster.id }],
@@ -1480,6 +1784,7 @@ export class Matterbridge extends EventEmitter {
1480
1784
  attributes.forEach((attribute) => {
1481
1785
  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}`);
1482
1786
  });
1787
+ // Log SwitchCluster
1483
1788
  cluster = SwitchCluster;
1484
1789
  attributes = await interactionClient.getMultipleAttributes({
1485
1790
  attributes: [{ clusterId: cluster.id }],
@@ -1500,6 +1805,15 @@ export class Matterbridge extends EventEmitter {
1500
1805
  this.log.info('Subscribed to all attributes and events');
1501
1806
  }
1502
1807
  }
1808
+ /** ***********************************************************************************************************************************/
1809
+ /** Matter.js methods */
1810
+ /** ***********************************************************************************************************************************/
1811
+ /**
1812
+ * Starts the matter storage process based on the specified storage type and name.
1813
+ * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1814
+ * @param {string} storageName - The name of the storage file.
1815
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1816
+ */
1503
1817
  async startMatterStorage(storageType, storageName) {
1504
1818
  this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1505
1819
  if (storageType === 'disk') {
@@ -1548,6 +1862,12 @@ export class Matterbridge extends EventEmitter {
1548
1862
  await this.matterbridgeContext.set('passcode', this.passcode);
1549
1863
  await this.matterbridgeContext.set('discriminator', this.discriminator);
1550
1864
  }
1865
+ /**
1866
+ * Convert the old API matter storage to the new API format.
1867
+ * @param {StorageContext} context - The context of Matterbridge or of the plugin.
1868
+ * @param {string} pluginName - The name of the plugin or Matterbridge.
1869
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1870
+ */
1551
1871
  async convertStorage(context, pluginName) {
1552
1872
  const storageService = Environment.default.get(StorageService);
1553
1873
  Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
@@ -1559,13 +1879,19 @@ export class Matterbridge extends EventEmitter {
1559
1879
  else {
1560
1880
  this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1561
1881
  }
1882
+ // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1562
1883
  const fabricManagerContext = context.createContext('FabricManager');
1563
1884
  const fabrics = (await fabricManagerContext.get('fabrics', []));
1564
1885
  const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 1);
1886
+ // Read EventHandler from the old storage
1565
1887
  const eventHandlerContext = context.createContext('EventHandler');
1888
+ // Read SessionManager from the old storage
1566
1889
  const sessionManagerContext = context.createContext('SessionManager');
1890
+ // Read EndpointStructure from the old storage
1567
1891
  const endpointStructureContext = context.createContext('EndpointStructure');
1892
+ // Read generalCommissioning from the old storage
1568
1893
  const generalCommissioningContext = context.createContext('Cluster-0-48');
1894
+ // Read basicInformation from the old storage
1569
1895
  const basicInformationContext = context.createContext('Cluster-0-40');
1570
1896
  const fabricInfo = {};
1571
1897
  const fabricInfoArray = [];
@@ -1592,22 +1918,30 @@ export class Matterbridge extends EventEmitter {
1592
1918
  label: fabric.label,
1593
1919
  });
1594
1920
  nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
1921
+ // eslint-disable-next-line no-useless-escape
1595
1922
  trcArray.push('{\"__object__\":\"Uint8Array\",\"__value__\":\"' + Buffer.from(fabric.rootCert).toString('hex') + '\"}');
1923
+ // eslint-disable-next-line no-useless-escape
1596
1924
  aclArray.push({ fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: ['{\"__object__\":\"BigInt\",\"__value__\":\"' + fabric.rootNodeId.toString().replace('n', '') + '\"}'], targets: null });
1925
+ // this.log.debug(`- fabricinfo ${fabric.fabricIndex}`, fabricInfo[fabric.fabricIndex]);
1597
1926
  }
1598
1927
  await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
1599
1928
  await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
1600
1929
  await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
1601
1930
  await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
1602
1931
  await nodeStorage.createContext('root').set('__number__', 0);
1603
- await nodeStorage.createContext('root').set('__nextNumber__', 1);
1932
+ await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
1604
1933
  await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
1605
1934
  await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
1606
1935
  await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
1607
1936
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
1608
1937
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
1938
+ // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
1609
1939
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
1940
+ // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
1610
1941
  await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
1942
+ // ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
1943
+ // From fabric.rootNodeId
1944
+ // [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
1611
1945
  await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
1612
1946
  await nodeStorage
1613
1947
  .createContext('root')
@@ -1621,11 +1955,33 @@ export class Matterbridge extends EventEmitter {
1621
1955
  await nodeStorage.createContext('root').createContext('network').set('operationalPort', 5540);
1622
1956
  await nodeStorage.createContext('root').createContext('productDescription').set('productId', 0x8000);
1623
1957
  await nodeStorage.createContext('root').createContext('productDescription').set('vendorId', 0xfff1);
1958
+ /*
1959
+ "Matterbridge.EndpointStructure": {
1960
+ "unique_d60ca095a002f160-index_0": 1,
1961
+ "unique_d60ca095a002f160-index_0-custom_Switch0": 2,
1962
+ "unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
1963
+ "unique_d60ca095a002f160-index_0-custom_Light0": 4,
1964
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
1965
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
1966
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
1967
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
1968
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
1969
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
1970
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
1971
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
1972
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
1973
+ "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
1974
+ "nextEndpointId": 5
1975
+ },
1976
+ */
1624
1977
  for (const key of await endpointStructureContext.keys()) {
1625
- if (key === 'nextEndpointId')
1978
+ if (key === 'nextEndpointId') {
1979
+ await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
1626
1980
  continue;
1981
+ }
1627
1982
  const parts = key.split('-');
1628
1983
  const number = await endpointStructureContext.get(key);
1984
+ // this.log.debug(`- endpointStructure key ${key} value ${number}`);
1629
1985
  if (parts.length === 2) {
1630
1986
  this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.__number__:${number}`);
1631
1987
  await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', number);
@@ -1642,11 +1998,30 @@ export class Matterbridge extends EventEmitter {
1642
1998
  }
1643
1999
  }
1644
2000
  }
1645
- await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
1646
2001
  await nodeStorage.createContext('persist').set('converted', true);
2002
+ await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
2003
+ await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
2004
+ await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
2005
+ await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
2006
+ await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
2007
+ await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
2008
+ await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
2009
+ await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
2010
+ await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
2011
+ await nodeStorage.createContext('persist').set('uniqueId', 'UI' + (await context.get('uniqueId')));
2012
+ await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
2013
+ await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
2014
+ await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
2015
+ await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
1647
2016
  await context.set('converted', true);
1648
2017
  this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
1649
2018
  }
2019
+ /**
2020
+ * Makes a backup copy of the specified matter JSON storage file.
2021
+ *
2022
+ * @param storageName - The name of the JSON storage file to be backed up.
2023
+ * @param backupName - The name of the backup file to be created.
2024
+ */
1650
2025
  async backupMatterStorage(storageName, backupName) {
1651
2026
  try {
1652
2027
  this.log.debug(`Making backup copy of ${storageName}`);
@@ -1667,6 +2042,12 @@ export class Matterbridge extends EventEmitter {
1667
2042
  }
1668
2043
  }
1669
2044
  }
2045
+ /**
2046
+ * Restore the specified matter JSON storage file.
2047
+ *
2048
+ * @param backupName - The name of the backup file to restore from.
2049
+ * @param storageName - The name of the JSON storage file to restored.
2050
+ */
1670
2051
  async restoreMatterStorage(backupName, storageName) {
1671
2052
  try {
1672
2053
  this.log.notice(`Restoring the backup copy of ${storageName}`);
@@ -1687,6 +2068,10 @@ export class Matterbridge extends EventEmitter {
1687
2068
  }
1688
2069
  }
1689
2070
  }
2071
+ /**
2072
+ * Stops the matter storage.
2073
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2074
+ */
1690
2075
  async stopMatterStorage() {
1691
2076
  this.log.debug('Stopping storage');
1692
2077
  await this.storageManager?.close();
@@ -1695,8 +2080,14 @@ export class Matterbridge extends EventEmitter {
1695
2080
  this.matterbridgeContext = undefined;
1696
2081
  this.mattercontrollerContext = undefined;
1697
2082
  }
2083
+ /**
2084
+ * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
2085
+ * @param storageManager The storage manager to be used by the Matter server.
2086
+ *
2087
+ */
1698
2088
  createMatterServer(storageManager) {
1699
2089
  this.log.debug('Creating matter server');
2090
+ // Validate mdnsInterface
1700
2091
  if (this.mdnsInterface) {
1701
2092
  const networkInterfaces = os.networkInterfaces();
1702
2093
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -1712,6 +2103,10 @@ export class Matterbridge extends EventEmitter {
1712
2103
  this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
1713
2104
  return matterServer;
1714
2105
  }
2106
+ /**
2107
+ * Starts the Matter server.
2108
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
2109
+ */
1715
2110
  async startMatterServer() {
1716
2111
  if (!this.matterServer) {
1717
2112
  this.log.error('No matter server initialized');
@@ -1721,7 +2116,11 @@ export class Matterbridge extends EventEmitter {
1721
2116
  this.log.debug('Starting matter server...');
1722
2117
  await this.matterServer.start();
1723
2118
  this.log.debug('Started matter server');
2119
+ // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
1724
2120
  }
2121
+ /**
2122
+ * Stops the Matter server, commissioningServer and commissioningController.
2123
+ */
1725
2124
  async stopMatterServer() {
1726
2125
  this.log.debug('Stopping matter commissioningServer');
1727
2126
  await this.commissioningServer?.close();
@@ -1735,23 +2134,35 @@ export class Matterbridge extends EventEmitter {
1735
2134
  this.matterAggregator = undefined;
1736
2135
  this.matterServer = undefined;
1737
2136
  }
2137
+ /**
2138
+ * Creates a Matter Aggregator.
2139
+ * @param {StorageContext} context - The storage context.
2140
+ * @returns {Aggregator} - The created Matter Aggregator.
2141
+ */
1738
2142
  async createMatterAggregator(context, pluginName) {
1739
2143
  this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
1740
2144
  const matterAggregator = new Aggregator();
1741
2145
  return matterAggregator;
1742
2146
  }
2147
+ /**
2148
+ * Creates a matter commissioning server.
2149
+ *
2150
+ * @param {StorageContext} context - The storage context.
2151
+ * @param {string} pluginName - The name of the commissioning server.
2152
+ * @returns {CommissioningServer} The created commissioning server.
2153
+ */
1743
2154
  async createCommisioningServer(context, pluginName) {
1744
2155
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1745
2156
  const deviceName = await context.get('deviceName');
1746
2157
  const deviceType = await context.get('deviceType');
1747
2158
  const vendorId = await context.get('vendorId');
1748
- const vendorName = await context.get('vendorName');
2159
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1749
2160
  const productId = await context.get('productId');
1750
- const productName = await context.get('productName');
2161
+ const productName = await context.get('productName'); // Home app = Model
1751
2162
  const serialNumber = await context.get('serialNumber');
1752
2163
  const uniqueId = await context.get('uniqueId');
1753
2164
  const softwareVersion = await context.get('softwareVersion', 1);
1754
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
2165
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1755
2166
  const hardwareVersion = await context.get('hardwareVersion', 1);
1756
2167
  const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1757
2168
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
@@ -1759,6 +2170,7 @@ export class Matterbridge extends EventEmitter {
1759
2170
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1760
2171
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1761
2172
  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} `);
2173
+ // Validate ipv4address
1762
2174
  if (this.ipv4address) {
1763
2175
  const networkInterfaces = os.networkInterfaces();
1764
2176
  const availableAddresses = Object.values(networkInterfaces)
@@ -1773,6 +2185,7 @@ export class Matterbridge extends EventEmitter {
1773
2185
  this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
1774
2186
  }
1775
2187
  }
2188
+ // Validate ipv6address
1776
2189
  if (this.ipv6address) {
1777
2190
  const networkInterfaces = os.networkInterfaces();
1778
2191
  const availableAddresses = Object.values(networkInterfaces)
@@ -1803,7 +2216,7 @@ export class Matterbridge extends EventEmitter {
1803
2216
  nodeLabel: productName,
1804
2217
  productLabel: productName,
1805
2218
  softwareVersion,
1806
- softwareVersionString,
2219
+ softwareVersionString, // Home app = Firmware Revision
1807
2220
  hardwareVersion,
1808
2221
  hardwareVersionString,
1809
2222
  uniqueId,
@@ -1899,6 +2312,24 @@ export class Matterbridge extends EventEmitter {
1899
2312
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
1900
2313
  return commissioningServer;
1901
2314
  }
2315
+ /**
2316
+ * Creates a commissioning server storage context.
2317
+ *
2318
+ * @param pluginName - The name of the plugin.
2319
+ * @param deviceName - The name of the device.
2320
+ * @param deviceType - The type of the device.
2321
+ * @param vendorId - The vendor ID.
2322
+ * @param vendorName - The vendor name.
2323
+ * @param productId - The product ID.
2324
+ * @param productName - The product name.
2325
+ * @param serialNumber - The serial number of the device (optional).
2326
+ * @param uniqueId - The unique ID of the device (optional).
2327
+ * @param softwareVersion - The software version of the device (optional).
2328
+ * @param softwareVersionString - The software version string of the device (optional).
2329
+ * @param hardwareVersion - The hardware version of the device (optional).
2330
+ * @param hardwareVersionString - The hardware version string of the device (optional).
2331
+ * @returns The storage context for the commissioning server.
2332
+ */
1902
2333
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1903
2334
  if (!this.storageManager)
1904
2335
  throw new Error('No storage manager initialized');
@@ -1926,6 +2357,13 @@ export class Matterbridge extends EventEmitter {
1926
2357
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1927
2358
  return storageContext;
1928
2359
  }
2360
+ /**
2361
+ * Imports the commissioning server context for a specific plugin and device.
2362
+ * @param pluginName - The name of the plugin.
2363
+ * @param device - The MatterbridgeDevice object representing the device.
2364
+ * @returns The commissioning server context.
2365
+ * @throws Error if the BasicInformationCluster is not found.
2366
+ */
1929
2367
  async importCommissioningServerContext(pluginName, device) {
1930
2368
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1931
2369
  const basic = device.getClusterServer(BasicInformationCluster);
@@ -1960,6 +2398,14 @@ export class Matterbridge extends EventEmitter {
1960
2398
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1961
2399
  return storageContext;
1962
2400
  }
2401
+ /**
2402
+ * Shows the commissioning server QR code for a given plugin.
2403
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2404
+ * @param {StorageContext} storageContext - The storage context instance.
2405
+ * @param {NodeStorage} nodeContext - The node storage instance.
2406
+ * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2407
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2408
+ */
1963
2409
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1964
2410
  if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
1965
2411
  this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
@@ -1970,7 +2416,8 @@ export class Matterbridge extends EventEmitter {
1970
2416
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1971
2417
  const QrCode = new QrCodeSchema();
1972
2418
  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`);
1973
- if (this.log.logLevel === "debug" || this.log.logLevel === "info")
2419
+ // eslint-disable-next-line no-console
2420
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
1974
2421
  console.log(`${QrCode.encode(qrPairingCode)}\n`);
1975
2422
  this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
1976
2423
  if (pluginName === 'Matterbridge') {
@@ -2017,6 +2464,12 @@ export class Matterbridge extends EventEmitter {
2017
2464
  }
2018
2465
  this.wssSendRefreshRequired();
2019
2466
  }
2467
+ /**
2468
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2469
+ *
2470
+ * @param fabricInfo - The array of exposed fabric information objects.
2471
+ * @returns An array of sanitized exposed fabric information objects.
2472
+ */
2020
2473
  sanitizeFabricInformations(fabricInfo) {
2021
2474
  return fabricInfo.map((info) => {
2022
2475
  return {
@@ -2030,6 +2483,12 @@ export class Matterbridge extends EventEmitter {
2030
2483
  };
2031
2484
  });
2032
2485
  }
2486
+ /**
2487
+ * Sanitizes the session information by converting bigint properties to string.
2488
+ *
2489
+ * @param sessionInfo - The array of session information objects.
2490
+ * @returns An array of sanitized session information objects.
2491
+ */
2033
2492
  sanitizeSessionInformation(sessionInfo) {
2034
2493
  return sessionInfo
2035
2494
  .filter((session) => session.isPeerActive)
@@ -2057,6 +2516,12 @@ export class Matterbridge extends EventEmitter {
2057
2516
  };
2058
2517
  });
2059
2518
  }
2519
+ /**
2520
+ * Sets the reachability of a commissioning server and trigger.
2521
+ *
2522
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2523
+ * @param {boolean} reachable - The new reachability status.
2524
+ */
2060
2525
  setCommissioningServerReachability(commissioningServer, reachable) {
2061
2526
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2062
2527
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2064,6 +2529,11 @@ export class Matterbridge extends EventEmitter {
2064
2529
  if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2065
2530
  basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2066
2531
  }
2532
+ /**
2533
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2534
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2535
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2536
+ */
2067
2537
  setAggregatorReachability(matterAggregator, reachable) {
2068
2538
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2069
2539
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2076,6 +2546,12 @@ export class Matterbridge extends EventEmitter {
2076
2546
  device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2077
2547
  });
2078
2548
  }
2549
+ /**
2550
+ * Sets the reachability of a device and trigger.
2551
+ *
2552
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
2553
+ * @param {boolean} reachable - The new reachability status of the device.
2554
+ */
2079
2555
  setDeviceReachability(device, reachable) {
2080
2556
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2081
2557
  if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
@@ -2124,6 +2600,10 @@ export class Matterbridge extends EventEmitter {
2124
2600
  }
2125
2601
  return vendorName;
2126
2602
  };
2603
+ /**
2604
+ * Retrieves the base registered plugins sanitized for res.json().
2605
+ * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2606
+ */
2127
2607
  async getBaseRegisteredPlugins() {
2128
2608
  const baseRegisteredPlugins = [];
2129
2609
  for (const plugin of this.plugins) {
@@ -2155,13 +2635,36 @@ export class Matterbridge extends EventEmitter {
2155
2635
  }
2156
2636
  return baseRegisteredPlugins;
2157
2637
  }
2638
+ /**
2639
+ * Spawns a child process with the given command and arguments.
2640
+ * @param {string} command - The command to execute.
2641
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2642
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2643
+ */
2158
2644
  async spawnCommand(command, args = []) {
2645
+ /*
2646
+ npm > npm.cmd on windows
2647
+ cmd.exe ['dir'] on windows
2648
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2649
+ process.on('unhandledRejection', (reason, promise) => {
2650
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2651
+ });
2652
+
2653
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2654
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2655
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2656
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2657
+ */
2159
2658
  const cmdLine = command + ' ' + args.join(' ');
2160
2659
  if (process.platform === 'win32' && command === 'npm') {
2660
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2161
2661
  const argstring = 'npm ' + args.join(' ');
2162
2662
  args.splice(0, args.length, '/c', argstring);
2163
2663
  command = 'cmd.exe';
2164
2664
  }
2665
+ // Decide when using sudo on linux
2666
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2667
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2165
2668
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2166
2669
  args.unshift(command);
2167
2670
  command = 'sudo';
@@ -2219,55 +2722,102 @@ export class Matterbridge extends EventEmitter {
2219
2722
  }
2220
2723
  });
2221
2724
  }
2725
+ /**
2726
+ * Sends a WebSocket message to all connected clients.
2727
+ *
2728
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2729
+ * @param {string} time - The time string of the message
2730
+ * @param {string} name - The logger name of the message
2731
+ * @param {string} message - The content of the message.
2732
+ */
2222
2733
  wssSendMessage(level, time, name, message) {
2223
2734
  if (!level || !time || !name || !message)
2224
2735
  return;
2736
+ // Remove ANSI escape codes from the message
2737
+ // eslint-disable-next-line no-control-regex
2225
2738
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2739
+ // Remove leading asterisks from the message
2226
2740
  message = message.replace(/^\*+/, '');
2741
+ // Replace all occurrences of \t and \n
2227
2742
  message = message.replace(/[\t\n]/g, '');
2743
+ // Remove non-printable characters
2744
+ // eslint-disable-next-line no-control-regex
2228
2745
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2746
+ // Replace all occurrences of \" with "
2229
2747
  message = message.replace(/\\"/g, '"');
2748
+ // Define the maximum allowed length for continuous characters without a space
2230
2749
  const maxContinuousLength = 100;
2231
2750
  const keepStartLength = 20;
2232
2751
  const keepEndLength = 20;
2752
+ // Split the message into words
2233
2753
  message = message
2234
2754
  .split(' ')
2235
2755
  .map((word) => {
2756
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2236
2757
  if (word.length > maxContinuousLength) {
2237
2758
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2238
2759
  }
2239
2760
  return word;
2240
2761
  })
2241
2762
  .join(' ');
2763
+ // Send the message to all connected clients
2242
2764
  this.webSocketServer?.clients.forEach((client) => {
2243
2765
  if (client.readyState === WebSocket.OPEN) {
2244
2766
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2245
2767
  }
2246
2768
  });
2247
2769
  }
2770
+ /**
2771
+ * Sends a need to refresh WebSocket message to all connected clients.
2772
+ *
2773
+ */
2248
2774
  wssSendRefreshRequired() {
2249
2775
  this.matterbridgeInformation.refreshRequired = true;
2776
+ // Send the message to all connected clients
2250
2777
  this.webSocketServer?.clients.forEach((client) => {
2251
2778
  if (client.readyState === WebSocket.OPEN) {
2252
2779
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2253
2780
  }
2254
2781
  });
2255
2782
  }
2783
+ /**
2784
+ * Sends a need to restart WebSocket message to all connected clients.
2785
+ *
2786
+ */
2256
2787
  wssSendRestartRequired() {
2257
2788
  this.matterbridgeInformation.restartRequired = true;
2789
+ // Send the message to all connected clients
2258
2790
  this.webSocketServer?.clients.forEach((client) => {
2259
2791
  if (client.readyState === WebSocket.OPEN) {
2260
2792
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2261
2793
  }
2262
2794
  });
2263
2795
  }
2796
+ /**
2797
+ * Initializes the frontend of Matterbridge.
2798
+ *
2799
+ * @param port The port number to run the frontend server on. Default is 8283.
2800
+ */
2264
2801
  async initializeFrontend(port = 8283) {
2265
2802
  let initializeError = false;
2266
2803
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2804
+ // Create the express app that serves the frontend
2267
2805
  this.expressApp = express();
2806
+ // Log all requests to the server for debugging
2807
+ /*
2808
+ if (hasParameter('homedir')) {
2809
+ this.expressApp.use((req, res, next) => {
2810
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2811
+ next();
2812
+ });
2813
+ }
2814
+ */
2815
+ // Serve static files from '/static' endpoint
2268
2816
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2269
2817
  if (!hasParameter('ssl')) {
2818
+ // Create an HTTP server and attach the express app
2270
2819
  this.httpServer = createServer(this.expressApp);
2820
+ // Listen on the specified port
2271
2821
  if (hasParameter('ingress')) {
2272
2822
  this.httpServer.listen(port, '0.0.0.0', () => {
2273
2823
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2281,6 +2831,7 @@ export class Matterbridge extends EventEmitter {
2281
2831
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2282
2832
  });
2283
2833
  }
2834
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2284
2835
  this.httpServer.on('error', (error) => {
2285
2836
  this.log.error(`Frontend http server error listening on ${port}`);
2286
2837
  switch (error.code) {
@@ -2296,6 +2847,7 @@ export class Matterbridge extends EventEmitter {
2296
2847
  });
2297
2848
  }
2298
2849
  else {
2850
+ // Load the SSL certificate, the private key and optionally the CA certificate
2299
2851
  let cert;
2300
2852
  try {
2301
2853
  cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -2323,7 +2875,9 @@ export class Matterbridge extends EventEmitter {
2323
2875
  this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2324
2876
  }
2325
2877
  const serverOptions = { cert, key, ca };
2878
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2326
2879
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
2880
+ // Listen on the specified port
2327
2881
  if (hasParameter('ingress')) {
2328
2882
  this.httpsServer.listen(port, '0.0.0.0', () => {
2329
2883
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
@@ -2337,6 +2891,7 @@ export class Matterbridge extends EventEmitter {
2337
2891
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2338
2892
  });
2339
2893
  }
2894
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2340
2895
  this.httpsServer.on('error', (error) => {
2341
2896
  this.log.error(`Frontend https server error listening on ${port}`);
2342
2897
  switch (error.code) {
@@ -2353,12 +2908,13 @@ export class Matterbridge extends EventEmitter {
2353
2908
  }
2354
2909
  if (initializeError)
2355
2910
  return;
2911
+ // Createe a WebSocket server and attach it to the http or https server
2356
2912
  const wssPort = port;
2357
2913
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2358
2914
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2359
2915
  this.webSocketServer.on('connection', (ws, request) => {
2360
2916
  const clientIp = request.socket.remoteAddress;
2361
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
2917
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2362
2918
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2363
2919
  ws.on('message', (message) => {
2364
2920
  this.log.debug(`WebSocket client message: ${message}`);
@@ -2391,6 +2947,7 @@ export class Matterbridge extends EventEmitter {
2391
2947
  this.webSocketServer.on('error', (ws, error) => {
2392
2948
  this.log.error(`WebSocketServer error: ${error}`);
2393
2949
  });
2950
+ // Endpoint to validate login code
2394
2951
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2395
2952
  const { password } = req.body;
2396
2953
  this.log.debug('The frontend sent /api/login', password);
@@ -2409,12 +2966,14 @@ export class Matterbridge extends EventEmitter {
2409
2966
  this.log.warn('/api/login error wrong password');
2410
2967
  res.json({ valid: false });
2411
2968
  }
2969
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2412
2970
  }
2413
2971
  catch (error) {
2414
2972
  this.log.error('/api/login error getting password');
2415
2973
  res.json({ valid: false });
2416
2974
  }
2417
2975
  });
2976
+ // Endpoint to provide settings
2418
2977
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2419
2978
  this.log.debug('The frontend sent /api/settings');
2420
2979
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
@@ -2435,13 +2994,17 @@ export class Matterbridge extends EventEmitter {
2435
2994
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2436
2995
  this.matterbridgeInformation.profile = this.profile;
2437
2996
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2997
+ // this.log.debug('Response:', debugStringify(response));
2438
2998
  res.json(response);
2439
2999
  });
3000
+ // Endpoint to provide plugins
2440
3001
  this.expressApp.get('/api/plugins', async (req, res) => {
2441
3002
  this.log.debug('The frontend sent /api/plugins');
2442
3003
  const response = await this.getBaseRegisteredPlugins();
3004
+ // this.log.debug('Response:', debugStringify(response));
2443
3005
  res.json(response);
2444
3006
  });
3007
+ // Endpoint to provide devices
2445
3008
  this.expressApp.get('/api/devices', (req, res) => {
2446
3009
  this.log.debug('The frontend sent /api/devices');
2447
3010
  const devices = [];
@@ -2474,8 +3037,10 @@ export class Matterbridge extends EventEmitter {
2474
3037
  cluster: cluster,
2475
3038
  });
2476
3039
  });
3040
+ // this.log.debug('Response:', debugStringify(data));
2477
3041
  res.json(devices);
2478
3042
  });
3043
+ // Endpoint to provide the cluster servers of the devices
2479
3044
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2480
3045
  const selectedPluginName = req.params.selectedPluginName;
2481
3046
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -2495,6 +3060,7 @@ export class Matterbridge extends EventEmitter {
2495
3060
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2496
3061
  if (clusterServer.name === 'EveHistory')
2497
3062
  return;
3063
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2498
3064
  let attributeValue;
2499
3065
  try {
2500
3066
  if (typeof value.getLocal() === 'object')
@@ -2505,6 +3071,7 @@ export class Matterbridge extends EventEmitter {
2505
3071
  catch (error) {
2506
3072
  attributeValue = 'Fabric-Scoped';
2507
3073
  this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3074
+ // console.log(error);
2508
3075
  }
2509
3076
  data.push({
2510
3077
  endpoint: device.number ? device.number.toString() : '...',
@@ -2517,12 +3084,14 @@ export class Matterbridge extends EventEmitter {
2517
3084
  });
2518
3085
  });
2519
3086
  device.getChildEndpoints().forEach((childEndpoint) => {
3087
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2520
3088
  const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
2521
3089
  const clusterServers = childEndpoint.getAllClusterServers();
2522
3090
  clusterServers.forEach((clusterServer) => {
2523
3091
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2524
3092
  if (clusterServer.name === 'EveHistory')
2525
3093
  return;
3094
+ // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
2526
3095
  let attributeValue;
2527
3096
  try {
2528
3097
  if (typeof value.getLocal() === 'object')
@@ -2533,6 +3102,7 @@ export class Matterbridge extends EventEmitter {
2533
3102
  catch (error) {
2534
3103
  attributeValue = 'Unavailable';
2535
3104
  this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3105
+ // console.log(error);
2536
3106
  }
2537
3107
  data.push({
2538
3108
  endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
@@ -2549,6 +3119,7 @@ export class Matterbridge extends EventEmitter {
2549
3119
  });
2550
3120
  res.json(data);
2551
3121
  });
3122
+ // Endpoint to view the log
2552
3123
  this.expressApp.get('/api/view-log', async (req, res) => {
2553
3124
  this.log.debug('The frontend sent /api/log');
2554
3125
  try {
@@ -2561,10 +3132,12 @@ export class Matterbridge extends EventEmitter {
2561
3132
  res.status(500).send('Error reading log file');
2562
3133
  }
2563
3134
  });
3135
+ // Endpoint to download the matterbridge log
2564
3136
  this.expressApp.get('/api/download-mblog', async (req, res) => {
2565
3137
  this.log.debug('The frontend sent /api/download-mblog');
2566
3138
  try {
2567
3139
  await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3140
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2568
3141
  }
2569
3142
  catch (error) {
2570
3143
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2576,10 +3149,12 @@ export class Matterbridge extends EventEmitter {
2576
3149
  }
2577
3150
  });
2578
3151
  });
3152
+ // Endpoint to download the matter log
2579
3153
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
2580
3154
  this.log.debug('The frontend sent /api/download-mjlog');
2581
3155
  try {
2582
3156
  await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3157
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2583
3158
  }
2584
3159
  catch (error) {
2585
3160
  fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -2591,6 +3166,7 @@ export class Matterbridge extends EventEmitter {
2591
3166
  }
2592
3167
  });
2593
3168
  });
3169
+ // Endpoint to download the matter storage file
2594
3170
  this.expressApp.get('/api/download-mjstorage', (req, res) => {
2595
3171
  this.log.debug('The frontend sent /api/download-mjstorage');
2596
3172
  res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
@@ -2600,6 +3176,7 @@ export class Matterbridge extends EventEmitter {
2600
3176
  }
2601
3177
  });
2602
3178
  });
3179
+ // Endpoint to download the matterbridge storage directory
2603
3180
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2604
3181
  this.log.debug('The frontend sent /api/download-mbstorage');
2605
3182
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
@@ -2610,6 +3187,7 @@ export class Matterbridge extends EventEmitter {
2610
3187
  }
2611
3188
  });
2612
3189
  });
3190
+ // Endpoint to download the matterbridge plugin directory
2613
3191
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2614
3192
  this.log.debug('The frontend sent /api/download-pluginstorage');
2615
3193
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
@@ -2620,9 +3198,11 @@ export class Matterbridge extends EventEmitter {
2620
3198
  }
2621
3199
  });
2622
3200
  });
3201
+ // Endpoint to download the matterbridge plugin config files
2623
3202
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2624
3203
  this.log.debug('The frontend sent /api/download-pluginconfig');
2625
3204
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3205
+ // 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')));
2626
3206
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2627
3207
  if (error) {
2628
3208
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -2630,6 +3210,7 @@ export class Matterbridge extends EventEmitter {
2630
3210
  }
2631
3211
  });
2632
3212
  });
3213
+ // Endpoint to download the matterbridge plugin config files
2633
3214
  this.expressApp.get('/api/download-backup', async (req, res) => {
2634
3215
  this.log.debug('The frontend sent /api/download-backup');
2635
3216
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -2639,6 +3220,7 @@ export class Matterbridge extends EventEmitter {
2639
3220
  }
2640
3221
  });
2641
3222
  });
3223
+ // Endpoint to receive commands
2642
3224
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2643
3225
  const command = req.params.command;
2644
3226
  let param = req.params.param;
@@ -2648,13 +3230,15 @@ export class Matterbridge extends EventEmitter {
2648
3230
  return;
2649
3231
  }
2650
3232
  this.log.debug(`Received frontend command: ${command}:${param}`);
3233
+ // Handle the command setpassword from Settings
2651
3234
  if (command === 'setpassword') {
2652
- const password = param.slice(1, -1);
3235
+ const password = param.slice(1, -1); // Remove the first and last characters
2653
3236
  this.log.debug('setpassword', param, password);
2654
3237
  await this.nodeContext?.set('password', password);
2655
3238
  res.json({ message: 'Command received' });
2656
3239
  return;
2657
3240
  }
3241
+ // Handle the command setbridgemode from Settings
2658
3242
  if (command === 'setbridgemode') {
2659
3243
  this.log.debug(`setbridgemode: ${param}`);
2660
3244
  this.wssSendRestartRequired();
@@ -2662,6 +3246,7 @@ export class Matterbridge extends EventEmitter {
2662
3246
  res.json({ message: 'Command received' });
2663
3247
  return;
2664
3248
  }
3249
+ // Handle the command backup from Settings
2665
3250
  if (command === 'backup') {
2666
3251
  this.log.notice(`Prepairing the backup...`);
2667
3252
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
@@ -2669,25 +3254,26 @@ export class Matterbridge extends EventEmitter {
2669
3254
  res.json({ message: 'Command received' });
2670
3255
  return;
2671
3256
  }
3257
+ // Handle the command setmbloglevel from Settings
2672
3258
  if (command === 'setmbloglevel') {
2673
3259
  this.log.debug('Matterbridge log level:', param);
2674
3260
  if (param === 'Debug') {
2675
- this.log.logLevel = "debug";
3261
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2676
3262
  }
2677
3263
  else if (param === 'Info') {
2678
- this.log.logLevel = "info";
3264
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2679
3265
  }
2680
3266
  else if (param === 'Notice') {
2681
- this.log.logLevel = "notice";
3267
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2682
3268
  }
2683
3269
  else if (param === 'Warn') {
2684
- this.log.logLevel = "warn";
3270
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2685
3271
  }
2686
3272
  else if (param === 'Error') {
2687
- this.log.logLevel = "error";
3273
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2688
3274
  }
2689
3275
  else if (param === 'Fatal') {
2690
- this.log.logLevel = "fatal";
3276
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2691
3277
  }
2692
3278
  await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2693
3279
  MatterbridgeDevice.logLevel = this.log.logLevel;
@@ -2695,12 +3281,13 @@ export class Matterbridge extends EventEmitter {
2695
3281
  for (const plugin of this.plugins) {
2696
3282
  if (!plugin.platform || !plugin.platform.config)
2697
3283
  continue;
2698
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
2699
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
3284
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3285
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2700
3286
  }
2701
3287
  res.json({ message: 'Command received' });
2702
3288
  return;
2703
3289
  }
3290
+ // Handle the command setmbloglevel from Settings
2704
3291
  if (command === 'setmjloglevel') {
2705
3292
  this.log.debug('Matter.js log level:', param);
2706
3293
  if (param === 'Debug') {
@@ -2725,30 +3312,34 @@ export class Matterbridge extends EventEmitter {
2725
3312
  res.json({ message: 'Command received' });
2726
3313
  return;
2727
3314
  }
3315
+ // Handle the command setmdnsinterface from Settings
2728
3316
  if (command === 'setmdnsinterface') {
2729
- param = param.slice(1, -1);
3317
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
2730
3318
  this.matterbridgeInformation.mattermdnsinterface = param;
2731
3319
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
2732
3320
  await this.nodeContext?.set('mattermdnsinterface', param);
2733
3321
  res.json({ message: 'Command received' });
2734
3322
  return;
2735
3323
  }
3324
+ // Handle the command setipv4address from Settings
2736
3325
  if (command === 'setipv4address') {
2737
- param = param.slice(1, -1);
3326
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2738
3327
  this.matterbridgeInformation.matteripv4address = param;
2739
3328
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
2740
3329
  await this.nodeContext?.set('matteripv4address', param);
2741
3330
  res.json({ message: 'Command received' });
2742
3331
  return;
2743
3332
  }
3333
+ // Handle the command setipv6address from Settings
2744
3334
  if (command === 'setipv6address') {
2745
- param = param.slice(1, -1);
3335
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
2746
3336
  this.matterbridgeInformation.matteripv6address = param;
2747
3337
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
2748
3338
  await this.nodeContext?.set('matteripv6address', param);
2749
3339
  res.json({ message: 'Command received' });
2750
3340
  return;
2751
3341
  }
3342
+ // Handle the command setmatterport from Settings
2752
3343
  if (command === 'setmatterport') {
2753
3344
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
2754
3345
  this.matterbridgeInformation.matterPort = port;
@@ -2757,6 +3348,7 @@ export class Matterbridge extends EventEmitter {
2757
3348
  res.json({ message: 'Command received' });
2758
3349
  return;
2759
3350
  }
3351
+ // Handle the command setmatterdiscriminator from Settings
2760
3352
  if (command === 'setmatterdiscriminator') {
2761
3353
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
2762
3354
  this.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -2765,6 +3357,7 @@ export class Matterbridge extends EventEmitter {
2765
3357
  res.json({ message: 'Command received' });
2766
3358
  return;
2767
3359
  }
3360
+ // Handle the command setmatterpasscode from Settings
2768
3361
  if (command === 'setmatterpasscode') {
2769
3362
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
2770
3363
  this.matterbridgeInformation.matterPasscode = passcode;
@@ -2773,17 +3366,20 @@ export class Matterbridge extends EventEmitter {
2773
3366
  res.json({ message: 'Command received' });
2774
3367
  return;
2775
3368
  }
3369
+ // Handle the command setmbloglevel from Settings
2776
3370
  if (command === 'setmblogfile') {
2777
3371
  this.log.debug('Matterbridge file log:', param);
2778
3372
  this.matterbridgeInformation.fileLogger = param === 'true';
2779
3373
  await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3374
+ // Create the file logger for matterbridge
2780
3375
  if (param === 'true')
2781
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug", true);
3376
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2782
3377
  else
2783
3378
  AnsiLogger.setGlobalLogfile(undefined);
2784
3379
  res.json({ message: 'Command received' });
2785
3380
  return;
2786
3381
  }
3382
+ // Handle the command setmbloglevel from Settings
2787
3383
  if (command === 'setmjlogfile') {
2788
3384
  this.log.debug('Matter file log:', param);
2789
3385
  this.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -2810,36 +3406,43 @@ export class Matterbridge extends EventEmitter {
2810
3406
  res.json({ message: 'Command received' });
2811
3407
  return;
2812
3408
  }
3409
+ // Handle the command unregister from Settings
2813
3410
  if (command === 'unregister') {
2814
3411
  await this.unregisterAndShutdownProcess();
2815
3412
  res.json({ message: 'Command received' });
2816
3413
  return;
2817
3414
  }
3415
+ // Handle the command reset from Settings
2818
3416
  if (command === 'reset') {
2819
3417
  await this.shutdownProcessAndReset();
2820
3418
  res.json({ message: 'Command received' });
2821
3419
  return;
2822
3420
  }
3421
+ // Handle the command factoryreset from Settings
2823
3422
  if (command === 'factoryreset') {
2824
3423
  await this.shutdownProcessAndFactoryReset();
2825
3424
  res.json({ message: 'Command received' });
2826
3425
  return;
2827
3426
  }
3427
+ // Handle the command shutdown from Header
2828
3428
  if (command === 'shutdown') {
2829
3429
  await this.shutdownProcess();
2830
3430
  res.json({ message: 'Command received' });
2831
3431
  return;
2832
3432
  }
3433
+ // Handle the command restart from Header
2833
3434
  if (command === 'restart') {
2834
3435
  await this.restartProcess();
2835
3436
  res.json({ message: 'Command received' });
2836
3437
  return;
2837
3438
  }
3439
+ // Handle the command update from Header
2838
3440
  if (command === 'update') {
2839
3441
  this.log.info('Updating matterbridge...');
2840
3442
  try {
2841
3443
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
2842
3444
  this.log.info('Matterbridge has been updated. Full restart required.');
3445
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2843
3446
  }
2844
3447
  catch (error) {
2845
3448
  this.log.error('Error updating matterbridge');
@@ -2849,9 +3452,11 @@ export class Matterbridge extends EventEmitter {
2849
3452
  res.json({ message: 'Command received' });
2850
3453
  return;
2851
3454
  }
3455
+ // Handle the command saveconfig from Home
2852
3456
  if (command === 'saveconfig') {
2853
3457
  param = param.replace(/\*/g, '\\');
2854
3458
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3459
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
2855
3460
  if (!this.plugins.has(param)) {
2856
3461
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
2857
3462
  }
@@ -2865,33 +3470,39 @@ export class Matterbridge extends EventEmitter {
2865
3470
  res.json({ message: 'Command received' });
2866
3471
  return;
2867
3472
  }
3473
+ // Handle the command installplugin from Home
2868
3474
  if (command === 'installplugin') {
2869
3475
  param = param.replace(/\*/g, '\\');
2870
3476
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2871
3477
  try {
2872
3478
  await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
2873
3479
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3480
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2874
3481
  }
2875
3482
  catch (error) {
2876
3483
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2877
3484
  }
2878
3485
  this.wssSendRestartRequired();
2879
3486
  param = param.split('@')[0];
3487
+ // Also add the plugin to matterbridge so no return!
2880
3488
  if (param === 'matterbridge') {
3489
+ // 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
2881
3490
  res.json({ message: 'Command received' });
2882
3491
  return;
2883
3492
  }
2884
3493
  }
3494
+ // Handle the command addplugin from Home
2885
3495
  if (command === 'addplugin' || command === 'installplugin') {
2886
3496
  param = param.replace(/\*/g, '\\');
2887
3497
  const plugin = await this.plugins.add(param);
2888
3498
  if (plugin) {
2889
- this.plugins.load(plugin, true, 'The plugin has been added', true);
3499
+ this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2890
3500
  }
2891
3501
  res.json({ message: 'Command received' });
2892
3502
  this.wssSendRefreshRequired();
2893
3503
  return;
2894
3504
  }
3505
+ // Handle the command removeplugin from Home
2895
3506
  if (command === 'removeplugin') {
2896
3507
  if (!this.plugins.has(param)) {
2897
3508
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2905,6 +3516,7 @@ export class Matterbridge extends EventEmitter {
2905
3516
  this.wssSendRefreshRequired();
2906
3517
  return;
2907
3518
  }
3519
+ // Handle the command enableplugin from Home
2908
3520
  if (command === 'enableplugin') {
2909
3521
  if (!this.plugins.has(param)) {
2910
3522
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2922,13 +3534,14 @@ export class Matterbridge extends EventEmitter {
2922
3534
  plugin.registeredDevices = undefined;
2923
3535
  plugin.addedDevices = undefined;
2924
3536
  await this.plugins.enable(param);
2925
- this.plugins.load(plugin, true, 'The plugin has been enabled', true);
3537
+ this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
2926
3538
  }
2927
3539
  }
2928
3540
  res.json({ message: 'Command received' });
2929
3541
  this.wssSendRefreshRequired();
2930
3542
  return;
2931
3543
  }
3544
+ // Handle the command disableplugin from Home
2932
3545
  if (command === 'disableplugin') {
2933
3546
  if (!this.plugins.has(param)) {
2934
3547
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -2945,6 +3558,7 @@ export class Matterbridge extends EventEmitter {
2945
3558
  return;
2946
3559
  }
2947
3560
  });
3561
+ // Fallback for routing (must be the last route)
2948
3562
  this.expressApp.get('*', (req, res) => {
2949
3563
  this.log.debug('The frontend sent:', req.url);
2950
3564
  this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
@@ -2952,6 +3566,11 @@ export class Matterbridge extends EventEmitter {
2952
3566
  });
2953
3567
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2954
3568
  }
3569
+ /**
3570
+ * Retrieves the cluster text description from a given device.
3571
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3572
+ * @returns {string} The attributes description of the cluster servers in the device.
3573
+ */
2955
3574
  getClusterTextFromDevice(device) {
2956
3575
  const stringifyUserLabel = (endpoint) => {
2957
3576
  const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
@@ -2974,9 +3593,11 @@ export class Matterbridge extends EventEmitter {
2974
3593
  return '';
2975
3594
  };
2976
3595
  let attributes = '';
3596
+ // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
2977
3597
  const clusterServers = device.getAllClusterServers();
2978
3598
  clusterServers.forEach((clusterServer) => {
2979
3599
  try {
3600
+ // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2980
3601
  if (clusterServer.name === 'OnOff')
2981
3602
  attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
2982
3603
  if (clusterServer.name === 'Switch')
@@ -3027,18 +3648,30 @@ export class Matterbridge extends EventEmitter {
3027
3648
  attributes += `${stringifyFixedLabel(device)} `;
3028
3649
  if (clusterServer.name === 'UserLabel')
3029
3650
  attributes += `${stringifyUserLabel(device)} `;
3651
+ // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3030
3652
  }
3031
3653
  catch (error) {
3032
3654
  this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3033
3655
  }
3034
3656
  });
3657
+ // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3035
3658
  return attributes;
3036
3659
  }
3660
+ /**
3661
+ * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3662
+ * @deprecated This method is deprecated and will be removed in a future version.
3663
+ *
3664
+ * @returns A Promise that resolves when the initialization is complete.
3665
+ */
3037
3666
  async startExtension(dataPath, extensionVersion, port = 5540) {
3667
+ // Set the bridge mode
3038
3668
  this.bridgeMode = 'bridge';
3669
+ // Set the first port to use
3039
3670
  this.port = port;
3040
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "info" });
3671
+ // Set Matterbridge logger
3672
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3041
3673
  this.log.debug('Matterbridge extension is starting...');
3674
+ // Initialize NodeStorage
3042
3675
  this.matterbridgeDirectory = dataPath;
3043
3676
  this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3044
3677
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
@@ -3057,10 +3690,13 @@ export class Matterbridge extends EventEmitter {
3057
3690
  };
3058
3691
  this.plugins.set(plugin);
3059
3692
  this.plugins.saveToStorage();
3693
+ // Log system info and create .matterbridge directory
3060
3694
  await this.logNodeAndSystemInfo();
3061
3695
  this.matterbridgeDirectory = dataPath;
3696
+ // Set matter.js logger level and format
3062
3697
  Logger.defaultLogLevel = MatterLogLevel.INFO;
3063
3698
  Logger.format = MatterLogFormat.ANSI;
3699
+ // Start the storage and create matterbridgeContext
3064
3700
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3065
3701
  if (!this.storageManager)
3066
3702
  return false;
@@ -3070,7 +3706,7 @@ export class Matterbridge extends EventEmitter {
3070
3706
  await this.matterbridgeContext.set('softwareVersion', 1);
3071
3707
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3072
3708
  await this.matterbridgeContext.set('hardwareVersion', 1);
3073
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
3709
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
3074
3710
  this.matterServer = this.createMatterServer(this.storageManager);
3075
3711
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3076
3712
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
@@ -3083,6 +3719,7 @@ export class Matterbridge extends EventEmitter {
3083
3719
  await this.startMatterServer();
3084
3720
  this.log.info('Matter server started');
3085
3721
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3722
+ // Set reachability to true and trigger event after 60 seconds
3086
3723
  setTimeout(() => {
3087
3724
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3088
3725
  if (this.commissioningServer)
@@ -3092,14 +3729,31 @@ export class Matterbridge extends EventEmitter {
3092
3729
  }, 60 * 1000);
3093
3730
  return this.commissioningServer.isCommissioned();
3094
3731
  }
3732
+ /**
3733
+ * Close the Matterbridge instance as extension for zigbee2mqtt.
3734
+ * @deprecated This method is deprecated and will be removed in a future version.
3735
+ *
3736
+ * @returns A Promise that resolves when the initialization is complete.
3737
+ */
3095
3738
  async stopExtension() {
3739
+ // Closing matter
3096
3740
  await this.stopMatterServer();
3741
+ // Clearing the session manager
3742
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
3743
+ // Closing storage
3097
3744
  await this.stopMatterStorage();
3098
3745
  this.log.info('Matter server stopped');
3099
3746
  }
3747
+ /**
3748
+ * Checks if the extension is commissioned.
3749
+ * @deprecated This method is deprecated and will be removed in a future version.
3750
+ *
3751
+ * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3752
+ */
3100
3753
  isExtensionCommissioned() {
3101
3754
  if (!this.commissioningServer)
3102
3755
  return false;
3103
3756
  return this.commissioningServer.isCommissioned();
3104
3757
  }
3105
3758
  }
3759
+ //# sourceMappingURL=matterbridge.js.map