matterbridge 3.1.0-dev-20250627-2b5adba → 3.1.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 (187) hide show
  1. package/CHANGELOG.md +4 -8
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +91 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/clusters/export.d.ts +2 -0
  7. package/dist/clusters/export.d.ts.map +1 -0
  8. package/dist/clusters/export.js +2 -0
  9. package/dist/clusters/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +28 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +24 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +112 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/devices/export.d.ts +5 -0
  19. package/dist/devices/export.d.ts.map +1 -0
  20. package/dist/devices/export.js +2 -0
  21. package/dist/devices/export.js.map +1 -0
  22. package/dist/evse.d.ts +72 -0
  23. package/dist/evse.d.ts.map +1 -0
  24. package/dist/evse.js +70 -9
  25. package/dist/evse.js.map +1 -0
  26. package/dist/frontend.d.ts +285 -0
  27. package/dist/frontend.d.ts.map +1 -0
  28. package/dist/frontend.js +413 -16
  29. package/dist/frontend.js.map +1 -0
  30. package/dist/globalMatterbridge.d.ts +59 -0
  31. package/dist/globalMatterbridge.d.ts.map +1 -0
  32. package/dist/globalMatterbridge.js +47 -0
  33. package/dist/globalMatterbridge.js.map +1 -0
  34. package/dist/helpers.d.ts +48 -0
  35. package/dist/helpers.d.ts.map +1 -0
  36. package/dist/helpers.js +53 -0
  37. package/dist/helpers.js.map +1 -0
  38. package/dist/index.d.ts +38 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +32 -1
  41. package/dist/index.js.map +1 -0
  42. package/dist/laundryWasher.d.ts +243 -0
  43. package/dist/laundryWasher.d.ts.map +1 -0
  44. package/dist/laundryWasher.js +92 -7
  45. package/dist/laundryWasher.js.map +1 -0
  46. package/dist/logger/export.d.ts +2 -0
  47. package/dist/logger/export.d.ts.map +1 -0
  48. package/dist/logger/export.js +1 -0
  49. package/dist/logger/export.js.map +1 -0
  50. package/dist/matter/behaviors.d.ts +2 -0
  51. package/dist/matter/behaviors.d.ts.map +1 -0
  52. package/dist/matter/behaviors.js +2 -0
  53. package/dist/matter/behaviors.js.map +1 -0
  54. package/dist/matter/clusters.d.ts +2 -0
  55. package/dist/matter/clusters.d.ts.map +1 -0
  56. package/dist/matter/clusters.js +2 -0
  57. package/dist/matter/clusters.js.map +1 -0
  58. package/dist/matter/devices.d.ts +2 -0
  59. package/dist/matter/devices.d.ts.map +1 -0
  60. package/dist/matter/devices.js +2 -0
  61. package/dist/matter/devices.js.map +1 -0
  62. package/dist/matter/endpoints.d.ts +2 -0
  63. package/dist/matter/endpoints.d.ts.map +1 -0
  64. package/dist/matter/endpoints.js +2 -0
  65. package/dist/matter/endpoints.js.map +1 -0
  66. package/dist/matter/export.d.ts +5 -0
  67. package/dist/matter/export.d.ts.map +1 -0
  68. package/dist/matter/export.js +3 -0
  69. package/dist/matter/export.js.map +1 -0
  70. package/dist/matter/types.d.ts +3 -0
  71. package/dist/matter/types.d.ts.map +1 -0
  72. package/dist/matter/types.js +3 -0
  73. package/dist/matter/types.js.map +1 -0
  74. package/dist/matterbridge.d.ts +450 -0
  75. package/dist/matterbridge.d.ts.map +1 -0
  76. package/dist/matterbridge.js +810 -56
  77. package/dist/matterbridge.js.map +1 -0
  78. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  79. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  80. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  81. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  82. package/dist/matterbridgeBehaviors.d.ts +1334 -0
  83. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  84. package/dist/matterbridgeBehaviors.js +55 -1
  85. package/dist/matterbridgeBehaviors.js.map +1 -0
  86. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  87. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeDeviceTypes.js +579 -15
  89. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  90. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  91. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  92. package/dist/matterbridgeDynamicPlatform.js +36 -0
  93. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  94. package/dist/matterbridgeEndpoint.d.ts +1173 -0
  95. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  96. package/dist/matterbridgeEndpoint.js +1023 -41
  97. package/dist/matterbridgeEndpoint.js.map +1 -0
  98. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  99. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  100. package/dist/matterbridgeEndpointHelpers.js +322 -12
  101. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  102. package/dist/matterbridgePlatform.d.ts +310 -0
  103. package/dist/matterbridgePlatform.d.ts.map +1 -0
  104. package/dist/matterbridgePlatform.js +233 -0
  105. package/dist/matterbridgePlatform.js.map +1 -0
  106. package/dist/matterbridgeTypes.d.ts +184 -0
  107. package/dist/matterbridgeTypes.d.ts.map +1 -0
  108. package/dist/matterbridgeTypes.js +25 -0
  109. package/dist/matterbridgeTypes.js.map +1 -0
  110. package/dist/pluginManager.d.ts +291 -0
  111. package/dist/pluginManager.d.ts.map +1 -0
  112. package/dist/pluginManager.js +269 -3
  113. package/dist/pluginManager.js.map +1 -0
  114. package/dist/roboticVacuumCleaner.d.ts +104 -0
  115. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  116. package/dist/roboticVacuumCleaner.js +83 -6
  117. package/dist/roboticVacuumCleaner.js.map +1 -0
  118. package/dist/shelly.d.ts +174 -0
  119. package/dist/shelly.d.ts.map +1 -0
  120. package/dist/shelly.js +168 -7
  121. package/dist/shelly.js.map +1 -0
  122. package/dist/storage/export.d.ts +2 -0
  123. package/dist/storage/export.d.ts.map +1 -0
  124. package/dist/storage/export.js +1 -0
  125. package/dist/storage/export.js.map +1 -0
  126. package/dist/update.d.ts +59 -0
  127. package/dist/update.d.ts.map +1 -0
  128. package/dist/update.js +54 -0
  129. package/dist/update.js.map +1 -0
  130. package/dist/utils/colorUtils.d.ts +117 -0
  131. package/dist/utils/colorUtils.d.ts.map +1 -0
  132. package/dist/utils/colorUtils.js +263 -2
  133. package/dist/utils/colorUtils.js.map +1 -0
  134. package/dist/utils/commandLine.d.ts +59 -0
  135. package/dist/utils/commandLine.d.ts.map +1 -0
  136. package/dist/utils/commandLine.js +54 -0
  137. package/dist/utils/commandLine.js.map +1 -0
  138. package/dist/utils/copyDirectory.d.ts +33 -0
  139. package/dist/utils/copyDirectory.d.ts.map +1 -0
  140. package/dist/utils/copyDirectory.js +38 -1
  141. package/dist/utils/copyDirectory.js.map +1 -0
  142. package/dist/utils/createDirectory.d.ts +34 -0
  143. package/dist/utils/createDirectory.d.ts.map +1 -0
  144. package/dist/utils/createDirectory.js +33 -0
  145. package/dist/utils/createDirectory.js.map +1 -0
  146. package/dist/utils/createZip.d.ts +39 -0
  147. package/dist/utils/createZip.d.ts.map +1 -0
  148. package/dist/utils/createZip.js +47 -2
  149. package/dist/utils/createZip.js.map +1 -0
  150. package/dist/utils/deepCopy.d.ts +32 -0
  151. package/dist/utils/deepCopy.d.ts.map +1 -0
  152. package/dist/utils/deepCopy.js +39 -0
  153. package/dist/utils/deepCopy.js.map +1 -0
  154. package/dist/utils/deepEqual.d.ts +54 -0
  155. package/dist/utils/deepEqual.d.ts.map +1 -0
  156. package/dist/utils/deepEqual.js +72 -1
  157. package/dist/utils/deepEqual.js.map +1 -0
  158. package/dist/utils/export.d.ts +12 -0
  159. package/dist/utils/export.d.ts.map +1 -0
  160. package/dist/utils/export.js +1 -0
  161. package/dist/utils/export.js.map +1 -0
  162. package/dist/utils/hex.d.ts +49 -0
  163. package/dist/utils/hex.d.ts.map +1 -0
  164. package/dist/utils/hex.js +58 -0
  165. package/dist/utils/hex.js.map +1 -0
  166. package/dist/utils/isvalid.d.ts +103 -0
  167. package/dist/utils/isvalid.d.ts.map +1 -0
  168. package/dist/utils/isvalid.js +101 -0
  169. package/dist/utils/isvalid.js.map +1 -0
  170. package/dist/utils/network.d.ts +76 -0
  171. package/dist/utils/network.d.ts.map +1 -0
  172. package/dist/utils/network.js +83 -5
  173. package/dist/utils/network.js.map +1 -0
  174. package/dist/utils/spawn.d.ts +14 -0
  175. package/dist/utils/spawn.d.ts.map +1 -0
  176. package/dist/utils/spawn.js +18 -0
  177. package/dist/utils/spawn.js.map +1 -0
  178. package/dist/utils/wait.d.ts +56 -0
  179. package/dist/utils/wait.d.ts.map +1 -0
  180. package/dist/utils/wait.js +62 -9
  181. package/dist/utils/wait.js.map +1 -0
  182. package/dist/waterHeater.d.ts +106 -0
  183. package/dist/waterHeater.d.ts.map +1 -0
  184. package/dist/waterHeater.js +77 -2
  185. package/dist/waterHeater.js.map +1 -0
  186. package/npm-shrinkwrap.json +2 -2
  187. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,29 +1,125 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @created 2025-01-13
