matterbridge 3.0.6 → 3.0.7-dev-20250615-2a6da14

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 +20 -0
  2. package/README-DEV.md +66 -35
  3. package/README.md +1 -1
  4. package/dist/cli.js +2 -62
  5. package/dist/clusters/export.js +1 -0
  6. package/dist/defaultConfigSchema.js +0 -23
  7. package/dist/deviceManager.js +1 -94
  8. package/dist/devices/export.js +4 -0
  9. package/dist/evse.js +9 -65
  10. package/dist/frontend.js +16 -374
  11. package/dist/globalMatterbridge.js +0 -20
  12. package/dist/helpers.js +0 -51
  13. package/dist/index.js +1 -28
  14. package/dist/laundryWasher.js +7 -92
  15. package/dist/logger/export.js +0 -1
  16. package/dist/matter/behaviors.js +0 -2
  17. package/dist/matter/clusters.js +0 -2
  18. package/dist/matter/devices.js +0 -2
  19. package/dist/matter/endpoints.js +0 -2
  20. package/dist/matter/export.js +0 -2
  21. package/dist/matter/types.js +0 -2
  22. package/dist/matterbridge.js +46 -748
  23. package/dist/matterbridgeAccessoryPlatform.js +0 -34
  24. package/dist/matterbridgeBehaviors.js +1 -54
  25. package/dist/matterbridgeDeviceTypes.js +15 -578
  26. package/dist/matterbridgeDynamicPlatform.js +0 -34
  27. package/dist/matterbridgeEndpoint.js +43 -997
  28. package/dist/matterbridgeEndpointHelpers.js +10 -204
  29. package/dist/matterbridgePlatform.js +7 -225
  30. package/dist/matterbridgeTypes.js +0 -24
  31. package/dist/pluginManager.js +3 -269
  32. package/dist/roboticVacuumCleaner.js +6 -81
  33. package/dist/shelly.js +7 -155
  34. package/dist/storage/export.js +0 -1
  35. package/dist/update.js +0 -53
  36. package/dist/utils/colorUtils.js +2 -205
  37. package/dist/utils/commandLine.js +0 -53
  38. package/dist/utils/copyDirectory.js +1 -37
  39. package/dist/utils/createDirectory.js +0 -31
  40. package/dist/utils/createZip.js +2 -42
  41. package/dist/utils/deepCopy.js +0 -38
  42. package/dist/utils/deepEqual.js +1 -71
  43. package/dist/utils/export.js +0 -1
  44. package/dist/utils/hex.js +0 -57
  45. package/dist/utils/isvalid.js +0 -100
  46. package/dist/utils/network.js +5 -76
  47. package/dist/utils/spawn.js +0 -16
  48. package/dist/utils/wait.js +9 -58
  49. package/dist/waterHeater.js +2 -62
  50. package/npm-shrinkwrap.json +41 -29
  51. package/package.json +23 -20
  52. package/dist/cli.d.ts +0 -29
  53. package/dist/cli.d.ts.map +0 -1
  54. package/dist/cli.js.map +0 -1
  55. package/dist/cluster/export.d.ts +0 -2
  56. package/dist/cluster/export.d.ts.map +0 -1
  57. package/dist/cluster/export.js +0 -3
  58. package/dist/cluster/export.js.map +0 -1
  59. package/dist/defaultConfigSchema.d.ts +0 -27
  60. package/dist/defaultConfigSchema.d.ts.map +0 -1
  61. package/dist/defaultConfigSchema.js.map +0 -1
  62. package/dist/deviceManager.d.ts +0 -114
  63. package/dist/deviceManager.d.ts.map +0 -1
  64. package/dist/deviceManager.js.map +0 -1
  65. package/dist/evse.d.ts +0 -67
  66. package/dist/evse.d.ts.map +0 -1
  67. package/dist/evse.js.map +0 -1
  68. package/dist/frontend.d.ts +0 -256
  69. package/dist/frontend.d.ts.map +0 -1
  70. package/dist/frontend.js.map +0 -1
  71. package/dist/globalMatterbridge.d.ts +0 -32
  72. package/dist/globalMatterbridge.d.ts.map +0 -1
  73. package/dist/globalMatterbridge.js.map +0 -1
  74. package/dist/helpers.d.ts +0 -47
  75. package/dist/helpers.d.ts.map +0 -1
  76. package/dist/helpers.js.map +0 -1
  77. package/dist/index.d.ts +0 -37
  78. package/dist/index.d.ts.map +0 -1
  79. package/dist/index.js.map +0 -1
  80. package/dist/laundryWasher.d.ts +0 -243
  81. package/dist/laundryWasher.d.ts.map +0 -1
  82. package/dist/laundryWasher.js.map +0 -1
  83. package/dist/logger/export.d.ts +0 -2
  84. package/dist/logger/export.d.ts.map +0 -1
  85. package/dist/logger/export.js.map +0 -1
  86. package/dist/matter/behaviors.d.ts +0 -2
  87. package/dist/matter/behaviors.d.ts.map +0 -1
  88. package/dist/matter/behaviors.js.map +0 -1
  89. package/dist/matter/clusters.d.ts +0 -2
  90. package/dist/matter/clusters.d.ts.map +0 -1
  91. package/dist/matter/clusters.js.map +0 -1
  92. package/dist/matter/devices.d.ts +0 -2
  93. package/dist/matter/devices.d.ts.map +0 -1
  94. package/dist/matter/devices.js.map +0 -1
  95. package/dist/matter/endpoints.d.ts +0 -2
  96. package/dist/matter/endpoints.d.ts.map +0 -1
  97. package/dist/matter/endpoints.js.map +0 -1
  98. package/dist/matter/export.d.ts +0 -5
  99. package/dist/matter/export.d.ts.map +0 -1
  100. package/dist/matter/export.js.map +0 -1
  101. package/dist/matter/types.d.ts +0 -3
  102. package/dist/matter/types.d.ts.map +0 -1
  103. package/dist/matter/types.js.map +0 -1
  104. package/dist/matterbridge.d.ts +0 -445
  105. package/dist/matterbridge.d.ts.map +0 -1
  106. package/dist/matterbridge.js.map +0 -1
  107. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
  108. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  109. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  110. package/dist/matterbridgeBehaviors.d.ts +0 -1333
  111. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  112. package/dist/matterbridgeBehaviors.js.map +0 -1
  113. package/dist/matterbridgeDeviceTypes.d.ts +0 -644
  114. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  115. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  116. package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
  117. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  118. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  119. package/dist/matterbridgeEndpoint.d.ts +0 -1144
  120. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  121. package/dist/matterbridgeEndpoint.js.map +0 -1
  122. package/dist/matterbridgeEndpointHelpers.d.ts +0 -3083
  123. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  124. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  125. package/dist/matterbridgePlatform.d.ts +0 -294
  126. package/dist/matterbridgePlatform.d.ts.map +0 -1
  127. package/dist/matterbridgePlatform.js.map +0 -1
  128. package/dist/matterbridgeTypes.d.ts +0 -196
  129. package/dist/matterbridgeTypes.d.ts.map +0 -1
  130. package/dist/matterbridgeTypes.js.map +0 -1
  131. package/dist/pluginManager.d.ts +0 -273
  132. package/dist/pluginManager.d.ts.map +0 -1
  133. package/dist/pluginManager.js.map +0 -1
  134. package/dist/roboticVacuumCleaner.d.ts +0 -102
  135. package/dist/roboticVacuumCleaner.d.ts.map +0 -1
  136. package/dist/roboticVacuumCleaner.js.map +0 -1
  137. package/dist/shelly.d.ts +0 -161
  138. package/dist/shelly.d.ts.map +0 -1
  139. package/dist/shelly.js.map +0 -1
  140. package/dist/storage/export.d.ts +0 -2
  141. package/dist/storage/export.d.ts.map +0 -1
  142. package/dist/storage/export.js.map +0 -1
  143. package/dist/update.d.ts +0 -58
  144. package/dist/update.d.ts.map +0 -1
  145. package/dist/update.js.map +0 -1
  146. package/dist/utils/colorUtils.d.ts +0 -61
  147. package/dist/utils/colorUtils.d.ts.map +0 -1
  148. package/dist/utils/colorUtils.js.map +0 -1
  149. package/dist/utils/commandLine.d.ts +0 -58
  150. package/dist/utils/commandLine.d.ts.map +0 -1
  151. package/dist/utils/commandLine.js.map +0 -1
  152. package/dist/utils/copyDirectory.d.ts +0 -32
  153. package/dist/utils/copyDirectory.d.ts.map +0 -1
  154. package/dist/utils/copyDirectory.js.map +0 -1
  155. package/dist/utils/createDirectory.d.ts +0 -32
  156. package/dist/utils/createDirectory.d.ts.map +0 -1
  157. package/dist/utils/createDirectory.js.map +0 -1
  158. package/dist/utils/createZip.d.ts +0 -38
  159. package/dist/utils/createZip.d.ts.map +0 -1
  160. package/dist/utils/createZip.js.map +0 -1
  161. package/dist/utils/deepCopy.d.ts +0 -31
  162. package/dist/utils/deepCopy.d.ts.map +0 -1
  163. package/dist/utils/deepCopy.js.map +0 -1
  164. package/dist/utils/deepEqual.d.ts +0 -53
  165. package/dist/utils/deepEqual.d.ts.map +0 -1
  166. package/dist/utils/deepEqual.js.map +0 -1
  167. package/dist/utils/export.d.ts +0 -12
  168. package/dist/utils/export.d.ts.map +0 -1
  169. package/dist/utils/export.js.map +0 -1
  170. package/dist/utils/hex.d.ts +0 -48
  171. package/dist/utils/hex.d.ts.map +0 -1
  172. package/dist/utils/hex.js.map +0 -1
  173. package/dist/utils/isvalid.d.ts +0 -102
  174. package/dist/utils/isvalid.d.ts.map +0 -1
  175. package/dist/utils/isvalid.js.map +0 -1
  176. package/dist/utils/network.d.ts +0 -69
  177. package/dist/utils/network.d.ts.map +0 -1
  178. package/dist/utils/network.js.map +0 -1
  179. package/dist/utils/spawn.d.ts +0 -12
  180. package/dist/utils/spawn.d.ts.map +0 -1
  181. package/dist/utils/spawn.js.map +0 -1
  182. package/dist/utils/wait.d.ts +0 -52
  183. package/dist/utils/wait.d.ts.map +0 -1
  184. package/dist/utils/wait.js.map +0 -1
  185. package/dist/waterHeater.d.ts +0 -90
  186. package/dist/waterHeater.d.ts.map +0 -1
  187. package/dist/waterHeater.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,113 +1,30 @@
