matterbridge 3.5.0-dev-20260117-88ddbe4 → 3.5.0

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 (326) hide show
  1. package/CHANGELOG.md +130 -122
  2. package/README.md +14 -14
  3. package/dist/broadcastServer.d.ts +115 -0
  4. package/dist/broadcastServer.d.ts.map +1 -0
  5. package/dist/broadcastServer.js +117 -0
  6. package/dist/broadcastServer.js.map +1 -0
  7. package/dist/broadcastServerTypes.d.ts +43 -0
  8. package/dist/broadcastServerTypes.d.ts.map +1 -0
  9. package/dist/broadcastServerTypes.js +24 -0
  10. package/dist/broadcastServerTypes.js.map +1 -0
  11. package/dist/cli.d.ts +24 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +98 -1
  14. package/dist/cli.js.map +1 -0
  15. package/dist/cliEmitter.d.ts +36 -0
  16. package/dist/cliEmitter.d.ts.map +1 -0
  17. package/dist/cliEmitter.js +37 -0
  18. package/dist/cliEmitter.js.map +1 -0
  19. package/dist/cliHistory.d.ts +42 -0
  20. package/dist/cliHistory.d.ts.map +1 -0
  21. package/dist/cliHistory.js +38 -0
  22. package/dist/cliHistory.js.map +1 -0
  23. package/dist/clusters/export.d.ts +1 -0
  24. package/dist/clusters/export.d.ts.map +1 -0
  25. package/dist/clusters/export.js +2 -0
  26. package/dist/clusters/export.js.map +1 -0
  27. package/dist/deviceManager.d.ts +108 -0
  28. package/dist/deviceManager.d.ts.map +1 -0
  29. package/dist/deviceManager.js +113 -1
  30. package/dist/deviceManager.js.map +1 -0
  31. package/dist/devices/airConditioner.d.ts +75 -0
  32. package/dist/devices/airConditioner.d.ts.map +1 -0
  33. package/dist/devices/airConditioner.js +57 -0
  34. package/dist/devices/airConditioner.js.map +1 -0
  35. package/dist/devices/batteryStorage.d.ts +43 -0
  36. package/dist/devices/batteryStorage.d.ts.map +1 -0
  37. package/dist/devices/batteryStorage.js +48 -1
  38. package/dist/devices/batteryStorage.js.map +1 -0
  39. package/dist/devices/cooktop.d.ts +55 -0
  40. package/dist/devices/cooktop.d.ts.map +1 -0
  41. package/dist/devices/cooktop.js +56 -0
  42. package/dist/devices/cooktop.js.map +1 -0
  43. package/dist/devices/dishwasher.d.ts +55 -0
  44. package/dist/devices/dishwasher.d.ts.map +1 -0
  45. package/dist/devices/dishwasher.js +57 -0
  46. package/dist/devices/dishwasher.js.map +1 -0
  47. package/dist/devices/evse.d.ts +57 -0
  48. package/dist/devices/evse.d.ts.map +1 -0
  49. package/dist/devices/evse.js +74 -10
  50. package/dist/devices/evse.js.map +1 -0
  51. package/dist/devices/export.d.ts +1 -0
  52. package/dist/devices/export.d.ts.map +1 -0
  53. package/dist/devices/export.js +5 -0
  54. package/dist/devices/export.js.map +1 -0
  55. package/dist/devices/extractorHood.d.ts +41 -0
  56. package/dist/devices/extractorHood.d.ts.map +1 -0
  57. package/dist/devices/extractorHood.js +43 -0
  58. package/dist/devices/extractorHood.js.map +1 -0
  59. package/dist/devices/heatPump.d.ts +43 -0
  60. package/dist/devices/heatPump.d.ts.map +1 -0
  61. package/dist/devices/heatPump.js +50 -2
  62. package/dist/devices/heatPump.js.map +1 -0
  63. package/dist/devices/laundryDryer.d.ts +58 -0
  64. package/dist/devices/laundryDryer.d.ts.map +1 -0
  65. package/dist/devices/laundryDryer.js +62 -3
  66. package/dist/devices/laundryDryer.js.map +1 -0
  67. package/dist/devices/laundryWasher.d.ts +64 -0
  68. package/dist/devices/laundryWasher.d.ts.map +1 -0
  69. package/dist/devices/laundryWasher.js +70 -4
  70. package/dist/devices/laundryWasher.js.map +1 -0
  71. package/dist/devices/microwaveOven.d.ts +77 -1
  72. package/dist/devices/microwaveOven.d.ts.map +1 -0
  73. package/dist/devices/microwaveOven.js +88 -5
  74. package/dist/devices/microwaveOven.js.map +1 -0
  75. package/dist/devices/oven.d.ts +82 -0
  76. package/dist/devices/oven.d.ts.map +1 -0
  77. package/dist/devices/oven.js +85 -0
  78. package/dist/devices/oven.js.map +1 -0
  79. package/dist/devices/refrigerator.d.ts +100 -0
  80. package/dist/devices/refrigerator.d.ts.map +1 -0
  81. package/dist/devices/refrigerator.js +102 -0
  82. package/dist/devices/refrigerator.js.map +1 -0
  83. package/dist/devices/roboticVacuumCleaner.d.ts +83 -0
  84. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  85. package/dist/devices/roboticVacuumCleaner.js +100 -9
  86. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  87. package/dist/devices/solarPower.d.ts +36 -0
  88. package/dist/devices/solarPower.d.ts.map +1 -0
  89. package/dist/devices/solarPower.js +38 -0
  90. package/dist/devices/solarPower.js.map +1 -0
  91. package/dist/devices/speaker.d.ts +79 -0
  92. package/dist/devices/speaker.d.ts.map +1 -0
  93. package/dist/devices/speaker.js +84 -0
  94. package/dist/devices/speaker.js.map +1 -0
  95. package/dist/devices/temperatureControl.d.ts +21 -0
  96. package/dist/devices/temperatureControl.d.ts.map +1 -0
  97. package/dist/devices/temperatureControl.js +24 -3
  98. package/dist/devices/temperatureControl.js.map +1 -0
  99. package/dist/devices/waterHeater.d.ts +74 -0
  100. package/dist/devices/waterHeater.d.ts.map +1 -0
  101. package/dist/devices/waterHeater.js +82 -2
  102. package/dist/devices/waterHeater.js.map +1 -0
  103. package/dist/dgram/coap.d.ts +171 -0
  104. package/dist/dgram/coap.d.ts.map +1 -0
  105. package/dist/dgram/coap.js +126 -13
  106. package/dist/dgram/coap.js.map +1 -0
  107. package/dist/dgram/dgram.d.ts +99 -0
  108. package/dist/dgram/dgram.d.ts.map +1 -0
  109. package/dist/dgram/dgram.js +114 -2
  110. package/dist/dgram/dgram.js.map +1 -0
  111. package/dist/dgram/mb_coap.d.ts +23 -0
  112. package/dist/dgram/mb_coap.d.ts.map +1 -0
  113. package/dist/dgram/mb_coap.js +41 -3
  114. package/dist/dgram/mb_coap.js.map +1 -0
  115. package/dist/dgram/mb_mdns.d.ts +23 -0
  116. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  117. package/dist/dgram/mb_mdns.js +80 -24
  118. package/dist/dgram/mb_mdns.js.map +1 -0
  119. package/dist/dgram/mdns.d.ts +187 -4
  120. package/dist/dgram/mdns.d.ts.map +1 -0
  121. package/dist/dgram/mdns.js +371 -139
  122. package/dist/dgram/mdns.js.map +1 -0
  123. package/dist/dgram/multicast.d.ts +49 -0
  124. package/dist/dgram/multicast.d.ts.map +1 -0
  125. package/dist/dgram/multicast.js +62 -1
  126. package/dist/dgram/multicast.js.map +1 -0
  127. package/dist/dgram/unicast.d.ts +53 -0
  128. package/dist/dgram/unicast.d.ts.map +1 -0
  129. package/dist/dgram/unicast.js +60 -0
  130. package/dist/dgram/unicast.js.map +1 -0
  131. package/dist/frontend.d.ts +187 -0
  132. package/dist/frontend.d.ts.map +1 -0
  133. package/dist/frontend.js +543 -73
  134. package/dist/frontend.js.map +1 -0
  135. package/dist/frontendTypes.d.ts +57 -0
  136. package/dist/frontendTypes.d.ts.map +1 -0
  137. package/dist/frontendTypes.js +45 -0
  138. package/dist/frontendTypes.js.map +1 -0
  139. package/dist/helpers.d.ts +43 -0
  140. package/dist/helpers.d.ts.map +1 -0
  141. package/dist/helpers.js +53 -0
  142. package/dist/helpers.js.map +1 -0
  143. package/dist/index.d.ts +23 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +25 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/jestutils/export.d.ts +1 -0
  148. package/dist/jestutils/export.d.ts.map +1 -0
  149. package/dist/jestutils/export.js +1 -0
  150. package/dist/jestutils/export.js.map +1 -0
  151. package/dist/jestutils/jestHelpers.d.ts +255 -0
  152. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  153. package/dist/jestutils/jestHelpers.js +372 -14
  154. package/dist/jestutils/jestHelpers.js.map +1 -0
  155. package/dist/logger/export.d.ts +1 -0
  156. package/dist/logger/export.d.ts.map +1 -0
  157. package/dist/logger/export.js +1 -0
  158. package/dist/logger/export.js.map +1 -0
  159. package/dist/matter/behaviors.d.ts +1 -0
  160. package/dist/matter/behaviors.d.ts.map +1 -0
  161. package/dist/matter/behaviors.js +2 -0
  162. package/dist/matter/behaviors.js.map +1 -0
  163. package/dist/matter/clusters.d.ts +1 -0
  164. package/dist/matter/clusters.d.ts.map +1 -0
  165. package/dist/matter/clusters.js +2 -0
  166. package/dist/matter/clusters.js.map +1 -0
  167. package/dist/matter/devices.d.ts +1 -0
  168. package/dist/matter/devices.d.ts.map +1 -0
  169. package/dist/matter/devices.js +2 -0
  170. package/dist/matter/devices.js.map +1 -0
  171. package/dist/matter/endpoints.d.ts +1 -0
  172. package/dist/matter/endpoints.d.ts.map +1 -0
  173. package/dist/matter/endpoints.js +2 -0
  174. package/dist/matter/endpoints.js.map +1 -0
  175. package/dist/matter/export.d.ts +1 -0
  176. package/dist/matter/export.d.ts.map +1 -0
  177. package/dist/matter/export.js +2 -0
  178. package/dist/matter/export.js.map +1 -0
  179. package/dist/matter/types.d.ts +1 -0
  180. package/dist/matter/types.d.ts.map +1 -0
  181. package/dist/matter/types.js +2 -0
  182. package/dist/matter/types.js.map +1 -0
  183. package/dist/matterNode.d.ts +258 -0
  184. package/dist/matterNode.d.ts.map +1 -0
  185. package/dist/matterNode.js +362 -9
  186. package/dist/matterNode.js.map +1 -0
  187. package/dist/matterbridge.d.ts +362 -0
  188. package/dist/matterbridge.d.ts.map +1 -0
  189. package/dist/matterbridge.js +879 -56
  190. package/dist/matterbridge.js.map +1 -0
  191. package/dist/matterbridgeAccessoryPlatform.d.ts +36 -0
  192. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  193. package/dist/matterbridgeAccessoryPlatform.js +38 -0
  194. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  195. package/dist/matterbridgeBehaviors.d.ts +24 -0
  196. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  197. package/dist/matterbridgeBehaviors.js +68 -5
  198. package/dist/matterbridgeBehaviors.js.map +1 -0
  199. package/dist/matterbridgeDeviceTypes.d.ts +649 -0
  200. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  201. package/dist/matterbridgeDeviceTypes.js +673 -6
  202. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  203. package/dist/matterbridgeDynamicPlatform.d.ts +36 -0
  204. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  205. package/dist/matterbridgeDynamicPlatform.js +38 -0
  206. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  207. package/dist/matterbridgeEndpoint.d.ts +1332 -0
  208. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  209. package/dist/matterbridgeEndpoint.js +1457 -53
  210. package/dist/matterbridgeEndpoint.js.map +1 -0
  211. package/dist/matterbridgeEndpointHelpers.d.ts +425 -0
  212. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  213. package/dist/matterbridgeEndpointHelpers.js +483 -20
  214. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  215. package/dist/matterbridgeEndpointTypes.d.ts +70 -0
  216. package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
  217. package/dist/matterbridgeEndpointTypes.js +25 -0
  218. package/dist/matterbridgeEndpointTypes.js.map +1 -0
  219. package/dist/matterbridgePlatform.d.ts +425 -0
  220. package/dist/matterbridgePlatform.d.ts.map +1 -0
  221. package/dist/matterbridgePlatform.js +451 -1
  222. package/dist/matterbridgePlatform.js.map +1 -0
  223. package/dist/matterbridgeTypes.d.ts +46 -0
  224. package/dist/matterbridgeTypes.d.ts.map +1 -0
  225. package/dist/matterbridgeTypes.js +26 -0
  226. package/dist/matterbridgeTypes.js.map +1 -0
  227. package/dist/pluginManager.d.ts +305 -0
  228. package/dist/pluginManager.d.ts.map +1 -0
  229. package/dist/pluginManager.js +341 -5
  230. package/dist/pluginManager.js.map +1 -0
  231. package/dist/shelly.d.ts +157 -0
  232. package/dist/shelly.d.ts.map +1 -0
  233. package/dist/shelly.js +178 -7
  234. package/dist/shelly.js.map +1 -0
  235. package/dist/storage/export.d.ts +1 -0
  236. package/dist/storage/export.d.ts.map +1 -0
  237. package/dist/storage/export.js +1 -0
  238. package/dist/storage/export.js.map +1 -0
  239. package/dist/update.d.ts +75 -0
  240. package/dist/update.d.ts.map +1 -0
  241. package/dist/update.js +93 -1
  242. package/dist/update.js.map +1 -0
  243. package/dist/utils/colorUtils.d.ts +77 -0
  244. package/dist/utils/colorUtils.d.ts.map +1 -0
  245. package/dist/utils/colorUtils.js +97 -2
  246. package/dist/utils/colorUtils.js.map +1 -0
  247. package/dist/utils/commandLine.d.ts +60 -0
  248. package/dist/utils/commandLine.d.ts.map +1 -0
  249. package/dist/utils/commandLine.js +60 -0
  250. package/dist/utils/commandLine.js.map +1 -0
  251. package/dist/utils/copyDirectory.d.ts +33 -0
  252. package/dist/utils/copyDirectory.d.ts.map +1 -0
  253. package/dist/utils/copyDirectory.js +37 -0
  254. package/dist/utils/copyDirectory.js.map +1 -0
  255. package/dist/utils/createDirectory.d.ts +32 -0
  256. package/dist/utils/createDirectory.d.ts.map +1 -0
  257. package/dist/utils/createDirectory.js +33 -0
  258. package/dist/utils/createDirectory.js.map +1 -0
  259. package/dist/utils/createZip.d.ts +38 -0
  260. package/dist/utils/createZip.d.ts.map +1 -0
  261. package/dist/utils/createZip.js +47 -2
  262. package/dist/utils/createZip.js.map +1 -0
  263. package/dist/utils/deepCopy.d.ts +31 -0
  264. package/dist/utils/deepCopy.d.ts.map +1 -0
  265. package/dist/utils/deepCopy.js +39 -0
  266. package/dist/utils/deepCopy.js.map +1 -0
  267. package/dist/utils/deepEqual.d.ts +53 -0
  268. package/dist/utils/deepEqual.d.ts.map +1 -0
  269. package/dist/utils/deepEqual.js +72 -1
  270. package/dist/utils/deepEqual.js.map +1 -0
  271. package/dist/utils/error.d.ts +42 -0
  272. package/dist/utils/error.d.ts.map +1 -0
  273. package/dist/utils/error.js +42 -0
  274. package/dist/utils/error.js.map +1 -0
  275. package/dist/utils/export.d.ts +1 -0
  276. package/dist/utils/export.d.ts.map +1 -0
  277. package/dist/utils/export.js +1 -0
  278. package/dist/utils/export.js.map +1 -0
  279. package/dist/utils/format.d.ts +49 -0
  280. package/dist/utils/format.d.ts.map +1 -0
  281. package/dist/utils/format.js +49 -0
  282. package/dist/utils/format.js.map +1 -0
  283. package/dist/utils/hex.d.ts +85 -0
  284. package/dist/utils/hex.d.ts.map +1 -0
  285. package/dist/utils/hex.js +124 -0
  286. package/dist/utils/hex.js.map +1 -0
  287. package/dist/utils/inspector.d.ts +63 -0
  288. package/dist/utils/inspector.d.ts.map +1 -0
  289. package/dist/utils/inspector.js +69 -1
  290. package/dist/utils/inspector.js.map +1 -0
  291. package/dist/utils/isValid.d.ts +93 -0
  292. package/dist/utils/isValid.d.ts.map +1 -0
  293. package/dist/utils/isValid.js +93 -0
  294. package/dist/utils/isValid.js.map +1 -0
  295. package/dist/utils/network.d.ts +116 -0
  296. package/dist/utils/network.d.ts.map +1 -0
  297. package/dist/utils/network.js +126 -5
  298. package/dist/utils/network.js.map +1 -0
  299. package/dist/utils/spawn.d.ts +32 -0
  300. package/dist/utils/spawn.d.ts.map +1 -0
  301. package/dist/utils/spawn.js +71 -1
  302. package/dist/utils/spawn.js.map +1 -0
  303. package/dist/utils/tracker.d.ts +56 -0
  304. package/dist/utils/tracker.d.ts.map +1 -0
  305. package/dist/utils/tracker.js +64 -1
  306. package/dist/utils/tracker.js.map +1 -0
  307. package/dist/utils/wait.d.ts +51 -0
  308. package/dist/utils/wait.d.ts.map +1 -0
  309. package/dist/utils/wait.js +60 -8
  310. package/dist/utils/wait.js.map +1 -0
  311. package/dist/workerGlobalPrefix.d.ts +24 -0
  312. package/dist/workerGlobalPrefix.d.ts.map +1 -0
  313. package/dist/workerGlobalPrefix.js +37 -5
  314. package/dist/workerGlobalPrefix.js.map +1 -0
  315. package/dist/workerTypes.d.ts +25 -0
  316. package/dist/workerTypes.d.ts.map +1 -0
  317. package/dist/workerTypes.js +24 -0
  318. package/dist/workerTypes.js.map +1 -0
  319. package/dist/workers.d.ts +61 -0
  320. package/dist/workers.d.ts.map +1 -0
  321. package/dist/workers.js +68 -4
  322. package/dist/workers.js.map +1 -0
  323. package/frontend/build/assets/index.js +4 -4
  324. package/frontend/package.json +1 -1
  325. package/npm-shrinkwrap.json +5 -35
  326. package/package.json +7 -7