7
+ * @version 1.0.2
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2025, 2026, 2027 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
+ // Node modules
1
25
  import { createServer } from 'node:http';
2
26
  import https from 'node:https';
3
27
  import os from 'node:os';
4
28
  import path from 'node:path';
5
29
  import { promises as fs } from 'node:fs';
30
+ // Third-party modules
6
31
  import express from 'express';
7
32
  import WebSocket, { WebSocketServer } from 'ws';
8
33
  import multer from 'multer';
34
+ // AnsiLogger module
9
35
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from 'node-ansi-logger';
36
+ // @matter
10
37
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
11
38
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
39
+ // Matterbridge
12
40
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
13
41
  import { plg } from './matterbridgeTypes.js';
14
42
  import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
15
43
  import spawn from './utils/spawn.js';
44
+ /**
45
+ * Websocket message ID for logging.
46
+ *
47
+ * @constant {number}
48
+ */
16
49
  export const WS_ID_LOG = 0;
50
+ /**
51
+ * Websocket message ID indicating a refresh is needed.
52
+ *
53
+ * @constant {number}
54
+ */
17
55
  export const WS_ID_REFRESH_NEEDED = 1;
56
+ /**
57
+ * Websocket message ID indicating a restart is needed.
58
+ *
59
+ * @constant {number}
60
+ */
18
61
  export const WS_ID_RESTART_NEEDED = 2;
62
+ /**
63
+ * Websocket message ID indicating a cpu update.
64
+ *
65
+ * @constant {number}
66
+ */
19
67
  export const WS_ID_CPU_UPDATE = 3;
68
+ /**
69
+ * Websocket message ID indicating a memory update.
70
+ *
71
+ * @constant {number}
72
+ */
20
73
  export const WS_ID_MEMORY_UPDATE = 4;
74
+ /**
75
+ * Websocket message ID indicating an uptime update.
76
+ *
77
+ * @constant {number}
78
+ */
21
79
  export const WS_ID_UPTIME_UPDATE = 5;
80
+ /**
81
+ * Websocket message ID indicating a snackbar message.
82
+ *
83
+ * @constant {number}
84
+ */
22
85
  export const WS_ID_SNACKBAR = 6;