1
- /**
2
- * This file contains the class Frontend.
3
- *
4
- * @file frontend.ts
5
- * @author Luca Liguori
6
- * @date 2025-01-13
7
- * @version 1.0.2
8
- *
9
- * Copyright 2025, 2026, 2027 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- // @matter
24
1
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
2
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
26
- // Node modules
27
3
  import { createServer } from 'node:http';
28
4
  import https from 'node:https';
29
5
  import os from 'node:os';
30
6
  import path from 'node:path';
31
7
  import { promises as fs } from 'node:fs';
32
- // Third-party modules
33
8
  import express from 'express';
34
9
  import WebSocket, { WebSocketServer } from 'ws';
35
10
  import multer from 'multer';
36
- // AnsiLogger module
37
11
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
38
- // Matterbridge
39
12
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
40
13
  import { plg } from './matterbridgeTypes.js';
41
14
  import { hasParameter } from './utils/export.js';
42
15
  import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
43
16
  import spawn from './utils/spawn.js';
44
- /**
45
- * Websocket message ID for logging.
46
- * @constant {number}
47
- */
48
17
  export const WS_ID_LOG = 0;
49
- /**
50
- * Websocket message ID indicating a refresh is needed.
51
- * @constant {number}
52
- */
53
18
  export const WS_ID_REFRESH_NEEDED = 1;