@@ -1,19 +1,48 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @created 2023-12-29
7
+ * @version 1.6.2
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // eslint-disable-next-line no-console
1
25
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
26
  console.log('\u001B[32mMatterbridge loaded.\u001B[40;0m');
27
+ // Node.js modules
3
28
  import os from 'node:os';
4
29
  import path from 'node:path';
5
- import fs from 'node:fs';
30
+ import fs, { unlinkSync } from 'node:fs';
6
31
  import EventEmitter from 'node:events';
7
32
  import { inspect } from 'node:util';
33
+ // AnsiLogger module
8
34
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } from 'node-ansi-logger';
35
+ // NodeStorage module
9
36
  import { NodeStorageManager } from 'node-persist-manager';
10
- import '@matter/nodejs';
37
+ // @matter
38
+ import '@matter/nodejs'; // Set up Node.js environment for matter.js
11
39
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, UINT32_MAX, UINT16_MAX, Crypto, Environment, StorageService } from '@matter/general';
12
40
  import { PaseClient } from '@matter/protocol';
13
41
  import { Endpoint, ServerNode } from '@matter/node';
14
42
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
15
43
  import { AggregatorEndpoint } from '@matter/node/endpoints';
16
44
  import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
45
+ // Matterbridge
17
46
  import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
18
47
  import { copyDirectory } from './utils/copyDirectory.js';
19
48
  import { createDirectory } from './utils/createDirectory.js';
@@ -27,19 +56,27 @@ import { bridge } from './matterbridgeDeviceTypes.js';
27
56
  import { Frontend } from './frontend.js';
28
57
  import { addVirtualDevice, addVirtualDevices } from './helpers.js';
29
58
  import { BroadcastServer } from './broadcastServer.js';
