matterbridge 2.2.0 → 2.2.2-dev.1

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 (157) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +2 -2
  3. package/dist/cli.js +2 -37
  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/frontend.js +124 -283
  8. package/dist/index.js +1 -28
  9. package/dist/logger/export.js +0 -1
  10. package/dist/matter/behaviors.js +0 -2
  11. package/dist/matter/clusters.js +0 -2
  12. package/dist/matter/devices.js +0 -2
  13. package/dist/matter/endpoints.js +0 -2
  14. package/dist/matter/export.js +0 -2
  15. package/dist/matter/types.js +0 -2
  16. package/dist/matterbridge.js +63 -732
  17. package/dist/matterbridgeAccessoryPlatform.js +0 -33
  18. package/dist/matterbridgeBehaviors.js +1 -32
  19. package/dist/matterbridgeDeviceTypes.js +11 -112
  20. package/dist/matterbridgeDynamicPlatform.js +0 -33
  21. package/dist/matterbridgeEndpoint.js +6 -690
  22. package/dist/matterbridgeEndpointHelpers.js +9 -118
  23. package/dist/matterbridgePlatform.js +67 -153
  24. package/dist/matterbridgeTypes.js +0 -24
  25. package/dist/pluginManager.js +6 -229
  26. package/dist/shelly.js +7 -122
  27. package/dist/storage/export.js +0 -1
  28. package/dist/update.js +3 -47
  29. package/dist/utils/colorUtils.js +2 -205
  30. package/dist/utils/copyDirectory.js +1 -37
  31. package/dist/utils/createZip.js +2 -42
  32. package/dist/utils/deepCopy.js +0 -40
  33. package/dist/utils/deepEqual.js +1 -65
  34. package/dist/utils/export.js +0 -1
  35. package/dist/utils/isvalid.js +0 -86
  36. package/dist/utils/network.js +5 -77
  37. package/dist/utils/parameter.js +0 -41
  38. package/dist/utils/wait.js +5 -48
  39. package/frontend/build/asset-manifest.json +6 -6
  40. package/frontend/build/index.html +1 -1
  41. package/frontend/build/static/css/{main.cf25d33e.css → main.b9449869.css} +2 -2
  42. package/frontend/build/static/css/{main.cf25d33e.css.map → main.b9449869.css.map} +1 -1
  43. package/frontend/build/static/js/main.92802eb1.js +115 -0
  44. package/frontend/build/static/js/main.92802eb1.js.map +1 -0
  45. package/npm-shrinkwrap.json +49 -49
  46. package/package.json +2 -3
  47. package/dist/cli.d.ts +0 -29
  48. package/dist/cli.d.ts.map +0 -1
  49. package/dist/cli.js.map +0 -1
  50. package/dist/cluster/export.d.ts +0 -2
  51. package/dist/cluster/export.d.ts.map +0 -1
  52. package/dist/cluster/export.js.map +0 -1
  53. package/dist/defaultConfigSchema.d.ts +0 -27
  54. package/dist/defaultConfigSchema.d.ts.map +0 -1
  55. package/dist/defaultConfigSchema.js.map +0 -1
  56. package/dist/deviceManager.d.ts +0 -114
  57. package/dist/deviceManager.d.ts.map +0 -1
  58. package/dist/deviceManager.js.map +0 -1
  59. package/dist/frontend.d.ts +0 -172
  60. package/dist/frontend.d.ts.map +0 -1
  61. package/dist/frontend.js.map +0 -1
  62. package/dist/index.d.ts +0 -35
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js.map +0 -1
  65. package/dist/logger/export.d.ts +0 -2
  66. package/dist/logger/export.d.ts.map +0 -1
  67. package/dist/logger/export.js.map +0 -1
  68. package/dist/matter/behaviors.d.ts +0 -2
  69. package/dist/matter/behaviors.d.ts.map +0 -1
  70. package/dist/matter/behaviors.js.map +0 -1
  71. package/dist/matter/clusters.d.ts +0 -2
  72. package/dist/matter/clusters.d.ts.map +0 -1
  73. package/dist/matter/clusters.js.map +0 -1
  74. package/dist/matter/devices.d.ts +0 -2
  75. package/dist/matter/devices.d.ts.map +0 -1
  76. package/dist/matter/devices.js.map +0 -1
  77. package/dist/matter/endpoints.d.ts +0 -2
  78. package/dist/matter/endpoints.d.ts.map +0 -1
  79. package/dist/matter/endpoints.js.map +0 -1
  80. package/dist/matter/export.d.ts +0 -5
  81. package/dist/matter/export.d.ts.map +0 -1
  82. package/dist/matter/export.js.map +0 -1
  83. package/dist/matter/types.d.ts +0 -3
  84. package/dist/matter/types.d.ts.map +0 -1
  85. package/dist/matter/types.js.map +0 -1
  86. package/dist/matterbridge.d.ts +0 -411
  87. package/dist/matterbridge.d.ts.map +0 -1
  88. package/dist/matterbridge.js.map +0 -1
  89. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
  90. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  91. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  92. package/dist/matterbridgeBehaviors.d.ts +0 -1056
  93. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  94. package/dist/matterbridgeBehaviors.js.map +0 -1
  95. package/dist/matterbridgeDeviceTypes.d.ts +0 -177
  96. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  97. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  98. package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
  99. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  100. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  101. package/dist/matterbridgeEndpoint.d.ts +0 -835
  102. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  103. package/dist/matterbridgeEndpoint.js.map +0 -1
  104. package/dist/matterbridgeEndpointHelpers.d.ts +0 -2275
  105. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  106. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  107. package/dist/matterbridgePlatform.d.ts +0 -181
  108. package/dist/matterbridgePlatform.d.ts.map +0 -1
  109. package/dist/matterbridgePlatform.js.map +0 -1
  110. package/dist/matterbridgeTypes.d.ts +0 -174
  111. package/dist/matterbridgeTypes.d.ts.map +0 -1
  112. package/dist/matterbridgeTypes.js.map +0 -1
  113. package/dist/pluginManager.d.ts +0 -236
  114. package/dist/pluginManager.d.ts.map +0 -1
  115. package/dist/pluginManager.js.map +0 -1
  116. package/dist/shelly.d.ts +0 -77
  117. package/dist/shelly.d.ts.map +0 -1
  118. package/dist/shelly.js.map +0 -1
  119. package/dist/storage/export.d.ts +0 -2
  120. package/dist/storage/export.d.ts.map +0 -1
  121. package/dist/storage/export.js.map +0 -1
  122. package/dist/update.d.ts +0 -32
  123. package/dist/update.d.ts.map +0 -1
  124. package/dist/update.js.map +0 -1
  125. package/dist/utils/colorUtils.d.ts +0 -61
  126. package/dist/utils/colorUtils.d.ts.map +0 -1
  127. package/dist/utils/colorUtils.js.map +0 -1
  128. package/dist/utils/copyDirectory.d.ts +0 -32
  129. package/dist/utils/copyDirectory.d.ts.map +0 -1
  130. package/dist/utils/copyDirectory.js.map +0 -1
  131. package/dist/utils/createZip.d.ts +0 -38
  132. package/dist/utils/createZip.d.ts.map +0 -1
  133. package/dist/utils/createZip.js.map +0 -1
  134. package/dist/utils/deepCopy.d.ts +0 -31
  135. package/dist/utils/deepCopy.d.ts.map +0 -1
  136. package/dist/utils/deepCopy.js.map +0 -1
  137. package/dist/utils/deepEqual.d.ts +0 -53
  138. package/dist/utils/deepEqual.d.ts.map +0 -1
  139. package/dist/utils/deepEqual.js.map +0 -1
  140. package/dist/utils/export.d.ts +0 -10
  141. package/dist/utils/export.d.ts.map +0 -1
  142. package/dist/utils/export.js.map +0 -1
  143. package/dist/utils/isvalid.d.ts +0 -87
  144. package/dist/utils/isvalid.d.ts.map +0 -1
  145. package/dist/utils/isvalid.js.map +0 -1
  146. package/dist/utils/network.d.ts +0 -70
  147. package/dist/utils/network.d.ts.map +0 -1
  148. package/dist/utils/network.js.map +0 -1
  149. package/dist/utils/parameter.d.ts +0 -44
  150. package/dist/utils/parameter.d.ts.map +0 -1
  151. package/dist/utils/parameter.js.map +0 -1
  152. package/dist/utils/wait.d.ts +0 -43
  153. package/dist/utils/wait.d.ts.map +0 -1
  154. package/dist/utils/wait.js.map +0 -1
  155. package/frontend/build/static/js/main.8240902c.js +0 -115
  156. package/frontend/build/static/js/main.8240902c.js.map +0 -1
  157. /package/frontend/build/static/js/{main.8240902c.js.LICENSE.txt → main.92802eb1.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,28 +1,4 @@
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 { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
- // Node modules
26
2
  import { createServer } from 'node:http';
27
3
  import https from 'https';
28
4
  import express from 'express';
@@ -30,70 +6,21 @@ import WebSocket, { WebSocketServer } from 'ws';
30
6
  import os from 'node:os';
31
7
  import path from 'node:path';
32
8
  import { promises as fs } from 'node:fs';
33
- // AnsiLogger module
34
9
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
35
- // Matterbridge
36
- import { createZip, deepCopy, isValidNumber, isValidObject, isValidString } from './utils/export.js';
10
+ import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString } from './utils/export.js';
37
11
  import { plg } from './matterbridgeTypes.js';
38
12
  import { hasParameter } from './utils/export.js';
39
- /**
40
- * Websocket message ID for logging.
41
- * @constant {number}
42
- */
13
+ import { BridgedDeviceBasicInformation } from '@matter/main/clusters';
43
14
  export const WS_ID_LOG = 0;
44
- /**
45
- * Websocket message ID indicating a refresh is needed.
46
- * @constant {number}
47
- */
48
15
  export const WS_ID_REFRESH_NEEDED = 1;
49
- /**
50
- * Websocket message ID indicating a restart is needed.
51
- * @constant {number}
52
- */
53
16
  export const WS_ID_RESTART_NEEDED = 2;
54
- /**
55
- * Websocket message ID indicating a cpu update.
56
- * @constant {number}
57
- */
58
17
  export const WS_ID_CPU_UPDATE = 3;
59
- /**
60
- * Websocket message ID indicating a memory update.
61
- * @constant {number}
62
- */
63
18
  export const WS_ID_MEMORY_UPDATE = 4;
64
- /**
65
- * Websocket message ID indicating an uptime update.
66
- * @constant {number}
67
- */
68
19
  export const WS_ID_UPTIME_UPDATE = 5;
69
- /**
70
- * Websocket message ID indicating a memory update.
71
- * @constant {number}
72
- */
73
20
  export const WS_ID_SNACKBAR = 6;
74
- /**
75
- * Websocket message ID indicating a shelly system update.
76
- * check:
77
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
78
- * perform:
79
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
80
- * @constant {number}
81
- */
21
+ export const WS_ID_UPDATE_NEEDED = 7;
82
22
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
83
- /**
84
- * Websocket message ID indicating a shelly main update.
85
- * check:
86
- * curl -k http://127.0.0.1:8101/api/updates/main/check
87
- * perform:
88
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
89
- * @constant {number}
90
- */
91
23
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
92
- /**
93
- * Initializes the frontend of Matterbridge.
94
- *
95
- * @param port The port number to run the frontend server on. Default is 8283.
96
- */
97
24
  export class Frontend {
98
25
  matterbridge;
99
26
  log;
@@ -110,7 +37,7 @@ export class Frontend {
110
37
  memoryTimeout;
111
38
  constructor(matterbridge) {
112
39
  this.matterbridge = matterbridge;
113
- 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" });
114
41
  }
115
42
  set logLevel(logLevel) {
116
43
  this.log.logLevel = logLevel;
@@ -118,21 +45,10 @@ export class Frontend {
118
45
  async start(port = 8283) {
119
46
  this.port = port;
120
47
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
121
- // Create the express app that serves the frontend
122
48
  this.expressApp = express();
123
- // Log all requests to the server for debugging
124
- /*
125
- this.expressApp.use((req, res, next) => {
126
- this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
127
- next();
128
- });
129
- */
130
- // Serve static files from '/static' endpoint
131
49
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
132
50
  if (!hasParameter('ssl')) {
133
- // Create an HTTP server and attach the express app
134
51
  this.httpServer = createServer(this.expressApp);
135
- // Listen on the specified port
136
52
  if (hasParameter('ingress')) {
137
53
  this.httpServer.listen(this.port, '0.0.0.0', () => {
138
54
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -146,7 +62,6 @@ export class Frontend {
146
62
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
147
63
  });
148
64
  }
149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
65
  this.httpServer.on('error', (error) => {
151
66
  this.log.error(`Frontend http server error listening on ${this.port}`);
152
67
  switch (error.code) {
@@ -162,7 +77,6 @@ export class Frontend {
162
77
  });
163
78
  }
164
79
  else {
165
- // Load the SSL certificate, the private key and optionally the CA certificate
166
80
  let cert;
167
81
  try {
168
82
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -190,9 +104,7 @@ export class Frontend {
190
104
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
191
105
  }
192
106
  const serverOptions = { cert, key, ca };
193
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
194
107
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
195
- // Listen on the specified port
196
108
  if (hasParameter('ingress')) {
197
109
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
198
110
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -206,7 +118,6 @@ export class Frontend {
206
118
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
207
119
  });
208
120
  }
209
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
121
  this.httpsServer.on('error', (error) => {
211
122
  this.log.error(`Frontend https server error listening on ${this.port}`);
212
123
  switch (error.code) {
@@ -223,18 +134,16 @@ export class Frontend {
223
134
  }
224
135
  if (this.initializeError)
225
136
  return;
226
- // Create a WebSocket server and attach it to the http or https server
227
137
  const wssPort = this.port;
228
138
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
229
139
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
230
140
  this.webSocketServer.on('connection', (ws, request) => {
231
141
  const clientIp = request.socket.remoteAddress;
232
- // Set the global logger callback for the WebSocketServer
233
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
234
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
235
- callbackLogLevel = "info" /* LogLevel.INFO */;
236
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
237
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
142
+ let callbackLogLevel = "notice";
143
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
144
+ callbackLogLevel = "info";
145
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
146
+ callbackLogLevel = "debug";
238
147
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
239
148
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
240
149
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -268,7 +177,6 @@ export class Frontend {
268
177
  this.webSocketServer.on('error', (ws, error) => {
269
178
  this.log.error(`WebSocketServer error: ${error}`);
270
179
  });
271
- // Subscribe to cli events
272
180
  const { cliEmitter } = await import('./cli.js');
273
181
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
274
182
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -279,7 +187,6 @@ export class Frontend {
279
187
  cliEmitter.on('cpu', (cpuUsage) => {
280
188
  this.wssSendCpuUpdate(cpuUsage);
281
189
  });
282
- // Endpoint to validate login code
283
190
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
284
191
  const { password } = req.body;
285
192
  this.log.debug('The frontend sent /api/login', password);
@@ -298,27 +205,23 @@ export class Frontend {
298
205
  this.log.warn('/api/login error wrong password');
299
206
  res.json({ valid: false });
300
207
  }
301
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
208
  }
303
209
  catch (error) {
304
210
  this.log.error('/api/login error getting password');
305
211
  res.json({ valid: false });
306
212
  }
307
213
  });
308
- // Endpoint to provide health check
309
214
  this.expressApp.get('/health', (req, res) => {
310
215
  this.log.debug('Express received /health');
311
216
  const healthStatus = {
312
- status: 'ok', // Indicate service is healthy
313
- uptime: process.uptime(), // Server uptime in seconds
314
- timestamp: new Date().toISOString(), // Current timestamp
217
+ status: 'ok',
218
+ uptime: process.uptime(),
219
+ timestamp: new Date().toISOString(),
315
220
  };
316
221
  res.status(200).json(healthStatus);
317
222
  });
318
- // Endpoint to provide memory usage details
319
223
  this.expressApp.get('/memory', async (req, res) => {
320
224
  this.log.debug('Express received /memory');
321
- // Memory usage from process
322
225
  const memoryUsageRaw = process.memoryUsage();
323
226
  const memoryUsage = {
324
227
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -327,13 +230,10 @@ export class Frontend {
327
230
  external: this.formatMemoryUsage(memoryUsageRaw.external),
328
231
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
329
232
  };
330
- // V8 heap statistics
331
233
  const { default: v8 } = await import('node:v8');
332
234
  const heapStatsRaw = v8.getHeapStatistics();
333
235
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
334
- // Format heapStats
335
236
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
336
- // Format heapSpaces
337
237
  const heapSpaces = heapSpacesRaw.map((space) => ({
338
238
  ...space,
339
239
  space_size: this.formatMemoryUsage(space.space_size),
@@ -351,7 +251,6 @@ export class Frontend {
351
251
  };
352
252
  res.status(200).json(memoryReport);
353
253
  });
354
- // Endpoint to start advertising the server node
355
254
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
356
255
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
357
256
  if (pairingCodes) {
@@ -362,22 +261,18 @@ export class Frontend {
362
261
  res.status(500).json({ error: 'Failed to generate pairing codes' });
363
262
  }
364
263
  });
365
- // Endpoint to provide settings
366
264
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
367
265
  this.log.debug('The frontend sent /api/settings');
368
266
  res.json(await this.getApiSettings());
369
267
  });
370
- // Endpoint to provide plugins
371
268
  this.expressApp.get('/api/plugins', async (req, res) => {
372
269
  this.log.debug('The frontend sent /api/plugins');
373
270
  res.json(this.getBaseRegisteredPlugins());
374
271
  });
375
- // Endpoint to provide devices
376
272
  this.expressApp.get('/api/devices', (req, res) => {
377
273
  this.log.debug('The frontend sent /api/devices');
378
274
  const devices = [];
379
275
  this.matterbridge.devices.forEach(async (device) => {
380
- // Check if the device has the required properties
381
276
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
382
277
  return;
383
278
  const cluster = this.getClusterTextFromDevice(device);
@@ -390,12 +285,12 @@ export class Frontend {
390
285
  productUrl: device.productUrl,
391
286
  configUrl: device.configUrl,
392
287
  uniqueId: device.uniqueId,
288
+ reachable: this.getReachability(device),
393
289
  cluster: cluster,
394
290
  });
395
291
  });
396
292
  res.json(devices);
397
293
  });
398
- // Endpoint to provide the cluster servers of the devices
399
294
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
400
295
  const selectedPluginName = req.params.selectedPluginName;
401
296
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -468,7 +363,6 @@ export class Frontend {
468
363
  });
469
364
  res.json(data);
470
365
  });
471
- // Endpoint to view the log
472
366
  this.expressApp.get('/api/view-log', async (req, res) => {
473
367
  this.log.debug('The frontend sent /api/log');
474
368
  try {
@@ -481,12 +375,10 @@ export class Frontend {
481
375
  res.status(500).send('Error reading log file');
482
376
  }
483
377
  });
484
- // Endpoint to download the matterbridge log
485
378
  this.expressApp.get('/api/download-mblog', async (req, res) => {
486
379
  this.log.debug('The frontend sent /api/download-mblog');
487
380
  try {
488
381
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
489
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
490
382
  }
491
383
  catch (error) {
492
384
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -498,12 +390,10 @@ export class Frontend {
498
390
  }
499
391
  });
500
392
  });
501
- // Endpoint to download the matter log
502
393
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
503
394
  this.log.debug('The frontend sent /api/download-mjlog');
504
395
  try {
505
396
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
506
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
507
397
  }
508
398
  catch (error) {
509
399
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -515,12 +405,10 @@ export class Frontend {
515
405
  }
516
406
  });
517
407
  });
518
- // Endpoint to download the matter log
519
408
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
520
409
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
521
410
  try {
522
411
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
523
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
524
412
  }
525
413
  catch (error) {
526
414
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
@@ -532,7 +420,6 @@ export class Frontend {
532
420
  }
533
421
  });
534
422
  });
535
- // Endpoint to download the matter storage file
536
423
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
537
424
  this.log.debug('The frontend sent /api/download-mjstorage');
538
425
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -543,7 +430,6 @@ export class Frontend {
543
430
  }
544
431
  });
545
432
  });
546
- // Endpoint to download the matterbridge storage directory
547
433
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
548
434
  this.log.debug('The frontend sent /api/download-mbstorage');
549
435
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -554,7 +440,6 @@ export class Frontend {
554
440
  }
555
441
  });