86
+ /**
87
+ * Websocket message ID indicating matterbridge has un update available.
88
+ *
89
+ * @constant {number}
90
+ */
23
91
  export const WS_ID_UPDATE_NEEDED = 7;
92
+ /**
93
+ * Websocket message ID indicating a state update.
94
+ *
95
+ * @constant {number}
96
+ */
24
97
  export const WS_ID_STATEUPDATE = 8;
98
+ /**
99
+ * Websocket message ID indicating to close a permanent snackbar message.
100
+ *
101
+ * @constant {number}
102
+ */
25
103
  export const WS_ID_CLOSE_SNACKBAR = 9;
104
+ /**
105
+ * Websocket message ID indicating a shelly system update.
106
+ * check:
107
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
108
+ * perform:
109
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
110
+ *
111
+ * @constant {number}
112
+ */
26
113
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
114
+ /**
115
+ * Websocket message ID indicating a shelly main update.
116
+ * check:
117
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
118
+ * perform:
119
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
120
+ *
121
+ * @constant {number}
122
+ */
27
123
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
28
124
  export class Frontend {
29
125
  matterbridge;
@@ -36,7 +132,7 @@ export class Frontend {
36
132
  webSocketServer;
37
133
  constructor(matterbridge) {
38
134
  this.matterbridge = matterbridge;
39
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
135
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
40
136
  }
41
137
  set logLevel(logLevel) {
42
138
  this.log.logLevel = logLevel;
@@ -44,13 +140,43 @@ export class Frontend {
44
140
  async start(port = 8283) {
45
141
  this.port = port;
46
142
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
143
+ // Initialize multer with the upload directory
47
144
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
48
145
  await fs.mkdir(uploadDir, { recursive: true });
49
146
  const upload = multer({ dest: uploadDir });
147
+ // Create the express app that serves the frontend
50
148
  this.expressApp = express();
149
+ // Inject logging/debug wrapper for route/middleware registration
150
+ /*
151
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
152
+ for (const method of methods) {
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
157
+ try {
158
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
159
+ return original(path, ...rest);
160
+ } catch (err) {
161
+ console.error(`[ERROR] Failed to register route: ${path}`);
162
+ throw err;
163
+ }
164
+ };
165
+ }
166
+ */
167
+ // Log all requests to the server for debugging
168
+ /*
169
+ this.expressApp.use((req, res, next) => {
170
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
171
+ next();
172
+ });
173
+ */
174
+ // Serve static files from '/static' endpoint
51
175
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
52
176
  if (!hasParameter('ssl')) {
177
+ // Create an HTTP server and attach the express app
53
178
  this.httpServer = createServer(this.expressApp);
179
+ // Listen on the specified port
54
180
  if (hasParameter('ingress')) {
55
181
  this.httpServer.listen(this.port, '0.0.0.0', () => {
56
182
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -64,6 +190,7 @@ export class Frontend {
64
190
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
65
191
  });
66
192
  }
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
194
  this.httpServer.on('error', (error) => {
68
195
  this.log.error(`Frontend http server error listening on ${this.port}`);
69
196
  switch (error.code) {
@@ -79,6 +206,7 @@ export class Frontend {
79
206
  });
80
207
  }
81
208
  else {
209
+ // Load the SSL certificate, the private key and optionally the CA certificate
82
210
  let cert;
83
211
  try {
84
212
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -106,7 +234,9 @@ export class Frontend {
106
234
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
107
235
  }
108
236
  const serverOptions = { cert, key, ca };
237
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
109
238
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
239
+ // Listen on the specified port
110
240
  if (hasParameter('ingress')) {
111
241
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
112
242
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -120,6 +250,7 @@ export class Frontend {
120
250
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
121
251
  });
122
252
  }
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
254
  this.httpsServer.on('error', (error) => {
124
255
  this.log.error(`Frontend https server error listening on ${this.port}`);
125
256
  switch (error.code) {
@@ -136,16 +267,18 @@ export class Frontend {
136
267
  }
137
268
  if (this.initializeError)
138
269
  return;
270
+ // Create a WebSocket server and attach it to the http or https server
139
271
  const wssPort = this.port;
140
272
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
141
273
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
142
274
  this.webSocketServer.on('connection', (ws, request) => {
143
275
  const clientIp = request.socket.remoteAddress;
144
- let callbackLogLevel = "notice";
145
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
146
- callbackLogLevel = "info";
147
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
148
- callbackLogLevel = "debug";
276
+ // Set the global logger callback for the WebSocketServer
277
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
278
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
279
+ callbackLogLevel = "info" /* LogLevel.INFO */;
280
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
281
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
149
282
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
150
283
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
151
284
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -179,6 +312,7 @@ export class Frontend {
179
312
  this.webSocketServer.on('error', (ws, error) => {
180
313
  this.log.error(`WebSocketServer error: ${error}`);
181
314
  });
315
+ // Subscribe to cli events
182
316
  const { cliEmitter } = await import('./cli.js');
183
317
  cliEmitter.removeAllListeners();
184
318
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -190,6 +324,8 @@ export class Frontend {
190
324
  cliEmitter.on('cpu', (cpuUsage) => {
191
325
  this.wssSendCpuUpdate(cpuUsage);
192
326
  });
327
+ // Endpoint to validate login code
328
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
193
329
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
194
330
  const { password } = req.body;
195
331
  this.log.debug('The frontend sent /api/login', password);
@@ -208,23 +344,27 @@ export class Frontend {
208
344
  this.log.warn('/api/login error wrong password');
209
345
  res.json({ valid: false });
210
346
  }
347
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
211
348
  }
212
349
  catch (error) {
213
350
  this.log.error('/api/login error getting password');
214
351
  res.json({ valid: false });
215
352
  }
216
353
  });
354
+ // Endpoint to provide health check for docker
217
355
  this.expressApp.get('/health', (req, res) => {
218
356
  this.log.debug('Express received /health');
219
357
  const healthStatus = {
220
- status: 'ok',
221
- uptime: process.uptime(),
222
- timestamp: new Date().toISOString(),
358
+ status: 'ok', // Indicate service is healthy
359
+ uptime: process.uptime(), // Server uptime in seconds
360
+ timestamp: new Date().toISOString(), // Current timestamp
223
361
  };
224
362
  res.status(200).json(healthStatus);
225
363
  });
364
+ // Endpoint to provide memory usage details
226
365
  this.expressApp.get('/memory', async (req, res) => {
227
366
  this.log.debug('Express received /memory');
367
+ // Memory usage from process
228
368
  const memoryUsageRaw = process.memoryUsage();
229
369
  const memoryUsage = {
230
370
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -233,10 +373,13 @@ export class Frontend {
233
373
  external: this.formatMemoryUsage(memoryUsageRaw.external),
234
374
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
235
375
  };
376
+ // V8 heap statistics
236
377
  const { default: v8 } = await import('node:v8');
237
378
  const heapStatsRaw = v8.getHeapStatistics();
238
379
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
380
+ // Format heapStats
239
381
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
382
+ // Format heapSpaces
240
383
  const heapSpaces = heapSpacesRaw.map((space) => ({
241
384
  ...space,
242
385
  space_size: this.formatMemoryUsage(space.space_size),
@@ -254,19 +397,23 @@ export class Frontend {
254
397
  };
255
398
  res.status(200).json(memoryReport);
256
399
  });
400
+ // Endpoint to provide settings
257
401
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
258
402
  this.log.debug('The frontend sent /api/settings');
259
403
  res.json(await this.getApiSettings());
260
404
  });
405
+ // Endpoint to provide plugins
261
406
  this.expressApp.get('/api/plugins', async (req, res) => {
262
407
  this.log.debug('The frontend sent /api/plugins');
263
408
  res.json(this.getBaseRegisteredPlugins());
264
409
  });
410
+ // Endpoint to provide devices
265
411
  this.expressApp.get('/api/devices', async (req, res) => {
266
412
  this.log.debug('The frontend sent /api/devices');
267
413
  const devices = await this.getDevices();
268
414
  res.json(devices);
269
415
  });
416
+ // Endpoint to view the matterbridge log
270
417
  this.expressApp.get('/api/view-mblog', async (req, res) => {
271
418
  this.log.debug('The frontend sent /api/view-mblog');
272
419
  try {
@@ -279,6 +426,7 @@ export class Frontend {
279
426
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
280
427
  }
281
428
  });
429
+ // Endpoint to view the matter.js log
282
430
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
283
431
  this.log.debug('The frontend sent /api/view-mjlog');
284
432
  try {
@@ -291,6 +439,7 @@ export class Frontend {
291
439
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
292
440
  }
293
441
  });
442
+ // Endpoint to view the shelly log
294
443
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
295
444
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
296
445
  try {
@@ -303,9 +452,11 @@ export class Frontend {
303
452
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
304
453
  }
305
454
  });
455
+ // Endpoint to download the matterbridge log
306
456
  this.expressApp.get('/api/download-mblog', async (req, res) => {
307
457
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
308
458
  try {
459
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
309
460
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
310
461
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
311
462
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
@@ -315,6 +466,7 @@ export class Frontend {
315
466
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
316
467
  }
317
468
  res.type('text/plain');
469
+ // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
318
470
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
319
471
  if (error) {
320
472
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -322,9 +474,11 @@ export class Frontend {
322
474
  }
323
475
  });
324
476
  });
477
+ // Endpoint to download the matter log
325
478
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
326
479
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
327
480
  try {
481
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
328
482
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
329
483
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
330
484
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
@@ -341,9 +495,11 @@ export class Frontend {
341
495
  }
342
496
  });
343
497
  });
498
+ // Endpoint to download the shelly log
344
499
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
345
500
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
346
501
  try {
502
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
347
503
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
348
504
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
349
505
  await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
@@ -360,6 +516,7 @@ export class Frontend {
360
516
  }
361
517
  });
362
518
  });
519
+ // Endpoint to download the matterbridge storage directory
363
520
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
364
521
  this.log.debug('The frontend sent /api/download-mbstorage');
365
522
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -370,6 +527,7 @@ export class Frontend {
370
527
  }
371
528
  });
372
529
  });
530
+ // Endpoint to download the matter storage file
373
531
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
374
532
  this.log.debug('The frontend sent /api/download-mjstorage');
375
533
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -380,6 +538,7 @@ export class Frontend {
380
538
  }
381
539
  });
382
540
  });