59
+ /**
60
+ * Represents the Matterbridge application.
61
+ */
30
62
  export class Matterbridge extends EventEmitter {
63
+ /** Matterbridge system information */
31
64
  systemInformation = {
65
+ // Network properties
32
66
  interfaceName: '',
33
67
  macAddress: '',
34
68
  ipv4Address: '',
35
69
  ipv6Address: '',
70
+ // Node.js properties
36
71
  nodeVersion: '',
72
+ // Fixed system properties
37
73
  hostname: '',
38
74
  user: '',
39
75
  osType: '',
40
76
  osRelease: '',
41
77
  osPlatform: '',
42
78
  osArch: '',
79
+ // Cpu and memory properties
43
80
  totalMemory: '',
44
81
  freeMemory: '',
45
82
  systemUptime: '',
@@ -50,39 +87,66 @@ export class Matterbridge extends EventEmitter {
50
87
  heapTotal: '',
51
88
  heapUsed: '',
52
89
  };
90
+ // Matterbridge settings
91
+ /** It indicates the home directory of the Matterbridge application. The home directory is the base directory where Matterbridge creates the matterbridge directories (os.homedir() if not overridden). */
53
92
  homeDirectory = '';
93
+ /** It indicates the root directory of the Matterbridge application. The root directory is the directory where Matterbridge is executed. */
54
94
  rootDirectory = '';
95
+ /** It indicates where the directory .matterbridge is located. */
55
96
  matterbridgeDirectory = '';
97
+ /** It indicates where the directory Matterbridge is located. */
56
98
  matterbridgePluginDirectory = '';
99
+ /** It indicates where the directory .mattercert is located. */
57
100
  matterbridgeCertDirectory = '';
101
+ /** It indicates the global modules directory for npm. */
58
102
  globalModulesDirectory = '';
59
103
  matterbridgeVersion = '';
60
104
  matterbridgeLatestVersion = '';
61
105
  matterbridgeDevVersion = '';
62
106
  frontendVersion = '';
107
+ /** It indicates the mode of the Matterbridge instance. It can be 'bridge', 'childbridge', 'controller' or ''. */
63
108
  bridgeMode = '';
109
+ /** It indicates the restart mode of the Matterbridge instance. It can be 'service', 'docker' or ''. */
64
110
  restartMode = '';
111
+ /** It indicates whether virtual mode is enabled and its type. The virtual mode control the creation of "Update matterbridge" and "Restart matterbridge" endpoints. */
65
112
  virtualMode = 'outlet';
113
+ /** It indicates the Matterbridge profile in use. */
66
114
  profile = getParameter('profile');
67
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
115
+ /** Matterbridge logger */
116
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
117
+ /** Matterbridge logger level */
68
118
  logLevel = this.log.logLevel;
119
+ /** Whether to log to a file */
69
120
  fileLogger = false;
70
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
121
+ /** Matter logger */
122
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
123
+ /** Matter logger level */
71
124
  matterLogLevel = this.matterLog.logLevel;
125
+ /** Whether to log Matter to a file */
72
126
  matterFileLogger = false;
127
+ // Frontend settings
73
128
  readOnly = hasParameter('readonly') || hasParameter('shelly');
74
129
  shellyBoard = hasParameter('shelly');
75
130
  shellySysUpdate = false;
76
131
  shellyMainUpdate = false;
132
+ /** It indicates whether a restart is required. It can be unset in childbridge mode by restarting the plugin that triggered the restart. */
77
133
  restartRequired = false;
134
+ /** It indicates whether a fixed restart is required. It cannot be unset once set. */
78
135
  fixedRestartRequired = false;
136
+ /** It indicates whether an update is available. */
79
137
  updateRequired = false;
138
+ // Managers
80
139
  plugins = new PluginManager(this);
81
140
  devices = new DeviceManager();
141
+ // Frontend
82
142
  frontend = new Frontend(this);
143
+ /** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
83
144
  nodeStorage;
145
+ /** Matterbridge node context created with name 'matterbridge' */
84
146
  nodeContext;
147
+ /** The main instance of the Matterbridge class (singleton) */
85
148
  static instance;
149
+ // Instance properties
86
150
  shutdown = false;
87
151
  failCountLimit = hasParameter('shelly') ? 600 : 120;
88
152
  hasCleanupStarted = false;
@@ -97,19 +161,32 @@ export class Matterbridge extends EventEmitter {
97
161
  sigtermHandler;
98
162
  exceptionHandler;
99
163
  rejectionHandler;
164
+ /** Matter environment default */
100
165
  environment = Environment.default;
166
+ /** Matter storage service from environment default */
101
167
  matterStorageService;
168
+ /** Matter storage manager created with name 'Matterbridge' */
102
169
  matterStorageManager;
170
+ /** Matter matterbridge storage context created in the storage manager with name 'persist' */
103
171
  matterbridgeContext;
104
172
  controllerContext;
173
+ /** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
105
174
  mdnsInterface;
175
+ /** Matter listeningAddressIpv4 address */
106
176
  ipv4Address;
177
+ /** Matter listeningAddressIpv6 address */
107
178
  ipv6Address;
108
- port;
109
- passcode;
110
- discriminator;
111
- certification;
179
+ /** Matter commissioning port */
180
+ port; // first server node port
181
+ /** Matter commissioning passcode */
182
+ passcode; // first server node passcode
183
+ /** Matter commissioning discriminator */
184
+ discriminator; // first server node discriminator
185
+ /** Matter device certification */
186
+ certification; // device certification
187
+ /** Matter server node in bridge mode */
112
188
  serverNode;
189
+ /** Matter aggregator node in bridge mode */
113
190
  aggregatorNode;
114
191
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
115
192
  aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
@@ -118,9 +195,12 @@ export class Matterbridge extends EventEmitter {
118
195
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
119
196
  aggregatorSerialNumber = getParameter('serialNumber');
120
197
  aggregatorUniqueId = getParameter('uniqueId');
198
+ /** Advertising nodes map: time advertising started keyed by storeId */
121
199
  advertisingNodes = new Map();
200
+ /** Broadcast server */
122
201
  server;
123
202
  verbose = hasParameter('verbose');
203
+ /** We load asyncronously so is private */
124
204
  constructor() {
125
205
  super();
126
206
  this.log.logNameColor = '\x1b[38;5;115m';
@@ -171,8 +251,19 @@ export class Matterbridge extends EventEmitter {
171
251
  }
172
252
  }
173
253
  }
254
+ //* ************************************************************************************************************************************ */
255
+ // loadInstance() and cleanup() methods */
256
+ //* ************************************************************************************************************************************ */
257
+ /**
258
+ * Loads an instance of the Matterbridge class.
259
+ * If an instance already exists, return that instance.
260
+ *
261
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
262
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
263
+ */
174
264
  static async loadInstance(initialize = false) {
175
265
  if (!Matterbridge.instance) {
266
+ // eslint-disable-next-line no-console
176
267
  if (hasParameter('debug'))
177
268
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
178
269
  Matterbridge.instance = new Matterbridge();
@@ -181,56 +272,84 @@ export class Matterbridge extends EventEmitter {
181
272
  }
182
273
  return Matterbridge.instance;
183
274
  }
275
+ /**
276
+ * Initializes the Matterbridge application.
277
+ *
278
+ * @remarks
279
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
280
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
281
+ * node version, registers signal handlers, initializes storage, and parses the command line.
282
+ *
283
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
284
+ */
184
285
  async initialize() {
286
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
287
+ // Emit the initialize_started event
185
288
  this.emit('initialize_started');
289
+ // Set the restart mode
186
290
  if (hasParameter('service'))
187
291
  this.restartMode = 'service';
188
292
  if (hasParameter('docker'))
189
293
  this.restartMode = 'docker';
294
+ // Set the matterbridge home directory
190
295
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
191
296
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
297
+ // Set the matterbridge directory
192
298
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
193
299
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
194
300
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
195
301
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
302
+ // Set the matterbridge plugin directory
196
303
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
197
304
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
305
+ // Set the matterbridge cert directory
198
306
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
199
307
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
308
+ // Set the matterbridge root directory
200
309
  const { fileURLToPath } = await import('node:url');
201
310
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
202
- this.rootDirectory = path.resolve(currentFileDirectory, '../');
311
+ this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
312
+ // Setup the matter environment with default values
203
313
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
204
314
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
205
315
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
206
316
  this.environment.vars.set('runtime.signals', false);
207
317
  this.environment.vars.set('runtime.exitcode', false);
318
+ // Register process handlers
208
319
  this.registerProcessHandlers();
320
+ // Initialize nodeStorage and nodeContext
209
321
  try {
210
322
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
211
323
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
212
324
  this.log.debug('Creating node storage context for matterbridge');
213
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
214
328
  const keys = (await this.nodeStorage?.storage.keys());
215
329
  for (const key of keys) {
216
330
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
332
  await this.nodeStorage?.storage.get(key);
218
333
  }
219
334
  const storages = await this.nodeStorage.getStorageNames();
220
335
  for (const storage of storages) {
221
336
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
222
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
223
340
  const keys = (await nodeContext?.storage.keys());
224
341
  keys.forEach(async (key) => {
225
342
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
226
343
  await nodeContext?.get(key);
227
344
  });
228
345
  }
346
+ // Creating a backup of the node storage since it is not corrupted
229
347
  this.log.debug('Creating node storage backup...');
230
348
  await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
231
349
  this.log.debug('Created node storage backup');
232
350
  }
233
351
  catch (error) {
352
+ // Restoring the backup of the node storage since it is corrupted
234
353
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
235
354
  if (hasParameter('norestore')) {
236
355
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -244,14 +363,19 @@ export class Matterbridge extends EventEmitter {
244
363
  if (!this.nodeStorage || !this.nodeContext) {
245
364
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
246
365
  }
366
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
247
367
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
368
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
248
369
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
370
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
249
371
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
372
+ // Certificate management
250
373
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
251
374
  try {
252
375
  await fs.promises.access(pairingFilePath, fs.constants.R_OK);
253
376
  const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
254
377
  const pairingFileJson = JSON.parse(pairingFileContent);
378
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
255
379
  if (isValidNumber(pairingFileJson.vendorId)) {
256
380
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
257
381
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -280,11 +404,13 @@ export class Matterbridge extends EventEmitter {
280
404
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
281
405
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
282
406
  }
407
+ // Override the passcode and discriminator if they are present in the pairing file
283
408
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
284
409
  this.passcode = pairingFileJson.passcode;
285
410
  this.discriminator = pairingFileJson.discriminator;
286
411
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
287
412
  }
413
+ // Set the certification for matter.js if it is present in the pairing file
288
414
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
289
415
  const { hexToBuffer } = await import('./utils/hex.js');
290
416
  this.certification = {
@@ -299,41 +425,44 @@ export class Matterbridge extends EventEmitter {
299
425
  catch (error) {
300
426
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
301
427
  }
428
+ // Store the passcode, discriminator and port in the node context
302
429
  await this.nodeContext.set('matterport', this.port);
303
430
  await this.nodeContext.set('matterpasscode', this.passcode);
304
431
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
305
432
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
433
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
306
434
  if (hasParameter('logger')) {
307
435
  const level = getParameter('logger');
308
436
  if (level === 'debug') {
309
- this.log.logLevel = "debug";
437
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
310
438
  }
311
439
  else if (level === 'info') {
312
- this.log.logLevel = "info";
440
+ this.log.logLevel = "info" /* LogLevel.INFO */;
313
441
  }
314
442
  else if (level === 'notice') {
315
- this.log.logLevel = "notice";
443
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
316
444
  }
317
445
  else if (level === 'warn') {
318
- this.log.logLevel = "warn";
446
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
319
447
  }
320
448
  else if (level === 'error') {
321
- this.log.logLevel = "error";
449
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
322
450
  }
323
451
  else if (level === 'fatal') {
324
- this.log.logLevel = "fatal";
452
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
325
453
  }
326
454
  else {
327
455
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
328
- this.log.logLevel = "info";
456
+ this.log.logLevel = "info" /* LogLevel.INFO */;
329
457
  }
330
458
  }
331
459
  else {
332
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
460
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
333
461
  }
334
462
  this.logLevel = this.log.logLevel;
335
463
  this.frontend.logLevel = this.log.logLevel;
336
464
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
465
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
337
466
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
338
467
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
339
468
  this.fileLogger = true;
@@ -342,6 +471,7 @@ export class Matterbridge extends EventEmitter {
342
471
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
343
472
  if (this.profile !== undefined)
344
473
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
474
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
345
475
  if (hasParameter('matterlogger')) {
346
476
  const level = getParameter('matterlogger');
347
477
  if (level === 'debug') {
@@ -372,11 +502,13 @@ export class Matterbridge extends EventEmitter {
372
502
  }
373
503
  Logger.format = MatterLogFormat.ANSI;
374
504
  this.matterLogLevel = MatterLogLevel.names[Logger.level];
505
+ // Create the logger for matter.js with file logging (context: matterFileLog)
375
506
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
376
507
  this.matterFileLogger = true;
377
508
  }
378
509
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
379
510
  this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
511
+ // Log network interfaces
380
512
  const networkInterfaces = os.networkInterfaces();
381
513
  const availableAddresses = Object.entries(networkInterfaces);
382
514
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -389,6 +521,7 @@ export class Matterbridge extends EventEmitter {
389
521
  });
390
522
  }
391
523
  }
524
+ // Set the interface to use for matter server node mdnsInterface
392
525
  if (hasParameter('mdnsinterface')) {
393
526
  this.mdnsInterface = getParameter('mdnsinterface');
394
527
  }
@@ -397,6 +530,7 @@ export class Matterbridge extends EventEmitter {
397
530
  if (this.mdnsInterface === '')
398
531
  this.mdnsInterface = undefined;
399
532
  }
533
+ // Validate mdnsInterface
400
534
  if (this.mdnsInterface) {
401
535
  if (!availableInterfaceNames.includes(this.mdnsInterface)) {
402
536
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
@@ -409,6 +543,7 @@ export class Matterbridge extends EventEmitter {
409
543
  }
410
544
  if (this.mdnsInterface)
411
545
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
546
+ // Set the listeningAddressIpv4 for the matter commissioning server
412
547
  if (hasParameter('ipv4address')) {
413
548
  this.ipv4Address = getParameter('ipv4address');
414
549
  }
@@ -417,6 +552,7 @@ export class Matterbridge extends EventEmitter {
417
552
  if (this.ipv4Address === '')
418
553
  this.ipv4Address = undefined;
419
554
  }
555
+ // Validate ipv4address
420
556
  if (this.ipv4Address) {
421
557
  let isValid = false;
422
558
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -432,6 +568,7 @@ export class Matterbridge extends EventEmitter {
432
568
  await this.nodeContext.remove('matteripv4address');
433
569
  }
434
570
  }
571
+ // Set the listeningAddressIpv6 for the matter commissioning server
435
572
  if (hasParameter('ipv6address')) {
436
573
  this.ipv6Address = getParameter('ipv6address');
437
574
  }
@@ -440,6 +577,7 @@ export class Matterbridge extends EventEmitter {
440
577
  if (this.ipv6Address === '')
441
578
  this.ipv6Address = undefined;
442
579
  }
580
+ // Validate ipv6address
443
581
  if (this.ipv6Address) {
444
582
  let isValid = false;
445
583
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -448,6 +586,7 @@ export class Matterbridge extends EventEmitter {
448
586
  isValid = true;
449
587
  break;
450
588
  }
589
+ /* istanbul ignore next */
451
590
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
452
591
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
453
592
  isValid = true;
@@ -460,6 +599,7 @@ export class Matterbridge extends EventEmitter {
460
599
  await this.nodeContext.remove('matteripv6address');
461
600
  }
462
601
  }
602
+ // Initialize the virtual mode
463
603
  if (hasParameter('novirtual')) {
464
604
  this.virtualMode = 'disabled';
465
605
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -468,14 +608,19 @@ export class Matterbridge extends EventEmitter {
468
608
  this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
469
609
  }
470
610
  this.log.debug(`Virtual mode ${this.virtualMode}.`);
611
+ // Initialize PluginManager
471
612
  this.plugins.logLevel = this.log.logLevel;
472
613
  await this.plugins.loadFromStorage();
614
+ // Initialize DeviceManager
473
615
  this.devices.logLevel = this.log.logLevel;
616
+ // Get the plugins from node storage and create the plugins node storage contexts
474
617
  for (const plugin of this.plugins) {
618
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
619
+ // We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
475
620
  if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
476
621
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
477
622
  const { spawnCommand } = await import('./utils/spawn.js');
478
- if (await spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], 'install', plugin.name)) {
623
+ if (await spawnCommand('npm', ['install', '-g', `${plugin.name}${plugin.version.includes('-dev-') ? '@dev' : ''}`, '--omit=dev', '--verbose'], 'install', plugin.name)) {
479
624
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
480
625
  plugin.error = false;
481
626
  }
@@ -501,6 +646,7 @@ export class Matterbridge extends EventEmitter {
501
646
  await plugin.nodeContext.set('description', plugin.description);
502
647
  await plugin.nodeContext.set('author', plugin.author);
503
648
  }
649
+ // Log system info and create .matterbridge directory
504
650
  await this.logNodeAndSystemInfo();
505
651
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
506
652
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -508,6 +654,7 @@ export class Matterbridge extends EventEmitter {
508
654
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
509
655
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
510
656
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
657
+ // Check node version and throw error
511
658
  const minNodeVersion = 20;
512
659
  const nodeVersion = process.versions.node;
513
660
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -515,25 +662,46 @@ export class Matterbridge extends EventEmitter {
515
662
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
516
663
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
517
664
  }
665
+ // Parse command line
518
666
  await this.parseCommandLine();
667
+ // Emit the initialize_completed event
519
668
  this.emit('initialize_completed');
520
669
  this.initialized = true;
521
670
  }
671
+ /**
672
+ * Parses the command line arguments and performs the corresponding actions.
673
+ *
674
+ * @private
675
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
676
+ */
522
677
  async parseCommandLine() {
523
678
  if (hasParameter('list')) {
524
679
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
525
680
  let index = 0;
526
681
  for (const plugin of this.plugins) {
527
682
  if (index !== this.plugins.length - 1) {
528
- this.log.info(`├─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${plugin.enabled ? GREEN : RED}enabled${nf}`);
683
+ this.log.info(`├─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}enabled${nf}`);
529
684
  this.log.info(`│ └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
530
685
  }
531
686
  else {
532
- this.log.info(`└─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${plugin.enabled ? GREEN : RED}disabled${nf}`);
687
+ this.log.info(`└─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} version: ${plg}${plugin.version}${nf} ${plugin.enabled ? GREEN : RED}disabled${nf}`);
533
688
  this.log.info(` └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
534
689
  }
535
690
  index++;
536
691
  }
692
+ /*
693
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
694
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
695
+ serializedRegisteredDevices?.forEach((device, index) => {
696
+ if (index !== serializedRegisteredDevices.length - 1) {
697
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
698
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
699
+ } else {
700
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
701
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
702
+ }
703
+ });
704
+ */
537
705
  this.shutdown = true;
538
706
  return;
539
707
  }
@@ -583,8 +751,10 @@ export class Matterbridge extends EventEmitter {
583
751
  this.shutdown = true;
584
752
  return;
585
753
  }
754
+ // Initialize frontend
586
755
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
587
756
  await this.frontend.start(getIntParameter('frontend'));
757
+ // Start the matter storage and create the matterbridge context
588
758
  try {
589
759
  await this.startMatterStorage();
590
760
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -598,18 +768,21 @@ export class Matterbridge extends EventEmitter {
598
768
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
599
769
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
600
770
  }
771
+ // Clear the matterbridge context if the reset parameter is set (bridge mode)
601
772
  if (hasParameter('reset') && getParameter('reset') === undefined) {
602
773
  this.initialized = true;
603
774
  await this.shutdownProcessAndReset();
604
775
  this.shutdown = true;
605
776
  return;
606
777
  }
778
+ // Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
607
779
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
608
780
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
609
781
  const plugin = this.plugins.get(getParameter('reset'));
610
782
  if (plugin) {
611
783
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
612
784
  if (!matterStorageManager) {
785
+ /* istanbul ignore next */
613
786
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
614
787
  }
615
788
  else {
@@ -628,47 +801,56 @@ export class Matterbridge extends EventEmitter {
628
801
  this.shutdown = true;
629
802
  return;
630
803
  }
804
+ // Check in 5 minutes the latest and dev versions of matterbridge and the plugins
631
805
  clearTimeout(this.checkUpdateTimeout);
632
806
  this.checkUpdateTimeout = setTimeout(async () => {
633
807
  const { checkUpdates } = await import('./update.js');
634
808
  checkUpdates(this);
635
809
  }, 300 * 1000).unref();
810
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
636
811
  clearInterval(this.checkUpdateInterval);
637
812
  this.checkUpdateInterval = setInterval(async () => {
638
813
  const { checkUpdates } = await import('./update.js');
639
814
  checkUpdates(this);
640
815
  }, 12 * 60 * 60 * 1000).unref();
816
+ // Start the matterbridge in mode test
641
817
  if (hasParameter('test')) {
642
818
  this.bridgeMode = 'bridge';
643
819
  return;
644
820
  }
821
+ // Start the matterbridge in mode controller
645
822
  if (hasParameter('controller')) {
646
823
  this.bridgeMode = 'controller';
647
824
  await this.startController();
648
825
  return;
649
826
  }
827
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
650
828
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
651
829
  this.log.info('Setting default matterbridge start mode to bridge');
652
830
  await this.nodeContext?.set('bridgeMode', 'bridge');
653
831
  }
832
+ // Wait delay if specified (default 2 minutes) and the system uptime is less than 5 minutes. It solves race conditions on system startup.
654
833
  if (hasParameter('delay') && os.uptime() <= 60 * 5) {
655
834
  const { wait } = await import('./utils/wait.js');
656
835
  const delay = getIntParameter('delay') || 120;
657
836
  this.log.warn('Delay switch found with system uptime less then 5 minutes. Waiting for ' + delay + ' seconds before starting matterbridge...');
658
837
  await wait(delay * 1000, 'Race condition delay', true);
659
838
  }
839
+ // Wait delay if specified (default 2 minutes). It solves race conditions on docker compose startup.
660
840
  if (hasParameter('fixed_delay')) {
661
841
  const { wait } = await import('./utils/wait.js');
662
842
  const delay = getIntParameter('fixed_delay') || 120;
663
843
  this.log.warn('Fixed delay switch found. Waiting for ' + delay + ' seconds before starting matterbridge...');
664
844
  await wait(delay * 1000, 'Fixed race condition delay', true);
665
845
  }
846
+ // Start matterbridge in bridge mode
666
847
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
667
848
  this.bridgeMode = 'bridge';
668
849
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
669
850
  await this.startBridge();
670
851
  return;
671
852
  }
853
+ // Start matterbridge in childbridge mode
672
854
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
673
855
  this.bridgeMode = 'childbridge';
674
856
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -676,10 +858,22 @@ export class Matterbridge extends EventEmitter {
676
858
  return;
677
859
  }
678
860
  }
861
+ /**
862
+ * Asynchronously loads and starts the registered plugins.
863
+ *
864
+ * This method is responsible for initializing and starting all enabled plugins.
865
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
866
+ *
867
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving. Defaults to false.
868
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them. Defaults to true.
869
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
870
+ */
679
871
  async startPlugins(wait = false, start = true) {
872
+ // Check, load and start the plugins
680
873
  for (const plugin of this.plugins) {
681
874
  plugin.configJson = await this.plugins.loadConfig(plugin);
682
875
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
876
+ // Check if the plugin is available
683
877
  if (!(await this.plugins.resolve(plugin.path))) {
684
878
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
685
879
  plugin.enabled = false;
@@ -699,10 +893,16 @@ export class Matterbridge extends EventEmitter {
699
893
  if (wait)
700
894
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
701
895
  else
702
- this.plugins.load(plugin, start, 'Matterbridge is starting');
896
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
703
897
  }
704
898
  this.frontend.wssSendRefreshRequired('plugins');
705
899
  }
900
+ /**
901
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
902
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
903
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
904
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
905
+ */
706
906
  registerProcessHandlers() {
707
907
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
708
908
  process.removeAllListeners('uncaughtException');
@@ -729,6 +929,9 @@ export class Matterbridge extends EventEmitter {
729
929
  };
730
930
  process.on('SIGTERM', this.sigtermHandler);
731
931
  }
932
+ /**
933
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
934
+ */
732
935
  deregisterProcessHandlers() {
733
936
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
734
937
  if (this.exceptionHandler)
@@ -745,7 +948,20 @@ export class Matterbridge extends EventEmitter {
745
948
  process.off('SIGTERM', this.sigtermHandler);
746
949
  this.sigtermHandler = undefined;
747
950
  }
951
+ /**
952
+ * Logs the node and system information.
953
+ *
954
+ * @remarks
955
+ * This method retrieves and logs various details about the host system, including:
956
+ * - IP address information (IPv4, IPv6, MAC address)
957
+ * - Node.js version
958
+ * - Hostname and user information
959
+ * - Operating system details (type, release, platform, architecture)
960
+ * - Memory usage statistics
961
+ * - Uptime information for both the system and the process
962
+ */
748
963
  async logNodeAndSystemInfo() {
964
+ // IP address information
749
965
  const excludedInterfaceNamePattern = /(tailscale|wireguard|openvpn|zerotier|hamachi|\bwg\d+\b|\btun\d+\b|\btap\d+\b|\butun\d+\b|docker|podman|\bveth[a-z0-9]*\b|\bbr-[a-z0-9]+\b|cni|kube|flannel|calico|virbr\d*\b|vmware|vmnet\d*\b|virtualbox|vboxnet\d*\b|teredo|isatap)/i;
750
966
  const networkInterfaces = os.networkInterfaces();
751
967
  this.systemInformation.interfaceName = '';
@@ -779,16 +995,18 @@ export class Matterbridge extends EventEmitter {
779
995
  break;
780
996
  }
781
997
  }
998
+ // Node information
782
999
  this.systemInformation.nodeVersion = process.versions.node;
783
1000
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
784
1001
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
785
1002
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1003
+ // Host system information
786
1004
  this.systemInformation.hostname = os.hostname();
787
1005
  this.systemInformation.user = os.userInfo().username;
788
- this.systemInformation.osType = os.type();
789
- this.systemInformation.osRelease = os.release();
790
- this.systemInformation.osPlatform = os.platform();
791
- this.systemInformation.osArch = os.arch();
1006
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1007
+ this.systemInformation.osRelease = os.release(); // Kernel version
1008
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1009
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
792
1010
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
793
1011
  this.systemInformation.freeMemory = formatBytes(os.freemem());
794
1012
  this.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -798,6 +1016,7 @@ export class Matterbridge extends EventEmitter {
798
1016
  this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
799
1017
  this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
800
1018
  this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
1019
+ // Log the system information
801
1020
  this.log.debug('Host System Information:');
802
1021
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
803
1022
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -817,14 +1036,17 @@ export class Matterbridge extends EventEmitter {
817
1036
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
818
1037
  this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
819
1038
  this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
1039
+ // Log directories
820
1040
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
821
1041
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
822
1042
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
823
1043
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
824
1044
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1045
+ // Global node_modules directory
825
1046
  if (this.nodeContext)
826
1047
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
827
1048
  if (this.globalModulesDirectory === '') {
1049
+ // First run of Matterbridge so the node storage is empty
828
1050
  this.log.debug(`Getting global node_modules directory...`);
829
1051
  try {
830
1052
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -837,29 +1059,42 @@ export class Matterbridge extends EventEmitter {
837
1059
  }
838
1060
  }
839
1061
  else {
1062
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
840
1063
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
841
1064
  const { createESMWorker } = await import('./workers.js');
842
1065
  createESMWorker('NpmGlobalPrefix', path.join(this.rootDirectory, 'dist/workerGlobalPrefix.js'));
843
1066
  }
1067
+ // Matterbridge version
844
1068
  this.log.debug(`Reading matterbridge package.json...`);
845
1069
  const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
846
1070
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
847
1071
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1072
+ // Matterbridge latest version (will be set in the checkUpdate function)
848
1073
  if (this.nodeContext)
849
1074
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
850
1075
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1076
+ // Matterbridge dev version (will be set in the checkUpdate function)
851
1077
  if (this.nodeContext)
852
1078
  this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
853
1079
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1080
+ // Frontend version
854
1081
  this.log.debug(`Reading frontend package.json...`);
855
1082
  const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
856
1083
  this.frontendVersion = frontendPackageJson.version;
857
1084
  this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
1085
+ // Current working directory
858
1086
  const currentDir = process.cwd();
859
1087
  this.log.debug(`Current Working Directory: ${currentDir}`);
1088
+ // Command line arguments (excluding 'node' and the script name)
860
1089
  const cmdArgs = process.argv.slice(2).join(' ');
861
1090
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
862
1091
  }
1092
+ /**
1093
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
1094
+ *
1095
+ * @param {LogLevel} logLevel The logger logLevel to set.
1096
+ * @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
1097
+ */
863
1098
  async setLogLevel(logLevel) {
864
1099
  this.logLevel = logLevel;
865
1100
  this.log.logLevel = logLevel;
@@ -873,58 +1108,87 @@ export class Matterbridge extends EventEmitter {
873
1108
  continue;
874
1109
  if (plugin.platform.config.debug === true)
875
1110
  pluginDebug = true;
876
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
877
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
878
- }
879
- let callbackLogLevel = "notice";
880
- if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
881
- callbackLogLevel = "info";
882
- if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG || pluginDebug)
883
- callbackLogLevel = "debug";
1111
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
1112
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
1113
+ }
1114
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1115
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1116
+ if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1117
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1118
+ if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG || pluginDebug)
1119
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
884
1120
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
885
1121
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
886
1122
  return logLevel;
887
1123
  }
1124
+ /**
1125
+ * Get the current logger logLevel.
1126
+ *
1127
+ * @returns {LogLevel} The current logger logLevel.
1128
+ */
888
1129
  getLogLevel() {
889
1130
  return this.log.logLevel;
890
1131
  }
1132
+ /**
1133
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1134
+ * It also logs to file (matter.log) if fileLogger is true.
1135
+ *
1136
+ * @param {boolean} fileLogger - Whether to log to file or not.
1137
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1138
+ */
891
1139
  createDestinationMatterLogger(fileLogger) {
892
- this.matterLog.logNameColor = '\x1b[34m';
1140
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
893
1141
  if (fileLogger) {
894
1142
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
895
1143
  }
896
1144
  return (text, message) => {
1145
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
897
1146
  const logger = text.slice(44, 44 + 20).trim();
898
1147
  const msg = text.slice(65);
899
1148
  this.matterLog.logName = logger;
900
1149
  switch (message.level) {
901
1150
  case MatterLogLevel.DEBUG:
902
- this.matterLog.log("debug", msg);
1151
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
903
1152
  break;
904
1153
  case MatterLogLevel.INFO:
905
- this.matterLog.log("info", msg);
1154
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
906
1155
  break;
907
1156
  case MatterLogLevel.NOTICE:
908
- this.matterLog.log("notice", msg);
1157
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
909
1158
  break;
910
1159
  case MatterLogLevel.WARN:
911
- this.matterLog.log("warn", msg);
1160
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
912
1161
  break;
913
1162
  case MatterLogLevel.ERROR:
914
- this.matterLog.log("error", msg);
1163
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
915
1164
  break;
916
1165
  case MatterLogLevel.FATAL:
917
- this.matterLog.log("fatal", msg);
1166
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
918
1167
  break;
919
1168
  }
920
1169
  };
921
1170
  }
1171
+ /**
1172
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1173
+ *
1174
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1175
+ */
922
1176
  async restartProcess() {
923
1177
  await this.cleanup('restarting...', true);
924
1178
  }
1179
+ /**
1180
+ * Shut down the process (/api/shutdown).
1181
+ *
1182
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1183
+ */
925
1184
  async shutdownProcess() {
926
1185
  await this.cleanup('shutting down...', false);
927
1186
  }
1187
+ /**
1188
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1189
+ *
1190
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1191
+ */
928
1192
  async updateProcess() {
929
1193
  this.log.info('Updating matterbridge...');
930
1194
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -937,6 +1201,13 @@ export class Matterbridge extends EventEmitter {
937
1201
  this.frontend.wssSendRestartRequired();
938
1202
  await this.cleanup('updating...', false);
939
1203
  }
1204
+ /**
1205
+ * Unregister all devices and shut down the process (/api/unregister).
1206
+ *
1207
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1208
+ *
1209
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1210
+ */
940
1211
  async unregisterAndShutdownProcess(timeout = 1000) {
941
1212
  const { wait } = await import('./utils/wait.js');
942
1213
  this.log.info('Unregistering all devices and shutting down...');
@@ -949,46 +1220,71 @@ export class Matterbridge extends EventEmitter {
949
1220
  await this.removeAllBridgedEndpoints(plugin.name, 100);
950
1221
  }
951
1222
  this.log.debug('Waiting for the MessageExchange to finish...');
952
- await wait(timeout);
1223
+ await wait(timeout); // Wait for MessageExchange to finish
953
1224
  this.log.debug('Cleaning up and shutting down...');
954
1225
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
955
1226
  }
1227
+ /**
1228
+ * Reset commissioning and shut down the process (/api/reset).
1229
+ *
1230
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1231
+ */
956
1232
  async shutdownProcessAndReset() {
957
1233
  await this.cleanup('shutting down with reset...', false);
958
1234
  }
1235
+ /**
1236
+ * Factory reset and shut down the process (/api/factory-reset).
1237
+ *
1238
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1239
+ */
959
1240
  async shutdownProcessAndFactoryReset() {
960
1241
  await this.cleanup('shutting down with factory reset...', false);
961
1242
  }
1243
+ /**
1244
+ * Cleans up the Matterbridge instance.
1245
+ *
1246
+ * @param {string} message - The cleanup message.
1247
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1248
+ * @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
1249
+ *
1250
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1251
+ */
962
1252
  async cleanup(message, restart = false, pause = 1000) {
963
1253
  if (this.initialized && !this.hasCleanupStarted) {
964
1254
  this.emit('cleanup_started');
965
1255
  this.hasCleanupStarted = true;
966
1256
  this.log.info(message);
1257
+ // Clear the start matter interval
967
1258
  if (this.startMatterInterval) {
968
1259
  clearInterval(this.startMatterInterval);
969
1260
  this.startMatterInterval = undefined;
970
1261
  this.log.debug('Start matter interval cleared');
971
1262
  }
1263
+ // Clear the check update timeout
972
1264
  if (this.checkUpdateTimeout) {
973
1265
  clearTimeout(this.checkUpdateTimeout);
974
1266
  this.checkUpdateTimeout = undefined;
975
1267
  this.log.debug('Check update timeout cleared');
976
1268
  }
1269
+ // Clear the check update interval
977
1270
  if (this.checkUpdateInterval) {
978
1271
  clearInterval(this.checkUpdateInterval);
979
1272
  this.checkUpdateInterval = undefined;
980
1273
  this.log.debug('Check update interval cleared');
981
1274
  }
1275
+ // Clear the configure timeout
982
1276
  if (this.configureTimeout) {
983
1277
  clearTimeout(this.configureTimeout);
984
1278
  this.configureTimeout = undefined;
985
1279
  this.log.debug('Matterbridge configure timeout cleared');
986
1280
  }
1281
+ // Clear the reachability timeout
987
1282
  if (this.reachabilityTimeout) {
988
1283
  clearTimeout(this.reachabilityTimeout);
989
1284
  this.reachabilityTimeout = undefined;
990
1285
  this.log.debug('Matterbridge reachability timeout cleared');
991
1286
  }
1287
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
992
1288
  for (const plugin of this.plugins) {
993
1289
  if (!plugin.enabled || plugin.error)
994
1290
  continue;
@@ -999,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
999
1295
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1000
1296
  }
1001
1297
  }
1298
+ // Stop matter server nodes
1002
1299
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1003
1300
  if (pause > 0) {
1004
1301
  const { wait } = await import('./utils/wait.js');
@@ -1026,6 +1323,7 @@ export class Matterbridge extends EventEmitter {
1026
1323
  }
1027
1324
  }
1028
1325
  this.log.notice('Stopped matter server nodes');
1326
+ // Matter commisioning reset
1029
1327
  if (message === 'shutting down with reset...') {
1030
1328
  this.log.info('Resetting Matterbridge commissioning information...');
1031
1329
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1035,6 +1333,7 @@ export class Matterbridge extends EventEmitter {
1035
1333
  await this.matterbridgeContext?.clearAll();
1036
1334
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1037
1335
  }
1336
+ // Unregister all devices
1038
1337
  if (message === 'unregistered all devices and shutting down...') {
1039
1338
  if (this.bridgeMode === 'bridge') {
1040
1339
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1052,16 +1351,69 @@ export class Matterbridge extends EventEmitter {
1052
1351
  }
1053
1352
  this.log.info('Matter storage reset done!');
1054
1353
  }
1354
+ // Stop matter storage
1055
1355
  await this.stopMatterStorage();
1356
+ /**
1357
+ * Unlink a file safely, ignoring errors.
1358
+ *
1359
+ * @param {string} path - The path to the file to unlink.
1360
+ * @param {AnsiLogger} log - The logger to use for logging.
1361
+ */
1362
+ function unlinkSafe(path, log) {
1363
+ try {
1364
+ log.debug(`Removing ${path}...`);
1365
+ unlinkSync(path);
1366
+ log.debug(`Removed ${path}`);
1367
+ }
1368
+ catch {
1369
+ // Ignore errors if the file does not exist
1370
+ }
1371
+ }
1372
+ // Remove the resumption records for Matterbridge (bridge mode)
1373
+ this.log.debug(`Cleaning matter storage context for ${GREEN}Matterbridge${db}...`);
1374
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'sessions.resumptionRecords'), this.log);
1375
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'root.subscriptions.subscriptions'), this.log);
1376
+ for (const plugin of this.plugins.array()) {
1377
+ // Remove the resumption records for the plugins (childbridge mode)
1378
+ this.log.debug(`Cleaning matter storage context for plugin ${plg}${plugin.name}${db}...`);
1379
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'sessions.resumptionRecords'), this.log);
1380
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'root.subscriptions.subscriptions'), this.log);
1381
+ }
1382
+ for (const device of this.devices.array().filter((d) => d.mode === 'server')) {
1383
+ if (!device.deviceName)
1384
+ continue;
1385
+ // Remove the resumption records for the server mode devices
1386
+ this.log.debug(`Cleaning matter storage context for server node device ${dev}${device.deviceName}${db}...`);
1387
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'sessions.resumptionRecords'), this.log);
1388
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'root.subscriptions.subscriptions'), this.log);
1389
+ }
1390
+ // Stop the frontend
1056
1391
  await this.frontend.stop();
1057
1392
  this.frontend.destroy();
1393
+ // Close PluginManager and DeviceManager
1058
1394
  this.plugins.destroy();
1059
1395
  this.devices.destroy();
1396
+ // Stop thread messaging server
1060
1397
  this.server.close();
1398
+ // Close the matterbridge node storage and context
1061
1399
  if (this.nodeStorage && this.nodeContext) {
1400
+ /*
1401
+ TODO: Implement serialization of registered devices
1402
+ this.log.info('Saving registered devices...');
1403
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1404
+ this.devices.forEach(async (device) => {
1405
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1406
+ this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1407
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1408
+ });
1409
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1410
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1411
+ */
1412
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1062
1413
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1063
1414
  await this.nodeContext.close();
1064
1415
  this.nodeContext = undefined;
1416
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1065
1417
  for (const plugin of this.plugins) {
1066
1418
  if (plugin.nodeContext) {
1067
1419
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1078,8 +1430,10 @@ export class Matterbridge extends EventEmitter {
1078
1430
  }
1079
1431
  this.plugins.clear();
1080
1432
  this.devices.clear();
1433
+ // Factory reset
1081
1434
  if (message === 'shutting down with factory reset...') {
1082
1435
  try {
1436
+ // Delete matter storage directory with its subdirectories and backup
1083
1437
  const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1084
1438
  this.log.info(`Removing matter storage directory: ${dir}`);
1085
1439
  await fs.promises.rm(dir, { recursive: true });
@@ -1088,11 +1442,13 @@ export class Matterbridge extends EventEmitter {
1088
1442
  await fs.promises.rm(backup, { recursive: true });
1089
1443
  }
1090
1444
  catch (error) {
1445
+ // istanbul ignore next if
1091
1446
  if (error instanceof Error && error.code !== 'ENOENT') {
1092
1447
  this.log.error(`Error removing matter storage directory: ${error}`);
1093
1448
  }
1094
1449
  }
1095
1450
  try {
1451
+ // Delete matterbridge storage directory with its subdirectories and backup
1096
1452
  const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1097
1453
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1098
1454
  await fs.promises.rm(dir, { recursive: true });
@@ -1101,18 +1457,20 @@ export class Matterbridge extends EventEmitter {
1101
1457
  await fs.promises.rm(backup, { recursive: true });
1102
1458
  }
1103
1459
  catch (error) {
1460
+ // istanbul ignore next if
1104
1461
  if (error instanceof Error && error.code !== 'ENOENT') {
1105
1462
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1106
1463
  }
1107
1464
  }
1108
1465
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1109
1466
  }
1467
+ // Deregisters the process handlers
1110
1468
  this.deregisterProcessHandlers();
1111
1469
  if (restart) {
1112
1470
  if (message === 'updating...') {
1113
1471
  this.log.info('Cleanup completed. Updating...');
1114
1472
  Matterbridge.instance = undefined;
1115
- this.emit('update');
1473
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1116
1474
  }
1117
1475
  else if (message === 'restarting...') {
1118
1476
  this.log.info('Cleanup completed. Restarting...');
@@ -1141,7 +1499,14 @@ export class Matterbridge extends EventEmitter {
1141
1499
  this.log.debug('Cleanup already started...');
1142
1500
  }
1143
1501
  }
1502
+ /**
1503
+ * Starts the Matterbridge in bridge mode.
1504
+ *
1505
+ * @private
1506
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1507
+ */
1144
1508
  async startBridge() {
1509
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1145
1510
  if (!this.matterStorageManager)
1146
1511
  throw new Error('No storage manager initialized');
1147
1512
  if (!this.matterbridgeContext)
@@ -1155,6 +1520,7 @@ export class Matterbridge extends EventEmitter {
1155
1520
  this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
1156
1521
  let failCount = 0;
1157
1522
  this.startMatterInterval = setInterval(async () => {
1523
+ // istanbul ignore if cause is just a logging statement
1158
1524
  if (failCount && failCount % 10 === 0) {
1159
1525
  this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1160
1526
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1188,13 +1554,16 @@ export class Matterbridge extends EventEmitter {
1188
1554
  clearInterval(this.startMatterInterval);
1189
1555
  this.startMatterInterval = undefined;
1190
1556
  this.log.debug('Cleared startMatterInterval interval in bridge mode');
1191
- this.startServerNode(this.serverNode);
1557
+ // Start the Matter server node
1558
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1559
+ // Start the Matter server node of single devices in mode 'server'
1192
1560
  for (const device of this.devices.array()) {
1193
1561
  if (device.mode === 'server' && device.serverNode) {
1194
1562
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1195
- this.startServerNode(device.serverNode);
1563
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1196
1564
  }
1197
1565
  }
1566
+ // Configure the plugins
1198
1567
  this.configureTimeout = setTimeout(async () => {
1199
1568
  for (const plugin of this.plugins.array()) {
1200
1569
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1212,11 +1581,13 @@ export class Matterbridge extends EventEmitter {
1212
1581
  }
1213
1582
  this.frontend.wssSendRefreshRequired('plugins');
1214
1583
  }, 30 * 1000).unref();
1584
+ // Setting reachability to true
1215
1585
  this.reachabilityTimeout = setTimeout(() => {
1216
1586
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1217
1587
  if (this.aggregatorNode)
1218
1588
  this.setAggregatorReachability(this.aggregatorNode, true);
1219
1589
  }, 60 * 1000).unref();
1590
+ // Logger.get('LogServerNode').info(this.serverNode);
1220
1591
  this.emit('bridge_started');
1221
1592
  this.log.notice('Matterbridge bridge started successfully');
1222
1593
  this.frontend.wssSendRefreshRequired('settings');
@@ -1224,22 +1595,33 @@ export class Matterbridge extends EventEmitter {
1224
1595
  this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1225
1596
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1226
1597
  }
1598
+ /**
1599
+ * Starts the Matterbridge in childbridge mode.
1600
+ *
1601
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1602
+ *
1603
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1604
+ */
1227
1605
  async startChildbridge(delay = 1000) {
1228
1606
  if (!this.matterStorageManager)
1229
1607
  throw new Error('No storage manager initialized');
1230
1608
  const { wait } = await import('./utils/wait.js');
1609
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1231
1610
  this.log.debug('Loading all plugins in childbridge mode...');
1232
1611
  await this.startPlugins(true, false);
1612
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1233
1613
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1234
1614
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1235
1615
  if (plugin.type === 'DynamicPlatform')
1236
1616
  await this.createDynamicPlugin(plugin);
1237
- this.plugins.start(plugin, 'Matterbridge is starting');
1617
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1238
1618
  }
1619
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1239
1620
  this.log.debug('Starting start matter interval in childbridge mode...');
1240
1621
  this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
1241
1622
  let failCount = 0;
1242
1623
  this.startMatterInterval = setInterval(async () => {
1624
+ // istanbul ignore if cause is just a logging statement
1243
1625
  if (failCount && failCount % 10 === 0) {
1244
1626
  this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1245
1627
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1277,8 +1659,9 @@ export class Matterbridge extends EventEmitter {
1277
1659
  clearInterval(this.startMatterInterval);
1278
1660
  this.startMatterInterval = undefined;
1279
1661
  if (delay > 0)
1280
- await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1662
+ await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1281
1663
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1664
+ // Configure the plugins
1282
1665
  this.configureTimeout = setTimeout(async () => {
1283
1666
  for (const plugin of this.plugins.array()) {
1284
1667
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1303,6 +1686,7 @@ export class Matterbridge extends EventEmitter {
1303
1686
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1304
1687
  continue;
1305
1688
  }
1689
+ // istanbul ignore next if cause is just a safety check
1306
1690
  if (!plugin.serverNode) {
1307
1691
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1308
1692
  continue;
@@ -1315,19 +1699,23 @@ export class Matterbridge extends EventEmitter {
1315
1699
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1316
1700
  continue;
1317
1701
  }
1318
- this.startServerNode(plugin.serverNode);
1702
+ // Start the Matter server node
1703
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1704
+ // Setting reachability to true
1319
1705
  plugin.reachabilityTimeout = setTimeout(() => {
1320
1706
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1321
1707
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1322
1708
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1323
1709
  }, 60 * 1000).unref();
1324
1710
  }
1711
+ // Start the Matter server node of single devices in mode 'server'
1325
1712
  for (const device of this.devices.array()) {
1326
1713
  if (device.mode === 'server' && device.serverNode) {
1327
1714
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1328
- this.startServerNode(device.serverNode);
1715
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1329
1716
  }
1330
1717
  }
1718
+ // Logger.get('LogServerNode').info(this.serverNode);
1331
1719
  this.emit('childbridge_started');
1332
1720
  this.log.notice('Matterbridge childbridge started successfully');
1333
1721
  this.frontend.wssSendRefreshRequired('settings');
@@ -1335,9 +1723,229 @@ export class Matterbridge extends EventEmitter {
1335
1723
  this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
1336
1724
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1337
1725
  }
1726
+ /**
1727
+ * Starts the Matterbridge controller.
1728
+ *
1729
+ * @private
1730
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1731
+ */
1338
1732
  async startController() {
1733
+ /*
1734
+ if (!this.matterStorageManager) {
1735
+ this.log.error('No storage manager initialized');
1736
+ await this.cleanup('No storage manager initialized');
1737
+ return;
1738
+ }
1739
+ this.log.info('Creating context: mattercontrollerContext');
1740
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1741
+ if (!this.controllerContext) {
1742
+ this.log.error('No storage context mattercontrollerContext initialized');
1743
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1744
+ return;
1745
+ }
1746
+
1747
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1748
+ this.matterServer = await this.createMatterServer(this.storageManager);
1749
+ this.log.info('Creating matter commissioning controller');
1750
+ this.commissioningController = new CommissioningController({
1751
+ autoConnect: false,
1752
+ });
1753
+ this.log.info('Adding matter commissioning controller to matter server');
1754
+ await this.matterServer.addCommissioningController(this.commissioningController);
1755
+
1756
+ this.log.info('Starting matter server');
1757
+ await this.matterServer.start();
1758
+ this.log.info('Matter server started');
1759
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1760
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1761
+ regulatoryCountryCode: 'XX',
1762
+ };
1763
+ const commissioningController = new CommissioningController({
1764
+ environment: {
1765
+ environment,
1766
+ id: uniqueId,
1767
+ },
1768
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1769
+ adminFabricLabel,
1770
+ });
1771
+
1772
+ if (hasParameter('pairingcode')) {
1773
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1774
+ const pairingCode = getParameter('pairingcode');
1775
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1776
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1777
+
1778
+ let longDiscriminator, setupPin, shortDiscriminator;
1779
+ if (pairingCode !== undefined) {
1780
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1781
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1782
+ longDiscriminator = undefined;
1783
+ setupPin = pairingCodeCodec.passcode;
1784
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1785
+ } else {
1786
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1787
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1788
+ setupPin = this.controllerContext.get('pin', 20202021);
1789
+ }
1790
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1791
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1792
+ }
1793
+
1794
+ const options = {
1795
+ commissioning: commissioningOptions,
1796
+ discovery: {
1797
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1798
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1799
+ },
1800
+ passcode: setupPin,
1801
+ } as NodeCommissioningOptions;
1802
+ this.log.info('Commissioning with options:', options);
1803
+ const nodeId = await this.commissioningController.commissionNode(options);
1804
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1805
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1806
+ } // (hasParameter('pairingcode'))
1807
+
1808
+ if (hasParameter('unpairall')) {
1809
+ this.log.info('***Commissioning controller unpairing all nodes...');
1810
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1811
+ for (const nodeId of nodeIds) {
1812
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1813
+ await this.commissioningController.removeNode(nodeId);
1814
+ }
1815
+ return;
1816
+ }
1817
+
1818
+ if (hasParameter('discover')) {
1819
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1820
+ // console.log(discover);
1821
+ }
1822
+
1823
+ if (!this.commissioningController.isCommissioned()) {
1824
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1825
+ return;
1826
+ }
1827
+
1828
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1829
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1830
+ for (const nodeId of nodeIds) {
1831
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1832
+
1833
+ const node = await this.commissioningController.connectNode(nodeId, {
1834
+ autoSubscribe: false,
1835
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1836
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1837
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1838
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1839
+ stateInformationCallback: (peerNodeId, info) => {
1840
+ switch (info) {
1841
+ case NodeStateInformation.Connected:
1842
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1843
+ break;
1844
+ case NodeStateInformation.Disconnected:
1845
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1846
+ break;
1847
+ case NodeStateInformation.Reconnecting:
1848
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1849
+ break;
1850
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1851
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1852
+ break;
1853
+ case NodeStateInformation.StructureChanged:
1854
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1855
+ break;
1856
+ case NodeStateInformation.Decommissioned:
1857
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1858
+ break;
1859
+ default:
1860
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1861
+ break;
1862
+ }
1863
+ },
1864
+ });
1865
+
1866
+ node.logStructure();
1867
+
1868
+ // Get the interaction client
1869
+ this.log.info('Getting the interaction client');
1870
+ const interactionClient = await node.getInteractionClient();
1871
+ let cluster;
1872
+ let attributes;
1873
+
1874
+ // Log BasicInformationCluster
1875
+ cluster = BasicInformationCluster;
1876
+ attributes = await interactionClient.getMultipleAttributes({
1877
+ attributes: [{ clusterId: cluster.id }],
1878
+ });
1879
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1880
+ attributes.forEach((attribute) => {
1881
+ this.log.info(
1882
+ `- 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}`,
1883
+ );
1884
+ });
1885
+
1886
+ // Log PowerSourceCluster
1887
+ cluster = PowerSourceCluster;
1888
+ attributes = await interactionClient.getMultipleAttributes({
1889
+ attributes: [{ clusterId: cluster.id }],
1890
+ });
1891
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1892
+ attributes.forEach((attribute) => {
1893
+ this.log.info(
1894
+ `- 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}`,
1895
+ );
1896
+ });
1897
+
1898
+ // Log ThreadNetworkDiagnostics
1899
+ cluster = ThreadNetworkDiagnosticsCluster;
1900
+ attributes = await interactionClient.getMultipleAttributes({
1901
+ attributes: [{ clusterId: cluster.id }],
1902
+ });
1903
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1904
+ attributes.forEach((attribute) => {
1905
+ this.log.info(
1906
+ `- 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}`,
1907
+ );
1908
+ });
1909
+
1910
+ // Log SwitchCluster
1911
+ cluster = SwitchCluster;
1912
+ attributes = await interactionClient.getMultipleAttributes({
1913
+ attributes: [{ clusterId: cluster.id }],
1914
+ });
1915
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1916
+ attributes.forEach((attribute) => {
1917
+ this.log.info(
1918
+ `- 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}`,
1919
+ );
1920
+ });
1921
+
1922
+ this.log.info('Subscribing to all attributes and events');
1923
+ await node.subscribeAllAttributesAndEvents({
1924
+ ignoreInitialTriggers: false,
1925
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1926
+ this.log.info(
1927
+ `***${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}`,
1928
+ ),
1929
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1930
+ this.log.info(
1931
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1932
+ );
1933
+ },
1934
+ });
1935
+ this.log.info('Subscribed to all attributes and events');
1936
+ }
1937
+ */
1339
1938
  }
1939
+ /** */
1940
+ /** Matter.js methods */
1941
+ /** */
1942
+ /**
1943
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1944
+ *
1945
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1946
+ */
1340
1947
  async startMatterStorage() {
1948
+ // Setup Matter storage
1341
1949
  this.log.info(`Starting matter node storage...`);
1342
1950
  this.matterStorageService = this.environment.get(StorageService);
1343
1951
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1345,8 +1953,17 @@ export class Matterbridge extends EventEmitter {
1345
1953
  this.log.info('Matter node storage manager "Matterbridge" created');
1346
1954
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1347
1955
  this.log.info('Matter node storage started');
1956
+ // Backup matter storage since it is created/opened correctly
1348
1957
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1349
1958
  }
1959
+ /**
1960
+ * Makes a backup copy of the specified matter storage directory.
1961
+ *
1962
+ * @param {string} storageName - The name of the storage directory to be backed up.
1963
+ * @param {string} backupName - The name of the backup directory to be created.
1964
+ * @private
1965
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1966
+ */
1350
1967
  async backupMatterStorage(storageName, backupName) {
1351
1968
  this.log.info('Creating matter node storage backup...');
1352
1969
  try {
@@ -1357,6 +1974,11 @@ export class Matterbridge extends EventEmitter {
1357
1974
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1358
1975
  }
1359
1976
  }
1977
+ /**
1978
+ * Stops the matter storage.
1979
+ *
1980
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1981
+ */
1360
1982
  async stopMatterStorage() {
1361
1983
  this.log.info('Closing matter node storage...');
1362
1984
  await this.matterStorageManager?.close();
@@ -1365,6 +1987,20 @@ export class Matterbridge extends EventEmitter {
1365
1987
  this.matterbridgeContext = undefined;
1366
1988
  this.log.info('Matter node storage closed');
1367
1989
  }
1990
+ /**
1991
+ * Creates a server node storage context.
1992
+ *
1993
+ * @param {string} storeId - The storeId.
1994
+ * @param {string} deviceName - The name of the device.
1995
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1996
+ * @param {number} vendorId - The vendor ID.
1997
+ * @param {string} vendorName - The vendor name.
1998
+ * @param {number} productId - The product ID.
1999
+ * @param {string} productName - The product name.
2000
+ * @param {string} [serialNumber] - The serial number of the device (optional).
2001
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
2002
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
2003
+ */
1368
2004
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1369
2005
  const { randomBytes } = await import('node:crypto');
1370
2006
  if (!this.matterStorageService)
@@ -1380,8 +2016,8 @@ export class Matterbridge extends EventEmitter {
1380
2016
  await storageContext.set('vendorName', vendorName.slice(0, 32));
1381
2017
  await storageContext.set('productId', productId);
1382
2018
  await storageContext.set('productName', productName.slice(0, 32));
1383
- await storageContext.set('nodeLabel', productName.slice(0, 32));
1384
2019
  await storageContext.set('productLabel', productName.slice(0, 32));
2020
+ await storageContext.set('nodeLabel', deviceName.slice(0, 32));
1385
2021
  await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
1386
2022
  await storageContext.set('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
1387
2023
  await storageContext.set('softwareVersion', isValidNumber(parseVersionString(this.matterbridgeVersion), 0, UINT32_MAX) ? parseVersionString(this.matterbridgeVersion) : 1);
@@ -1396,14 +2032,23 @@ export class Matterbridge extends EventEmitter {
1396
2032
  this.log.debug(`- vendorName: ${await storageContext.get('vendorName')}`);
1397
2033
  this.log.debug(`- productId: ${await storageContext.get('productId')}`);
1398
2034
  this.log.debug(`- productName: ${await storageContext.get('productName')}`);
1399
- this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
1400
2035
  this.log.debug(`- productLabel: ${await storageContext.get('productLabel')}`);
2036
+ this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
1401
2037
  this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1402
2038
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1403
2039
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1404
2040
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1405
2041
  return storageContext;
1406
2042
  }
2043
+ /**
2044
+ * Creates a server node.
2045
+ *
2046
+ * @param {StorageContext} storageContext - The storage context for the server node.
2047
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2048
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2049
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2050
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2051
+ */
1407
2052
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1408
2053
  const storeId = await storageContext.get('storeId');
1409
2054
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1413,32 +2058,42 @@ export class Matterbridge extends EventEmitter {
1413
2058
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1414
2059
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1415
2060
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2061
+ /**
2062
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2063
+ */
1416
2064
  const serverNode = await ServerNode.create({
2065
+ // Required: Give the Node a unique ID which is used to store the state of this node
1417
2066
  id: storeId,
2067
+ // Environment to run the server node in
1418
2068
  environment: this.environment,
2069
+ // Provide Network relevant configuration like the port
1419
2070
  network: {
1420
2071
  listeningAddressIpv4: this.ipv4Address,
1421
2072
  listeningAddressIpv6: this.ipv6Address,
1422
2073
  port,
1423
2074
  },
2075
+ // Provide the certificate for the device
1424
2076
  operationalCredentials: {
1425
2077
  certification: this.certification,
1426
2078
  },
2079
+ // Provide Commissioning relevant settings
1427
2080
  commissioning: {
1428
2081
  passcode,
1429
2082
  discriminator,
1430
2083
  },
2084
+ // Provide Node announcement settings
1431
2085
  productDescription: {
1432
2086
  name: await storageContext.get('deviceName'),
1433
2087
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1434
2088
  },
2089
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1435
2090
  basicInformation: {
2091
+ nodeLabel: await storageContext.get('nodeLabel'),
1436
2092
  vendorId: VendorId(await storageContext.get('vendorId')),
1437
2093
  vendorName: await storageContext.get('vendorName'),
1438
2094
  productId: await storageContext.get('productId'),
1439
2095
  productName: await storageContext.get('productName'),
1440
2096
  productLabel: await storageContext.get('productName'),
1441
- nodeLabel: await storageContext.get('productName'),
1442
2097
  serialNumber: await storageContext.get('serialNumber'),
1443
2098
  uniqueId: await storageContext.get('uniqueId'),
1444
2099
  softwareVersion: await storageContext.get('softwareVersion'),
@@ -1448,17 +2103,23 @@ export class Matterbridge extends EventEmitter {
1448
2103
  reachable: true,
1449
2104
  },
1450
2105
  });
2106
+ /**
2107
+ * This event is triggered when the device is initially commissioned successfully.
2108
+ * This means: It is added to the first fabric.
2109
+ */
1451
2110
  serverNode.lifecycle.commissioned.on(() => {
1452
2111
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1453
2112
  this.advertisingNodes.delete(storeId);
1454
2113
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1455
2114
  });
2115
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1456
2116
  serverNode.lifecycle.decommissioned.on(() => {
1457
2117
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1458
2118
  this.advertisingNodes.delete(storeId);
1459
2119
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1460
2120
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1461
2121
  });
2122
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1462
2123
  serverNode.lifecycle.online.on(async () => {
1463
2124
  this.log.notice(`Server node for ${storeId} is online`);
1464
2125
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1469,13 +2130,16 @@ export class Matterbridge extends EventEmitter {
1469
2130
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1470
2131
  }
1471
2132
  else {
2133
+ // istanbul ignore next
1472
2134
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2135
+ // istanbul ignore next
1473
2136
  this.advertisingNodes.delete(storeId);
1474
2137
  }
1475
2138
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1476
2139
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1477
2140
  this.emit('online', storeId);
1478
2141
  });
2142
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1479
2143
  serverNode.lifecycle.offline.on(() => {
1480
2144
  this.log.notice(`Server node for ${storeId} is offline`);
1481
2145
  this.advertisingNodes.delete(storeId);
@@ -1483,11 +2147,15 @@ export class Matterbridge extends EventEmitter {
1483
2147
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1484
2148
  this.emit('offline', storeId);
1485
2149
  });
2150
+ /**
2151
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2152
+ * information is needed.
2153
+ */
1486
2154
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1487
2155
  let action = '';
1488
2156
  switch (fabricAction) {
1489
2157
  case 'added':
1490
- this.advertisingNodes.delete(storeId);
2158
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1491
2159
  action = 'added';
1492
2160
  break;
1493
2161
  case 'deleted':
@@ -1500,14 +2168,22 @@ export class Matterbridge extends EventEmitter {
1500
2168
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1501
2169
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1502
2170
  });
2171
+ /**
2172
+ * This event is triggered when an operative new session was opened by a Controller.
2173
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2174
+ */
1503
2175
  serverNode.events.sessions.opened.on((session) => {
1504
2176
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1505
2177
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1506
2178
  });
2179
+ /**
2180
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2181
+ */
1507
2182
  serverNode.events.sessions.closed.on((session) => {
1508
2183
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1509
2184
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1510
2185
  });
2186
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1511
2187
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1512
2188
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1513
2189
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1515,6 +2191,12 @@ export class Matterbridge extends EventEmitter {
1515
2191
  this.log.info(`Created server node for ${storeId}`);
1516
2192
  return serverNode;
1517
2193
  }
2194
+ /**
2195
+ * Gets the matter sanitized data of the specified server node.
2196
+ *
2197
+ * @param {ServerNode} [serverNode] - The server node to start.
2198
+ * @returns {ApiMatter} The sanitized data of the server node.
2199
+ */
1518
2200
  getServerNodeData(serverNode) {
1519
2201
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1520
2202
  return {
@@ -1531,13 +2213,26 @@ export class Matterbridge extends EventEmitter {
1531
2213
  serialNumber: serverNode.state.basicInformation.serialNumber,
1532
2214
  };
1533
2215
  }
2216
+ /**
2217
+ * Starts the specified server node.
2218
+ *
2219
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2220
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2221
+ */
1534
2222
  async startServerNode(matterServerNode) {
1535
2223
  if (!matterServerNode)
1536
2224
  return;
1537
2225
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1538
2226
  await matterServerNode.start();
1539
2227
  }
1540
- async stopServerNode(matterServerNode, timeout = 30000) {
2228
+ /**
2229
+ * Stops the specified server node.
2230
+ *
2231
+ * @param {ServerNode} matterServerNode - The server node to stop.
2232
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 10 seconds.
2233
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2234
+ */
2235
+ async stopServerNode(matterServerNode, timeout = 10000) {
1541
2236
  const { withTimeout } = await import('./utils/wait.js');
1542
2237
  if (!matterServerNode)
1543
2238
  return;
@@ -1550,12 +2245,25 @@ export class Matterbridge extends EventEmitter {
1550
2245
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1551
2246
  }
1552
2247
  }
2248
+ /**
2249
+ * Creates an aggregator node with the specified storage context.
2250
+ *
2251
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2252
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2253
+ */
1553
2254
  async createAggregatorNode(storageContext) {
1554
2255
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1555
2256
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1556
2257
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1557
2258
  return aggregatorNode;
1558
2259
  }
2260
+ /**
2261
+ * Creates and configures the server node for an accessory plugin for a given device.
2262
+ *
2263
+ * @param {Plugin} plugin - The plugin to configure.
2264
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2265
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2266
+ */
1559
2267
  async createAccessoryPlugin(plugin, device) {
1560
2268
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1561
2269
  plugin.locked = true;
@@ -1567,11 +2275,17 @@ export class Matterbridge extends EventEmitter {
1567
2275
  await plugin.serverNode.add(device);
1568
2276
  }
1569
2277
  }
2278
+ /**
2279
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2280
+ *
2281
+ * @param {Plugin} plugin - The plugin to configure.
2282
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2283
+ */
1570
2284
  async createDynamicPlugin(plugin) {
1571
2285
  if (!plugin.locked) {
1572
2286
  plugin.locked = true;
1573
2287
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
1574
- plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
2288
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1575
2289
  plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1576
2290
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
1577
2291
  plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
@@ -1579,6 +2293,13 @@ export class Matterbridge extends EventEmitter {
1579
2293
  await plugin.serverNode.add(plugin.aggregatorNode);
1580
2294
  }
1581
2295
  }
2296
+ /**
2297
+ * Creates and configures the server node for a single not bridged device.
2298
+ *
2299
+ * @param {Plugin} plugin - The plugin to configure.
2300
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2301
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2302
+ */
1582
2303
  async createDeviceServerNode(plugin, device) {
1583
2304
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1584
2305
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1589,8 +2310,16 @@ export class Matterbridge extends EventEmitter {
1589
2310
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1590
2311
  }
1591
2312
  }
2313
+ /**
2314
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2315
+ *
2316
+ * @param {string} pluginName - The name of the plugin.
2317
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2318
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2319
+ */
1592
2320
  async addBridgedEndpoint(pluginName, device) {
1593
2321
  const { waiter } = await import('./utils/wait.js');
2322
+ // Check if the plugin is registered
1594
2323
  const plugin = this.plugins.get(pluginName);
1595
2324
  if (!plugin) {
1596
2325
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1610,6 +2339,7 @@ export class Matterbridge extends EventEmitter {
1610
2339
  }
1611
2340
  else if (this.bridgeMode === 'bridge') {
1612
2341
  if (device.mode === 'matter') {
2342
+ // Register and add the device to the matterbridge server node
1613
2343
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1614
2344
  if (!this.serverNode) {
1615
2345
  this.log.error('Server node not found for Matterbridge');
@@ -1626,6 +2356,7 @@ export class Matterbridge extends EventEmitter {
1626
2356
  }
1627
2357
  }
1628
2358
  else {
2359
+ // Register and add the device to the matterbridge aggregator node
1629
2360
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1630
2361
  if (!this.aggregatorNode) {
1631
2362
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1643,6 +2374,7 @@ export class Matterbridge extends EventEmitter {
1643
2374
  }
1644
2375
  }
1645
2376
  else if (this.bridgeMode === 'childbridge') {
2377
+ // Register and add the device to the plugin server node
1646
2378
  if (plugin.type === 'AccessoryPlatform') {
1647
2379
  try {
1648
2380
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1666,10 +2398,12 @@ export class Matterbridge extends EventEmitter {
1666
2398
  return;
1667
2399
  }
1668
2400
  }
2401
+ // Register and add the device to the plugin aggregator node
1669
2402
  if (plugin.type === 'DynamicPlatform') {
1670
2403
  try {
1671
2404
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1672
2405
  await this.createDynamicPlugin(plugin);
2406
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1673
2407
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1674
2408
  if (!plugin.aggregatorNode) {
1675
2409
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1690,18 +2424,32 @@ export class Matterbridge extends EventEmitter {
1690
2424
  }
1691
2425
  if (plugin.registeredDevices !== undefined)
1692
2426
  plugin.registeredDevices++;
2427
+ // Add the device to the DeviceManager
1693
2428
  this.devices.set(device);
2429
+ // Subscribe to the attributes changed event
1694
2430
  await this.subscribeAttributeChanged(plugin, device);
1695
2431
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1696
2432
  }
2433
+ /**
2434
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2435
+ *
2436
+ * @param {string} pluginName - The name of the plugin.
2437
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2438
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2439
+ */
1697
2440
  async removeBridgedEndpoint(pluginName, device) {
1698
2441
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2442
+ // Check if the plugin is registered
1699
2443
  const plugin = this.plugins.get(pluginName);
1700
2444
  if (!plugin) {
1701
2445
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1702
2446
  return;
1703
2447
  }
1704
- if (this.bridgeMode === 'bridge') {
2448
+ if (device.mode === 'server') {
2449
+ this.log.info(`Removed mode server bridged endpoint(${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
2450
+ }
2451
+ else if (this.bridgeMode === 'bridge') {
2452
+ // Unregister and remove the device from the matterbridge aggregator node
1705
2453
  if (!this.aggregatorNode) {
1706
2454
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1707
2455
  return;
@@ -1713,8 +2461,10 @@ export class Matterbridge extends EventEmitter {
1713
2461
  }
1714
2462
  else if (this.bridgeMode === 'childbridge') {
1715
2463
  if (plugin.type === 'AccessoryPlatform') {
2464
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1716
2465
  }
1717
2466
  else if (plugin.type === 'DynamicPlatform') {
2467
+ // Unregister and remove the device from the plugin aggregator node
1718
2468
  if (!plugin.aggregatorNode) {
1719
2469
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1720
2470
  return;
@@ -1725,8 +2475,21 @@ export class Matterbridge extends EventEmitter {
1725
2475
  if (plugin.registeredDevices !== undefined)
1726
2476
  plugin.registeredDevices--;
1727
2477
  }
2478
+ // Remove the device from the DeviceManager
1728
2479
  this.devices.remove(device);
1729
2480
  }
2481
+ /**
2482
+ * Removes all bridged endpoints from the specified plugin.
2483
+ *
2484
+ * @param {string} pluginName - The name of the plugin.
2485
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2486
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2487
+ *
2488
+ * @remarks
2489
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2490
+ * It also applies a delay between each removal if specified.
2491
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2492
+ */
1730
2493
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1731
2494
  const { wait } = await import('./utils/wait.js');
1732
2495
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
@@ -1738,8 +2501,28 @@ export class Matterbridge extends EventEmitter {
1738
2501
  if (delay > 0)
1739
2502
  await wait(2000);
1740
2503
  }
2504
+ /**
2505
+ * Registers a virtual device.
2506
+ * Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
2507
+ *
2508
+ * The virtual device is created as an instance of `Endpoint` with the provided device type.
2509
+ * When the virtual device is turned on, the provided callback function is executed.
2510
+ * The onOff state of the virtual device always reverts to false when the device is turned on.
2511
+ *
2512
+ * @param { string } pluginName - The name of the plugin to register the virtual device under.
2513
+ * @param { string } name - The name of the virtual device.
2514
+ * @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
2515
+ * @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
2516
+ *
2517
+ * @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
2518
+ *
2519
+ * @remarks
2520
+ * The virtual devices don't show up in the device list of the frontend.
2521
+ * Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
2522
+ */
1741
2523
  async addVirtualEndpoint(pluginName, name, type, callback) {
1742
2524
  this.log.debug(`Adding virtual endpoint ${plg}${pluginName}${db}:${dev}${name}${db}...`);
2525
+ // Check if the plugin is registered
1743
2526
  const plugin = this.plugins.get(pluginName);
1744
2527
  if (!plugin) {
1745
2528
  this.log.error(`Error adding virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er}: plugin not found`);
@@ -1766,13 +2549,24 @@ export class Matterbridge extends EventEmitter {
1766
2549
  this.log.error(`Virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er} not created. Virtual endpoints are only supported in bridge mode and childbridge mode with a DynamicPlatform.`);
1767
2550
  return false;
1768
2551
  }
2552
+ /**
2553
+ * Subscribes to the attribute change event for the given device and plugin.
2554
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2555
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2556
+ *
2557
+ * @param {Plugin} plugin - The plugin associated with the device.
2558
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2559
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2560
+ */
1769
2561
  async subscribeAttributeChanged(plugin, device) {
1770
2562
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1771
2563
  return;
1772
2564
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2565
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1773
2566
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1774
2567
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1775
2568
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2569
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1776
2570
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1777
2571
  });
1778
2572
  }
@@ -1822,6 +2616,7 @@ export class Matterbridge extends EventEmitter {
1822
2616
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1823
2617
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1824
2618
  this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${isValidObject(value) ? debugStringify(value) : value}${db}`);
2619
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1825
2620
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1826
2621
  });
1827
2622
  }
@@ -1830,12 +2625,19 @@ export class Matterbridge extends EventEmitter {
1830
2625
  this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1831
2626
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1832
2627
  this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${isValidObject(value) ? debugStringify(value) : value}${db}`);
2628
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1833
2629
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1834
2630
  });
1835
2631
  }
1836
2632
  }
1837
2633
  }
1838
2634
  }
2635
+ /**
2636
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2637
+ *
2638
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2639
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2640
+ */
1839
2641
  sanitizeFabricInformations(fabricInfo) {
1840
2642
  return fabricInfo.map((info) => {
1841
2643
  return {
@@ -1849,6 +2651,12 @@ export class Matterbridge extends EventEmitter {
1849
2651
  };
1850
2652
  });
1851
2653
  }
2654
+ /**
2655
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2656
+ *
2657
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2658
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2659
+ */
1852
2660
  sanitizeSessionInformation(sessions) {
1853
2661
  return sessions
1854
2662
  .filter((session) => session.isPeerActive)
@@ -1875,7 +2683,21 @@ export class Matterbridge extends EventEmitter {
1875
2683
  };
1876
2684
  });
1877
2685
  }
2686
+ /**
2687
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2688
+ *
2689
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2690
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2691
+ */
2692
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1878
2693
  async setAggregatorReachability(aggregatorNode, reachable) {
2694
+ /*
2695
+ for (const child of aggregatorNode.parts) {
2696
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2697
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2698
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2699
+ }
2700
+ */
1879
2701
  }
1880
2702
  getVendorIdName = (vendorId) => {
1881
2703
  if (!vendorId)
@@ -1915,10 +2737,11 @@ export class Matterbridge extends EventEmitter {
1915
2737
  case 0x1488:
1916
2738
  vendorName = '(ShortcutLabsFlic)';
1917
2739
  break;
1918
- case 65521:
2740
+ case 65521: // 0xFFF1
1919
2741
  vendorName = '(MatterTest)';
1920
2742
  break;
1921
2743
  }
1922
2744
  return vendorName;
1923
2745
  };
1924
2746
  }
2747
+ //# sourceMappingURL=matterbridge.js.map