54
- /**
55
- * Websocket message ID indicating a restart is needed.
56
- * @constant {number}
57
- */
58
19
  export const WS_ID_RESTART_NEEDED = 2;
59
- /**
60
- * Websocket message ID indicating a cpu update.
61
- * @constant {number}
62
- */
63
20
  export const WS_ID_CPU_UPDATE = 3;
64
- /**
65
- * Websocket message ID indicating a memory update.
66
- * @constant {number}
67
- */
68
21
  export const WS_ID_MEMORY_UPDATE = 4;
69
- /**
70
- * Websocket message ID indicating an uptime update.
71
- * @constant {number}
72
- */
73
22
  export const WS_ID_UPTIME_UPDATE = 5;
74
- /**
75
- * Websocket message ID indicating a snackbar message.
76
- * @constant {number}
77
- */
78
23
  export const WS_ID_SNACKBAR = 6;
79
- /**
80
- * Websocket message ID indicating matterbridge has un update available.
81
- * @constant {number}
82
- */
83
24
  export const WS_ID_UPDATE_NEEDED = 7;
84
- /**
85
- * Websocket message ID indicating a state update.
86
- * @constant {number}
87
- */
88
25
  export const WS_ID_STATEUPDATE = 8;
89
- /**
90
- * Websocket message ID indicating to close a permanent snackbar message.
91
- * @constant {number}
92
- */
93
26
  export const WS_ID_CLOSE_SNACKBAR = 9;
