matterbridge 2.2.2-dev.3 → 2.2.3

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 (152) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +114 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +201 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +307 -24
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -1
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +412 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +723 -49
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +32 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +112 -11
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +33 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +835 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +690 -6
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +118 -9
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +251 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +185 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +178 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +236 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +229 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +77 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +121 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +45 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +87 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +86 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +70 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +77 -5
  137. package/dist/utils/network.js.map +1 -0
  138. package/dist/utils/parameter.d.ts +44 -0
  139. package/dist/utils/parameter.d.ts.map +1 -0
  140. package/dist/utils/parameter.js +41 -0
  141. package/dist/utils/parameter.js.map +1 -0
  142. package/dist/utils/wait.d.ts +43 -0
  143. package/dist/utils/wait.d.ts.map +1 -0
  144. package/dist/utils/wait.js +48 -5
  145. package/dist/utils/wait.js.map +1 -0
  146. package/frontend/build/asset-manifest.json +3 -3
  147. package/frontend/build/index.html +1 -1
  148. package/frontend/build/static/js/{main.a02de6aa.js → main.6ffd2c31.js} +6 -6
  149. package/frontend/build/static/js/{main.a02de6aa.js.map → main.6ffd2c31.js.map} +1 -1
  150. package/npm-shrinkwrap.json +2 -2
  151. package/package.json +2 -1
  152. /package/frontend/build/static/js/{main.a02de6aa.js.LICENSE.txt → main.6ffd2c31.js.LICENSE.txt} +0 -0
@@ -1,9 +1,35 @@
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 os from 'node:os';
2
25
  import path from 'node:path';
3
26
  import { promises as fs } from 'node:fs';
4
27
  import EventEmitter from 'node:events';
28
+ // AnsiLogger module
5
29
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
30
+ // NodeStorage module
6
31
  import { NodeStorageManager } from './storage/export.js';
32
+ // Matterbridge
7
33
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
8
34
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
9
35
  import { PluginManager } from './pluginManager.js';
@@ -11,13 +37,18 @@ import { DeviceManager } from './deviceManager.js';
11
37
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
12
38
  import { bridge } from './matterbridgeDeviceTypes.js';
13
39
  import { Frontend } from './frontend.js';
40
+ // @matter
14
41
  import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
15
42
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
16
43
  import { AggregatorEndpoint } from '@matter/main/endpoints';
17
44
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
45
+ // Default colors
18
46
  const plg = '\u001B[38;5;33m';
19
47
  const dev = '\u001B[38;5;79m';
20
48
  const typ = '\u001B[38;5;207m';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