556
442
  });
557
- // Endpoint to download the matterbridge plugin directory
558
443
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
559
444
  this.log.debug('The frontend sent /api/download-pluginstorage');
560
445
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -565,11 +450,9 @@ export class Frontend {
565
450
  }
566
451
  });
567
452
  });
568
- // Endpoint to download the matterbridge plugin config files
569
453
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
570
454
  this.log.debug('The frontend sent /api/download-pluginconfig');
571
455
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
572
- // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
573
456
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
574
457
  if (error) {
575
458
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -577,7 +460,6 @@ export class Frontend {
577
460
  }
578
461
  });
579
462
  });
580
- // Endpoint to download the matterbridge plugin config files
581
463
  this.expressApp.get('/api/download-backup', async (req, res) => {
582
464
  this.log.debug('The frontend sent /api/download-backup');
583
465
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -587,7 +469,6 @@ export class Frontend {
587
469
  }
588
470
  });
589
471
  });
590
- // Endpoint to receive commands
591
472
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
592
473
  const command = req.params.command;
593
474
  let param = req.params.param;
@@ -597,15 +478,13 @@ export class Frontend {
597
478
  return;
598
479
  }
599
480
  this.log.debug(`Received frontend command: ${command}:${param}`);
600
- // Handle the command setpassword from Settings
601
481
  if (command === 'setpassword') {
602
- const password = param.slice(1, -1); // Remove the first and last characters
482
+ const password = param.slice(1, -1);
603
483
  this.log.debug('setpassword', param, password);
604
484
  await this.matterbridge.nodeContext?.set('password', password);
605
485
  res.json({ message: 'Command received' });
606
486
  return;
607
487
  }