94
- /**
95
- * Websocket message ID indicating a shelly system update.
96
- * check:
97
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
98
- * perform:
99
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
100
- * @constant {number}
101
- */
102
27
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
103
- /**
104
- * Websocket message ID indicating a shelly main update.
105
- * check:
106
- * curl -k http://127.0.0.1:8101/api/updates/main/check
107
- * perform:
108
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
109
- * @constant {number}
110
- */
111
28
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
112
29
  export class Frontend {
113
30
  matterbridge;
@@ -120,7 +37,7 @@ export class Frontend {
120
37
  webSocketServer;
121
38
  constructor(matterbridge) {
122
39
  this.matterbridge = matterbridge;
123
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
40
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
124
41
  }
125
42
  set logLevel(logLevel) {
126
43
  this.log.logLevel = logLevel;
@@ -128,43 +45,13 @@ export class Frontend {
128
45
  async start(port = 8283) {
129
46
  this.port = port;
130
47
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
131
- // Initialize multer with the upload directory
132
48
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
133
49
  await fs.mkdir(uploadDir, { recursive: true });
134
50
  const upload = multer({ dest: uploadDir });
135
- // Create the express app that serves the frontend
136
51
  this.expressApp = express();
137
- // Inject logging/debug wrapper for route/middleware registration
138
- /*
139
- const methods = ['get', 'post', 'put', 'delete', 'use'];
140
- for (const method of methods) {
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
- const original = (this.expressApp as any)[method].bind(this.expressApp);
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
145
- try {
146
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
147
- return original(path, ...rest);
148
- } catch (err) {
149
- console.error(`[ERROR] Failed to register route: ${path}`);
150
- throw err;
151
- }
152
- };
153
- }
154
- */
155
- // Log all requests to the server for debugging
156
- /*
157
- this.expressApp.use((req, res, next) => {
158
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
159
- next();
160
- });
161
- */
162
- // Serve static files from '/static' endpoint
163
52
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
164
53
  if (!hasParameter('ssl')) {
165
- // Create an HTTP server and attach the express app
166
54
  this.httpServer = createServer(this.expressApp);
167
- // Listen on the specified port
168
55
  if (hasParameter('ingress')) {
169
56
  this.httpServer.listen(this.port, '0.0.0.0', () => {
170
57
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -178,7 +65,6 @@ export class Frontend {
178
65
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
179
66
  });
180
67
  }
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
68
  this.httpServer.on('error', (error) => {
183
69
  this.log.error(`Frontend http server error listening on ${this.port}`);
184
70
  switch (error.code) {
@@ -194,7 +80,6 @@ export class Frontend {
194
80
  });
195
81
  }
196
82
  else {
197
- // Load the SSL certificate, the private key and optionally the CA certificate
198
83
  let cert;
199
84
  try {
200
85
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -222,9 +107,7 @@ export class Frontend {
222
107
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
223
108
  }
224
109
  const serverOptions = { cert, key, ca };
225
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
226
110
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
227
- // Listen on the specified port
228
111
  if (hasParameter('ingress')) {
229
112
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
230
113
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -238,7 +121,6 @@ export class Frontend {
238
121
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
239
122
  });
240
123
  }
241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
124
  this.httpsServer.on('error', (error) => {
243
125
  this.log.error(`Frontend https server error listening on ${this.port}`);
244
126
  switch (error.code) {
@@ -255,18 +137,16 @@ export class Frontend {
255
137
  }
256
138
  if (this.initializeError)
257
139
  return;
258
- // Create a WebSocket server and attach it to the http or https server
259
140
  const wssPort = this.port;
260
141
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
261
142
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
262
143
  this.webSocketServer.on('connection', (ws, request) => {
263
144
  const clientIp = request.socket.remoteAddress;
264
- // Set the global logger callback for the WebSocketServer
265
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
266
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
267
- callbackLogLevel = "info" /* LogLevel.INFO */;
268
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
269
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
145
+ let callbackLogLevel = "notice";
146
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
147
+ callbackLogLevel = "info";
148
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
149
+ callbackLogLevel = "debug";
270
150
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
271
151
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
272
152
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -300,7 +180,6 @@ export class Frontend {
300
180
  this.webSocketServer.on('error', (ws, error) => {
301
181
  this.log.error(`WebSocketServer error: ${error}`);
302
182
  });
303
- // Subscribe to cli events
304
183
  const { cliEmitter } = await import('./cli.js');
305
184
  cliEmitter.removeAllListeners();
306
185
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -312,8 +191,6 @@ export class Frontend {
312
191
  cliEmitter.on('cpu', (cpuUsage) => {
313
192
  this.wssSendCpuUpdate(cpuUsage);
314
193
  });
315
- // Endpoint to validate login code
316
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
317
194
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
318
195
  const { password } = req.body;
319
196
  this.log.debug('The frontend sent /api/login', password);
@@ -332,27 +209,23 @@ export class Frontend {
332
209
  this.log.warn('/api/login error wrong password');
333
210
  res.json({ valid: false });
334
211
  }
335
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
336
212
  }
337
213
  catch (error) {
338
214
  this.log.error('/api/login error getting password');
339
215
  res.json({ valid: false });
340
216
  }
341
217
  });
342
- // Endpoint to provide health check for docker
343
218
  this.expressApp.get('/health', (req, res) => {
344
219
  this.log.debug('Express received /health');
345
220
  const healthStatus = {
346
- status: 'ok', // Indicate service is healthy
347
- uptime: process.uptime(), // Server uptime in seconds
348
- timestamp: new Date().toISOString(), // Current timestamp
221
+ status: 'ok',
222
+ uptime: process.uptime(),
223
+ timestamp: new Date().toISOString(),
349
224
  };
350
225
  res.status(200).json(healthStatus);
351
226
  });
352
- // Endpoint to provide memory usage details
353
227
  this.expressApp.get('/memory', async (req, res) => {
354
228
  this.log.debug('Express received /memory');
355
- // Memory usage from process
356
229
  const memoryUsageRaw = process.memoryUsage();
357
230
  const memoryUsage = {
358
231
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -361,13 +234,10 @@ export class Frontend {
361
234
  external: this.formatMemoryUsage(memoryUsageRaw.external),
362
235
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
363
236
  };
364
- // V8 heap statistics
365
237
  const { default: v8 } = await import('node:v8');
366
238
  const heapStatsRaw = v8.getHeapStatistics();
367
239
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
368
- // Format heapStats
369
240
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
370
- // Format heapSpaces
371
241
  const heapSpaces = heapSpacesRaw.map((space) => ({
372
242
  ...space,
373
243
  space_size: this.formatMemoryUsage(space.space_size),
@@ -385,23 +255,19 @@ export class Frontend {
385
255
  };
386
256
  res.status(200).json(memoryReport);
387
257
  });
388
- // Endpoint to provide settings
389
258
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
390
259
  this.log.debug('The frontend sent /api/settings');
391
260
  res.json(await this.getApiSettings());
392
261
  });
393
- // Endpoint to provide plugins
394
262
  this.expressApp.get('/api/plugins', async (req, res) => {
395
263
  this.log.debug('The frontend sent /api/plugins');
396
264
  res.json(this.getBaseRegisteredPlugins());
397
265
  });
398
- // Endpoint to provide devices
399
266
  this.expressApp.get('/api/devices', async (req, res) => {
400
267
  this.log.debug('The frontend sent /api/devices');
401
268
  const devices = await this.getDevices();
402
269
  res.json(devices);
403
270
  });
404
- // Endpoint to view the matterbridge log
405
271
  this.expressApp.get('/api/view-mblog', async (req, res) => {
406
272
  this.log.debug('The frontend sent /api/view-mblog');
407
273
  try {
@@ -414,7 +280,6 @@ export class Frontend {
414
280
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
415
281
  }
416
282
  });
417
- // Endpoint to view the matter.js log
418
283
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
419
284
  this.log.debug('The frontend sent /api/view-mjlog');
420
285
  try {
@@ -427,7 +292,6 @@ export class Frontend {
427
292
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
428
293
  }
429
294
  });
430
- // Endpoint to view the shelly log
431
295
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
432
296
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
433
297
  try {
@@ -440,7 +304,6 @@ export class Frontend {
440
304
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
441
305
  }
442
306
  });
443
- // Endpoint to download the matterbridge log
444
307
  this.expressApp.get('/api/download-mblog', async (req, res) => {
445
308
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
446
309
  try {
@@ -453,7 +316,6 @@ export class Frontend {
453
316
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
454
317
  }
455
318
  res.type('text/plain');
456
- // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
457
319
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
458
320
  if (error) {
459
321
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -461,7 +323,6 @@ export class Frontend {
461
323
  }
462
324
  });
463
325
  });
464
- // Endpoint to download the matter log
465
326
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
466
327
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
467
328
  try {
@@ -481,7 +342,6 @@ export class Frontend {
481
342
  }
482
343
  });
483
344
  });
484
- // Endpoint to download the shelly log
485
345
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
486
346
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
487
347
  try {
@@ -501,7 +361,6 @@ export class Frontend {
501
361
  }
502
362
  });
503
363
  });
504
- // Endpoint to download the matterbridge storage directory
505
364
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
506
365
  this.log.debug('The frontend sent /api/download-mbstorage');
507
366
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -512,7 +371,6 @@ export class Frontend {
512
371
  }
513
372
  });
514
373
  });
515
- // Endpoint to download the matter storage file
516
374
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
517
375
  this.log.debug('The frontend sent /api/download-mjstorage');
518
376
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -523,7 +381,6 @@ export class Frontend {
523
381
  }
524
382
  });
525
383
  });
526
- // Endpoint to download the matterbridge plugin directory
527
384
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
528
385
  this.log.debug('The frontend sent /api/download-pluginstorage');
529
386
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -534,7 +391,6 @@ export class Frontend {
534
391
  }
535
392
  });
536
393
  });