541
+ // Endpoint to download the matterbridge plugin directory
383
542
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
384
543
  this.log.debug('The frontend sent /api/download-pluginstorage');
385
544
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -390,6 +549,7 @@ export class Frontend {
390
549
  }
391
550
  });
392
551
  });
552
+ // Endpoint to download the matterbridge plugin config files
393
553
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
394
554
  this.log.debug('The frontend sent /api/download-pluginconfig');
395
555
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -400,6 +560,7 @@ export class Frontend {
400
560
  }
401
561
  });
402
562
  });
563
+ // Endpoint to download the matterbridge backup (created with the backup command)
403
564
  this.expressApp.get('/api/download-backup', async (req, res) => {
404
565
  this.log.debug('The frontend sent /api/download-backup');
405
566
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -409,6 +570,7 @@ export class Frontend {
409
570
  }
410
571
  });
411
572
  });
573
+ // Endpoint to upload a package
412
574
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
413
575
  const { filename } = req.body;
414
576
  const file = req.file;
@@ -418,10 +580,13 @@ export class Frontend {
418
580
  return;
419
581
  }
420
582
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
583
+ // Define the path where the plugin file will be saved
421
584
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
422
585
  try {
586
+ // Move the uploaded file to the specified path
423
587
  await fs.rename(file.path, filePath);
424
588
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
589
+ // Install the plugin package
425
590
  if (filename.endsWith('.tgz')) {
426
591
  await spawn.spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
427
592
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -440,6 +605,7 @@ export class Frontend {
440
605
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
441
606
  }
442
607
  });
