matterbridge 2.2.6-dev.5 → 2.2.6
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.
- package/CHANGELOG.md +2 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -2
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +221 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +325 -19
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +425 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +753 -47
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +118 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +285 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +216 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +179 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +92 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +146 -6
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +32 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +32 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +42 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +40 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +65 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +10 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/isvalid.d.ts +87 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +86 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +69 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -8
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts +44 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +41 -0
- package/dist/utils/parameter.js.map +1 -0
- package/dist/utils/wait.d.ts +43 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +48 -5
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
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
|
|
1
24
|
import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
25
|
+
// Node modules
|
|
2
26
|
import { createServer } from 'node:http';
|
|
3
27
|
import os from 'node:os';
|
|
4
28
|
import path from 'node:path';
|
|
@@ -7,21 +31,75 @@ import https from 'https';
|
|
|
7
31
|
import express from 'express';
|
|
8
32
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
33
|
import multer from 'multer';
|
|
34
|
+
// AnsiLogger module
|
|
10
35
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
|
|
36
|
+
// Matterbridge
|
|
11
37
|
import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
12
38
|
import { plg } from './matterbridgeTypes.js';
|
|
13
39
|
import { hasParameter } from './utils/export.js';
|
|
14
40
|
import { BridgedDeviceBasicInformation } from '@matter/main/clusters';
|
|
41
|
+
/**
|
|
42
|
+
* Websocket message ID for logging.
|
|
43
|
+
* @constant {number}
|
|
44
|
+
*/
|
|
15
45
|
export const WS_ID_LOG = 0;
|
|
46
|
+
/**
|
|
47
|
+
* Websocket message ID indicating a refresh is needed.
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
16
50
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a restart is needed.
|
|
53
|
+
* @constant {number}
|
|
54
|
+
*/
|
|
17
55
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
56
|
+
/**
|
|
57
|
+
* Websocket message ID indicating a cpu update.
|
|
58
|
+
* @constant {number}
|
|
59
|
+
*/
|
|
18
60
|
export const WS_ID_CPU_UPDATE = 3;
|
|
61
|
+
/**
|
|
62
|
+
* Websocket message ID indicating a memory update.
|
|
63
|
+
* @constant {number}
|
|
64
|
+
*/
|
|
19
65
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
66
|
+
/**
|
|
67
|
+
* Websocket message ID indicating an uptime update.
|
|
68
|
+
* @constant {number}
|
|
69
|
+
*/
|
|
20
70
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
71
|
+
/**
|
|
72
|
+
* Websocket message ID indicating a snackbar message.
|
|
73
|
+
* @constant {number}
|
|
74
|
+
*/
|
|
21
75
|
export const WS_ID_SNACKBAR = 6;
|
|
76
|
+
/**
|
|
77
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
22
80
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a state update.
|
|
83
|
+
* @constant {number}
|
|
84
|
+
*/
|
|
23
85
|
export const WS_ID_STATEUPDATE = 8;
|
|
86
|
+
/**
|
|
87
|
+
* Websocket message ID indicating a shelly system update.
|
|
88
|
+
* check:
|
|
89
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
90
|
+
* perform:
|
|
91
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
92
|
+
* @constant {number}
|
|
93
|
+
*/
|
|
24
94
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
95
|
+
/**
|
|
96
|
+
* Websocket message ID indicating a shelly main update.
|
|
97
|
+
* check:
|
|
98
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
99
|
+
* perform:
|
|
100
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
101
|
+
* @constant {number}
|
|
102
|
+
*/
|
|
25
103
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
26
104
|
export class Frontend {
|
|
27
105
|
matterbridge;
|
|
@@ -39,7 +117,7 @@ export class Frontend {
|
|
|
39
117
|
memoryTimeout;
|
|
40
118
|
constructor(matterbridge) {
|
|
41
119
|
this.matterbridge = matterbridge;
|
|
42
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
120
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
43
121
|
}
|
|
44
122
|
set logLevel(logLevel) {
|
|
45
123
|
this.log.logLevel = logLevel;
|
|
@@ -47,13 +125,25 @@ export class Frontend {
|
|
|
47
125
|
async start(port = 8283) {
|
|
48
126
|
this.port = port;
|
|
49
127
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
128
|
+
// Initialize multer with the upload directory
|
|
50
129
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
51
130
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
52
131
|
const upload = multer({ dest: uploadDir });
|
|
132
|
+
// Create the express app that serves the frontend
|
|
53
133
|
this.expressApp = express();
|
|
134
|
+
// Log all requests to the server for debugging
|
|
135
|
+
/*
|
|
136
|
+
this.expressApp.use((req, res, next) => {
|
|
137
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
138
|
+
next();
|
|
139
|
+
});
|
|
140
|
+
*/
|
|
141
|
+
// Serve static files from '/static' endpoint
|
|
54
142
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
55
143
|
if (!hasParameter('ssl')) {
|
|
144
|
+
// Create an HTTP server and attach the express app
|
|
56
145
|
this.httpServer = createServer(this.expressApp);
|
|
146
|
+
// Listen on the specified port
|
|
57
147
|
if (hasParameter('ingress')) {
|
|
58
148
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
59
149
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -67,6 +157,7 @@ export class Frontend {
|
|
|
67
157
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
68
158
|
});
|
|
69
159
|
}
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
161
|
this.httpServer.on('error', (error) => {
|
|
71
162
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
72
163
|
switch (error.code) {
|
|
@@ -82,6 +173,7 @@ export class Frontend {
|
|
|
82
173
|
});
|
|
83
174
|
}
|
|
84
175
|
else {
|
|
176
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
85
177
|
let cert;
|
|
86
178
|
try {
|
|
87
179
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -109,7 +201,9 @@ export class Frontend {
|
|
|
109
201
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
110
202
|
}
|
|
111
203
|
const serverOptions = { cert, key, ca };
|
|
204
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
112
205
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
206
|
+
// Listen on the specified port
|
|
113
207
|
if (hasParameter('ingress')) {
|
|
114
208
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
115
209
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -123,6 +217,7 @@ export class Frontend {
|
|
|
123
217
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
124
218
|
});
|
|
125
219
|
}
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
221
|
this.httpsServer.on('error', (error) => {
|
|
127
222
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
128
223
|
switch (error.code) {
|
|
@@ -139,16 +234,18 @@ export class Frontend {
|
|
|
139
234
|
}
|
|
140
235
|
if (this.initializeError)
|
|
141
236
|
return;
|
|
237
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
142
238
|
const wssPort = this.port;
|
|
143
239
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
144
240
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
145
241
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
146
242
|
const clientIp = request.socket.remoteAddress;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
243
|
+
// Set the global logger callback for the WebSocketServer
|
|
244
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
245
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
246
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
247
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
248
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
152
249
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
153
250
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
154
251
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -182,6 +279,7 @@ export class Frontend {
|
|
|
182
279
|
this.webSocketServer.on('error', (ws, error) => {
|
|
183
280
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
184
281
|
});
|
|
282
|
+
// Subscribe to cli events
|
|
185
283
|
const { cliEmitter } = await import('./cli.js');
|
|
186
284
|
cliEmitter.removeAllListeners();
|
|
187
285
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
@@ -193,6 +291,7 @@ export class Frontend {
|
|
|
193
291
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
194
292
|
this.wssSendCpuUpdate(cpuUsage);
|
|
195
293
|
});
|
|
294
|
+
// Endpoint to validate login code
|
|
196
295
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
197
296
|
const { password } = req.body;
|
|
198
297
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -211,23 +310,27 @@ export class Frontend {
|
|
|
211
310
|
this.log.warn('/api/login error wrong password');
|
|
212
311
|
res.json({ valid: false });
|
|
213
312
|
}
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
214
314
|
}
|
|
215
315
|
catch (error) {
|
|
216
316
|
this.log.error('/api/login error getting password');
|
|
217
317
|
res.json({ valid: false });
|
|
218
318
|
}
|
|
219
319
|
});
|
|
320
|
+
// Endpoint to provide health check
|
|
220
321
|
this.expressApp.get('/health', (req, res) => {
|
|
221
322
|
this.log.debug('Express received /health');
|
|
222
323
|
const healthStatus = {
|
|
223
|
-
status: 'ok',
|
|
224
|
-
uptime: process.uptime(),
|
|
225
|
-
timestamp: new Date().toISOString(),
|
|
324
|
+
status: 'ok', // Indicate service is healthy
|
|
325
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
326
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
226
327
|
};
|
|
227
328
|
res.status(200).json(healthStatus);
|
|
228
329
|
});
|
|
330
|
+
// Endpoint to provide memory usage details
|
|
229
331
|
this.expressApp.get('/memory', async (req, res) => {
|
|
230
332
|
this.log.debug('Express received /memory');
|
|
333
|
+
// Memory usage from process
|
|
231
334
|
const memoryUsageRaw = process.memoryUsage();
|
|
232
335
|
const memoryUsage = {
|
|
233
336
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -236,10 +339,13 @@ export class Frontend {
|
|
|
236
339
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
237
340
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
238
341
|
};
|
|
342
|
+
// V8 heap statistics
|
|
239
343
|
const { default: v8 } = await import('node:v8');
|
|
240
344
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
241
345
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
346
|
+
// Format heapStats
|
|
242
347
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
348
|
+
// Format heapSpaces
|
|
243
349
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
244
350
|
...space,
|
|
245
351
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -257,6 +363,7 @@ export class Frontend {
|
|
|
257
363
|
};
|
|
258
364
|
res.status(200).json(memoryReport);
|
|
259
365
|
});
|
|
366
|
+
// Endpoint to start advertising the server node
|
|
260
367
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
261
368
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
262
369
|
if (pairingCodes) {
|
|
@@ -267,18 +374,22 @@ export class Frontend {
|
|
|
267
374
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
268
375
|
}
|
|
269
376
|
});
|
|
377
|
+
// Endpoint to provide settings
|
|
270
378
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
271
379
|
this.log.debug('The frontend sent /api/settings');
|
|
272
380
|
res.json(await this.getApiSettings());
|
|
273
381
|
});
|
|
382
|
+
// Endpoint to provide plugins
|
|
274
383
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
275
384
|
this.log.debug('The frontend sent /api/plugins');
|
|
276
385
|
res.json(this.getBaseRegisteredPlugins());
|
|
277
386
|
});
|
|
387
|
+
// Endpoint to provide devices
|
|
278
388
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
279
389
|
this.log.debug('The frontend sent /api/devices');
|
|
280
390
|
const devices = [];
|
|
281
391
|
this.matterbridge.devices.forEach(async (device) => {
|
|
392
|
+
// Check if the device has the required properties
|
|
282
393
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
283
394
|
return;
|
|
284
395
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -297,6 +408,7 @@ export class Frontend {
|
|
|
297
408
|
});
|
|
298
409
|
res.json(devices);
|
|
299
410
|
});
|
|
411
|
+
// Endpoint to provide the cluster servers of the devices
|
|
300
412
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
301
413
|
const selectedPluginName = req.params.selectedPluginName;
|
|
302
414
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -369,6 +481,7 @@ export class Frontend {
|
|
|
369
481
|
});
|
|
370
482
|
res.json(data);
|
|
371
483
|
});
|
|
484
|
+
// Endpoint to view the log
|
|
372
485
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
373
486
|
this.log.debug('The frontend sent /api/log');
|
|
374
487
|
try {
|
|
@@ -381,10 +494,12 @@ export class Frontend {
|
|
|
381
494
|
res.status(500).send('Error reading log file');
|
|
382
495
|
}
|
|
383
496
|
});
|
|
497
|
+
// Endpoint to download the matterbridge log
|
|
384
498
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
385
499
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
386
500
|
try {
|
|
387
501
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
502
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
388
503
|
}
|
|
389
504
|
catch (error) {
|
|
390
505
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -396,10 +511,12 @@ export class Frontend {
|
|
|
396
511
|
}
|
|
397
512
|
});
|
|
398
513
|
});
|
|
514
|
+
// Endpoint to download the matter log
|
|
399
515
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
400
516
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
401
517
|
try {
|
|
402
518
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
519
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
403
520
|
}
|
|
404
521
|
catch (error) {
|
|
405
522
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -411,10 +528,12 @@ export class Frontend {
|
|
|
411
528
|
}
|
|
412
529
|
});
|
|
413
530
|
});
|
|
531
|
+
// Endpoint to download the matter log
|
|
414
532
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
415
533
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
416
534
|
try {
|
|
417
535
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
536
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
418
537
|
}
|
|
419
538
|
catch (error) {
|
|
420
539
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
|
|
@@ -426,6 +545,7 @@ export class Frontend {
|
|
|
426
545
|
}
|
|
427
546
|
});
|
|
428
547
|
});
|
|
548
|
+
// Endpoint to download the matter storage file
|
|
429
549
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
430
550
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
431
551
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -436,6 +556,7 @@ export class Frontend {
|
|
|
436
556
|
}
|
|
437
557
|
});
|
|
438
558
|
});
|
|
559
|
+
// Endpoint to download the matterbridge storage directory
|
|
439
560
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
440
561
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
441
562
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -446,6 +567,7 @@ export class Frontend {
|
|
|
446
567
|
}
|
|
447
568
|
});
|
|
448
569
|
});
|
|
570
|
+
// Endpoint to download the matterbridge plugin directory
|
|
449
571
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
450
572
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
451
573
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -456,9 +578,11 @@ export class Frontend {
|
|
|
456
578
|
}
|
|
457
579
|
});
|
|
458
580
|
});
|
|
581
|
+
// Endpoint to download the matterbridge plugin config files
|
|
459
582
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
460
583
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
461
584
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
585
|
+
// 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')));
|
|
462
586
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
463
587
|
if (error) {
|
|
464
588
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -466,6 +590,7 @@ export class Frontend {
|
|
|
466
590
|
}
|
|
467
591
|
});
|
|
468
592
|
});
|
|
593
|
+
// Endpoint to download the matterbridge plugin config files
|
|
469
594
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
470
595
|
this.log.debug('The frontend sent /api/download-backup');
|
|
471
596
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -475,6 +600,7 @@ export class Frontend {
|
|
|
475
600
|
}
|
|
476
601
|
});
|
|
477
602
|
});
|
|
603
|
+
// Endpoint to receive commands
|
|
478
604
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
479
605
|
const command = req.params.command;
|
|
480
606
|
let param = req.params.param;
|
|
@@ -484,13 +610,15 @@ export class Frontend {
|
|
|
484
610
|
return;
|
|
485
611
|
}
|
|
486
612
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
613
|
+
// Handle the command setpassword from Settings
|
|
487
614
|
if (command === 'setpassword') {
|
|
488
|
-
const password = param.slice(1, -1);
|
|
615
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
489
616
|
this.log.debug('setpassword', param, password);
|
|
490
617
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
491
618
|
res.json({ message: 'Command received' });
|
|
492
619
|
return;
|
|
493
620
|
}
|
|
621
|
+
// Handle the command setbridgemode from Settings
|
|
494
622
|
if (command === 'setbridgemode') {
|
|
495
623
|
this.log.debug(`setbridgemode: ${param}`);
|
|
496
624
|
this.wssSendRestartRequired();
|
|
@@ -498,6 +626,7 @@ export class Frontend {
|
|
|
498
626
|
res.json({ message: 'Command received' });
|
|
499
627
|
return;
|
|
500
628
|
}
|
|
629
|
+
// Handle the command backup from Settings
|
|
501
630
|
if (command === 'backup') {
|
|
502
631
|
this.log.notice(`Prepairing the backup...`);
|
|
503
632
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -506,31 +635,33 @@ export class Frontend {
|
|
|
506
635
|
res.json({ message: 'Command received' });
|
|
507
636
|
return;
|
|
508
637
|
}
|
|
638
|
+
// Handle the command setmbloglevel from Settings
|
|
509
639
|
if (command === 'setmbloglevel') {
|
|
510
640
|
this.log.debug('Matterbridge log level:', param);
|
|
511
641
|
if (param === 'Debug') {
|
|
512
|
-
this.log.logLevel = "debug"
|
|
642
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
513
643
|
}
|
|
514
644
|
else if (param === 'Info') {
|
|
515
|
-
this.log.logLevel = "info"
|
|
645
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
516
646
|
}
|
|
517
647
|
else if (param === 'Notice') {
|
|
518
|
-
this.log.logLevel = "notice"
|
|
648
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
519
649
|
}
|
|
520
650
|
else if (param === 'Warn') {
|
|
521
|
-
this.log.logLevel = "warn"
|
|
651
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
522
652
|
}
|
|
523
653
|
else if (param === 'Error') {
|
|
524
|
-
this.log.logLevel = "error"
|
|
654
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
525
655
|
}
|
|
526
656
|
else if (param === 'Fatal') {
|
|
527
|
-
this.log.logLevel = "fatal"
|
|
657
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
528
658
|
}
|
|
529
659
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
530
660
|
await this.matterbridge.setLogLevel(this.log.logLevel);
|
|
531
661
|
res.json({ message: 'Command received' });
|
|
532
662
|
return;
|
|
533
663
|
}
|
|
664
|
+
// Handle the command setmbloglevel from Settings
|
|
534
665
|
if (command === 'setmjloglevel') {
|
|
535
666
|
this.log.debug('Matter.js log level:', param);
|
|
536
667
|
if (param === 'Debug') {
|
|
@@ -555,6 +686,7 @@ export class Frontend {
|
|
|
555
686
|
res.json({ message: 'Command received' });
|
|
556
687
|
return;
|
|
557
688
|
}
|
|
689
|
+
// Handle the command setmdnsinterface from Settings
|
|
558
690
|
if (command === 'setmdnsinterface') {
|
|
559
691
|
if (param === 'json' && isValidString(req.body.value)) {
|
|
560
692
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = req.body.value;
|
|
@@ -564,6 +696,7 @@ export class Frontend {
|
|
|
564
696
|
res.json({ message: 'Command received' });
|
|
565
697
|
return;
|
|
566
698
|
}
|
|
699
|
+
// Handle the command setipv4address from Settings
|
|
567
700
|
if (command === 'setipv4address') {
|
|
568
701
|
if (param === 'json' && isValidString(req.body.value)) {
|
|
569
702
|
this.log.debug(`Matter.js ipv4 address: ${req.body.value === '' ? 'all ipv4 addresses' : req.body.value}`);
|
|
@@ -573,6 +706,7 @@ export class Frontend {
|
|
|
573
706
|
res.json({ message: 'Command received' });
|
|
574
707
|
return;
|
|
575
708
|
}
|
|
709
|
+
// Handle the command setipv6address from Settings
|
|
576
710
|
if (command === 'setipv6address') {
|
|
577
711
|
if (param === 'json' && isValidString(req.body.value)) {
|
|
578
712
|
this.log.debug(`Matter.js ipv6 address: ${req.body.value === '' ? 'all ipv6 addresses' : req.body.value}`);
|
|
@@ -582,6 +716,7 @@ export class Frontend {
|
|
|
582
716
|
res.json({ message: 'Command received' });
|
|
583
717
|
return;
|
|
584
718
|
}
|
|
719
|
+
// Handle the command setmatterport from Settings
|
|
585
720
|
if (command === 'setmatterport') {
|
|
586
721
|
const port = Math.min(Math.max(parseInt(req.body.value), 5540), 5560);
|
|
587
722
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -590,6 +725,7 @@ export class Frontend {
|
|
|
590
725
|
res.json({ message: 'Command received' });
|
|
591
726
|
return;
|
|
592
727
|
}
|
|
728
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
593
729
|
if (command === 'setmatterdiscriminator') {
|
|
594
730
|
const discriminator = Math.min(Math.max(parseInt(req.body.value), 1000), 4095);
|
|
595
731
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -598,6 +734,7 @@ export class Frontend {
|
|
|
598
734
|
res.json({ message: 'Command received' });
|
|
599
735
|
return;
|
|
600
736
|
}
|
|
737
|
+
// Handle the command setmatterpasscode from Settings
|
|
601
738
|
if (command === 'setmatterpasscode') {
|
|
602
739
|
const passcode = Math.min(Math.max(parseInt(req.body.value), 10000000), 90000000);
|
|
603
740
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -606,17 +743,20 @@ export class Frontend {
|
|
|
606
743
|
res.json({ message: 'Command received' });
|
|
607
744
|
return;
|
|
608
745
|
}
|
|
746
|
+
// Handle the command setmbloglevel from Settings
|
|
609
747
|
if (command === 'setmblogfile') {
|
|
610
748
|
this.log.debug('Matterbridge file log:', param);
|
|
611
749
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
612
750
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
751
|
+
// Create the file logger for matterbridge
|
|
613
752
|
if (param === 'true')
|
|
614
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
753
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
615
754
|
else
|
|
616
755
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
617
756
|
res.json({ message: 'Command received' });
|
|
618
757
|
return;
|
|
619
758
|
}
|
|
759
|
+
// Handle the command setmbloglevel from Settings
|
|
620
760
|
if (command === 'setmjlogfile') {
|
|
621
761
|
this.log.debug('Matter file log:', param);
|
|
622
762
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -643,40 +783,48 @@ export class Frontend {
|
|
|
643
783
|
res.json({ message: 'Command received' });
|
|
644
784
|
return;
|
|
645
785
|
}
|
|
786
|
+
// Handle the command unregister from Settings
|
|
646
787
|
if (command === 'unregister') {
|
|
647
788
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
648
789
|
res.json({ message: 'Command received' });
|
|
649
790
|
return;
|
|
650
791
|
}
|
|
792
|
+
// Handle the command reset from Settings
|
|
651
793
|
if (command === 'reset') {
|
|
652
794
|
await this.matterbridge.shutdownProcessAndReset();
|
|
653
795
|
res.json({ message: 'Command received' });
|
|
654
796
|
return;
|
|
655
797
|
}
|
|
798
|
+
// Handle the command factoryreset from Settings
|
|
656
799
|
if (command === 'factoryreset') {
|
|
657
800
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
658
801
|
res.json({ message: 'Command received' });
|
|
659
802
|
return;
|
|
660
803
|
}
|
|
804
|
+
// Handle the command shutdown from Header
|
|
661
805
|
if (command === 'shutdown') {
|
|
662
806
|
await this.matterbridge.shutdownProcess();
|
|
663
807
|
res.json({ message: 'Command received' });
|
|
664
808
|
return;
|
|
665
809
|
}
|
|
810
|
+
// Handle the command restart from Header
|
|
666
811
|
if (command === 'restart') {
|
|
667
812
|
await this.matterbridge.restartProcess();
|
|
668
813
|
res.json({ message: 'Command received' });
|
|
669
814
|
return;
|
|
670
815
|
}
|
|
816
|
+
// Handle the command update from Header
|
|
671
817
|
if (command === 'update') {
|
|
672
818
|
await this.matterbridge.updateProcess();
|
|
673
819
|
this.wssSendRestartRequired();
|
|
674
820
|
res.json({ message: 'Command received' });
|
|
675
821
|
return;
|
|
676
822
|
}
|
|
823
|
+
// Handle the command saveconfig from Home
|
|
677
824
|
if (command === 'saveconfig') {
|
|
678
825
|
param = param.replace(/\*/g, '\\');
|
|
679
826
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
827
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
680
828
|
if (!this.matterbridge.plugins.has(param)) {
|
|
681
829
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
682
830
|
}
|
|
@@ -691,6 +839,7 @@ export class Frontend {
|
|
|
691
839
|
res.json({ message: 'Command received' });
|
|
692
840
|
return;
|
|
693
841
|
}
|
|
842
|
+
// Handle the command installplugin from Home
|
|
694
843
|
if (command === 'installplugin') {
|
|
695
844
|
param = param.replace(/\*/g, '\\');
|
|
696
845
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
@@ -699,6 +848,7 @@ export class Frontend {
|
|
|
699
848
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
700
849
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
701
850
|
this.wssSendSnackbarMessage(`Installed package ${param}`, 10, 'success');
|
|
851
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
702
852
|
}
|
|
703
853
|
catch (error) {
|
|
704
854
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
@@ -706,17 +856,22 @@ export class Frontend {
|
|
|
706
856
|
}
|
|
707
857
|
this.wssSendRestartRequired();
|
|
708
858
|
param = param.split('@')[0];
|
|
859
|
+
// Also add the plugin to matterbridge so no return!
|
|
709
860
|
if (param === 'matterbridge') {
|
|
861
|
+
// 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
|
|
710
862
|
res.json({ message: 'Command received' });
|
|
711
863
|
return;
|
|
712
864
|
}
|
|
713
865
|
}
|
|
866
|
+
// Handle the command addplugin from Home
|
|
714
867
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
715
868
|
param = param.replace(/\*/g, '\\');
|
|
716
869
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
717
870
|
if (plugin) {
|
|
718
871
|
this.wssSendSnackbarMessage(`Added plugin ${param}`);
|
|
719
872
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
873
|
+
// 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
|
|
874
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
720
875
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
721
876
|
}
|
|
722
877
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
@@ -726,13 +881,14 @@ export class Frontend {
|
|
|
726
881
|
res.json({ message: 'Command received' });
|
|
727
882
|
return;
|
|
728
883
|
}
|
|
884
|
+
// Handle the command removeplugin from Home
|
|
729
885
|
if (command === 'removeplugin') {
|
|
730
886
|
if (!this.matterbridge.plugins.has(param)) {
|
|
731
887
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
732
888
|
}
|
|
733
889
|
else {
|
|
734
890
|
const plugin = this.matterbridge.plugins.get(param);
|
|
735
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
891
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
736
892
|
await this.matterbridge.plugins.remove(param);
|
|
737
893
|
this.wssSendSnackbarMessage(`Removed plugin ${param}`);
|
|
738
894
|
this.wssSendRefreshRequired('plugins');
|
|
@@ -740,6 +896,7 @@ export class Frontend {
|
|
|
740
896
|
res.json({ message: 'Command received' });
|
|
741
897
|
return;
|
|
742
898
|
}
|
|
899
|
+
// Handle the command enableplugin from Home
|
|
743
900
|
if (command === 'enableplugin') {
|
|
744
901
|
if (!this.matterbridge.plugins.has(param)) {
|
|
745
902
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -758,6 +915,7 @@ export class Frontend {
|
|
|
758
915
|
await this.matterbridge.plugins.enable(param);
|
|
759
916
|
this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
|
|
760
917
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
918
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
761
919
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
762
920
|
}
|
|
763
921
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
|
|
@@ -768,6 +926,7 @@ export class Frontend {
|
|
|
768
926
|
res.json({ message: 'Command received' });
|
|
769
927
|
return;
|
|
770
928
|
}
|
|
929
|
+
// Handle the command disableplugin from Home
|
|
771
930
|
if (command === 'disableplugin') {
|
|
772
931
|
if (!this.matterbridge.plugins.has(param)) {
|
|
773
932
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -775,7 +934,7 @@ export class Frontend {
|
|
|
775
934
|
else {
|
|
776
935
|
const plugin = this.matterbridge.plugins.get(param);
|
|
777
936
|
if (plugin && plugin.enabled) {
|
|
778
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
937
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
779
938
|
await this.matterbridge.plugins.disable(param);
|
|
780
939
|
this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
|
|
781
940
|
this.wssSendRefreshRequired('plugins');
|
|
@@ -794,10 +953,13 @@ export class Frontend {
|
|
|
794
953
|
return;
|
|
795
954
|
}
|
|
796
955
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`);
|
|
956
|
+
// Define the path where the plugin file will be saved
|
|
797
957
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
798
958
|
try {
|
|
959
|
+
// Move the uploaded file to the specified path
|
|
799
960
|
await fs.rename(file.path, filePath);
|
|
800
961
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
962
|
+
// Install the plugin package
|
|
801
963
|
if (filename.endsWith('.tgz')) {
|
|
802
964
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
803
965
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
@@ -814,6 +976,7 @@ export class Frontend {
|
|
|
814
976
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
815
977
|
}
|
|
816
978
|
});
|
|
979
|
+
// Fallback for routing (must be the last route)
|
|
817
980
|
this.expressApp.get('*', (req, res) => {
|
|
818
981
|
this.log.debug('The frontend sent:', req.url);
|
|
819
982
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -822,24 +985,31 @@ export class Frontend {
|
|
|
822
985
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
823
986
|
}
|
|
824
987
|
async stop() {
|
|
988
|
+
// Remove all listeners from the cliEmitter
|
|
989
|
+
// cliEmitter.removeAllListeners();
|
|
990
|
+
// Close the http server
|
|
825
991
|
if (this.httpServer) {
|
|
826
992
|
this.httpServer.close();
|
|
827
993
|
this.httpServer.removeAllListeners();
|
|
828
994
|
this.httpServer = undefined;
|
|
829
995
|
this.log.debug('Frontend http server closed successfully');
|
|
830
996
|
}
|
|
997
|
+
// Close the https server
|
|
831
998
|
if (this.httpsServer) {
|
|
832
999
|
this.httpsServer.close();
|
|
833
1000
|
this.httpsServer.removeAllListeners();
|
|
834
1001
|
this.httpsServer = undefined;
|
|
835
1002
|
this.log.debug('Frontend https server closed successfully');
|
|
836
1003
|
}
|
|
1004
|
+
// Remove listeners from the express app
|
|
837
1005
|
if (this.expressApp) {
|
|
838
1006
|
this.expressApp.removeAllListeners();
|
|
839
1007
|
this.expressApp = undefined;
|
|
840
1008
|
this.log.debug('Frontend app closed successfully');
|
|
841
1009
|
}
|
|
1010
|
+
// Close the WebSocket server
|
|
842
1011
|
if (this.webSocketServer) {
|
|
1012
|
+
// Close all active connections
|
|
843
1013
|
this.webSocketServer.clients.forEach((client) => {
|
|
844
1014
|
if (client.readyState === WebSocket.OPEN) {
|
|
845
1015
|
client.close();
|
|
@@ -856,6 +1026,7 @@ export class Frontend {
|
|
|
856
1026
|
this.webSocketServer = undefined;
|
|
857
1027
|
}
|
|
858
1028
|
}
|
|
1029
|
+
// Function to format bytes to KB, MB, or GB
|
|
859
1030
|
formatMemoryUsage = (bytes) => {
|
|
860
1031
|
if (bytes >= 1024 ** 3) {
|
|
861
1032
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -867,6 +1038,7 @@ export class Frontend {
|
|
|
867
1038
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
868
1039
|
}
|
|
869
1040
|
};
|
|
1041
|
+
// Function to format system uptime with only the most significant unit
|
|
870
1042
|
formatOsUpTime = (seconds) => {
|
|
871
1043
|
if (seconds >= 86400) {
|
|
872
1044
|
const days = Math.floor(seconds / 86400);
|
|
@@ -882,8 +1054,13 @@ export class Frontend {
|
|
|
882
1054
|
}
|
|
883
1055
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
884
1056
|
};
|
|
1057
|
+
/**
|
|
1058
|
+
* Retrieves the api settings data.
|
|
1059
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
1060
|
+
*/
|
|
885
1061
|
async getApiSettings() {
|
|
886
1062
|
const { lastCpuUsage } = await import('./cli.js');
|
|
1063
|
+
// Update the system information
|
|
887
1064
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
888
1065
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
889
1066
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -892,6 +1069,7 @@ export class Frontend {
|
|
|
892
1069
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
893
1070
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
894
1071
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1072
|
+
// Update the matterbridge information
|
|
895
1073
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
896
1074
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
897
1075
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -910,6 +1088,11 @@ export class Frontend {
|
|
|
910
1088
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
911
1089
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
912
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Retrieves the reachable attribute.
|
|
1093
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1094
|
+
* @returns {boolean} The reachable attribute.
|
|
1095
|
+
*/
|
|
913
1096
|
getReachability(device) {
|
|
914
1097
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
915
1098
|
return false;
|
|
@@ -919,6 +1102,11 @@ export class Frontend {
|
|
|
919
1102
|
return true;
|
|
920
1103
|
return false;
|
|
921
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Retrieves the cluster text description from a given device.
|
|
1107
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1108
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1109
|
+
*/
|
|
922
1110
|
getClusterTextFromDevice(device) {
|
|
923
1111
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
924
1112
|
return '';
|
|
@@ -959,6 +1147,7 @@ export class Frontend {
|
|
|
959
1147
|
};
|
|
960
1148
|
let attributes = '';
|
|
961
1149
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1150
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
962
1151
|
if (typeof attributeValue === 'undefined')
|
|
963
1152
|
return;
|
|
964
1153
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1036,8 +1225,13 @@ export class Frontend {
|
|
|
1036
1225
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1037
1226
|
attributes += `${getUserLabel(device)} `;
|
|
1038
1227
|
});
|
|
1228
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1039
1229
|
return attributes.trimStart().trimEnd();
|
|
1040
1230
|
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1233
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1234
|
+
*/
|
|
1041
1235
|
getBaseRegisteredPlugins() {
|
|
1042
1236
|
const baseRegisteredPlugins = [];
|
|
1043
1237
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -1070,6 +1264,13 @@ export class Frontend {
|
|
|
1070
1264
|
}
|
|
1071
1265
|
return baseRegisteredPlugins;
|
|
1072
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1269
|
+
*
|
|
1270
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1271
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1272
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1273
|
+
*/
|
|
1073
1274
|
async wsMessageHandler(client, message) {
|
|
1074
1275
|
let data;
|
|
1075
1276
|
try {
|
|
@@ -1225,8 +1426,10 @@ export class Frontend {
|
|
|
1225
1426
|
else if (data.method === '/api/devices') {
|
|
1226
1427
|
const devices = [];
|
|
1227
1428
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1429
|
+
// Filter by pluginName if provided
|
|
1228
1430
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1229
1431
|
return;
|
|
1432
|
+
// Check if the device has the required properties
|
|
1230
1433
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1231
1434
|
return;
|
|
1232
1435
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1310,6 +1513,7 @@ export class Frontend {
|
|
|
1310
1513
|
});
|
|
1311
1514
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1312
1515
|
deviceTypes = [];
|
|
1516
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1313
1517
|
const name = childEndpoint.endpoint?.id;
|
|
1314
1518
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1315
1519
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1363,6 +1567,7 @@ export class Frontend {
|
|
|
1363
1567
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select' }));
|
|
1364
1568
|
return;
|
|
1365
1569
|
}
|
|
1570
|
+
// const selectDeviceValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectDevice.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
|
|
1366
1571
|
const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1367
1572
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
|
|
1368
1573
|
return;
|
|
@@ -1377,6 +1582,7 @@ export class Frontend {
|
|
|
1377
1582
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' }));
|
|
1378
1583
|
return;
|
|
1379
1584
|
}
|
|
1585
|
+
// const selectEntityValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectEntity.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
|
|
1380
1586
|
const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1381
1587
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
|
|
1382
1588
|
return;
|
|
@@ -1408,6 +1614,7 @@ export class Frontend {
|
|
|
1408
1614
|
return;
|
|
1409
1615
|
}
|
|
1410
1616
|
const config = plugin.configJson;
|
|
1617
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1411
1618
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1412
1619
|
this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1413
1620
|
if (select === 'serial')
|
|
@@ -1415,9 +1622,11 @@ export class Frontend {
|
|
|
1415
1622
|
if (select === 'name')
|
|
1416
1623
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1417
1624
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1625
|
+
// Remove postfix from the serial if it exists
|
|
1418
1626
|
if (config.postfix) {
|
|
1419
1627
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1420
1628
|
}
|
|
1629
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1421
1630
|
if (isValidArray(config.whiteList, 1)) {
|
|
1422
1631
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1423
1632
|
config.whiteList.push(data.params.serial);
|
|
@@ -1426,6 +1635,7 @@ export class Frontend {
|
|
|
1426
1635
|
config.whiteList.push(data.params.name);
|
|
1427
1636
|
}
|
|
1428
1637
|
}
|
|
1638
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1429
1639
|
if (isValidArray(config.blackList, 1)) {
|
|
1430
1640
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1431
1641
|
config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
|
|
@@ -1447,6 +1657,7 @@ export class Frontend {
|
|
|
1447
1657
|
return;
|
|
1448
1658
|
}
|
|
1449
1659
|
const config = plugin.configJson;
|
|
1660
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1450
1661
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1451
1662
|
this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1452
1663
|
if (select === 'serial')
|
|
@@ -1457,6 +1668,7 @@ export class Frontend {
|
|
|
1457
1668
|
if (config.postfix) {
|
|
1458
1669
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1459
1670
|
}
|
|
1671
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1460
1672
|
if (isValidArray(config.whiteList, 1)) {
|
|
1461
1673
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1462
1674
|
config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
|
|
@@ -1465,6 +1677,7 @@ export class Frontend {
|
|
|
1465
1677
|
config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
|
|
1466
1678
|
}
|
|
1467
1679
|
}
|
|
1680
|
+
// Add the serial to the blackList
|
|
1468
1681
|
if (isValidArray(config.blackList)) {
|
|
1469
1682
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1470
1683
|
config.blackList.push(data.params.serial);
|
|
@@ -1491,102 +1704,194 @@ export class Frontend {
|
|
|
1491
1704
|
return;
|
|
1492
1705
|
}
|
|
1493
1706
|
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1709
|
+
*
|
|
1710
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1711
|
+
* @param {string} time - The time string of the message
|
|
1712
|
+
* @param {string} name - The logger name of the message
|
|
1713
|
+
* @param {string} message - The content of the message.
|
|
1714
|
+
*/
|
|
1494
1715
|
wssSendMessage(level, time, name, message) {
|
|
1495
1716
|
if (!level || !time || !name || !message)
|
|
1496
1717
|
return;
|
|
1718
|
+
// Remove ANSI escape codes from the message
|
|
1719
|
+
// eslint-disable-next-line no-control-regex
|
|
1497
1720
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1721
|
+
// Remove leading asterisks from the message
|
|
1498
1722
|
message = message.replace(/^\*+/, '');
|
|
1723
|
+
// Replace all occurrences of \t and \n
|
|
1499
1724
|
message = message.replace(/[\t\n]/g, '');
|
|
1725
|
+
// Remove non-printable characters
|
|
1726
|
+
// eslint-disable-next-line no-control-regex
|
|
1500
1727
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1728
|
+
// Replace all occurrences of \" with "
|
|
1501
1729
|
message = message.replace(/\\"/g, '"');
|
|
1730
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1502
1731
|
const maxContinuousLength = 100;
|
|
1503
1732
|
const keepStartLength = 20;
|
|
1504
1733
|
const keepEndLength = 20;
|
|
1734
|
+
// Split the message into words
|
|
1505
1735
|
message = message
|
|
1506
1736
|
.split(' ')
|
|
1507
1737
|
.map((word) => {
|
|
1738
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1508
1739
|
if (word.length > maxContinuousLength) {
|
|
1509
1740
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1510
1741
|
}
|
|
1511
1742
|
return word;
|
|
1512
1743
|
})
|
|
1513
1744
|
.join(' ');
|
|
1745
|
+
// Send the message to all connected clients
|
|
1514
1746
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1515
1747
|
if (client.readyState === WebSocket.OPEN) {
|
|
1516
1748
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1517
1749
|
}
|
|
1518
1750
|
});
|
|
1519
1751
|
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1754
|
+
*
|
|
1755
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1756
|
+
* possible values:
|
|
1757
|
+
* - 'matterbridgeLatestVersion'
|
|
1758
|
+
* - 'matterbridgeAdvertise'
|
|
1759
|
+
* - 'online'
|
|
1760
|
+
* - 'offline'
|
|
1761
|
+
* - 'reachability'
|
|
1762
|
+
* - 'settings'
|
|
1763
|
+
* - 'plugins'
|
|
1764
|
+
* - 'devices'
|
|
1765
|
+
* - 'fabrics'
|
|
1766
|
+
* - 'sessions'
|
|
1767
|
+
*/
|
|
1520
1768
|
wssSendRefreshRequired(changed = null) {
|
|
1521
1769
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1770
|
+
// Send the message to all connected clients
|
|
1522
1771
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1523
1772
|
if (client.readyState === WebSocket.OPEN) {
|
|
1524
1773
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1525
1774
|
}
|
|
1526
1775
|
});
|
|
1527
1776
|
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1779
|
+
*
|
|
1780
|
+
*/
|
|
1528
1781
|
wssSendRestartRequired(snackbar = true) {
|
|
1529
1782
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1530
1783
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1531
1784
|
if (snackbar === true)
|
|
1532
1785
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1786
|
+
// Send the message to all connected clients
|
|
1533
1787
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1534
1788
|
if (client.readyState === WebSocket.OPEN) {
|
|
1535
1789
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1536
1790
|
}
|
|
1537
1791
|
});
|
|
1538
1792
|
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1795
|
+
*
|
|
1796
|
+
*/
|
|
1539
1797
|
wssSendUpdateRequired() {
|
|
1540
1798
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1541
1799
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1800
|
+
// Send the message to all connected clients
|
|
1542
1801
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1543
1802
|
if (client.readyState === WebSocket.OPEN) {
|
|
1544
1803
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1545
1804
|
}
|
|
1546
1805
|
});
|
|
1547
1806
|
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Sends a memory update message to all connected clients.
|
|
1809
|
+
*
|
|
1810
|
+
*/
|
|
1548
1811
|
wssSendCpuUpdate(cpuUsage) {
|
|
1549
1812
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1813
|
+
// Send the message to all connected clients
|
|
1550
1814
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1551
1815
|
if (client.readyState === WebSocket.OPEN) {
|
|
1552
1816
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1553
1817
|
}
|
|
1554
1818
|
});
|
|
1555
1819
|
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Sends a cpu update message to all connected clients.
|
|
1822
|
+
*
|
|
1823
|
+
*/
|
|
1556
1824
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1557
1825
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1826
|
+
// Send the message to all connected clients
|
|
1558
1827
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1559
1828
|
if (client.readyState === WebSocket.OPEN) {
|
|
1560
1829
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1561
1830
|
}
|
|
1562
1831
|
});
|
|
1563
1832
|
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Sends a memory update message to all connected clients.
|
|
1835
|
+
*
|
|
1836
|
+
*/
|
|
1564
1837
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1565
1838
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1839
|
+
// Send the message to all connected clients
|
|
1566
1840
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1567
1841
|
if (client.readyState === WebSocket.OPEN) {
|
|
1568
1842
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1569
1843
|
}
|
|
1570
1844
|
});
|
|
1571
1845
|
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Sends a cpu update message to all connected clients.
|
|
1848
|
+
* @param {string} message - The message to send.
|
|
1849
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
1850
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
1851
|
+
*
|
|
1852
|
+
*/
|
|
1572
1853
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1573
1854
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1855
|
+
// Send the message to all connected clients
|
|
1574
1856
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1575
1857
|
if (client.readyState === WebSocket.OPEN) {
|
|
1576
1858
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
|
|
1577
1859
|
}
|
|
1578
1860
|
});
|
|
1579
1861
|
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
1864
|
+
*
|
|
1865
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
1866
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
1867
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
1868
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
1869
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
1870
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
1871
|
+
*
|
|
1872
|
+
* @remarks
|
|
1873
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
1874
|
+
* with the updated attribute information.
|
|
1875
|
+
*/
|
|
1580
1876
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1581
1877
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
1878
|
+
// Send the message to all connected clients
|
|
1582
1879
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1583
1880
|
if (client.readyState === WebSocket.OPEN) {
|
|
1584
1881
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1585
1882
|
}
|
|
1586
1883
|
});
|
|
1587
1884
|
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Sends a message to all connected clients.
|
|
1887
|
+
* @param {number} id - The message id.
|
|
1888
|
+
* @param {string} method - The message method.
|
|
1889
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
1890
|
+
*
|
|
1891
|
+
*/
|
|
1588
1892
|
wssBroadcastMessage(id, method, params) {
|
|
1589
1893
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
1894
|
+
// Send the message to all connected clients
|
|
1590
1895
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1591
1896
|
if (client.readyState === WebSocket.OPEN) {
|
|
1592
1897
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1594,3 +1899,4 @@ export class Frontend {
|
|
|
1594
1899
|
});
|
|
1595
1900
|
}
|
|
1596
1901
|
}
|
|
1902
|
+
//# sourceMappingURL=frontend.js.map
|