537
- // Endpoint to download the matterbridge plugin config files
538
394
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
539
395
  this.log.debug('The frontend sent /api/download-pluginconfig');
540
396
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -545,7 +401,6 @@ export class Frontend {
545
401
  }
546
402
  });
547
403
  });
548
- // Endpoint to download the matterbridge backup (created with the backup command)
549
404
  this.expressApp.get('/api/download-backup', async (req, res) => {
550
405
  this.log.debug('The frontend sent /api/download-backup');
551
406
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -555,7 +410,6 @@ export class Frontend {
555
410
  }
556
411
  });
557
412
  });
558
- // Endpoint to upload a package
559
413
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
560
414
  const { filename } = req.body;
561
415
  const file = req.file;
@@ -565,13 +419,10 @@ export class Frontend {
565
419
  return;
566
420
  }
567
421
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
568
- // Define the path where the plugin file will be saved
569
422
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
570
423
  try {
571
- // Move the uploaded file to the specified path
572
424
  await fs.rename(file.path, filePath);
573
425
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
574
- // Install the plugin package
575
426
  if (filename.endsWith('.tgz')) {
576
427
  await spawn.spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
577
428
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -590,7 +441,6 @@ export class Frontend {
590
441
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
591
442
  }
592
443
  });
593
- // Fallback for routing (must be the last route)
594
444
  this.expressApp.use((req, res) => {
595
445
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
596
446
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -599,15 +449,12 @@ export class Frontend {
599
449
  }
600
450
  async stop() {
601
451
  this.log.debug('Stopping the frontend...');
602
- // Remove listeners from the express app
603
452
  if (this.expressApp) {
604
453
  this.expressApp.removeAllListeners();
605
454
  this.expressApp = undefined;
606
455
  this.log.debug('Frontend app closed successfully');
607
456
  }
608
- // Close the WebSocket server
609
457
  if (this.webSocketServer) {
610
- // Close all active connections
611
458
  this.webSocketServer.clients.forEach((client) => {
612
459
  if (client.readyState === WebSocket.OPEN) {
613
460
  client.close();
@@ -627,7 +474,6 @@ export class Frontend {
627
474
  this.webSocketServer.removeAllListeners();
628
475
  this.webSocketServer = undefined;
629
476
  }
630
- // Close the http server
631
477
  if (this.httpServer) {
632
478
  await withTimeout(new Promise((resolve) => {
633
479
  this.httpServer?.close((error) => {
@@ -644,7 +490,6 @@ export class Frontend {
644
490
  this.httpServer = undefined;
645
491
  this.log.debug('Frontend http server closed successfully');
646
492
  }
647
- // Close the https server
648
493
  if (this.httpsServer) {
649
494
  await withTimeout(new Promise((resolve) => {
650
495
  this.httpsServer?.close((error) => {
@@ -663,7 +508,6 @@ export class Frontend {
663
508
  }
664
509
  this.log.debug('Frontend stopped successfully');
665
510
  }
666
- // Function to format bytes to KB, MB, or GB
667
511
  formatMemoryUsage = (bytes) => {
668
512
  if (bytes >= 1024 ** 3) {
669
513
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -675,7 +519,6 @@ export class Frontend {
675
519
  return `${(bytes / 1024).toFixed(2)} KB`;
676
520
  }
677
521
  };
678
- // Function to format system uptime with only the most significant unit
679
522
  formatOsUpTime = (seconds) => {
680
523
  if (seconds >= 86400) {
681
524
  const days = Math.floor(seconds / 86400);
@@ -691,14 +534,8 @@ export class Frontend {
691
534
  }
692
535
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
693
536
  };
694
- /**
695
- * Retrieves the api settings data.
696
- *
697
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
698
- */
699
537
  async getApiSettings() {
700
538
  const { lastCpuUsage } = await import('./cli.js');
701
- // Update the system information
702
539
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
703
540
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
704
541
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -707,7 +544,6 @@ export class Frontend {
707
544
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
708
545
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
709
546
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
710
- // Update the matterbridge information
711
547
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
712
548
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
713
549
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -726,11 +562,6 @@ export class Frontend {
726
562
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
727
563
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
728
564
  }
729
- /**
730
- * Retrieves the reachable attribute.
731
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
732
- * @returns {boolean} The reachable attribute.
733
- */
734
565
  getReachability(device) {
735
566
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
736
567
  return false;
@@ -740,11 +571,6 @@ export class Frontend {
740
571
  return true;
741
572
  return false;
742
573
  }
743
- /**
744
- * Retrieves the power source attribute.
745
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
746
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
747
- */
748
574
  getPowerSource(endpoint) {
749
575
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
750
576
  return undefined;
@@ -760,20 +586,13 @@ export class Frontend {
760
586
  }
761
587
  return;
762
588
  };
763
- // Root endpoint
764
589
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
765
590
  return powerSource(endpoint);
766
- // Child endpoints
767
591
  for (const child of endpoint.getChildEndpoints()) {
768
592
  if (child.hasClusterServer(PowerSource.Cluster.id))
769
593
  return powerSource(child);
770
594
  }
771
595
  }
772
- /**
773
- * Retrieves the cluster text description from a given device.
774
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
775
- * @returns {string} The attributes description of the cluster servers in the device.
776
- */
777
596
  getClusterTextFromDevice(device) {
778
597
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
779
598
  return '';
@@ -814,19 +633,7 @@ export class Frontend {
814
633
  };
815
634
  let attributes = '';
816
635
  let supportedModes = [];
817
- /*
818
- Object.keys(device.behaviors.supported).forEach((clusterName) => {
819
- const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
820
- // console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
821
- if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
822
- Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
823
- // console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
824
- });
825
- }
826
- });
827
- */
828
636
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
829
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
830
637
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
831
638
  return;
832
639
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -918,13 +725,8 @@ export class Frontend {
918
725
  if (clusterName === 'userLabel' && attributeName === 'labelList')
919
726
  attributes += `${getUserLabel(device)} `;
920
727
  });
921
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
922
728
  return attributes.trimStart().trimEnd();
923
729
  }
924
- /**
925
- * Retrieves the base registered plugins sanitized for res.json().
926
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
927
- */
928
730
  getBaseRegisteredPlugins() {
929
731
  const baseRegisteredPlugins = [];
930
732
  for (const plugin of this.matterbridge.plugins) {
@@ -963,18 +765,11 @@ export class Frontend {
963
765
  }
964
766
  return baseRegisteredPlugins;
965
767
  }
966
- /**
967
- * Retrieves the devices from Matterbridge.
968
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
969
- * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
970
- */
971
768
  async getDevices(pluginName) {
972
769
  const devices = [];
973
770
  this.matterbridge.devices.forEach(async (device) => {
974
- // Filter by pluginName if provided
975
771
  if (pluginName && pluginName !== device.plugin)
976
772
  return;
977
- // Check if the device has the required properties
978
773
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
979
774
  return;
980
775
  const cluster = this.getClusterTextFromDevice(device);
@@ -994,37 +789,22 @@ export class Frontend {
994
789
  });
995
790
  return devices;
996
791
  }
997
- /**
998
- * Retrieves the clusters from a given plugin and endpoint number.
999
- *
1000
- * Response for /api/clusters
1001
- *
1002
- * @param {string} pluginName - The name of the plugin.
1003
- * @param {number} endpointNumber - The endpoint number.
1004
- * @returns {Promise<ApiClustersResponse | undefined>} A promise that resolves to the clusters or undefined if not found.
1005
- */
1006
792
  getClusters(pluginName, endpointNumber) {
1007
793
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1008
794
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
1009
795
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1010
796
  return;
1011
797
  }
1012
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1013
- // Get the device types from the main endpoint
1014
798
  const deviceTypes = [];
1015
799
  const clusters = [];
1016
800
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1017
801
  deviceTypes.push(d.deviceType);
1018
802
  });
1019
- // Get the clusters from the main endpoint
1020
803
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1021
804
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1022
805
  return;
1023
806
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1024
807
  return;
1025
- // console.log(
1026
- // `${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}`,
1027
- // );
1028
808
  clusters.push({
1029
809
  endpoint: endpoint.number.toString(),
1030
810
  id: 'main',
@@ -1037,18 +817,12 @@ export class Frontend {
1037
817
  attributeLocalValue: attributeValue,
1038
818
  });
1039
819
  });
1040
- // Get the child endpoints
1041
820
  const childEndpoints = endpoint.getChildEndpoints();
1042
- // if (childEndpoints.length === 0) {
1043
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1044
- // }
1045
821
  childEndpoints.forEach((childEndpoint) => {
1046
822
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1047
823
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1048
824
  return;
1049
825
  }
1050
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1051
- // Get the device types of the child endpoint
1052
826
  const deviceTypes = [];
1053
827
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1054
828
  deviceTypes.push(d.deviceType);
@@ -1058,12 +832,9 @@ export class Frontend {
1058
832
  return;
1059
833
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1060
834
  return;
1061
- // console.log(
1062
- // `${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}`,
1063
- // );
1064
835
  clusters.push({
1065
836
  endpoint: childEndpoint.number.toString(),
1066
- id: childEndpoint.maybeId ?? 'null', // Never happens
837
+ id: childEndpoint.maybeId ?? 'null',
1067
838
  deviceTypes,
1068
839
  clusterName: capitalizeFirstLetter(clusterName),
1069
840
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -1076,13 +847,6 @@ export class Frontend {
1076
847
  });
1077
848
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
1078
849
  }
1079
- /**
1080
- * Handles incoming websocket messages for the Matterbridge frontend.
1081
- *
1082
- * @param {WebSocket} client - The websocket client that sent the message.
1083
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1084
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1085
- */
1086
850
  async wsMessageHandler(client, message) {
1087
851
  let data;
1088
852
  try {
@@ -1129,10 +893,8 @@ export class Frontend {
1129
893
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
1130
894
  const packageName = data.params.packageName.replace(/@.*$/, '');
1131
895
  if (data.params.restart === false && packageName !== 'matterbridge') {
1132
- // The install comes from InstallPlugins
1133
896
  this.matterbridge.plugins.add(packageName).then((plugin) => {
1134
897
  if (plugin) {
1135
- // The plugin is not registered
1136
898
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1137
899
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
1138
900
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -1140,7 +902,6 @@ export class Frontend {
1140
902
  });
1141
903
  }
1142
904
  else {
1143
- // The plugin is already registered
1144
905
  this.wssSendSnackbarMessage(`Restart required`, 0);
1145
906
  this.wssSendRefreshRequired('plugins');
1146
907
  this.wssSendRestartRequired();
@@ -1148,7 +909,6 @@ export class Frontend {
1148
909
  });
1149
910
  }
1150
911
  else {
1151
- // The package is matterbridge
1152
912
  if (this.matterbridge.restartMode !== '') {
1153
913
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1154
914
  this.matterbridge.shutdownProcess();
@@ -1170,7 +930,6 @@ export class Frontend {
1170
930
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1171
931
  return;
1172
932
  }
1173
- // The package is a plugin
1174
933
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1175
934
  if (plugin) {
1176
935
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -1179,7 +938,6 @@ export class Frontend {
1179
938
  this.wssSendRefreshRequired('plugins');
1180
939
  this.wssSendRefreshRequired('devices');
1181
940
  }
1182
- // Uninstall the package
1183
941
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1184
942
  spawn
1185
943
  .spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1491,22 +1249,22 @@ export class Frontend {
1491
1249
  if (isValidString(data.params.value, 4)) {
1492
1250
  this.log.debug('Matterbridge logger level:', data.params.value);
1493
1251
  if (data.params.value === 'Debug') {
1494
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1252
+ await this.matterbridge.setLogLevel("debug");
1495
1253
  }
1496
1254
  else if (data.params.value === 'Info') {
1497
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1255
+ await this.matterbridge.setLogLevel("info");
1498
1256
  }
1499
1257
  else if (data.params.value === 'Notice') {
1500
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1258
+ await this.matterbridge.setLogLevel("notice");
1501
1259
  }
1502
1260
  else if (data.params.value === 'Warn') {
1503
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1261
+ await this.matterbridge.setLogLevel("warn");
1504
1262
  }
1505
1263
  else if (data.params.value === 'Error') {
1506
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1264
+ await this.matterbridge.setLogLevel("error");
1507
1265
  }
1508
1266
  else if (data.params.value === 'Fatal') {
1509
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1267
+ await this.matterbridge.setLogLevel("fatal");
1510
1268
  }
1511
1269
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1512
1270
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1517,7 +1275,6 @@ export class Frontend {
1517
1275
  this.log.debug('Matterbridge file log:', data.params.value);
1518
1276
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1519
1277
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1520
- // Create the file logger for matterbridge
1521
1278
  if (data.params.value)
1522
1279
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1523
1280
  else
@@ -1682,19 +1439,15 @@ export class Frontend {
1682
1439
  return;
1683
1440
  }
1684
1441
  const config = plugin.configJson;
1685
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1686
1442
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1687
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1688
1443
  if (select === 'serial')
1689
1444
  this.log.info(`Selected device serial ${data.params.serial}`);
1690
1445
  if (select === 'name')
1691
1446
  this.log.info(`Selected device name ${data.params.name}`);
1692
1447
  if (config && select && (select === 'serial' || select === 'name')) {
1693
- // Remove postfix from the serial if it exists
1694
1448
  if (config.postfix) {
1695
1449
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1696
1450
  }
1697
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1698
1451
  if (isValidArray(config.whiteList, 1)) {
1699
1452
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1700
1453
  config.whiteList.push(data.params.serial);
@@ -1703,7 +1456,6 @@ export class Frontend {
1703
1456
  config.whiteList.push(data.params.name);
1704
1457
  }
1705
1458
  }
1706
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1707
1459
  if (isValidArray(config.blackList, 1)) {
1708
1460
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1709
1461
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1733,9 +1485,7 @@ export class Frontend {
1733
1485
  return;
1734
1486
  }
1735
1487
  const config = plugin.configJson;
1736
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1737
1488
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1738
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1739
1489
  if (select === 'serial')
1740
1490
  this.log.info(`Unselected device serial ${data.params.serial}`);
1741
1491
  if (select === 'name')
@@ -1744,7 +1494,6 @@ export class Frontend {
1744
1494
  if (config.postfix) {
1745
1495
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1746
1496
  }
1747
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1748
1497
  if (isValidArray(config.whiteList, 1)) {
1749
1498
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1750
1499
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1753,7 +1502,6 @@ export class Frontend {
1753
1502
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1754
1503
  }
1755
1504
  }
1756
- // Add the serial to the blackList
1757
1505
  if (isValidArray(config.blackList)) {
1758
1506
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1759
1507
  config.blackList.push(data.params.serial);
@@ -1786,219 +1534,114 @@ export class Frontend {
1786
1534
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1787
1535
  }
1788
1536
  }
1789
- /**
1790
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1791
- *
1792
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1793
- * @param {string} time - The time string of the message
1794
- * @param {string} name - The logger name of the message
1795
- * @param {string} message - The content of the message.
1796
- *
1797
- * @remark
1798
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1799
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1800
- * The function sends the message to all connected clients.
1801
- */
1802
1537
  wssSendMessage(level, time, name, message) {
1803
1538
  if (!level || !time || !name || !message)
1804
1539
  return;
1805
- // Remove ANSI escape codes from the message
1806
- // eslint-disable-next-line no-control-regex
1807
1540
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1808
- // Remove leading asterisks from the message
1809
1541
  message = message.replace(/^\*+/, '');
1810
- // Replace all occurrences of \t and \n
1811
1542
  message = message.replace(/[\t\n]/g, '');
1812
- // Remove non-printable characters
1813
- // eslint-disable-next-line no-control-regex
1814
1543
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1815
- // Replace all occurrences of \" with "
1816
1544
  message = message.replace(/\\"/g, '"');
1817
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
1818
1545
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1819
- // Define the maximum allowed length for continuous characters without a space
1820
1546
  const maxContinuousLength = 100;
1821
1547
  const keepStartLength = 20;
1822
1548
  const keepEndLength = 20;
1823
- // Split the message into words
1824
1549
  message = message
1825
1550
  .split(' ')
1826
1551
  .map((word) => {
1827
- // If the word length exceeds the max continuous length, insert spaces and truncate
1828
1552
  if (word.length > maxContinuousLength) {
1829
1553
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1830
1554
  }
1831
1555
  return word;
1832
1556
  })
1833
1557
  .join(' ');
1834
- // Send the message to all connected clients
1835
1558
  this.webSocketServer?.clients.forEach((client) => {
1836
1559
  if (client.readyState === WebSocket.OPEN) {
1837
1560
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1838
1561
  }
1839
1562
  });
1840
1563
  }
1841
- /**
1842
- * Sends a need to refresh WebSocket message to all connected clients.
1843
- *
1844
- * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1845
- * possible values:
1846
- * - 'matterbridgeLatestVersion'
1847
- * - 'matterbridgeAdvertise'
1848
- * - 'online'
1849
- * - 'offline'
1850
- * - 'reachability'
1851
- * - 'settings'
1852
- * - 'plugins'
1853
- * - 'pluginsRestart'
1854
- * - 'devices'
1855
- * - 'fabrics'
1856
- * - 'sessions'
1857
- */
1858
1564
  wssSendRefreshRequired(changed = null) {
1859
1565
  this.log.debug('Sending a refresh required message to all connected clients');
1860
- // Send the message to all connected clients
1861
1566
  this.webSocketServer?.clients.forEach((client) => {
1862
1567
  if (client.readyState === WebSocket.OPEN) {
1863
1568
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1864
1569
  }
1865
1570
  });
1866
1571
  }
1867
- /**
1868
- * Sends a need to restart WebSocket message to all connected clients.
1869
- *
1870
- */
1871
1572
  wssSendRestartRequired(snackbar = true) {
1872
1573
  this.log.debug('Sending a restart required message to all connected clients');
1873
1574
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1874
1575
  if (snackbar === true)
1875
1576
  this.wssSendSnackbarMessage(`Restart required`, 0);
1876
- // Send the message to all connected clients
1877
1577
  this.webSocketServer?.clients.forEach((client) => {
1878
1578
  if (client.readyState === WebSocket.OPEN) {
1879
1579
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1880
1580
  }
1881
1581
  });
1882
1582
  }
1883
- /**
1884
- * Sends a need to update WebSocket message to all connected clients.
1885
- *
1886
- */
1887
1583
  wssSendUpdateRequired() {
1888
1584
  this.log.debug('Sending an update required message to all connected clients');
1889
1585
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1890
- // Send the message to all connected clients
1891
1586
  this.webSocketServer?.clients.forEach((client) => {
1892
1587
  if (client.readyState === WebSocket.OPEN) {
1893
1588
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1894
1589
  }
1895
1590
  });
1896
1591
  }
1897
- /**
1898
- * Sends a cpu update message to all connected clients.
1899
- *
1900
- */
1901
1592
  wssSendCpuUpdate(cpuUsage) {
1902
1593
  if (hasParameter('debug'))
1903
1594
  this.log.debug('Sending a cpu update message to all connected clients');
1904
- // Send the message to all connected clients
1905
1595
  this.webSocketServer?.clients.forEach((client) => {
1906
1596
  if (client.readyState === WebSocket.OPEN) {
1907
1597
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1908
1598
  }
1909
1599
  });
1910
1600
  }
1911
- /**
1912
- * Sends a memory update message to all connected clients.
1913
- *
1914
- */
1915
1601
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1916
1602
  if (hasParameter('debug'))
1917
1603
  this.log.debug('Sending a memory update message to all connected clients');
1918
- // Send the message to all connected clients
1919
1604
  this.webSocketServer?.clients.forEach((client) => {
1920
1605
  if (client.readyState === WebSocket.OPEN) {
1921
1606
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1922
1607
  }
1923
1608
  });
1924
1609
  }
1925
- /**
1926
- * Sends an uptime update message to all connected clients.
1927
- *
1928
- */
1929
1610
  wssSendUptimeUpdate(systemUptime, processUptime) {
1930
1611
  if (hasParameter('debug'))
1931
1612
  this.log.debug('Sending a uptime update message to all connected clients');
1932
- // Send the message to all connected clients
1933
1613
  this.webSocketServer?.clients.forEach((client) => {
1934
1614
  if (client.readyState === WebSocket.OPEN) {
1935
1615
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1936
1616
  }
1937
1617
  });
1938
1618
  }
1939
- /**
1940
- * Sends an open snackbar message to all connected clients.
1941
- * @param {string} message - The message to send.
1942
- * @param {number} timeout - The timeout in seconds for the snackbar message.
1943
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1944
- *
1945
- */
1946
1619
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1947
1620
  this.log.debug('Sending a snackbar message to all connected clients');
1948
- // Send the message to all connected clients
1949
1621
  this.webSocketServer?.clients.forEach((client) => {
1950
1622
  if (client.readyState === WebSocket.OPEN) {
1951
1623
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1952
1624
  }
1953
1625
  });
1954
1626
  }
1955
- /**
1956
- * Sends a close snackbar message to all connected clients.
1957
- * @param {string} message - The message to send.
1958
- *
1959
- */
1960
1627
  wssSendCloseSnackbarMessage(message) {
1961
1628
  this.log.debug('Sending a close snackbar message to all connected clients');
1962
- // Send the message to all connected clients
1963
1629
  this.webSocketServer?.clients.forEach((client) => {
1964
1630
  if (client.readyState === WebSocket.OPEN) {
1965
1631
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1966
1632
  }
1967
1633
  });
1968
1634
  }
1969
- /**
1970
- * Sends an attribute update message to all connected WebSocket clients.
1971
- *
1972
- * @param {string | undefined} plugin - The name of the plugin.
1973
- * @param {string | undefined} serialNumber - The serial number of the device.
1974
- * @param {string | undefined} uniqueId - The unique identifier of the device.
1975
- * @param {string} cluster - The cluster name where the attribute belongs.
1976
- * @param {string} attribute - The name of the attribute that changed.
1977
- * @param {number | string | boolean} value - The new value of the attribute.
1978
- *
1979
- * @remarks
1980
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1981
- * with the updated attribute information.
1982
- */
1983
1635
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1984
1636
  this.log.debug('Sending an attribute update message to all connected clients');
1985
- // Send the message to all connected clients
1986
1637
  this.webSocketServer?.clients.forEach((client) => {
1987
1638
  if (client.readyState === WebSocket.OPEN) {
1988
1639
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1989
1640
  }
1990
1641
  });
1991
1642
  }
1992
- /**
1993
- * Sends a message to all connected clients.
1994
- * @param {number} id - The message id.
1995
- * @param {string} method - The message method.
1996
- * @param {Record<string, string | number | boolean>} params - The message parameters.
1997
- *
1998
- */
1999
1643
  wssBroadcastMessage(id, method, params) {
2000
1644
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2001
- // Send the message to all connected clients
2002
1645
  this.webSocketServer?.clients.forEach((client) => {
2003
1646
  if (client.readyState === WebSocket.OPEN) {
2004
1647
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -2006,4 +1649,3 @@ export class Frontend {
2006
1649
  });
2007
1650
  }
2008
1651
  }
2009
- //# sourceMappingURL=frontend.js.map