matterbridge 3.0.5 → 3.0.6-dev-20250611-6f49811

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