21
52
  export class Matterbridge extends EventEmitter {
22
53
  systemInformation = {
23
54
  interfaceName: '',
@@ -61,7 +92,7 @@ export class Matterbridge extends EventEmitter {
61
92
  shellySysUpdate: false,
62
93
  shellyMainUpdate: false,
63
94
  profile: getParameter('profile'),
64
- loggerLevel: "info",
95
+ loggerLevel: "info" /* LogLevel.INFO */,
65
96
  fileLogger: false,
66
97
  matterLoggerLevel: MatterLogLevel.INFO,
67
98
  matterFileLogger: false,
@@ -91,16 +122,18 @@ export class Matterbridge extends EventEmitter {
91
122
  profile = getParameter('profile');
92
123
  shutdown = false;
93
124
  edge = true;
94
- failCountLimit = hasParameter('shelly') ? 120 : 60;
125
+ failCountLimit = hasParameter('shelly') ? 600 : 60;
95
126
  log;
96
127
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
97
128
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
98
129
  plugins;
99
130
  devices;
100
131
  frontend = new Frontend(this);
132
+ // Matterbridge storage
101
133
  nodeStorage;
102
134
  nodeContext;
103
135
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
136
+ // Cleanup
104
137
  hasCleanupStarted = false;
105
138
  initialized = false;
106
139
  execRunningCount = 0;
@@ -113,38 +146,70 @@ export class Matterbridge extends EventEmitter {
113
146
  sigtermHandler;
114
147
  exceptionHandler;
115
148
  rejectionHandler;
149
+ // Matter environment
116
150
  environment = Environment.default;
151
+ // Matter storage
117
152
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
118
153
  matterStorageService;
119
154
  matterStorageManager;
120
155
  matterbridgeContext;
121
156
  mattercontrollerContext;
122
- mdnsInterface;
123
- ipv4address;
124
- ipv6address;
125
- port;
126
- passcode;
127
- discriminator;
157
+ // Matter parameters
158
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
159
+ ipv4address; // matter server node listeningAddressIpv4
160
+ ipv6address; // matter server node listeningAddressIpv6
161
+ port; // first server node port
162
+ passcode; // first server node passcode
163
+ discriminator; // first server node discriminator
128
164
  serverNode;
129
165
  aggregatorNode;
130
166
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
131
167
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
132
168
  static instance;
169
+ // We load asyncronously so is private
133
170
  constructor() {
134
171
  super();
135
172
  }
173
+ /**
174
+ * Emits an event of the specified type with the provided arguments.
175
+ *
176
+ * @template K - The type of the event.
177
+ * @param {K} eventName - The name of the event to emit.
178
+ * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
179
+ * @returns {boolean} - Returns true if the event had listeners, false otherwise.
180
+ */
136
181
  emit(eventName, ...args) {
137
182
  return super.emit(eventName, ...args);
138
183
  }
184
+ /**
185
+ * Registers an event listener for the specified event type.
186
+ *
187
+ * @template K - The type of the event.
188
+ * @param {K} eventName - The name of the event to listen for.
189
+ * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
190
+ * @returns {this} - Returns the instance of the Matterbridge class.
191
+ */
139
192
  on(eventName, listener) {
140
193
  return super.on(eventName, listener);
141
194
  }
195
+ /**
196
+ * Retrieves the list of Matterbridge devices.
197
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
198
+ */
142
199
  getDevices() {
143
200
  return this.devices.array();
144
201
  }
202
+ /**
203
+ * Retrieves the list of registered plugins.
204
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
205
+ */
145
206
  getPlugins() {
146
207
  return this.plugins.array();
147
208
  }
209
+ /**
210
+ * Set the logger logLevel for the Matterbridge classes.
211
+ * @param {LogLevel} logLevel The logger logLevel to set.
212
+ */
148
213
  async setLogLevel(logLevel) {
149
214
  if (this.log)
150
215
  this.log.logLevel = logLevel;
@@ -158,19 +223,31 @@ export class Matterbridge extends EventEmitter {
158
223
  for (const plugin of this.plugins) {
159
224
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
160
225
  continue;
161
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
162
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
163
- }
164
- let callbackLogLevel = "notice";
165
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
166
- callbackLogLevel = "info";
167
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
168
- callbackLogLevel = "debug";
226
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
227
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
228
+ }
229
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
230
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
231
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
232
+ callbackLogLevel = "info" /* LogLevel.INFO */;
233
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
234
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
169
235
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
170
236
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
171
237
  }
238
+ /** ***********************************************************************************************************************************/
239
+ /** loadInstance() and cleanup() methods */
240
+ /** ***********************************************************************************************************************************/
241
+ /**
242
+ * Loads an instance of the Matterbridge class.
243
+ * If an instance already exists, return that instance.
244
+ *
245
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
246
+ * @returns The loaded Matterbridge instance.
247
+ */
172
248
  static async loadInstance(initialize = false) {
173
249
  if (!Matterbridge.instance) {
250
+ // eslint-disable-next-line no-console
174
251
  if (hasParameter('debug'))
175
252
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
176
253
  Matterbridge.instance = new Matterbridge();
@@ -179,8 +256,14 @@ export class Matterbridge extends EventEmitter {
179
256
  }
180
257
  return Matterbridge.instance;
181
258
  }
259
+ /**
260
+ * Call cleanup().
261
+ * @deprecated This method is deprecated and is only used for jest tests.
262
+ *
263
+ */
182
264
  async destroyInstance() {
183
265
  this.log.info(`Destroy instance...`);
266
+ // Save server nodes to close
184
267
  const servers = [];
185
268
  if (this.bridgeMode === 'bridge') {
186
269
  if (this.serverNode)
@@ -192,55 +275,81 @@ export class Matterbridge extends EventEmitter {
192
275
  servers.push(plugin.serverNode);
193
276
  }
194
277
  }
278
+ // Cleanup
195
279
  await this.cleanup('destroying instance...', false);
280
+ // Close servers mdns service
196
281
  this.log.info(`Dispose ${servers.length} MdnsService...`);
197
282
  for (const server of servers) {
198
283
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
199
284
  this.log.info(`Closed ${server.id} MdnsService`);
200
285
  }
286
+ // Wait for the cleanup to finish
201
287
  await new Promise((resolve) => {
202
288
  setTimeout(resolve, 1000);
203
289
  });
204
290
  }
291
+ /**
292
+ * Initializes the Matterbridge application.
293
+ *
294
+ * @remarks
295
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
296
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
297
+ * node version, registers signal handlers, initializes storage, and parses the command line.
298
+ *
299
+ * @returns A Promise that resolves when the initialization is complete.
300
+ */
205
301
  async initialize() {
302
+ // Set the restart mode
206
303
  if (hasParameter('service'))
207
304
  this.restartMode = 'service';
208
305
  if (hasParameter('docker'))
209
306
  this.restartMode = 'docker';
307
+ // Set the matterbridge directory
210
308
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
211
309
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
310
+ // Setup the matter environment
212
311
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
213
312
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
214
313
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
215
314
  this.environment.vars.set('runtime.signals', false);
216
315
  this.environment.vars.set('runtime.exitcode', false);
217
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
316
+ // Create the matterbridge logger
317
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
318
+ // Register process handlers
218
319
  this.registerProcessHandlers();
320
+ // Initialize nodeStorage and nodeContext
219
321
  try {
220
322
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
221
323
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
222
324
  this.log.debug('Creating node storage context for matterbridge');
223
325
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
326
+ // TODO: Remove this code when node-persist-manager is updated
327
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
328
  const keys = (await this.nodeStorage?.storage.keys());
225
329
  for (const key of keys) {
226
330
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
332
  await this.nodeStorage?.storage.get(key);
228
333
  }
229
334
  const storages = await this.nodeStorage.getStorageNames();
230
335
  for (const storage of storages) {
231
336
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
232
337
  const nodeContext = await this.nodeStorage?.createStorage(storage);
338
+ // TODO: Remove this code when node-persist-manager is updated
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
340
  const keys = (await nodeContext?.storage.keys());
234
341
  keys.forEach(async (key) => {
235
342
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
236
343
  await nodeContext?.get(key);
237
344
  });
238
345
  }
346
+ // Creating a backup of the node storage since it is not corrupted
239
347
  this.log.debug('Creating node storage backup...');
240
348
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
241
349
  this.log.debug('Created node storage backup');
242
350
  }
243
351
  catch (error) {
352
+ // Restoring the backup of the node storage since it is corrupted
244
353
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
245
354
  if (hasParameter('norestore')) {
246
355
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -255,41 +364,46 @@ export class Matterbridge extends EventEmitter {
255
364
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
256
365
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
257
366
  }
367
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
258
368
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
369
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
259
370
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
371
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
260
372
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
261
373
  this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
374
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
262
375
  if (hasParameter('logger')) {
263
376
  const level = getParameter('logger');
264
377
  if (level === 'debug') {
265
- this.log.logLevel = "debug";
378
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
266
379
  }
267
380
  else if (level === 'info') {
268
- this.log.logLevel = "info";
381
+ this.log.logLevel = "info" /* LogLevel.INFO */;
269
382
  }
270
383
  else if (level === 'notice') {
271
- this.log.logLevel = "notice";
384
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
272
385
  }
273
386
  else if (level === 'warn') {
274
- this.log.logLevel = "warn";
387
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
275
388
  }
276
389
  else if (level === 'error') {
277
- this.log.logLevel = "error";
390
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
278
391
  }
279
392
  else if (level === 'fatal') {
280
- this.log.logLevel = "fatal";
393
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
281
394
  }
282
395
  else {
283
396
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
284
- this.log.logLevel = "info";
397
+ this.log.logLevel = "info" /* LogLevel.INFO */;
285
398
  }
286
399
  }
287
400
  else {
288
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
401
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
289
402
  }
290
403
  this.frontend.logLevel = this.log.logLevel;
291
404
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
292
405
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
406
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
293
407
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
294
408
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
295
409
  this.matterbridgeInformation.fileLogger = true;
@@ -298,6 +412,7 @@ export class Matterbridge extends EventEmitter {
298
412
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
299
413
  if (this.profile !== undefined)
300
414
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
415
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
301
416
  if (hasParameter('matterlogger')) {
302
417
  const level = getParameter('matterlogger');
303
418
  if (level === 'debug') {
@@ -329,6 +444,7 @@ export class Matterbridge extends EventEmitter {
329
444
  Logger.format = MatterLogFormat.ANSI;
330
445
  Logger.setLogger('default', this.createMatterLogger());
331
446
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
447
+ // Create the file logger for matter.js (context: matterFileLog)
332
448
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
333
449
  this.matterbridgeInformation.matterFileLogger = true;
334
450
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -337,6 +453,7 @@ export class Matterbridge extends EventEmitter {
337
453
  });
338
454
  }
339
455
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
456
+ // Set the interface to use for matter server node mdnsInterface
340
457
  if (hasParameter('mdnsinterface')) {
341
458
  this.mdnsInterface = getParameter('mdnsinterface');
342
459
  }
@@ -345,6 +462,7 @@ export class Matterbridge extends EventEmitter {
345
462
  if (this.mdnsInterface === '')
346
463
  this.mdnsInterface = undefined;
347
464
  }
465
+ // Validate mdnsInterface
348
466
  if (this.mdnsInterface) {
349
467
  const networkInterfaces = os.networkInterfaces();
350
468
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -358,6 +476,7 @@ export class Matterbridge extends EventEmitter {
358
476
  }
359
477
  if (this.mdnsInterface)
360
478
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
479
+ // Set the listeningAddressIpv4 for the matter commissioning server
361
480
  if (hasParameter('ipv4address')) {
362
481
  this.ipv4address = getParameter('ipv4address');
363
482
  }
@@ -366,6 +485,7 @@ export class Matterbridge extends EventEmitter {
366
485
  if (this.ipv4address === '')
367
486
  this.ipv4address = undefined;
368
487
  }
488
+ // Set the listeningAddressIpv6 for the matter commissioning server
369
489
  if (hasParameter('ipv6address')) {
370
490
  this.ipv6address = getParameter('ipv6address');
371
491
  }
@@ -374,14 +494,19 @@ export class Matterbridge extends EventEmitter {
374
494
  if (this.ipv6address === '')
375
495
  this.ipv6address = undefined;
376
496
  }
497
+ // Initialize PluginManager
377
498
  this.plugins = new PluginManager(this);
378
499
  await this.plugins.loadFromStorage();
379
500
  this.plugins.logLevel = this.log.logLevel;
501
+ // Initialize DeviceManager
380
502
  this.devices = new DeviceManager(this, this.nodeContext);
381
503
  this.devices.logLevel = this.log.logLevel;
504
+ // Get the plugins from node storage and create the plugins node storage contexts
382
505
  for (const plugin of this.plugins) {
383
506
  const packageJson = await this.plugins.parse(plugin);
384
507
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
508
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
509
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
385
510
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
386
511
  try {
387
512
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -403,6 +528,7 @@ export class Matterbridge extends EventEmitter {
403
528
  await plugin.nodeContext.set('description', plugin.description);
404
529
  await plugin.nodeContext.set('author', plugin.author);
405
530
  }
531
+ // Log system info and create .matterbridge directory
406
532
  await this.logNodeAndSystemInfo();
407
533
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
408
534
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -410,6 +536,7 @@ export class Matterbridge extends EventEmitter {
410
536
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
411
537
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
412
538
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
539
+ // Check node version and throw error
413
540
  const minNodeVersion = 18;
414
541
  const nodeVersion = process.versions.node;
415
542
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -417,9 +544,15 @@ export class Matterbridge extends EventEmitter {
417
544
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
418
545
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
419
546
  }
547
+ // Parse command line
420
548
  await this.parseCommandLine();
421
549
  this.initialized = true;
422
550
  }
551
+ /**
552
+ * Parses the command line arguments and performs the corresponding actions.
553
+ * @private
554
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
555
+ */
423
556
  async parseCommandLine() {
424
557
  if (hasParameter('help')) {
425
558
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -531,6 +664,7 @@ export class Matterbridge extends EventEmitter {
531
664
  this.shutdown = true;
532
665
  return;
533
666
  }
667
+ // Start the matter storage and create the matterbridge context
534
668
  try {
535
669
  await this.startMatterStorage();
536
670
  }
@@ -538,12 +672,14 @@ export class Matterbridge extends EventEmitter {
538
672
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
539
673
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
540
674
  }
675
+ // Clear the matterbridge context if the reset parameter is set
541
676
  if (hasParameter('reset') && getParameter('reset') === undefined) {
542
677
  this.initialized = true;
543
678
  await this.shutdownProcessAndReset();
544
679
  this.shutdown = true;
545
680
  return;
546
681
  }
682
+ // Clear matterbridge plugin context if the reset parameter is set
547
683
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
548
684
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
549
685
  const plugin = this.plugins.get(getParameter('reset'));
@@ -568,30 +704,37 @@ export class Matterbridge extends EventEmitter {
568
704
  this.shutdown = true;
569
705
  return;
570
706
  }
707
+ // Initialize frontend
571
708
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
572
709
  await this.frontend.start(getIntParameter('frontend'));
710
+ // Check in 30 seconds the latest versions
573
711
  this.checkUpdateTimeout = setTimeout(async () => {
574
712
  const { checkUpdates } = await import('./update.js');
575
713
  checkUpdates(this);
576
714
  }, 30 * 1000).unref();
715
+ // Check each 24 hours the latest versions
577
716
  this.checkUpdateInterval = setInterval(async () => {
578
717
  const { checkUpdates } = await import('./update.js');
579
718
  checkUpdates(this);
580
719
  }, 24 * 60 * 60 * 1000).unref();
720
+ // Start the matterbridge in mode test
581
721
  if (hasParameter('test')) {
582
722
  this.bridgeMode = 'bridge';
583
723
  MatterbridgeEndpoint.bridgeMode = 'bridge';
584
724
  return;
585
725
  }
726
+ // Start the matterbridge in mode controller
586
727
  if (hasParameter('controller')) {
587
728
  this.bridgeMode = 'controller';
588
729
  await this.startController();
589
730
  return;
590
731
  }
732
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
591
733
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
592
734
  this.log.info('Setting default matterbridge start mode to bridge');
593
735
  await this.nodeContext?.set('bridgeMode', 'bridge');
594
736
  }
737
+ // Start matterbridge in bridge mode
595
738
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
596
739
  this.bridgeMode = 'bridge';
597
740
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -599,6 +742,7 @@ export class Matterbridge extends EventEmitter {
599
742
  await this.startBridge();
600
743
  return;
601
744
  }
745
+ // Start matterbridge in childbridge mode
602
746
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
603
747
  this.bridgeMode = 'childbridge';
604
748
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -607,10 +751,20 @@ export class Matterbridge extends EventEmitter {
607
751
  return;
608
752
  }
609
753
  }
754
+ /**
755
+ * Asynchronously loads and starts the registered plugins.
756
+ *
757
+ * This method is responsible for initializing and staarting all enabled plugins.
758
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
759
+ *
760
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
761
+ */
610
762
  async startPlugins() {
763
+ // Check, load and start the plugins
611
764
  for (const plugin of this.plugins) {
612
765
  plugin.configJson = await this.plugins.loadConfig(plugin);
613
766
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
767
+ // Check if the plugin is available
614
768
  if (!(await this.plugins.resolve(plugin.path))) {
615
769
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
616
770
  plugin.enabled = false;
@@ -630,20 +784,26 @@ export class Matterbridge extends EventEmitter {
630
784
  plugin.addedDevices = undefined;
631
785
  plugin.qrPairingCode = undefined;
632
786
  plugin.manualPairingCode = undefined;
633
- this.plugins.load(plugin, true, 'Matterbridge is starting');
787
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
634
788
  }
635
789
  this.frontend.wssSendRefreshRequired('plugins');
636
790
  }
791
+ /**
792
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
793
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
794
+ */
637
795
  registerProcessHandlers() {
638
796
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
639
797
  process.removeAllListeners('uncaughtException');
640
798
  process.removeAllListeners('unhandledRejection');
641
799
  this.exceptionHandler = async (error) => {
642
800
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
801
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
643
802
  };
644
803
  process.on('uncaughtException', this.exceptionHandler);
645
804
  this.rejectionHandler = async (reason, promise) => {
646
805
  this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
806
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
647
807
  };
648
808
  process.on('unhandledRejection', this.rejectionHandler);
649
809
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -656,6 +816,9 @@ export class Matterbridge extends EventEmitter {
656
816
  };
657
817
  process.on('SIGTERM', this.sigtermHandler);
658
818
  }
819
+ /**
820
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
821
+ */
659
822
  deregisterProcesslHandlers() {
660
823
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
661
824
  if (this.exceptionHandler)
@@ -672,12 +835,17 @@ export class Matterbridge extends EventEmitter {
672
835
  process.off('SIGTERM', this.sigtermHandler);
673
836
  this.sigtermHandler = undefined;
674
837
  }
838
+ /**
839
+ * Logs the node and system information.
840
+ */
675
841
  async logNodeAndSystemInfo() {
842
+ // IP address information
676
843
  const networkInterfaces = os.networkInterfaces();
677
844
  this.systemInformation.interfaceName = '';
678
845
  this.systemInformation.ipv4Address = '';
679
846
  this.systemInformation.ipv6Address = '';
680
847
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
848
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
681
849
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
682
850
  continue;
683
851
  if (!interfaceDetails) {
@@ -703,19 +871,22 @@ export class Matterbridge extends EventEmitter {
703
871
  break;
704
872
  }
705
873
  }
874
+ // Node information
706
875
  this.systemInformation.nodeVersion = process.versions.node;
707
876
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
708
877
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
709
878
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
879
+ // Host system information
710
880
  this.systemInformation.hostname = os.hostname();
711
881
  this.systemInformation.user = os.userInfo().username;
712
- this.systemInformation.osType = os.type();
713
- this.systemInformation.osRelease = os.release();
714
- this.systemInformation.osPlatform = os.platform();
715
- this.systemInformation.osArch = os.arch();
716
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
717
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
718
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
882
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
883
+ this.systemInformation.osRelease = os.release(); // Kernel version
884
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
885
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
886
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
887
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
888
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
889
+ // Log the system information
719
890
  this.log.debug('Host System Information:');
720
891
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
721
892
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -731,16 +902,20 @@ export class Matterbridge extends EventEmitter {
731
902
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
732
903
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
733
904
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
905
+ // Home directory
734
906
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
735
907
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
736
908
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
909
+ // Package root directory
737
910
  const { fileURLToPath } = await import('node:url');
738
911
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
739
912
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
740
913
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
741
914
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
915
+ // Global node_modules directory
742
916
  if (this.nodeContext)
743
917
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
918
+ // First run of Matterbridge so the node storage is empty
744
919
  if (this.globalModulesDirectory === '') {
745
920
  try {
746
921
  this.execRunningCount++;
@@ -756,6 +931,20 @@ export class Matterbridge extends EventEmitter {
756
931
  }
757
932
  else
758
933
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
934
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
935
+ else {
936
+ this.getGlobalNodeModules()
937
+ .then(async (globalModulesDirectory) => {
938
+ this.globalModulesDirectory = globalModulesDirectory;
939
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
940
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
941
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
942
+ })
943
+ .catch((error) => {
944
+ this.log.error(`Error getting global node_modules directory: ${error}`);
945
+ });
946
+ }*/
947
+ // Create the data directory .matterbridge in the home directory
759
948
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
760
949
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
761
950
  try {
@@ -779,6 +968,7 @@ export class Matterbridge extends EventEmitter {
779
968
  }
780
969
  }
781
970
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
971
+ // Create the plugin directory Matterbridge in the home directory
782
972
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
783
973
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
784
974
  try {
@@ -802,50 +992,68 @@ export class Matterbridge extends EventEmitter {
802
992
  }
803
993
  }
804
994
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
995
+ // Matterbridge version
805
996
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
806
997
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
807
998
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
808
999
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1000
+ // Matterbridge latest version
809
1001
  if (this.nodeContext)
810
1002
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
811
1003
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1004
+ // this.getMatterbridgeLatestVersion();
1005
+ // Current working directory
812
1006
  const currentDir = process.cwd();
813
1007
  this.log.debug(`Current Working Directory: ${currentDir}`);
1008
+ // Command line arguments (excluding 'node' and the script name)
814
1009
  const cmdArgs = process.argv.slice(2).join(' ');
815
1010
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
816
1011
  }
1012
+ /**
1013
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1014
+ *
1015
+ * @returns {Function} The MatterLogger function.
1016
+ */
817
1017
  createMatterLogger() {
818
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1018
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
819
1019
  return (_level, formattedLog) => {
820
1020
  const logger = formattedLog.slice(44, 44 + 20).trim();
821
1021
  const message = formattedLog.slice(65);
822
1022
  matterLogger.logName = logger;
823
1023
  switch (_level) {
824
1024
  case MatterLogLevel.DEBUG:
825
- matterLogger.log("debug", message);
1025
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
826
1026
  break;
827
1027
  case MatterLogLevel.INFO:
828
- matterLogger.log("info", message);
1028
+ matterLogger.log("info" /* LogLevel.INFO */, message);
829
1029
  break;
830
1030
  case MatterLogLevel.NOTICE:
831
- matterLogger.log("notice", message);
1031
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
832
1032
  break;
833
1033
  case MatterLogLevel.WARN:
834
- matterLogger.log("warn", message);
1034
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
835
1035
  break;
836
1036
  case MatterLogLevel.ERROR:
837
- matterLogger.log("error", message);
1037
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
838
1038
  break;
839
1039
  case MatterLogLevel.FATAL:
840
- matterLogger.log("fatal", message);
1040
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
841
1041
  break;
842
1042
  default:
843
- matterLogger.log("debug", message);
1043
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
844
1044
  break;
845
1045
  }
846
1046
  };
847
1047
  }
1048
+ /**
1049
+ * Creates a Matter File Logger.
1050
+ *
1051
+ * @param {string} filePath - The path to the log file.
1052
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1053
+ * @returns {Function} - A function that logs formatted messages to the log file.
1054
+ */
848
1055
  async createMatterFileLogger(filePath, unlink = false) {
1056
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
849
1057
  let fileSize = 0;
850
1058
  if (unlink) {
851
1059
  try {
@@ -894,12 +1102,21 @@ export class Matterbridge extends EventEmitter {
894
1102
  }
895
1103
  };
896
1104
  }
1105
+ /**
1106
+ * Restarts the process by exiting the current instance and loading a new instance.
1107
+ */
897
1108
  async restartProcess() {
898
1109
  await this.cleanup('restarting...', true);
899
1110
  }
1111
+ /**
1112
+ * Shut down the process by exiting the current process.
1113
+ */
900
1114
  async shutdownProcess() {
901
1115
  await this.cleanup('shutting down...', false);
902
1116
  }
1117
+ /**
1118
+ * Update matterbridge and and shut down the process.
1119
+ */
903
1120
  async updateProcess() {
904
1121
  this.log.info('Updating matterbridge...');
905
1122
  try {
@@ -912,51 +1129,72 @@ export class Matterbridge extends EventEmitter {
912
1129
  this.frontend.wssSendRestartRequired();
913
1130
  await this.cleanup('updating...', false);
914
1131
  }
1132
+ /**
1133
+ * Unregister all devices and shut down the process.
1134
+ */
915
1135
  async unregisterAndShutdownProcess() {
916
1136
  this.log.info('Unregistering all devices and shutting down...');
917
1137
  for (const plugin of this.plugins) {
918
1138
  await this.removeAllBridgedEndpoints(plugin.name, 250);
919
1139
  }
920
1140
  this.log.debug('Waiting for the MessageExchange to finish...');
921
- await new Promise((resolve) => setTimeout(resolve, 1000));
1141
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
922
1142
  this.log.debug('Cleaning up and shutting down...');
923
1143
  await this.cleanup('unregistered all devices and shutting down...', false);
924
1144
  }
1145
+ /**
1146
+ * Reset commissioning and shut down the process.
1147
+ */
925
1148
  async shutdownProcessAndReset() {
926
1149
  await this.cleanup('shutting down with reset...', false);
927
1150
  }
1151
+ /**
1152
+ * Factory reset and shut down the process.
1153
+ */
928
1154
  async shutdownProcessAndFactoryReset() {
929
1155
  await this.cleanup('shutting down with factory reset...', false);
930
1156
  }
1157
+ /**
1158
+ * Cleans up the Matterbridge instance.
1159
+ * @param message - The cleanup message.
1160
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1161
+ * @returns A promise that resolves when the cleanup is completed.
1162
+ */
931
1163
  async cleanup(message, restart = false) {
932
1164
  if (this.initialized && !this.hasCleanupStarted) {
933
1165
  this.hasCleanupStarted = true;
934
1166
  this.log.info(message);
1167
+ // Clear the start matter interval
935
1168
  if (this.startMatterInterval) {
936
1169
  clearInterval(this.startMatterInterval);
937
1170
  this.startMatterInterval = undefined;
938
1171
  this.log.debug('Start matter interval cleared');
939
1172
  }
1173
+ // Clear the check update timeout
940
1174
  if (this.checkUpdateTimeout) {
941
1175
  clearInterval(this.checkUpdateTimeout);
942
1176
  this.checkUpdateTimeout = undefined;
943
1177
  this.log.debug('Check update timeout cleared');
944
1178
  }
1179
+ // Clear the check update interval
945
1180
  if (this.checkUpdateInterval) {
946
1181
  clearInterval(this.checkUpdateInterval);
947
1182
  this.checkUpdateInterval = undefined;
948
1183
  this.log.debug('Check update interval cleared');
949
1184
  }
1185
+ // Clear the configure timeout
950
1186
  if (this.configureTimeout) {
951
1187
  clearTimeout(this.configureTimeout);
952
1188
  this.configureTimeout = undefined;
953
1189
  this.log.debug('Matterbridge configure timeout cleared');
954
1190
  }
1191
+ // Clear the reachability timeout
955
1192
  if (this.reachabilityTimeout) {
956
1193
  clearTimeout(this.reachabilityTimeout);
957
1194
  this.reachabilityTimeout = undefined;
958
1195
  this.log.debug('Matterbridge reachability timeout cleared');
959
1196
  }
1197
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
960
1198
  for (const plugin of this.plugins) {
961
1199
  if (!plugin.enabled || plugin.error)
962
1200
  continue;
@@ -967,9 +1205,10 @@ export class Matterbridge extends EventEmitter {
967
1205
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
968
1206
  }
969
1207
  }
1208
+ // Stopping matter server nodes
970
1209
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
971
1210
  this.log.debug('Waiting for the MessageExchange to finish...');
972
- await new Promise((resolve) => setTimeout(resolve, 1000));
1211
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
973
1212
  if (this.bridgeMode === 'bridge') {
974
1213
  if (this.serverNode) {
975
1214
  await this.stopServerNode(this.serverNode);
@@ -985,6 +1224,7 @@ export class Matterbridge extends EventEmitter {
985
1224
  }
986
1225
  }
987
1226
  this.log.notice('Stopped matter server nodes');
1227
+ // Matter commisioning reset
988
1228
  if (message === 'shutting down with reset...') {
989
1229
  this.log.info('Resetting Matterbridge commissioning information...');
990
1230
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -994,17 +1234,37 @@ export class Matterbridge extends EventEmitter {
994
1234
  await this.matterbridgeContext?.clearAll();
995
1235
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
996
1236
  }
1237
+ // Stop matter storage
997
1238
  await this.stopMatterStorage();
1239
+ // Stop the frontend
998
1240
  await this.frontend.stop();
1241
+ // Remove the matterfilelogger
999
1242
  try {
1000
1243
  Logger.removeLogger('matterfilelogger');
1244
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1001
1245
  }
1002
1246
  catch (error) {
1247
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1003
1248
  }
1249
+ // Serialize registeredDevices
1004
1250
  if (this.nodeStorage && this.nodeContext) {
1251
+ /*
1252
+ TODO: Implement serialization of registered devices in edge mode
1253
+ this.log.info('Saving registered devices...');
1254
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1255
+ this.devices.forEach(async (device) => {
1256
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1257
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1258
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1259
+ });
1260
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1261
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1262
+ */
1263
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1005
1264
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1006
1265
  await this.nodeContext.close();
1007
1266
  this.nodeContext = undefined;
1267
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1008
1268
  for (const plugin of this.plugins) {
1009
1269
  if (plugin.nodeContext) {
1010
1270
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1021,8 +1281,10 @@ export class Matterbridge extends EventEmitter {
1021
1281
  }
1022
1282
  this.plugins.clear();
1023
1283
  this.devices.clear();
1284
+ // Factory reset
1024
1285
  if (message === 'shutting down with factory reset...') {
1025
1286
  try {
1287
+ // Delete old matter storage file and backup
1026
1288
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1027
1289
  this.log.info(`Unlinking old matter storage file: ${file}`);
1028
1290
  await fs.unlink(file);
@@ -1036,6 +1298,7 @@ export class Matterbridge extends EventEmitter {
1036
1298
  }
1037
1299
  }
1038
1300
  try {
1301
+ // Delete matter node storage directory with its subdirectories and backup
1039
1302
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1040
1303
  this.log.info(`Removing matter node storage directory: ${dir}`);
1041
1304
  await fs.rm(dir, { recursive: true });
@@ -1049,6 +1312,7 @@ export class Matterbridge extends EventEmitter {
1049
1312
  }
1050
1313
  }
1051
1314
  try {
1315
+ // Delete node storage directory with its subdirectories and backup
1052
1316
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1053
1317
  this.log.info(`Removing storage directory: ${dir}`);
1054
1318
  await fs.rm(dir, { recursive: true });
@@ -1063,12 +1327,13 @@ export class Matterbridge extends EventEmitter {
1063
1327
  }
1064
1328
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1065
1329
  }
1330
+ // Deregisters the process handlers
1066
1331
  this.deregisterProcesslHandlers();
1067
1332
  if (restart) {
1068
1333
  if (message === 'updating...') {
1069
1334
  this.log.info('Cleanup completed. Updating...');
1070
1335
  Matterbridge.instance = undefined;
1071
- this.emit('update');
1336
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1072
1337
  }
1073
1338
  else if (message === 'restarting...') {
1074
1339
  this.log.info('Cleanup completed. Restarting...');
@@ -1088,6 +1353,14 @@ export class Matterbridge extends EventEmitter {
1088
1353
  this.log.debug('Cleanup already started...');
1089
1354
  }
1090
1355
  }
1356
+ /**
1357
+ * Creates and configures the server node for an accessory plugin for a given device.
1358
+ *
1359
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1360
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1361
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1362
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1363
+ */
1091
1364
  async createAccessoryPlugin(plugin, device, start = false) {
1092
1365
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1093
1366
  plugin.locked = true;
@@ -1100,6 +1373,13 @@ export class Matterbridge extends EventEmitter {
1100
1373
  await this.startServerNode(plugin.serverNode);
1101
1374
  }
1102
1375
  }
1376
+ /**
1377
+ * Creates and configures the server node for a dynamic plugin.
1378
+ *
1379
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1380
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1381
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1382
+ */
1103
1383
  async createDynamicPlugin(plugin, start = false) {
1104
1384
  if (!plugin.locked) {
1105
1385
  plugin.locked = true;
@@ -1111,7 +1391,13 @@ export class Matterbridge extends EventEmitter {
1111
1391
  await this.startServerNode(plugin.serverNode);
1112
1392
  }
1113
1393
  }
1394
+ /**
1395
+ * Starts the Matterbridge in bridge mode.
1396
+ * @private
1397
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1398
+ */
1114
1399
  async startBridge() {
1400
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1115
1401
  if (!this.matterStorageManager)
1116
1402
  throw new Error('No storage manager initialized');
1117
1403
  if (!this.matterbridgeContext)
@@ -1149,14 +1435,17 @@ export class Matterbridge extends EventEmitter {
1149
1435
  clearInterval(this.startMatterInterval);
1150
1436
  this.startMatterInterval = undefined;
1151
1437
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1438
+ // Start the Matter server node
1152
1439
  this.startServerNode(this.serverNode);
1440
+ // Configure the plugins
1153
1441
  this.configureTimeout = setTimeout(async () => {
1154
1442
  for (const plugin of this.plugins) {
1155
1443
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1156
1444
  continue;
1157
1445
  try {
1158
1446
  if ((await this.plugins.configure(plugin)) === undefined) {
1159
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1447
+ if (plugin.configured !== true)
1448
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1160
1449
  }
1161
1450
  }
1162
1451
  catch (error) {
@@ -1166,6 +1455,7 @@ export class Matterbridge extends EventEmitter {
1166
1455
  }
1167
1456
  this.frontend.wssSendRefreshRequired('plugins');
1168
1457
  }, 30 * 1000);
1458
+ // Setting reachability to true
1169
1459
  this.reachabilityTimeout = setTimeout(() => {
1170
1460
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1171
1461
  if (this.aggregatorNode)
@@ -1174,6 +1464,11 @@ export class Matterbridge extends EventEmitter {
1174
1464
  }, 60 * 1000);
1175
1465
  }, 1000);
1176
1466
  }
1467
+ /**
1468
+ * Starts the Matterbridge in childbridge mode.
1469
+ * @private
1470
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1471
+ */
1177
1472
  async startChildbridge() {
1178
1473
  if (!this.matterStorageManager)
1179
1474
  throw new Error('No storage manager initialized');
@@ -1218,13 +1513,15 @@ export class Matterbridge extends EventEmitter {
1218
1513
  clearInterval(this.startMatterInterval);
1219
1514
  this.startMatterInterval = undefined;
1220
1515
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1516
+ // Configure the plugins
1221
1517
  this.configureTimeout = setTimeout(async () => {
1222
1518
  for (const plugin of this.plugins) {
1223
1519
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1224
1520
  continue;
1225
1521
  try {
1226
1522
  if ((await this.plugins.configure(plugin)) === undefined) {
1227
- this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1523
+ if (plugin.configured !== true)
1524
+ this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
1228
1525
  }
1229
1526
  }
1230
1527
  catch (error) {
@@ -1253,7 +1550,9 @@ export class Matterbridge extends EventEmitter {
1253
1550
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1254
1551
  continue;
1255
1552
  }
1553
+ // Start the Matter server node
1256
1554
  this.startServerNode(plugin.serverNode);
1555
+ // Setting reachability to true
1257
1556
  plugin.reachabilityTimeout = setTimeout(() => {
1258
1557
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1259
1558
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1263,9 +1562,219 @@ export class Matterbridge extends EventEmitter {
1263
1562
  }
1264
1563
  }, 1000);
1265
1564
  }
1565
+ /**
1566
+ * Starts the Matterbridge controller.
1567
+ * @private
1568
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1569
+ */
1266
1570
  async startController() {
1571
+ /*
1572
+ if (!this.storageManager) {
1573
+ this.log.error('No storage manager initialized');
1574
+ await this.cleanup('No storage manager initialized');
1575
+ return;
1576
+ }
1577
+ this.log.info('Creating context: mattercontrollerContext');
1578
+ this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1579
+ if (!this.mattercontrollerContext) {
1580
+ this.log.error('No storage context mattercontrollerContext initialized');
1581
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1582
+ return;
1583
+ }
1584
+
1585
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1586
+ this.matterServer = await this.createMatterServer(this.storageManager);
1587
+ this.log.info('Creating matter commissioning controller');
1588
+ this.commissioningController = new CommissioningController({
1589
+ autoConnect: false,
1590
+ });
1591
+ this.log.info('Adding matter commissioning controller to matter server');
1592
+ await this.matterServer.addCommissioningController(this.commissioningController);
1593
+
1594
+ this.log.info('Starting matter server');
1595
+ await this.matterServer.start();
1596
+ this.log.info('Matter server started');
1597
+
1598
+ if (hasParameter('pairingcode')) {
1599
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1600
+ const pairingCode = getParameter('pairingcode');
1601
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1602
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1603
+
1604
+ let longDiscriminator, setupPin, shortDiscriminator;
1605
+ if (pairingCode !== undefined) {
1606
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1607
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1608
+ longDiscriminator = undefined;
1609
+ setupPin = pairingCodeCodec.passcode;
1610
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1611
+ } else {
1612
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1613
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1614
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1615
+ }
1616
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1617
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1618
+ }
1619
+
1620
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1621
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1622
+ regulatoryCountryCode: 'XX',
1623
+ };
1624
+ const options = {
1625
+ commissioning: commissioningOptions,
1626
+ discovery: {
1627
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1628
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1629
+ },
1630
+ passcode: setupPin,
1631
+ } as NodeCommissioningOptions;
1632
+ this.log.info('Commissioning with options:', options);
1633
+ const nodeId = await this.commissioningController.commissionNode(options);
1634
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1635
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1636
+ } // (hasParameter('pairingcode'))
1637
+
1638
+ if (hasParameter('unpairall')) {
1639
+ this.log.info('***Commissioning controller unpairing all nodes...');
1640
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1641
+ for (const nodeId of nodeIds) {
1642
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1643
+ await this.commissioningController.removeNode(nodeId);
1644
+ }
1645
+ return;
1646
+ }
1647
+
1648
+ if (hasParameter('discover')) {
1649
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1650
+ // console.log(discover);
1651
+ }
1652
+
1653
+ if (!this.commissioningController.isCommissioned()) {
1654
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1655
+ return;
1656
+ }
1657
+
1658
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1659
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1660
+ for (const nodeId of nodeIds) {
1661
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1662
+
1663
+ const node = await this.commissioningController.connectNode(nodeId, {
1664
+ autoSubscribe: false,
1665
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1666
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1667
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1668
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1669
+ stateInformationCallback: (peerNodeId, info) => {
1670
+ switch (info) {
1671
+ case NodeStateInformation.Connected:
1672
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1673
+ break;
1674
+ case NodeStateInformation.Disconnected:
1675
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1676
+ break;
1677
+ case NodeStateInformation.Reconnecting:
1678
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1679
+ break;
1680
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1681
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1682
+ break;
1683
+ case NodeStateInformation.StructureChanged:
1684
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1685
+ break;
1686
+ case NodeStateInformation.Decommissioned:
1687
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1688
+ break;
1689
+ default:
1690
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1691
+ break;
1692
+ }
1693
+ },
1694
+ });
1695
+
1696
+ node.logStructure();
1697
+
1698
+ // Get the interaction client
1699
+ this.log.info('Getting the interaction client');
1700
+ const interactionClient = await node.getInteractionClient();
1701
+ let cluster;
1702
+ let attributes;
1703
+
1704
+ // Log BasicInformationCluster
1705
+ cluster = BasicInformationCluster;
1706
+ attributes = await interactionClient.getMultipleAttributes({
1707
+ attributes: [{ clusterId: cluster.id }],
1708
+ });
1709
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1710
+ attributes.forEach((attribute) => {
1711
+ this.log.info(
1712
+ `- 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}`,
1713
+ );
1714
+ });
1715
+
1716
+ // Log PowerSourceCluster
1717
+ cluster = PowerSourceCluster;
1718
+ attributes = await interactionClient.getMultipleAttributes({
1719
+ attributes: [{ clusterId: cluster.id }],
1720
+ });
1721
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1722
+ attributes.forEach((attribute) => {
1723
+ this.log.info(
1724
+ `- 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}`,
1725
+ );
1726
+ });
1727
+
1728
+ // Log ThreadNetworkDiagnostics
1729
+ cluster = ThreadNetworkDiagnosticsCluster;
1730
+ attributes = await interactionClient.getMultipleAttributes({
1731
+ attributes: [{ clusterId: cluster.id }],
1732
+ });
1733
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1734
+ attributes.forEach((attribute) => {
1735
+ this.log.info(
1736
+ `- 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}`,
1737
+ );
1738
+ });
1739
+
1740
+ // Log SwitchCluster
1741
+ cluster = SwitchCluster;
1742
+ attributes = await interactionClient.getMultipleAttributes({
1743
+ attributes: [{ clusterId: cluster.id }],
1744
+ });
1745
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1746
+ attributes.forEach((attribute) => {
1747
+ this.log.info(
1748
+ `- 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}`,
1749
+ );
1750
+ });
1751
+
1752
+ this.log.info('Subscribing to all attributes and events');
1753
+ await node.subscribeAllAttributesAndEvents({
1754
+ ignoreInitialTriggers: false,
1755
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1756
+ this.log.info(
1757
+ `***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
1758
+ ),
1759
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1760
+ this.log.info(
1761
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1762
+ );
1763
+ },
1764
+ });
1765
+ this.log.info('Subscribed to all attributes and events');
1766
+ }
1767
+ */
1267
1768
  }
1769
+ /** ***********************************************************************************************************************************/
1770
+ /** Matter.js methods */
1771
+ /** ***********************************************************************************************************************************/
1772
+ /**
1773
+ * Starts the matter storage process with name Matterbridge.
1774
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1775
+ */
1268
1776
  async startMatterStorage() {
1777
+ // Setup Matter storage
1269
1778
  this.log.info(`Starting matter node storage...`);
1270
1779
  this.matterStorageService = this.environment.get(StorageService);
1271
1780
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1273,13 +1782,25 @@ export class Matterbridge extends EventEmitter {
1273
1782
  this.log.info('Matter node storage manager "Matterbridge" created');
1274
1783
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1275
1784
  this.log.info('Matter node storage started');
1785
+ // Backup matter storage since it is created/opened correctly
1276
1786
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1277
1787
  }
1788
+ /**
1789
+ * Makes a backup copy of the specified matter storage directory.
1790
+ *
1791
+ * @param storageName - The name of the storage directory to be backed up.
1792
+ * @param backupName - The name of the backup directory to be created.
1793
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1794
+ */
1278
1795
  async backupMatterStorage(storageName, backupName) {
1279
1796
  this.log.info('Creating matter node storage backup...');
1280
1797
  await copyDirectory(storageName, backupName);
1281
1798
  this.log.info('Created matter node storage backup');
1282
1799
  }
1800
+ /**
1801
+ * Stops the matter storage.
1802
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1803
+ */
1283
1804
  async stopMatterStorage() {
1284
1805
  this.log.info('Closing matter node storage...');
1285
1806
  this.matterStorageManager?.close();
@@ -1288,6 +1809,19 @@ export class Matterbridge extends EventEmitter {
1288
1809
  this.matterbridgeContext = undefined;
1289
1810
  this.log.info('Matter node storage closed');
1290
1811
  }
1812
+ /**
1813
+ * Creates a server node storage context.
1814
+ *
1815
+ * @param {string} pluginName - The name of the plugin.
1816
+ * @param {string} deviceName - The name of the device.
1817
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1818
+ * @param {number} vendorId - The vendor ID.
1819
+ * @param {string} vendorName - The vendor name.
1820
+ * @param {number} productId - The product ID.
1821
+ * @param {string} productName - The product name.
1822
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1823
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1824
+ */
1291
1825
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1292
1826
  const { randomBytes } = await import('node:crypto');
1293
1827
  if (!this.matterStorageService)
@@ -1321,6 +1855,15 @@ export class Matterbridge extends EventEmitter {
1321
1855
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1322
1856
  return storageContext;
1323
1857
  }
1858
+ /**
1859
+ * Creates a server node.
1860
+ *
1861
+ * @param {StorageContext} storageContext - The storage context for the server node.
1862
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1863
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1864
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1865
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1866
+ */
1324
1867
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1325
1868
  const storeId = await storageContext.get('storeId');
1326
1869
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1330,21 +1873,33 @@ export class Matterbridge extends EventEmitter {
1330
1873
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1331
1874
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1332
1875
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1876
+ /**
1877
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1878
+ */
1333
1879
  const serverNode = await ServerNode.create({
1880
+ // Required: Give the Node a unique ID which is used to store the state of this node
1334
1881
  id: storeId,
1882
+ // Provide Network relevant configuration like the port
1883
+ // Optional when operating only one device on a host, Default port is 5540
1335
1884
  network: {
1336
1885
  listeningAddressIpv4: this.ipv4address,
1337
1886
  listeningAddressIpv6: this.ipv6address,
1338
1887
  port,
1339
1888
  },
1889
+ // Provide Commissioning relevant settings
1890
+ // Optional for development/testing purposes
1340
1891
  commissioning: {
1341
1892
  passcode,
1342
1893
  discriminator,
1343
1894
  },
1895
+ // Provide Node announcement settings
1896
+ // Optional: If Ommitted some development defaults are used
1344
1897
  productDescription: {
1345
1898
  name: await storageContext.get('deviceName'),
1346
1899
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1347
1900
  },
1901
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1902
+ // Optional: If Omitted some development defaults are used
1348
1903
  basicInformation: {
1349
1904
  vendorId: VendorId(await storageContext.get('vendorId')),
1350
1905
  vendorName: await storageContext.get('vendorName'),
@@ -1361,12 +1916,13 @@ export class Matterbridge extends EventEmitter {
1361
1916
  },
1362
1917
  });
1363
1918
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1919
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1364
1920
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1365
1921
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1366
1922
  if (this.bridgeMode === 'bridge') {
1367
1923
  this.matterbridgeFabricInformations = sanitizedFabrics;
1368
1924
  if (resetSessions)
1369
- this.matterbridgeSessionInformations = undefined;
1925
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1370
1926
  this.matterbridgePaired = true;
1371
1927
  }
1372
1928
  if (this.bridgeMode === 'childbridge') {
@@ -1374,13 +1930,19 @@ export class Matterbridge extends EventEmitter {
1374
1930
  if (plugin) {
1375
1931
  plugin.fabricInformations = sanitizedFabrics;
1376
1932
  if (resetSessions)
1377
- plugin.sessionInformations = undefined;
1933
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1378
1934
  plugin.paired = true;
1379
1935
  }
1380
1936
  }
1381
1937
  };
1938
+ /**
1939
+ * This event is triggered when the device is initially commissioned successfully.
1940
+ * This means: It is added to the first fabric.
1941
+ */
1382
1942
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1943
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1383
1944
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1945
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1384
1946
  serverNode.lifecycle.online.on(async () => {
1385
1947
  this.log.notice(`Server node for ${storeId} is online`);
1386
1948
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1428,6 +1990,7 @@ export class Matterbridge extends EventEmitter {
1428
1990
  this.frontend.wssSendRefreshRequired('settings');
1429
1991
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1430
1992
  });
1993
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1431
1994
  serverNode.lifecycle.offline.on(() => {
1432
1995
  this.log.notice(`Server node for ${storeId} is offline`);
1433
1996
  if (this.bridgeMode === 'bridge') {
@@ -1451,6 +2014,10 @@ export class Matterbridge extends EventEmitter {
1451
2014
  this.frontend.wssSendRefreshRequired('settings');
1452
2015
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1453
2016
  });
2017
+ /**
2018
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2019
+ * information is needed.
2020
+ */
1454
2021
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1455
2022
  let action = '';
1456
2023
  switch (fabricAction) {
@@ -1484,16 +2051,24 @@ export class Matterbridge extends EventEmitter {
1484
2051
  }
1485
2052
  }
1486
2053
  };
2054
+ /**
2055
+ * This event is triggered when an operative new session was opened by a Controller.
2056
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2057
+ */
1487
2058
  serverNode.events.sessions.opened.on((session) => {
1488
2059
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1489
2060
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1490
2061
  this.frontend.wssSendRefreshRequired('sessions');
1491
2062
  });
2063
+ /**
2064
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2065
+ */
1492
2066
  serverNode.events.sessions.closed.on((session) => {
1493
2067
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1494
2068
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1495
2069
  this.frontend.wssSendRefreshRequired('sessions');
1496
2070
  });
2071
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1497
2072
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1498
2073
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1499
2074
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1502,24 +2077,42 @@ export class Matterbridge extends EventEmitter {
1502
2077
  this.log.info(`Created server node for ${storeId}`);
1503
2078
  return serverNode;
1504
2079
  }
2080
+ /**
2081
+ * Starts the specified server node.
2082
+ *
2083
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2084
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2085
+ */
1505
2086
  async startServerNode(matterServerNode) {
1506
2087
  if (!matterServerNode)
1507
2088
  return;
1508
2089
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1509
2090
  await matterServerNode.start();
1510
2091
  }
2092
+ /**
2093
+ * Stops the specified server node.
2094
+ *
2095
+ * @param {ServerNode} matterServerNode - The server node to stop.
2096
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2097
+ */
1511
2098
  async stopServerNode(matterServerNode) {
1512
2099
  if (!matterServerNode)
1513
2100
  return;
1514
2101
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1515
2102
  try {
1516
- await withTimeout(matterServerNode.close(), 30000);
2103
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1517
2104
  this.log.info(`Closed ${matterServerNode.id} server node`);
1518
2105
  }
1519
2106
  catch (error) {
1520
2107
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1521
2108
  }
1522
2109
  }
2110
+ /**
2111
+ * Advertises the specified server node.
2112
+ *
2113
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2114
+ * @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
2115
+ */
1523
2116
  async advertiseServerNode(matterServerNode) {
1524
2117
  if (matterServerNode) {
1525
2118
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1528,23 +2121,44 @@ export class Matterbridge extends EventEmitter {
1528
2121
  return { qrPairingCode, manualPairingCode };
1529
2122
  }
1530
2123
  }
2124
+ /**
2125
+ * Stop advertise the specified server node.
2126
+ *
2127
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2128
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2129
+ */
1531
2130
  async stopAdvertiseServerNode(matterServerNode) {
1532
2131
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1533
2132
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1534
2133
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1535
2134
  }
1536
2135
  }
2136
+ /**
2137
+ * Creates an aggregator node with the specified storage context.
2138
+ *
2139
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2140
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2141
+ */
1537
2142
  async createAggregatorNode(storageContext) {
1538
2143
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1539
2144
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1540
2145
  return aggregatorNode;
1541
2146
  }
2147
+ /**
2148
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2149
+ *
2150
+ * @param {string} pluginName - The name of the plugin.
2151
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2152
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2153
+ */
1542
2154
  async addBridgedEndpoint(pluginName, device) {
2155
+ // Check if the plugin is registered
1543
2156
  const plugin = this.plugins.get(pluginName);
1544
2157
  if (!plugin) {
1545
2158
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1546
2159
  return;
1547
2160
  }
2161
+ // Register and add the device to the matterbridge aggregator node
1548
2162
  if (this.bridgeMode === 'bridge') {
1549
2163
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1550
2164
  if (!this.aggregatorNode)
@@ -1567,16 +2181,26 @@ export class Matterbridge extends EventEmitter {
1567
2181
  plugin.registeredDevices++;
1568
2182
  if (plugin.addedDevices !== undefined)
1569
2183
  plugin.addedDevices++;
2184
+ // Add the device to the DeviceManager
1570
2185
  this.devices.set(device);
1571
2186
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1572
2187
  }
2188
+ /**
2189
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2190
+ *
2191
+ * @param {string} pluginName - The name of the plugin.
2192
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2193
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2194
+ */
1573
2195
  async removeBridgedEndpoint(pluginName, device) {
1574
2196
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2197
+ // Check if the plugin is registered
1575
2198
  const plugin = this.plugins.get(pluginName);
1576
2199
  if (!plugin) {
1577
2200
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1578
2201
  return;
1579
2202
  }
2203
+ // Register and add the device to the matterbridge aggregator node
1580
2204
  if (this.bridgeMode === 'bridge') {
1581
2205
  if (!this.aggregatorNode) {
1582
2206
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1591,6 +2215,7 @@ export class Matterbridge extends EventEmitter {
1591
2215
  }
1592
2216
  else if (this.bridgeMode === 'childbridge') {
1593
2217
  if (plugin.type === 'AccessoryPlatform') {
2218
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1594
2219
  }
1595
2220
  else if (plugin.type === 'DynamicPlatform') {
1596
2221
  if (!plugin.aggregatorNode) {
@@ -1605,8 +2230,16 @@ export class Matterbridge extends EventEmitter {
1605
2230
  if (plugin.addedDevices !== undefined)
1606
2231
  plugin.addedDevices--;
1607
2232
  }
2233
+ // Remove the device from the DeviceManager
1608
2234
  this.devices.remove(device);
1609
2235
  }
2236
+ /**
2237
+ * Removes all bridged endpoints from the specified plugin.
2238
+ *
2239
+ * @param {string} pluginName - The name of the plugin.
2240
+ * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2241
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2242
+ */
1610
2243
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1611
2244
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1612
2245
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1615,6 +2248,12 @@ export class Matterbridge extends EventEmitter {
1615
2248
  await new Promise((resolve) => setTimeout(resolve, delay));
1616
2249
  }
1617
2250
  }
2251
+ /**
2252
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2253
+ *
2254
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2255
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2256
+ */
1618
2257
  sanitizeFabricInformations(fabricInfo) {
1619
2258
  return fabricInfo.map((info) => {
1620
2259
  return {
@@ -1628,6 +2267,12 @@ export class Matterbridge extends EventEmitter {
1628
2267
  };
1629
2268
  });
1630
2269
  }
2270
+ /**
2271
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2272
+ *
2273
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2274
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2275
+ */
1631
2276
  sanitizeSessionInformation(sessionInfo) {
1632
2277
  return sessionInfo
1633
2278
  .filter((session) => session.isPeerActive)
@@ -1655,6 +2300,11 @@ export class Matterbridge extends EventEmitter {
1655
2300
  };
1656
2301
  });
1657
2302
  }
2303
+ /**
2304
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2305
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2306
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2307
+ */
1658
2308
  async setAggregatorReachability(aggregatorNode, reachable) {
1659
2309
  for (const child of aggregatorNode.parts) {
1660
2310
  this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
@@ -1700,14 +2350,37 @@ export class Matterbridge extends EventEmitter {
1700
2350
  }
1701
2351
  return vendorName;
1702
2352
  };
2353
+ /**
2354
+ * Spawns a child process with the given command and arguments.
2355
+ * @param {string} command - The command to execute.
2356
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2357
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2358
+ */
1703
2359
  async spawnCommand(command, args = []) {
1704
2360
  const { spawn } = await import('node:child_process');
2361
+ /*
2362
+ npm > npm.cmd on windows
2363
+ cmd.exe ['dir'] on windows
2364
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2365
+ process.on('unhandledRejection', (reason, promise) => {
2366
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2367
+ });
2368
+
2369
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2370
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2371
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2372
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2373
+ */
1705
2374
  const cmdLine = command + ' ' + args.join(' ');
1706
2375
  if (process.platform === 'win32' && command === 'npm') {
2376
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1707
2377
  const argstring = 'npm ' + args.join(' ');
1708
2378
  args.splice(0, args.length, '/c', argstring);
1709
2379
  command = 'cmd.exe';
1710
2380
  }
2381
+ // Decide when using sudo on linux
2382
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2383
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1711
2384
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1712
2385
  args.unshift(command);
1713
2386
  command = 'sudo';
@@ -1766,3 +2439,4 @@ export class Matterbridge extends EventEmitter {
1766
2439
  });
1767
2440
  }
1768
2441
  }
2442
+ //# sourceMappingURL=matterbridge.js.map