608
- // Handle the command setbridgemode from Settings
609
488
  if (command === 'setbridgemode') {
610
489
  this.log.debug(`setbridgemode: ${param}`);
611
490
  this.wssSendRestartRequired();
@@ -613,7 +492,6 @@ export class Frontend {
613
492
  res.json({ message: 'Command received' });
614
493
  return;
615
494
  }
616
- // Handle the command backup from Settings
617
495
  if (command === 'backup') {
618
496
  this.log.notice(`Prepairing the backup...`);
619
497
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -622,33 +500,31 @@ export class Frontend {
622
500
  res.json({ message: 'Command received' });
623
501
  return;
624
502
  }
625
- // Handle the command setmbloglevel from Settings
626
503
  if (command === 'setmbloglevel') {
627
504
  this.log.debug('Matterbridge log level:', param);
628
505
  if (param === 'Debug') {
629
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
506
+ this.log.logLevel = "debug";
630
507
  }
631
508
  else if (param === 'Info') {
632
- this.log.logLevel = "info" /* LogLevel.INFO */;
509
+ this.log.logLevel = "info";
633
510
  }
634
511
  else if (param === 'Notice') {
635
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
512
+ this.log.logLevel = "notice";
636
513
  }
637
514
  else if (param === 'Warn') {
638
- this.log.logLevel = "warn" /* LogLevel.WARN */;
515
+ this.log.logLevel = "warn";
639
516
  }
640
517
  else if (param === 'Error') {
641
- this.log.logLevel = "error" /* LogLevel.ERROR */;
518
+ this.log.logLevel = "error";
642
519
  }
643
520
  else if (param === 'Fatal') {
644
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
521
+ this.log.logLevel = "fatal";
645
522
  }
646
523
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
647
524
  await this.matterbridge.setLogLevel(this.log.logLevel);
648
525
  res.json({ message: 'Command received' });
649
526
  return;
650
527
  }
651
- // Handle the command setmbloglevel from Settings
652
528
  if (command === 'setmjloglevel') {
653
529
  this.log.debug('Matter.js log level:', param);
654
530
  if (param === 'Debug') {
@@ -673,34 +549,30 @@ export class Frontend {
673
549
  res.json({ message: 'Command received' });
674
550
  return;
675
551
  }
676
- // Handle the command setmdnsinterface from Settings
677
552
  if (command === 'setmdnsinterface') {
678
- param = param.slice(1, -1); // Remove the first and last characters *mdns*
553
+ param = param.slice(1, -1);
679
554
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
680
555
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
681
556
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
682
557
  res.json({ message: 'Command received' });
683
558
  return;
684
559
  }
685
- // Handle the command setipv4address from Settings
686
560
  if (command === 'setipv4address') {
687
- param = param.slice(1, -1); // Remove the first and last characters *ip*
561
+ param = param.slice(1, -1);
688
562
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
689
563
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
690
564
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
691
565
  res.json({ message: 'Command received' });
692
566
  return;
693
567
  }
694
- // Handle the command setipv6address from Settings
695
568
  if (command === 'setipv6address') {
696
- param = param.slice(1, -1); // Remove the first and last characters *ip*
569
+ param = param.slice(1, -1);
697
570
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
698
571
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
699
572
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
700
573
  res.json({ message: 'Command received' });
701
574
  return;
702
575
  }
703
- // Handle the command setmatterport from Settings
704
576
  if (command === 'setmatterport') {
705
577
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
706
578
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -709,7 +581,6 @@ export class Frontend {
709
581
  res.json({ message: 'Command received' });
710
582
  return;
711
583
  }
712
- // Handle the command setmatterdiscriminator from Settings
713
584
  if (command === 'setmatterdiscriminator') {
714
585
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
715
586
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -718,7 +589,6 @@ export class Frontend {
718
589
  res.json({ message: 'Command received' });
719
590
  return;
720
591
  }
721
- // Handle the command setmatterpasscode from Settings
722
592
  if (command === 'setmatterpasscode') {
723
593
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
724
594
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -727,20 +597,17 @@ export class Frontend {
727
597
  res.json({ message: 'Command received' });
728
598
  return;
729
599
  }
730
- // Handle the command setmbloglevel from Settings
731
600
  if (command === 'setmblogfile') {
732
601
  this.log.debug('Matterbridge file log:', param);
733
602
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
734
603
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
735
- // Create the file logger for matterbridge
736
604
  if (param === 'true')
737
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
605
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
738
606
  else
739
607
  AnsiLogger.setGlobalLogfile(undefined);
740
608
  res.json({ message: 'Command received' });
741
609
  return;
742
610
  }
743
- // Handle the command setmbloglevel from Settings
744
611
  if (command === 'setmjlogfile') {
745
612
  this.log.debug('Matter file log:', param);
746
613
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -767,48 +634,40 @@ export class Frontend {
767
634
  res.json({ message: 'Command received' });
768
635
  return;
769
636
  }
770
- // Handle the command unregister from Settings
771
637
  if (command === 'unregister') {
772
638
  await this.matterbridge.unregisterAndShutdownProcess();
773
639
  res.json({ message: 'Command received' });
774
640
  return;
775
641
  }
776
- // Handle the command reset from Settings
777
642
  if (command === 'reset') {
778
643
  await this.matterbridge.shutdownProcessAndReset();
779
644
  res.json({ message: 'Command received' });
780
645
  return;
781
646
  }
782
- // Handle the command factoryreset from Settings
783
647
  if (command === 'factoryreset') {
784
648
  await this.matterbridge.shutdownProcessAndFactoryReset();
785
649
  res.json({ message: 'Command received' });
786
650
  return;
787
651
  }
788
- // Handle the command shutdown from Header
789
652
  if (command === 'shutdown') {
790
653
  await this.matterbridge.shutdownProcess();
791
654
  res.json({ message: 'Command received' });
792
655
  return;
793
656
  }
794
- // Handle the command restart from Header
795
657
  if (command === 'restart') {
796
658
  await this.matterbridge.restartProcess();
797
659
  res.json({ message: 'Command received' });
798
660
  return;
799
661
  }
800
- // Handle the command update from Header
801
662
  if (command === 'update') {
802
663
  await this.matterbridge.updateProcess();
803
664
  this.wssSendRestartRequired();
804
665
  res.json({ message: 'Command received' });
805
666
  return;
806
667
  }
807
- // Handle the command saveconfig from Home
808
668
  if (command === 'saveconfig') {
809
669
  param = param.replace(/\*/g, '\\');
810
670
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
811
- // console.log('Req.body:', JSON.stringify(req.body, null, 2));
812
671
  if (!this.matterbridge.plugins.has(param)) {
813
672
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
814
673
  }
@@ -823,7 +682,6 @@ export class Frontend {
823
682
  res.json({ message: 'Command received' });
824
683
  return;
825
684
  }
826
- // Handle the command installplugin from Home
827
685
  if (command === 'installplugin') {
828
686
  param = param.replace(/\*/g, '\\');
829
687
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -832,54 +690,47 @@ export class Frontend {
832
690
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
833
691
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
834
692
  this.wssSendSnackbarMessage(`Installed package ${param}`);
835
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
836
693
  }
837
694
  catch (error) {
838
695
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
839
696
  this.wssSendSnackbarMessage(`Package ${param} not installed`);
840
697
  }
841
- this.wssSendSnackbarMessage(`Restart required`, 0);
842
698
  this.wssSendRestartRequired();
843
699
  param = param.split('@')[0];
844
- // Also add the plugin to matterbridge so no return!
845
700
  if (param === 'matterbridge') {
846
- // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
847
701
  res.json({ message: 'Command received' });
848
702
  return;
849
703
  }
850
704
  }
851
- // Handle the command addplugin from Home
852
705
  if (command === 'addplugin' || command === 'installplugin') {
853
706
  param = param.replace(/\*/g, '\\');
854
707
  const plugin = await this.matterbridge.plugins.add(param);
855
708
  if (plugin) {
709
+ this.wssSendSnackbarMessage(`Added plugin ${param}`);
856
710
  if (this.matterbridge.bridgeMode === 'childbridge') {
857
- // We don't know now if the plugin is a dynamic platform or an accessory platform so we create the server node and the aggregator node
858
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
859
711
  this.matterbridge.createDynamicPlugin(plugin, true);
860
712
  }
861
713
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
862
- this.wssSendRefreshRequired();
714
+ this.wssSendRefreshRequired('plugins');
863
715
  });
864
716
  }
865
717
  res.json({ message: 'Command received' });
866
718
  return;
867
719
  }
868
- // Handle the command removeplugin from Home
869
720
  if (command === 'removeplugin') {
870
721
  if (!this.matterbridge.plugins.has(param)) {
871
722
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
872
723
  }
873
724
  else {
874
725
  const plugin = this.matterbridge.plugins.get(param);
875
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
726
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
876
727
  await this.matterbridge.plugins.remove(param);
728
+ this.wssSendSnackbarMessage(`Removed plugin ${param}`);
729
+ this.wssSendRefreshRequired('plugins');
877
730
  }
878
731
  res.json({ message: 'Command received' });
879
- this.wssSendRefreshRequired();
880
732
  return;
881
733
  }
882
- // Handle the command enableplugin from Home
883
734
  if (command === 'enableplugin') {
884
735
  if (!this.matterbridge.plugins.has(param)) {
885
736
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -896,20 +747,18 @@ export class Frontend {
896
747
  plugin.registeredDevices = undefined;
897
748
  plugin.addedDevices = undefined;
898
749
  await this.matterbridge.plugins.enable(param);
750
+ this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
899
751
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
900
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
901
752
  this.matterbridge.createDynamicPlugin(plugin, true);
902
753
  }
903
754
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
904
- this.wssSendRefreshRequired();
755
+ this.wssSendRefreshRequired('plugins');
905
756
  });
906
757
  }
907
758
  }
908
759
  res.json({ message: 'Command received' });
909
- this.wssSendRefreshRequired();
910
760
  return;
911
761
  }
912
- // Handle the command disableplugin from Home
913
762
  if (command === 'disableplugin') {
914
763
  if (!this.matterbridge.plugins.has(param)) {
915
764
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -917,16 +766,16 @@ export class Frontend {
917
766
  else {
918
767
  const plugin = this.matterbridge.plugins.get(param);
919
768
  if (plugin && plugin.enabled) {
920
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
769
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
921
770
  await this.matterbridge.plugins.disable(param);
771
+ this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
772
+ this.wssSendRefreshRequired('plugins');
922
773
  }
923
774
  }
924
775
  res.json({ message: 'Command received' });
925
- this.wssSendRefreshRequired();
926
776
  return;
927
777
  }
928
778
  });
929
- // Fallback for routing (must be the last route)
930
779
  this.expressApp.get('*', (req, res) => {
931
780
  this.log.debug('The frontend sent:', req.url);
932
781
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -935,29 +784,24 @@ export class Frontend {
935
784
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
936
785
  }
937
786
  async stop() {
938
- // Close the http server
939
787
  if (this.httpServer) {
940
788
  this.httpServer.close();
941
789
  this.httpServer.removeAllListeners();
942
790
  this.httpServer = undefined;
943
791
  this.log.debug('Frontend http server closed successfully');
944
792
  }
945
- // Close the https server
946
793
  if (this.httpsServer) {
947
794
  this.httpsServer.close();
948
795
  this.httpsServer.removeAllListeners();
949
796
  this.httpsServer = undefined;
950
797
  this.log.debug('Frontend https server closed successfully');
951
798
  }
952
- // Remove listeners from the express app
953
799
  if (this.expressApp) {
954
800
  this.expressApp.removeAllListeners();
955
801
  this.expressApp = undefined;
956
802
  this.log.debug('Frontend app closed successfully');
957
803
  }
958
- // Close the WebSocket server
959
804
  if (this.webSocketServer) {
960
- // Close all active connections
961
805
  this.webSocketServer.clients.forEach((client) => {
962
806
  if (client.readyState === WebSocket.OPEN) {
963
807
  client.close();
@@ -974,7 +818,6 @@ export class Frontend {
974
818
  this.webSocketServer = undefined;
975
819
  }
976
820
  }
977
- // Function to format bytes to KB, MB, or GB
978
821
  formatMemoryUsage = (bytes) => {
979
822
  if (bytes >= 1024 ** 3) {
980
823
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -986,7 +829,6 @@ export class Frontend {
986
829
  return `${(bytes / 1024).toFixed(2)} KB`;
987
830
  }
988
831
  };
989
- // Function to format system uptime with only the most significant unit
990
832
  formatOsUpTime = (seconds) => {
991
833
  if (seconds >= 86400) {
992
834
  const days = Math.floor(seconds / 86400);
@@ -1002,13 +844,8 @@ export class Frontend {
1002
844
  }
1003
845
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
1004
846
  };
1005
- /**
1006
- * Retrieves the api settings data.
1007
- * @returns {Promise<object>} A promise that resolve in the api settings object.
1008
- */
1009
847
  async getApiSettings() {
1010
848
  const { lastCpuUsage } = await import('./cli.js');
1011
- // Update the system information
1012
849
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
1013
850
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
1014
851
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -1017,7 +854,6 @@ export class Frontend {
1017
854
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
1018
855
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
1019
856
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1020
- // Update the matterbridge information
1021
857
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
1022
858
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
1023
859
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -1036,11 +872,15 @@ export class Frontend {
1036
872
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
1037
873
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
1038
874
  }
1039
- /**
1040
- * Retrieves the cluster text description from a given device.
1041
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1042
- * @returns {string} The attributes description of the cluster servers in the device.
1043
- */
875
+ getReachability(device) {
876
+ if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
877
+ return false;
878
+ if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
879
+ return device.getAttribute(BridgedDeviceBasicInformation.Cluster.id, 'reachable');
880
+ if (this.matterbridge.bridgeMode === 'childbridge')
881
+ return true;
882
+ return false;
883
+ }
1044
884
  getClusterTextFromDevice(device) {
1045
885
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1046
886
  return '';
@@ -1081,7 +921,6 @@ export class Frontend {
1081
921
  };
1082
922
  let attributes = '';
1083
923
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1084
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1085
924
  if (typeof attributeValue === 'undefined')
1086
925
  return;
1087
926
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1159,13 +998,8 @@ export class Frontend {
1159
998
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1160
999
  attributes += `${getUserLabel(device)} `;
1161
1000
  });
1162
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1163
1001
  return attributes.trimStart().trimEnd();
1164
1002
  }
1165
- /**
1166
- * Retrieves the base registered plugins sanitized for res.json().
1167
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1168
- */
1169
1003
  getBaseRegisteredPlugins() {
1170
1004
  const baseRegisteredPlugins = [];
1171
1005
  for (const plugin of this.matterbridge.plugins) {
@@ -1192,17 +1026,12 @@ export class Frontend {
1192
1026
  manualPairingCode: plugin.manualPairingCode,
1193
1027
  configJson: plugin.configJson,
1194
1028
  schemaJson: plugin.schemaJson,
1029
+ hasWhiteList: plugin.configJson?.whiteList !== undefined,
1030
+ hasBlackList: plugin.configJson?.blackList !== undefined,
1195
1031
  });
1196
1032
  }
1197
1033
  return baseRegisteredPlugins;
1198
1034
  }
1199
- /**
1200
- * Handles incoming websocket messages for the Matterbridge frontend.
1201
- *
1202
- * @param {WebSocket} client - The websocket client that sent the message.
1203
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1204
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1205
- */
1206
1035
  async wsMessageHandler(client, message) {
1207
1036
  let data;
1208
1037
  try {
@@ -1323,7 +1152,7 @@ export class Frontend {
1323
1152
  this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
1324
1153
  this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
1325
1154
  this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
1326
- this.wssSendRefreshRequired();
1155
+ this.wssSendRefreshRequired('matterbridgeAdvertise');
1327
1156
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1328
1157
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes }));
1329
1158
  return;
@@ -1331,7 +1160,7 @@ export class Frontend {
1331
1160
  else if (data.method === '/api/stopadvertise') {
1332
1161
  await this.matterbridge.stopAdvertiseServerNode(this.matterbridge.serverNode);
1333
1162
  this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = false;
1334
- this.wssSendRefreshRequired();
1163
+ this.wssSendRefreshRequired('matterbridgeAdvertise');
1335
1164
  this.wssSendSnackbarMessage(`Stopped fabrics share`, 0);
1336
1165
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src }));
1337
1166
  return;
@@ -1348,10 +1177,8 @@ export class Frontend {
1348
1177
  else if (data.method === '/api/devices') {
1349
1178
  const devices = [];
1350
1179
  this.matterbridge.devices.forEach(async (device) => {
1351
- // Filter by pluginName if provided
1352
1180
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1353
1181
  return;
1354
- // Check if the device has the required properties
1355
1182
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1356
1183
  return;
1357
1184
  const cluster = this.getClusterTextFromDevice(device);
@@ -1364,6 +1191,7 @@ export class Frontend {
1364
1191
  productUrl: device.productUrl,
1365
1192
  configUrl: device.configUrl,
1366
1193
  uniqueId: device.uniqueId,
1194
+ reachable: this.getReachability(device),
1367
1195
  cluster: cluster,
1368
1196
  });
1369
1197
  });
@@ -1434,7 +1262,6 @@ export class Frontend {
1434
1262
  });
1435
1263
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1436
1264
  deviceTypes = [];
1437
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1438
1265
  const name = childEndpoint.endpoint?.id;
1439
1266
  const clusterServers = childEndpoint.getAllClusterServers();
1440
1267
  clusterServers.forEach((clusterServer) => {
@@ -1488,7 +1315,7 @@ export class Frontend {
1488
1315
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select' }));
1489
1316
  return;
1490
1317
  }
1491
- const selectDeviceValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectDevice.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1318
+ const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1492
1319
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
1493
1320
  return;
1494
1321
  }
@@ -1502,10 +1329,70 @@ export class Frontend {
1502
1329
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' }));
1503
1330
  return;
1504
1331
  }
1505
- const selectEntityValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectEntity.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1332
+ const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1506
1333
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
1507
1334
  return;
1508
1335
  }
1336
+ else if (data.method === '/api/command') {
1337
+ if (!isValidString(data.params.command, 5)) {
1338
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter command in /api/command' }));
1339
+ return;
1340
+ }
1341
+ if (data.params.command === 'selectdevice' && isValidString(data.params.plugin, 10) && isValidString(data.params.serial, 1)) {
1342
+ const plugin = this.matterbridge.plugins.get(data.params.plugin);
1343
+ if (!plugin) {
1344
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/command' }));
1345
+ return;
1346
+ }
1347
+ const config = plugin.configJson;
1348
+ if (config) {
1349
+ if (config.postfix) {
1350
+ data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1351
+ }
1352
+ if (isValidArray(config.whiteList, 1)) {
1353
+ if (!config.whiteList.includes(data.params.serial)) {
1354
+ config.whiteList.push(data.params.serial);
1355
+ }
1356
+ }
1357
+ if (isValidArray(config.blackList, 1)) {
1358
+ if (config.blackList.includes(data.params.serial)) {
1359
+ config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
1360
+ }
1361
+ }
1362
+ if (plugin.platform)
1363
+ plugin.platform.config = config;
1364
+ await this.matterbridge.plugins.saveConfigFromPlugin(plugin);
1365
+ this.wssSendRestartRequired(false);
1366
+ }
1367
+ }
1368
+ else if (data.params.command === 'unselectdevice' && isValidString(data.params.plugin, 10) && isValidString(data.params.serial, 1)) {
1369
+ const plugin = this.matterbridge.plugins.get(data.params.plugin);
1370
+ if (!plugin) {
1371
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/command' }));
1372
+ return;
1373
+ }
1374
+ const config = plugin.configJson;
1375
+ if (config) {
1376
+ if (config.postfix) {
1377
+ data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1378
+ }
1379
+ if (isValidArray(config.whiteList, 1)) {
1380
+ if (config.whiteList.includes(data.params.serial)) {
1381
+ config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
1382
+ }
1383
+ }
1384
+ if (isValidArray(config.blackList)) {
1385
+ if (!config.blackList.includes(data.params.serial)) {
1386
+ config.blackList.push(data.params.serial);
1387
+ }
1388
+ }
1389
+ if (plugin.platform)
1390
+ plugin.platform.config = config;
1391
+ await this.matterbridge.plugins.saveConfigFromPlugin(plugin);
1392
+ this.wssSendRestartRequired(false);
1393
+ }
1394
+ }
1395
+ }
1509
1396
  else {
1510
1397
  this.log.error(`Invalid method from websocket client: ${debugStringify(data)}`);
1511
1398
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid method' }));
@@ -1517,139 +1404,94 @@ export class Frontend {
1517
1404
  return;
1518
1405
  }
1519
1406
  }
1520
- /**
1521
- * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1522
- *
1523
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1524
- * @param {string} time - The time string of the message
1525
- * @param {string} name - The logger name of the message
1526
- * @param {string} message - The content of the message.
1527
- */
1528
1407
  wssSendMessage(level, time, name, message) {
1529
1408
  if (!level || !time || !name || !message)
1530
1409
  return;
1531
- // Remove ANSI escape codes from the message
1532
- // eslint-disable-next-line no-control-regex
1533
1410
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1534
- // Remove leading asterisks from the message
1535
1411
  message = message.replace(/^\*+/, '');
1536
- // Replace all occurrences of \t and \n
1537
1412
  message = message.replace(/[\t\n]/g, '');
1538
- // Remove non-printable characters
1539
- // eslint-disable-next-line no-control-regex
1540
1413
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1541
- // Replace all occurrences of \" with "
1542
1414
  message = message.replace(/\\"/g, '"');
1543
- // Define the maximum allowed length for continuous characters without a space
1544
1415
  const maxContinuousLength = 100;
1545
1416
  const keepStartLength = 20;
1546
1417
  const keepEndLength = 20;
1547
- // Split the message into words
1548
1418
  message = message
1549
1419
  .split(' ')
1550
1420
  .map((word) => {
1551
- // If the word length exceeds the max continuous length, insert spaces and truncate
1552
1421
  if (word.length > maxContinuousLength) {
1553
1422
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1554
1423
  }
1555
1424
  return word;
1556
1425
  })
1557
1426
  .join(' ');
1558
- // Send the message to all connected clients
1559
1427
  this.webSocketServer?.clients.forEach((client) => {
1560
1428
  if (client.readyState === WebSocket.OPEN) {
1561
1429
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1562
1430
  }
1563
1431
  });
1564
1432
  }
1565
- /**
1566
- * Sends a need to refresh WebSocket message to all connected clients.
1567
- *
1568
- */
1569
- wssSendRefreshRequired() {
1433
+ wssSendRefreshRequired(changed = null) {
1570
1434
  this.log.debug('Sending a refresh required message to all connected clients');
1571
- this.matterbridge.matterbridgeInformation.refreshRequired = true;
1572
- // Send the message to all connected clients
1573
1435
  this.webSocketServer?.clients.forEach((client) => {
1574
1436
  if (client.readyState === WebSocket.OPEN) {
1575
- client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1437
+ client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1576
1438
  }
1577
1439
  });
1578
1440
  }
1579
- /**
1580
- * Sends a need to restart WebSocket message to all connected clients.
1581
- *
1582
- */
1583
- wssSendRestartRequired() {
1441
+ wssSendRestartRequired(snackbar = true) {
1584
1442
  this.log.debug('Sending a restart required message to all connected clients');
1585
1443
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1586
- this.wssSendSnackbarMessage(`Restart required`, 0);
1587
- // Send the message to all connected clients
1444
+ if (snackbar === true)
1445
+ this.wssSendSnackbarMessage(`Restart required`, 0);
1588
1446
  this.webSocketServer?.clients.forEach((client) => {
1589
1447
  if (client.readyState === WebSocket.OPEN) {
1590
1448
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1591
1449
  }
1592
1450
  });
1593
1451
  }
1594
- /**
1595
- * Sends a memory update message to all connected clients.
1596
- *
1597
- */
1452
+ wssSendUpdateRequired() {
1453
+ this.log.debug('Sending an update required message to all connected clients');
1454
+ this.matterbridge.matterbridgeInformation.updateRequired = true;
1455
+ this.webSocketServer?.clients.forEach((client) => {
1456
+ if (client.readyState === WebSocket.OPEN) {
1457
+ client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1458
+ }
1459
+ });
1460
+ }
1598
1461
  wssSendCpuUpdate(cpuUsage) {
1599
1462
  this.log.debug('Sending a cpu update message to all connected clients');
1600
- // Send the message to all connected clients
1601
1463
  this.webSocketServer?.clients.forEach((client) => {
1602
1464
  if (client.readyState === WebSocket.OPEN) {
1603
1465
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1604
1466
  }
1605
1467
  });
1606
1468
  }
1607
- /**
1608
- * Sends a cpu update message to all connected clients.
1609
- *
1610
- */
1611
1469
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1612
1470
  this.log.debug('Sending a memory update message to all connected clients');
1613
- // Send the message to all connected clients
1614
1471
  this.webSocketServer?.clients.forEach((client) => {
1615
1472
  if (client.readyState === WebSocket.OPEN) {
1616
1473
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1617
1474
  }
1618
1475
  });
1619
1476
  }
1620
- /**
1621
- * Sends a memory update message to all connected clients.
1622
- *
1623
- */
1624
1477
  wssSendUptimeUpdate(systemUptime, processUptime) {
1625
1478
  this.log.debug('Sending a uptime update message to all connected clients');
1626
- // Send the message to all connected clients
1627
1479
  this.webSocketServer?.clients.forEach((client) => {
1628
1480
  if (client.readyState === WebSocket.OPEN) {
1629
1481
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1630
1482
  }
1631
1483
  });
1632
1484
  }
1633
- /**
1634
- * Sends a cpu update message to all connected clients.
1635
- *
1636
- */
1637
1485
  wssSendSnackbarMessage(message, timeout = 5) {
1638
1486
  this.log.debug('Sending a snackbar message to all connected clients');
1639
- // Send the message to all connected clients
1640
1487
  this.webSocketServer?.clients.forEach((client) => {
1641
1488
  if (client.readyState === WebSocket.OPEN) {
1642
1489
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
1643
1490
  }
1644
1491
  });
1645
1492
  }
1646
- /**
1647
- * Sends a message to all connected clients.
1648
- *
1649
- */
1650
1493
  wssBroadcastMessage(id, method, params) {
1651
1494
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1652
- // Send the message to all connected clients
1653
1495
  this.webSocketServer?.clients.forEach((client) => {
1654
1496
  if (client.readyState === WebSocket.OPEN) {
1655
1497
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1657,4 +1499,3 @@ export class Frontend {
1657
1499
  });
1658
1500
  }
1659
1501
  }
1660
- //# sourceMappingURL=frontend.js.map