608
+ // Fallback for routing (must be the last route)
443
609
  this.expressApp.use((req, res) => {
444
610
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
445
611
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -448,12 +614,15 @@ export class Frontend {
448
614
  }
449
615
  async stop() {
450
616
  this.log.debug('Stopping the frontend...');
617
+ // Remove listeners from the express app
451
618
  if (this.expressApp) {
452
619
  this.expressApp.removeAllListeners();
453
620
  this.expressApp = undefined;
454
621
  this.log.debug('Frontend app closed successfully');
455
622
  }
623
+ // Close the WebSocket server
456
624
  if (this.webSocketServer) {
625
+ // Close all active connections
457
626
  this.webSocketServer.clients.forEach((client) => {
458
627
  if (client.readyState === WebSocket.OPEN) {
459
628
  client.close();
@@ -473,6 +642,7 @@ export class Frontend {
473
642
  this.webSocketServer.removeAllListeners();
474
643
  this.webSocketServer = undefined;
475
644
  }
645
+ // Close the http server
476
646
  if (this.httpServer) {
477
647
  await withTimeout(new Promise((resolve) => {
478
648
  this.httpServer?.close((error) => {
@@ -489,6 +659,7 @@ export class Frontend {
489
659
  this.httpServer = undefined;
490
660
  this.log.debug('Frontend http server closed successfully');
491
661
  }
662
+ // Close the https server
492
663
  if (this.httpsServer) {
493
664
  await withTimeout(new Promise((resolve) => {
494
665
  this.httpsServer?.close((error) => {
@@ -507,6 +678,7 @@ export class Frontend {
507
678
  }
508
679
  this.log.debug('Frontend stopped successfully');
509
680
  }
681
+ // Function to format bytes to KB, MB, or GB
510
682
  formatMemoryUsage = (bytes) => {
511
683
  if (bytes >= 1024 ** 3) {
512
684
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -518,6 +690,7 @@ export class Frontend {
518
690
  return `${(bytes / 1024).toFixed(2)} KB`;
519
691
  }
520
692
  };
693
+ // Function to format system uptime with only the most significant unit
521
694
  formatOsUpTime = (seconds) => {
522
695
  if (seconds >= 86400) {
523
696
  const days = Math.floor(seconds / 86400);
@@ -533,8 +706,14 @@ export class Frontend {
533
706
  }
534
707
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
535
708
  };
709
+ /**
710
+ * Retrieves the api settings data.
711
+ *
712
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
713
+ */
536
714
  async getApiSettings() {
537
715
  const { lastCpuUsage } = await import('./cli.js');
716
+ // Update the system information
538
717
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
539
718
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
540
719
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -543,6 +722,7 @@ export class Frontend {
543
722
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
544
723
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
545
724
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
725
+ // Update the matterbridge information
546
726
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
547
727
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
548
728
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -561,6 +741,12 @@ export class Frontend {
561
741
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
562
742
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
563
743
  }
744
+ /**
745
+ * Retrieves the reachable attribute.
746
+ *
747
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
748
+ * @returns {boolean} The reachable attribute.
749
+ */
564
750
  getReachability(device) {
565
751
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
566
752
  return false;
@@ -570,6 +756,12 @@ export class Frontend {
570
756
  return true;
571
757
  return false;
572
758
  }
759
+ /**
760
+ * Retrieves the power source attribute.
761
+ *
762
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
763
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
764
+ */
573
765
  getPowerSource(endpoint) {
574
766
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
575
767
  return undefined;
@@ -585,13 +777,21 @@ export class Frontend {
585
777
  }
586
778
  return;
587
779
  };
780
+ // Root endpoint
588
781
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
589
782
  return powerSource(endpoint);
783
+ // Child endpoints
590
784
  for (const child of endpoint.getChildEndpoints()) {
591
785
  if (child.hasClusterServer(PowerSource.Cluster.id))
592
786
  return powerSource(child);
593
787
  }
594
788
  }
789
+ /**
790
+ * Retrieves the cluster text description from a given device.
791
+ *
792
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
793
+ * @returns {string} The attributes description of the cluster servers in the device.
794
+ */
595
795
  getClusterTextFromDevice(device) {
596
796
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
597
797
  return '';
@@ -632,7 +832,19 @@ export class Frontend {
632
832
  };
633
833
  let attributes = '';
634
834
  let supportedModes = [];
835
+ /*
836
+ Object.keys(device.behaviors.supported).forEach((clusterName) => {
837
+ const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
838
+ // console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
839
+ if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
840
+ Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
841
+ // console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
842
+ });
843
+ }
844
+ });
845
+ */
635
846
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
847
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
636
848
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
637
849
  return;
638
850
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -724,8 +936,14 @@ export class Frontend {
724
936
  if (clusterName === 'userLabel' && attributeName === 'labelList')
725
937
  attributes += `${getUserLabel(device)} `;
726
938
  });
939
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
727
940
  return attributes.trimStart().trimEnd();
728
941
  }
942
+ /**
943
+ * Retrieves the base registered plugins sanitized for res.json().
944
+ *
945
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
946
+ */
729
947
  getBaseRegisteredPlugins() {
730
948
  const baseRegisteredPlugins = [];
731
949
  for (const plugin of this.matterbridge.plugins) {
@@ -764,11 +982,19 @@ export class Frontend {
764
982
  }
765
983
  return baseRegisteredPlugins;
766
984
  }
985
+ /**
986
+ * Retrieves the devices from Matterbridge.
987
+ *
988
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
989
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
990
+ */
767
991
  async getDevices(pluginName) {
768
992
  const devices = [];
769
993
  this.matterbridge.devices.forEach(async (device) => {
994
+ // Filter by pluginName if provided
770
995
  if (pluginName && pluginName !== device.plugin)
771
996
  return;
997
+ // Check if the device has the required properties
772
998
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
773
999
  return;
774
1000
  const cluster = this.getClusterTextFromDevice(device);
@@ -788,22 +1014,37 @@ export class Frontend {
788
1014
  });
789
1015
  return devices;
790
1016
  }
1017
+ /**
1018
+ * Retrieves the clusters from a given plugin and endpoint number.
1019
+ *
1020
+ * Response for /api/clusters
1021
+ *
1022
+ * @param {string} pluginName - The name of the plugin.
1023
+ * @param {number} endpointNumber - The endpoint number.
1024
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1025
+ */
791
1026
  getClusters(pluginName, endpointNumber) {
792
1027
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
793
1028
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
794
1029
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
795
1030
  return;
796
1031
  }
1032
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1033
+ // Get the device types from the main endpoint
797
1034
  const deviceTypes = [];
798
1035
  const clusters = [];
799
1036
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
800
1037
  deviceTypes.push(d.deviceType);
801
1038
  });
1039
+ // Get the clusters from the main endpoint
802
1040
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
803
1041
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
804
1042
  return;
805
1043
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
806
1044
  return;
1045
+ // console.log(
1046
+ // `${idn}${endpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
1047
+ // );
807
1048
  clusters.push({
808
1049
  endpoint: endpoint.number.toString(),
809
1050
  id: 'main',
@@ -816,12 +1057,18 @@ export class Frontend {
816
1057
  attributeLocalValue: attributeValue,
817
1058
  });
818
1059
  });
1060
+ // Get the child endpoints
819
1061
  const childEndpoints = endpoint.getChildEndpoints();
1062
+ // if (childEndpoints.length === 0) {
1063
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1064
+ // }
820
1065
  childEndpoints.forEach((childEndpoint) => {
821
1066
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
822
1067
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
823
1068
  return;
824
1069
  }
1070
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1071
+ // Get the device types of the child endpoint
825
1072
  const deviceTypes = [];
826
1073
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
827
1074
  deviceTypes.push(d.deviceType);
@@ -831,9 +1078,12 @@ export class Frontend {
831
1078
  return;
832
1079
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
833
1080
  return;
1081
+ // console.log(
1082
+ // `${idn}${childEndpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
1083
+ // );
834
1084
  clusters.push({
835
1085
  endpoint: childEndpoint.number.toString(),
836
- id: childEndpoint.maybeId ?? 'null',
1086
+ id: childEndpoint.maybeId ?? 'null', // Never happens
837
1087
  deviceTypes,
838
1088
  clusterName: capitalizeFirstLetter(clusterName),
839
1089
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -846,6 +1096,13 @@ export class Frontend {
846
1096
  });
847
1097
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
848
1098
  }
1099
+ /**
1100
+ * Handles incoming websocket messages for the Matterbridge frontend.
1101
+ *
1102
+ * @param {WebSocket} client - The websocket client that sent the message.
1103
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1104
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1105
+ */
849
1106
  async wsMessageHandler(client, message) {
850
1107
  let data;
851
1108
  try {
@@ -892,32 +1149,41 @@ export class Frontend {
892
1149
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
893
1150
  const packageName = data.params.packageName.replace(/@.*$/, '');
894
1151
  if (data.params.restart === false && packageName !== 'matterbridge') {
1152
+ // The install comes from InstallPlugins
895
1153
  this.matterbridge.plugins
896
1154
  .add(packageName)
897
1155
  .then((plugin) => {
898
1156
  if (plugin) {
1157
+ // The plugin is not registered
899
1158
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
900
1159
  this.matterbridge.plugins
901
1160
  .load(plugin, true, 'The plugin has been added', true)
1161
+ // eslint-disable-next-line promise/no-nesting
902
1162
  .then(() => {
903
1163
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
904
1164
  this.wssSendRefreshRequired('plugins');
905
1165
  return;
906
1166
  })
1167
+ // eslint-disable-next-line promise/no-nesting
907
1168
  .catch((_error) => {
1169
+ //
908
1170
  });
909
1171
  }
910
1172
  else {
1173
+ // The plugin is already registered
911
1174
  this.wssSendSnackbarMessage(`Restart required`, 0);
912
1175
  this.wssSendRefreshRequired('plugins');
913
1176
  this.wssSendRestartRequired();
914
1177
  }
915
1178
  return;
916
1179
  })
1180
+ // eslint-disable-next-line promise/no-nesting
917
1181
  .catch((_error) => {
1182
+ //
918
1183
  });
919
1184
  }
920
1185
  else {
1186
+ // The package is matterbridge
921
1187
  if (this.matterbridge.restartMode !== '') {
922
1188
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
923
1189
  this.matterbridge.shutdownProcess();
@@ -940,6 +1206,7 @@ export class Frontend {
940
1206
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
941
1207
  return;
942
1208
  }
1209
+ // The package is a plugin
943
1210
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
944
1211
  if (plugin) {
945
1212
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -948,6 +1215,7 @@ export class Frontend {
948
1215
  this.wssSendRefreshRequired('plugins');
949
1216
  this.wssSendRefreshRequired('devices');
950
1217
  }
1218
+ // Uninstall the package
951
1219
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
952
1220
  spawn
953
1221
  .spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -988,6 +1256,7 @@ export class Frontend {
988
1256
  return;
989
1257
  })
990
1258
  .catch((_error) => {
1259
+ //
991
1260
  });
992
1261
  }
993
1262
  else {
@@ -1034,6 +1303,7 @@ export class Frontend {
1034
1303
  return;
1035
1304
  })
1036
1305
  .catch((_error) => {
1306
+ //
1037
1307
  });
1038
1308
  }
1039
1309
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1271,22 +1541,22 @@ export class Frontend {
1271
1541
  if (isValidString(data.params.value, 4)) {
1272
1542
  this.log.debug('Matterbridge logger level:', data.params.value);
1273
1543
  if (data.params.value === 'Debug') {
1274
- await this.matterbridge.setLogLevel("debug");
1544
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1275
1545
  }
1276
1546
  else if (data.params.value === 'Info') {
1277
- await this.matterbridge.setLogLevel("info");
1547
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1278
1548
  }
1279
1549
  else if (data.params.value === 'Notice') {
1280
- await this.matterbridge.setLogLevel("notice");
1550
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1281
1551
  }
1282
1552
  else if (data.params.value === 'Warn') {
1283
- await this.matterbridge.setLogLevel("warn");
1553
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1284
1554
  }
1285
1555
  else if (data.params.value === 'Error') {
1286
- await this.matterbridge.setLogLevel("error");
1556
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1287
1557
  }
1288
1558
  else if (data.params.value === 'Fatal') {
1289
- await this.matterbridge.setLogLevel("fatal");
1559
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1290
1560
  }
1291
1561
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1292
1562
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1297,6 +1567,7 @@ export class Frontend {
1297
1567
  this.log.debug('Matterbridge file log:', data.params.value);
1298
1568
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1299
1569
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1570
+ // Create the file logger for matterbridge
1300
1571
  if (data.params.value)
1301
1572
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1302
1573
  else
@@ -1461,15 +1732,19 @@ export class Frontend {
1461
1732
  return;
1462
1733
  }
1463
1734
  const config = plugin.configJson;
1735
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1464
1736
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1737
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1465
1738
  if (select === 'serial')
1466
1739
  this.log.info(`Selected device serial ${data.params.serial}`);
1467
1740
  if (select === 'name')
1468
1741
  this.log.info(`Selected device name ${data.params.name}`);
1469
1742
  if (config && select && (select === 'serial' || select === 'name')) {
1743
+ // Remove postfix from the serial if it exists
1470
1744
  if (config.postfix) {
1471
1745
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1472
1746
  }
1747
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1473
1748
  if (isValidArray(config.whiteList, 1)) {
1474
1749
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1475
1750
  config.whiteList.push(data.params.serial);
@@ -1478,6 +1753,7 @@ export class Frontend {
1478
1753
  config.whiteList.push(data.params.name);
1479
1754
  }
1480
1755
  }
1756
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1481
1757
  if (isValidArray(config.blackList, 1)) {
1482
1758
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1483
1759
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1507,7 +1783,9 @@ export class Frontend {
1507
1783
  return;
1508
1784
  }
1509
1785
  const config = plugin.configJson;
1786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1510
1787
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1788
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1511
1789
  if (select === 'serial')
1512
1790
  this.log.info(`Unselected device serial ${data.params.serial}`);
1513
1791
  if (select === 'name')
@@ -1516,6 +1794,7 @@ export class Frontend {
1516
1794
  if (config.postfix) {
1517
1795
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1518
1796
  }
1797
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1519
1798
  if (isValidArray(config.whiteList, 1)) {
1520
1799
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1521
1800
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1524,6 +1803,7 @@ export class Frontend {
1524
1803
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1525
1804
  }
1526
1805
  }
1806
+ // Add the serial to the blackList
1527
1807
  if (isValidArray(config.blackList)) {
1528
1808
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1529
1809
  config.blackList.push(data.params.serial);
@@ -1556,114 +1836,230 @@ export class Frontend {
1556
1836
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1557
1837
  }
1558
1838
  }
1839
+ /**
1840
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1841
+ *
1842
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1843
+ * @param {string} time - The time string of the message
1844
+ * @param {string} name - The logger name of the message
1845
+ * @param {string} message - The content of the message.
1846
+ *
1847
+ * @remarks
1848
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1849
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1850
+ * The function sends the message to all connected clients.
1851
+ */
1559
1852
  wssSendMessage(level, time, name, message) {
1560
1853
  if (!level || !time || !name || !message)
1561
1854
  return;
1855
+ // Remove ANSI escape codes from the message
1856
+ // eslint-disable-next-line no-control-regex
1562
1857
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1858
+ // Remove leading asterisks from the message
1563
1859
  message = message.replace(/^\*+/, '');
1860
+ // Replace all occurrences of \t and \n
1564
1861
  message = message.replace(/[\t\n]/g, '');
1862
+ // Remove non-printable characters
1863
+ // eslint-disable-next-line no-control-regex
1565
1864
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1865
+ // Replace all occurrences of \" with "
1566
1866
  message = message.replace(/\\"/g, '"');
1867
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1567
1868
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1869
+ // Define the maximum allowed length for continuous characters without a space
1568
1870
  const maxContinuousLength = 100;
1569
1871
  const keepStartLength = 20;
1570
1872
  const keepEndLength = 20;
1873
+ // Split the message into words
1571
1874
  message = message
1572
1875
  .split(' ')
1573
1876
  .map((word) => {
1877
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1574
1878
  if (word.length > maxContinuousLength) {
1575
1879
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1576
1880
  }
1577
1881
  return word;
1578
1882
  })
1579
1883
  .join(' ');
1884
+ // Send the message to all connected clients
1580
1885
  this.webSocketServer?.clients.forEach((client) => {
1581
1886
  if (client.readyState === WebSocket.OPEN) {
1582
1887
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1583
1888
  }
1584
1889
  });
1585
1890
  }
1891
+ /**
1892
+ * Sends a need to refresh WebSocket message to all connected clients.
1893
+ *
1894
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1895
+ * possible values:
1896
+ * - 'matterbridgeLatestVersion'
1897
+ * - 'matterbridgeAdvertise'
1898
+ * - 'online'
1899
+ * - 'offline'
1900
+ * - 'reachability'
1901
+ * - 'settings'
1902
+ * - 'plugins'
1903
+ * - 'pluginsRestart'
1904
+ * - 'devices'
1905
+ * - 'fabrics'
1906
+ * - 'sessions'
1907
+ */
1586
1908
  wssSendRefreshRequired(changed = null) {
1587
1909
  this.log.debug('Sending a refresh required message to all connected clients');
1910
+ // Send the message to all connected clients
1588
1911
  this.webSocketServer?.clients.forEach((client) => {
1589
1912
  if (client.readyState === WebSocket.OPEN) {
1590
1913
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1591
1914
  }
1592
1915
  });
1593
1916
  }
1917
+ /**
1918
+ * Sends a need to restart WebSocket message to all connected clients.
1919
+ *
1920
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
1921
+ */
1594
1922
  wssSendRestartRequired(snackbar = true) {
1595
1923
  this.log.debug('Sending a restart required message to all connected clients');
1596
1924
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1597
1925
  if (snackbar === true)
1598
1926
  this.wssSendSnackbarMessage(`Restart required`, 0);
1927
+ // Send the message to all connected clients
1599
1928
  this.webSocketServer?.clients.forEach((client) => {
1600
1929
  if (client.readyState === WebSocket.OPEN) {
1601
1930
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1602
1931
  }
1603
1932
  });
1604
1933
  }
1934
+ /**
1935
+ * Sends a need to update WebSocket message to all connected clients.
1936
+ *
1937
+ */
1605
1938
  wssSendUpdateRequired() {
1606
1939
  this.log.debug('Sending an update required message to all connected clients');
1607
1940
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1941
+ // Send the message to all connected clients
1608
1942
  this.webSocketServer?.clients.forEach((client) => {
1609
1943
  if (client.readyState === WebSocket.OPEN) {
1610
1944
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1611
1945
  }
1612
1946
  });
1613
1947
  }
1948
+ /**
1949
+ * Sends a cpu update message to all connected clients.
1950
+ *
1951
+ * @param {number} cpuUsage - The CPU usage percentage to send.
1952
+ */
1614
1953
  wssSendCpuUpdate(cpuUsage) {
1615
1954
  if (hasParameter('debug'))
1616
1955
  this.log.debug('Sending a cpu update message to all connected clients');
1956
+ // Send the message to all connected clients
1617
1957
  this.webSocketServer?.clients.forEach((client) => {
1618
1958
  if (client.readyState === WebSocket.OPEN) {
1619
1959
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1620
1960
  }
1621
1961
  });
1622
1962
  }
1963
+ /**
1964
+ * Sends a memory update message to all connected clients.
1965
+ *
1966
+ * @param {string} totalMemory - The total memory in bytes.
1967
+ * @param {string} freeMemory - The free memory in bytes.
1968
+ * @param {string} rss - The resident set size in bytes.
1969
+ * @param {string} heapTotal - The total heap memory in bytes.
1970
+ * @param {string} heapUsed - The used heap memory in bytes.
1971
+ * @param {string} external - The external memory in bytes.
1972
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
1973
+ */
1623
1974
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1624
1975
  if (hasParameter('debug'))
1625
1976
  this.log.debug('Sending a memory update message to all connected clients');
1977
+ // Send the message to all connected clients
1626
1978
  this.webSocketServer?.clients.forEach((client) => {
1627
1979
  if (client.readyState === WebSocket.OPEN) {
1628
1980
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1629
1981
  }
1630
1982
  });
1631
1983
  }
1984
+ /**
1985
+ * Sends an uptime update message to all connected clients.
1986
+ *
1987
+ * @param {string} systemUptime - The system uptime in a human-readable format.
1988
+ * @param {string} processUptime - The process uptime in a human-readable format.
1989
+ */
1632
1990
  wssSendUptimeUpdate(systemUptime, processUptime) {
1633
1991
  if (hasParameter('debug'))
1634
1992
  this.log.debug('Sending a uptime update message to all connected clients');
1993
+ // Send the message to all connected clients
1635
1994
  this.webSocketServer?.clients.forEach((client) => {
1636
1995
  if (client.readyState === WebSocket.OPEN) {
1637
1996
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1638
1997
  }
1639
1998
  });
1640
1999
  }
2000
+ /**
2001
+ * Sends an open snackbar message to all connected clients.
2002
+ *
2003
+ * @param {string} message - The message to send.
2004
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
2005
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2006
+ */
1641
2007
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1642
2008
  this.log.debug('Sending a snackbar message to all connected clients');
2009
+ // Send the message to all connected clients
1643
2010
  this.webSocketServer?.clients.forEach((client) => {
1644
2011
  if (client.readyState === WebSocket.OPEN) {
1645
2012
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1646
2013
  }
1647
2014
  });
1648
2015
  }
2016
+ /**
2017
+ * Sends a close snackbar message to all connected clients.
2018
+ *
2019
+ * @param {string} message - The message to send.
2020
+ */
1649
2021
  wssSendCloseSnackbarMessage(message) {
1650
2022
  this.log.debug('Sending a close snackbar message to all connected clients');
2023
+ // Send the message to all connected clients
1651
2024
  this.webSocketServer?.clients.forEach((client) => {
1652
2025
  if (client.readyState === WebSocket.OPEN) {
1653
2026
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1654
2027
  }
1655
2028
  });
1656
2029
  }
2030
+ /**
2031
+ * Sends an attribute update message to all connected WebSocket clients.
2032
+ *
2033
+ * @param {string | undefined} plugin - The name of the plugin.
2034
+ * @param {string | undefined} serialNumber - The serial number of the device.
2035
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2036
+ * @param {string} cluster - The cluster name where the attribute belongs.
2037
+ * @param {string} attribute - The name of the attribute that changed.
2038
+ * @param {number | string | boolean} value - The new value of the attribute.
2039
+ *
2040
+ * @remarks
2041
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2042
+ * with the updated attribute information.
2043
+ */
1657
2044
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1658
2045
  this.log.debug('Sending an attribute update message to all connected clients');
2046
+ // Send the message to all connected clients
1659
2047
  this.webSocketServer?.clients.forEach((client) => {
1660
2048
  if (client.readyState === WebSocket.OPEN) {
1661
2049
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1662
2050
  }
1663
2051
  });
1664
2052
  }
2053
+ /**
2054
+ * Sends a message to all connected clients.
2055
+ *
2056
+ * @param {number} id - The message id.
2057
+ * @param {string} method - The message method.
2058
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
2059
+ */
1665
2060
  wssBroadcastMessage(id, method, params) {
1666
2061
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2062
+ // Send the message to all connected clients
1667
2063
  this.webSocketServer?.clients.forEach((client) => {
1668
2064
  if (client.readyState === WebSocket.OPEN) {
1669
2065
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1671,3 +2067,4 @@ export class Frontend {
1671
2067
  });
1672
2068
  }
1673
2069
  }
2070
+ //# sourceMappingURL=frontend.js.map