matterbridge 2.2.0-dev.9 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -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 -0
- 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 +172 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +270 -23
- 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 +411 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +719 -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 +692 -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 +181 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +140 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +174 -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 +77 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +121 -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 +70 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -5
- 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
|
-
|
|
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
|
+
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 https from 'https';
|
|
4
28
|
import express from 'express';
|
|
@@ -6,19 +30,70 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
6
30
|
import os from 'node:os';
|
|
7
31
|
import path from 'node:path';
|
|
8
32
|
import { promises as fs } from 'node:fs';
|
|
33
|
+
// AnsiLogger module
|
|
9
34
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
|
|
35
|
+
// Matterbridge
|
|
10
36
|
import { createZip, deepCopy, isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
11
37
|
import { plg } from './matterbridgeTypes.js';
|
|
12
38
|
import { hasParameter } from './utils/export.js';
|
|
39
|
+
/**
|
|
40
|
+
* Websocket message ID for logging.
|
|
41
|
+
* @constant {number}
|
|
42
|
+
*/
|
|
13
43
|
export const WS_ID_LOG = 0;
|
|
44
|
+
/**
|
|
45
|
+
* Websocket message ID indicating a refresh is needed.
|
|
46
|
+
* @constant {number}
|
|
47
|
+
*/
|
|
14
48
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
49
|
+
/**
|
|
50
|
+
* Websocket message ID indicating a restart is needed.
|
|
51
|
+
* @constant {number}
|
|
52
|
+
*/
|
|
15
53
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
54
|
+
/**
|
|
55
|
+
* Websocket message ID indicating a cpu update.
|
|
56
|
+
* @constant {number}
|
|
57
|
+
*/
|
|
16
58
|
export const WS_ID_CPU_UPDATE = 3;
|
|
59
|
+
/**
|
|
60
|
+
* Websocket message ID indicating a memory update.
|
|
61
|
+
* @constant {number}
|
|
62
|
+
*/
|
|
17
63
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
64
|
+
/**
|
|
65
|
+
* Websocket message ID indicating an uptime update.
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
18
68
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
* @constant {number}
|
|
72
|
+
*/
|
|
19
73
|
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
|
+
*/
|
|
20
82
|
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
|
+
*/
|
|
21
91
|
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
|
+
*/
|
|
22
97
|
export class Frontend {
|
|
23
98
|
matterbridge;
|
|
24
99
|
log;
|
|
@@ -35,7 +110,7 @@ export class Frontend {
|
|
|
35
110
|
memoryTimeout;
|
|
36
111
|
constructor(matterbridge) {
|
|
37
112
|
this.matterbridge = matterbridge;
|
|
38
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
113
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
39
114
|
}
|
|
40
115
|
set logLevel(logLevel) {
|
|
41
116
|
this.log.logLevel = logLevel;
|
|
@@ -43,10 +118,21 @@ export class Frontend {
|
|
|
43
118
|
async start(port = 8283) {
|
|
44
119
|
this.port = port;
|
|
45
120
|
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
|
|
46
122
|
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
|
|
47
131
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
48
132
|
if (!hasParameter('ssl')) {
|
|
133
|
+
// Create an HTTP server and attach the express app
|
|
49
134
|
this.httpServer = createServer(this.expressApp);
|
|
135
|
+
// Listen on the specified port
|
|
50
136
|
if (hasParameter('ingress')) {
|
|
51
137
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
52
138
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -60,6 +146,7 @@ export class Frontend {
|
|
|
60
146
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
61
147
|
});
|
|
62
148
|
}
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
150
|
this.httpServer.on('error', (error) => {
|
|
64
151
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
65
152
|
switch (error.code) {
|
|
@@ -75,6 +162,7 @@ export class Frontend {
|
|
|
75
162
|
});
|
|
76
163
|
}
|
|
77
164
|
else {
|
|
165
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
78
166
|
let cert;
|
|
79
167
|
try {
|
|
80
168
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -102,7 +190,9 @@ export class Frontend {
|
|
|
102
190
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
103
191
|
}
|
|
104
192
|
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
|
|
105
194
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
195
|
+
// Listen on the specified port
|
|
106
196
|
if (hasParameter('ingress')) {
|
|
107
197
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
108
198
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -116,6 +206,7 @@ export class Frontend {
|
|
|
116
206
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
117
207
|
});
|
|
118
208
|
}
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
210
|
this.httpsServer.on('error', (error) => {
|
|
120
211
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
121
212
|
switch (error.code) {
|
|
@@ -132,16 +223,18 @@ export class Frontend {
|
|
|
132
223
|
}
|
|
133
224
|
if (this.initializeError)
|
|
134
225
|
return;
|
|
226
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
135
227
|
const wssPort = this.port;
|
|
136
228
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
137
229
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
138
230
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
139
231
|
const clientIp = request.socket.remoteAddress;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 */;
|
|
145
238
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
146
239
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
147
240
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -175,6 +268,7 @@ export class Frontend {
|
|
|
175
268
|
this.webSocketServer.on('error', (ws, error) => {
|
|
176
269
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
177
270
|
});
|
|
271
|
+
// Subscribe to cli events
|
|
178
272
|
const { cliEmitter } = await import('./cli.js');
|
|
179
273
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
180
274
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -185,6 +279,7 @@ export class Frontend {
|
|
|
185
279
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
186
280
|
this.wssSendCpuUpdate(cpuUsage);
|
|
187
281
|
});
|
|
282
|
+
// Endpoint to validate login code
|
|
188
283
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
189
284
|
const { password } = req.body;
|
|
190
285
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -203,23 +298,27 @@ export class Frontend {
|
|
|
203
298
|
this.log.warn('/api/login error wrong password');
|
|
204
299
|
res.json({ valid: false });
|
|
205
300
|
}
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
206
302
|
}
|
|
207
303
|
catch (error) {
|
|
208
304
|
this.log.error('/api/login error getting password');
|
|
209
305
|
res.json({ valid: false });
|
|
210
306
|
}
|
|
211
307
|
});
|
|
308
|
+
// Endpoint to provide health check
|
|
212
309
|
this.expressApp.get('/health', (req, res) => {
|
|
213
310
|
this.log.debug('Express received /health');
|
|
214
311
|
const healthStatus = {
|
|
215
|
-
status: 'ok',
|
|
216
|
-
uptime: process.uptime(),
|
|
217
|
-
timestamp: new Date().toISOString(),
|
|
312
|
+
status: 'ok', // Indicate service is healthy
|
|
313
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
314
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
218
315
|
};
|
|
219
316
|
res.status(200).json(healthStatus);
|
|
220
317
|
});
|
|
318
|
+
// Endpoint to provide memory usage details
|
|
221
319
|
this.expressApp.get('/memory', async (req, res) => {
|
|
222
320
|
this.log.debug('Express received /memory');
|
|
321
|
+
// Memory usage from process
|
|
223
322
|
const memoryUsageRaw = process.memoryUsage();
|
|
224
323
|
const memoryUsage = {
|
|
225
324
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -228,10 +327,13 @@ export class Frontend {
|
|
|
228
327
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
229
328
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
230
329
|
};
|
|
330
|
+
// V8 heap statistics
|
|
231
331
|
const { default: v8 } = await import('node:v8');
|
|
232
332
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
233
333
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
334
|
+
// Format heapStats
|
|
234
335
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
336
|
+
// Format heapSpaces
|
|
235
337
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
236
338
|
...space,
|
|
237
339
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -249,6 +351,7 @@ export class Frontend {
|
|
|
249
351
|
};
|
|
250
352
|
res.status(200).json(memoryReport);
|
|
251
353
|
});
|
|
354
|
+
// Endpoint to start advertising the server node
|
|
252
355
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
253
356
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
254
357
|
if (pairingCodes) {
|
|
@@ -259,18 +362,22 @@ export class Frontend {
|
|
|
259
362
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
260
363
|
}
|
|
261
364
|
});
|
|
365
|
+
// Endpoint to provide settings
|
|
262
366
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
263
367
|
this.log.debug('The frontend sent /api/settings');
|
|
264
368
|
res.json(await this.getApiSettings());
|
|
265
369
|
});
|
|
370
|
+
// Endpoint to provide plugins
|
|
266
371
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
267
372
|
this.log.debug('The frontend sent /api/plugins');
|
|
268
373
|
res.json(this.getBaseRegisteredPlugins());
|
|
269
374
|
});
|
|
375
|
+
// Endpoint to provide devices
|
|
270
376
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
271
377
|
this.log.debug('The frontend sent /api/devices');
|
|
272
378
|
const devices = [];
|
|
273
379
|
this.matterbridge.devices.forEach(async (device) => {
|
|
380
|
+
// Check if the device has the required properties
|
|
274
381
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
275
382
|
return;
|
|
276
383
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -288,6 +395,7 @@ export class Frontend {
|
|
|
288
395
|
});
|
|
289
396
|
res.json(devices);
|
|
290
397
|
});
|
|
398
|
+
// Endpoint to provide the cluster servers of the devices
|
|
291
399
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
292
400
|
const selectedPluginName = req.params.selectedPluginName;
|
|
293
401
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -360,6 +468,7 @@ export class Frontend {
|
|
|
360
468
|
});
|
|
361
469
|
res.json(data);
|
|
362
470
|
});
|
|
471
|
+
// Endpoint to view the log
|
|
363
472
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
364
473
|
this.log.debug('The frontend sent /api/log');
|
|
365
474
|
try {
|
|
@@ -372,10 +481,12 @@ export class Frontend {
|
|
|
372
481
|
res.status(500).send('Error reading log file');
|
|
373
482
|
}
|
|
374
483
|
});
|
|
484
|
+
// Endpoint to download the matterbridge log
|
|
375
485
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
376
486
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
377
487
|
try {
|
|
378
488
|
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
|
|
379
490
|
}
|
|
380
491
|
catch (error) {
|
|
381
492
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -387,10 +498,12 @@ export class Frontend {
|
|
|
387
498
|
}
|
|
388
499
|
});
|
|
389
500
|
});
|
|
501
|
+
// Endpoint to download the matter log
|
|
390
502
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
391
503
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
392
504
|
try {
|
|
393
505
|
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
|
|
394
507
|
}
|
|
395
508
|
catch (error) {
|
|
396
509
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -402,10 +515,12 @@ export class Frontend {
|
|
|
402
515
|
}
|
|
403
516
|
});
|
|
404
517
|
});
|
|
518
|
+
// Endpoint to download the matter log
|
|
405
519
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
406
520
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
407
521
|
try {
|
|
408
522
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
523
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
409
524
|
}
|
|
410
525
|
catch (error) {
|
|
411
526
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
|
|
@@ -417,6 +532,7 @@ export class Frontend {
|
|
|
417
532
|
}
|
|
418
533
|
});
|
|
419
534
|
});
|
|
535
|
+
// Endpoint to download the matter storage file
|
|
420
536
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
421
537
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
422
538
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -427,6 +543,7 @@ export class Frontend {
|
|
|
427
543
|
}
|
|
428
544
|
});
|
|
429
545
|
});
|
|
546
|
+
// Endpoint to download the matterbridge storage directory
|
|
430
547
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
431
548
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
432
549
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -437,6 +554,7 @@ export class Frontend {
|
|
|
437
554
|
}
|
|
438
555
|
});
|
|
439
556
|
});
|
|
557
|
+
// Endpoint to download the matterbridge plugin directory
|
|
440
558
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
441
559
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
442
560
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -447,9 +565,11 @@ export class Frontend {
|
|
|
447
565
|
}
|
|
448
566
|
});
|
|
449
567
|
});
|
|
568
|
+
// Endpoint to download the matterbridge plugin config files
|
|
450
569
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
451
570
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
452
571
|
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')));
|
|
453
573
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
454
574
|
if (error) {
|
|
455
575
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -457,6 +577,7 @@ export class Frontend {
|
|
|
457
577
|
}
|
|
458
578
|
});
|
|
459
579
|
});
|
|
580
|
+
// Endpoint to download the matterbridge plugin config files
|
|
460
581
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
461
582
|
this.log.debug('The frontend sent /api/download-backup');
|
|
462
583
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -466,6 +587,7 @@ export class Frontend {
|
|
|
466
587
|
}
|
|
467
588
|
});
|
|
468
589
|
});
|
|
590
|
+
// Endpoint to receive commands
|
|
469
591
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
470
592
|
const command = req.params.command;
|
|
471
593
|
let param = req.params.param;
|
|
@@ -475,13 +597,15 @@ export class Frontend {
|
|
|
475
597
|
return;
|
|
476
598
|
}
|
|
477
599
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
600
|
+
// Handle the command setpassword from Settings
|
|
478
601
|
if (command === 'setpassword') {
|
|
479
|
-
const password = param.slice(1, -1);
|
|
602
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
480
603
|
this.log.debug('setpassword', param, password);
|
|
481
604
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
482
605
|
res.json({ message: 'Command received' });
|
|
483
606
|
return;
|
|
484
607
|
}
|
|
608
|
+
// Handle the command setbridgemode from Settings
|
|
485
609
|
if (command === 'setbridgemode') {
|
|
486
610
|
this.log.debug(`setbridgemode: ${param}`);
|
|
487
611
|
this.wssSendRestartRequired();
|
|
@@ -489,6 +613,7 @@ export class Frontend {
|
|
|
489
613
|
res.json({ message: 'Command received' });
|
|
490
614
|
return;
|
|
491
615
|
}
|
|
616
|
+
// Handle the command backup from Settings
|
|
492
617
|
if (command === 'backup') {
|
|
493
618
|
this.log.notice(`Prepairing the backup...`);
|
|
494
619
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -497,31 +622,33 @@ export class Frontend {
|
|
|
497
622
|
res.json({ message: 'Command received' });
|
|
498
623
|
return;
|
|
499
624
|
}
|
|
625
|
+
// Handle the command setmbloglevel from Settings
|
|
500
626
|
if (command === 'setmbloglevel') {
|
|
501
627
|
this.log.debug('Matterbridge log level:', param);
|
|
502
628
|
if (param === 'Debug') {
|
|
503
|
-
this.log.logLevel = "debug"
|
|
629
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
504
630
|
}
|
|
505
631
|
else if (param === 'Info') {
|
|
506
|
-
this.log.logLevel = "info"
|
|
632
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
507
633
|
}
|
|
508
634
|
else if (param === 'Notice') {
|
|
509
|
-
this.log.logLevel = "notice"
|
|
635
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
510
636
|
}
|
|
511
637
|
else if (param === 'Warn') {
|
|
512
|
-
this.log.logLevel = "warn"
|
|
638
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
513
639
|
}
|
|
514
640
|
else if (param === 'Error') {
|
|
515
|
-
this.log.logLevel = "error"
|
|
641
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
516
642
|
}
|
|
517
643
|
else if (param === 'Fatal') {
|
|
518
|
-
this.log.logLevel = "fatal"
|
|
644
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
519
645
|
}
|
|
520
646
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
521
647
|
await this.matterbridge.setLogLevel(this.log.logLevel);
|
|
522
648
|
res.json({ message: 'Command received' });
|
|
523
649
|
return;
|
|
524
650
|
}
|
|
651
|
+
// Handle the command setmbloglevel from Settings
|
|
525
652
|
if (command === 'setmjloglevel') {
|
|
526
653
|
this.log.debug('Matter.js log level:', param);
|
|
527
654
|
if (param === 'Debug') {
|
|
@@ -546,30 +673,34 @@ export class Frontend {
|
|
|
546
673
|
res.json({ message: 'Command received' });
|
|
547
674
|
return;
|
|
548
675
|
}
|
|
676
|
+
// Handle the command setmdnsinterface from Settings
|
|
549
677
|
if (command === 'setmdnsinterface') {
|
|
550
|
-
param = param.slice(1, -1);
|
|
678
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
551
679
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
552
680
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
553
681
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
554
682
|
res.json({ message: 'Command received' });
|
|
555
683
|
return;
|
|
556
684
|
}
|
|
685
|
+
// Handle the command setipv4address from Settings
|
|
557
686
|
if (command === 'setipv4address') {
|
|
558
|
-
param = param.slice(1, -1);
|
|
687
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
559
688
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
560
689
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
561
690
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
562
691
|
res.json({ message: 'Command received' });
|
|
563
692
|
return;
|
|
564
693
|
}
|
|
694
|
+
// Handle the command setipv6address from Settings
|
|
565
695
|
if (command === 'setipv6address') {
|
|
566
|
-
param = param.slice(1, -1);
|
|
696
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
567
697
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
568
698
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
569
699
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
570
700
|
res.json({ message: 'Command received' });
|
|
571
701
|
return;
|
|
572
702
|
}
|
|
703
|
+
// Handle the command setmatterport from Settings
|
|
573
704
|
if (command === 'setmatterport') {
|
|
574
705
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
575
706
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -578,6 +709,7 @@ export class Frontend {
|
|
|
578
709
|
res.json({ message: 'Command received' });
|
|
579
710
|
return;
|
|
580
711
|
}
|
|
712
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
581
713
|
if (command === 'setmatterdiscriminator') {
|
|
582
714
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
583
715
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -586,6 +718,7 @@ export class Frontend {
|
|
|
586
718
|
res.json({ message: 'Command received' });
|
|
587
719
|
return;
|
|
588
720
|
}
|
|
721
|
+
// Handle the command setmatterpasscode from Settings
|
|
589
722
|
if (command === 'setmatterpasscode') {
|
|
590
723
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
591
724
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -594,17 +727,20 @@ export class Frontend {
|
|
|
594
727
|
res.json({ message: 'Command received' });
|
|
595
728
|
return;
|
|
596
729
|
}
|
|
730
|
+
// Handle the command setmbloglevel from Settings
|
|
597
731
|
if (command === 'setmblogfile') {
|
|
598
732
|
this.log.debug('Matterbridge file log:', param);
|
|
599
733
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
600
734
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
735
|
+
// Create the file logger for matterbridge
|
|
601
736
|
if (param === 'true')
|
|
602
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
737
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
603
738
|
else
|
|
604
739
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
605
740
|
res.json({ message: 'Command received' });
|
|
606
741
|
return;
|
|
607
742
|
}
|
|
743
|
+
// Handle the command setmbloglevel from Settings
|
|
608
744
|
if (command === 'setmjlogfile') {
|
|
609
745
|
this.log.debug('Matter file log:', param);
|
|
610
746
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -631,40 +767,48 @@ export class Frontend {
|
|
|
631
767
|
res.json({ message: 'Command received' });
|
|
632
768
|
return;
|
|
633
769
|
}
|
|
770
|
+
// Handle the command unregister from Settings
|
|
634
771
|
if (command === 'unregister') {
|
|
635
772
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
636
773
|
res.json({ message: 'Command received' });
|
|
637
774
|
return;
|
|
638
775
|
}
|
|
776
|
+
// Handle the command reset from Settings
|
|
639
777
|
if (command === 'reset') {
|
|
640
778
|
await this.matterbridge.shutdownProcessAndReset();
|
|
641
779
|
res.json({ message: 'Command received' });
|
|
642
780
|
return;
|
|
643
781
|
}
|
|
782
|
+
// Handle the command factoryreset from Settings
|
|
644
783
|
if (command === 'factoryreset') {
|
|
645
784
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
646
785
|
res.json({ message: 'Command received' });
|
|
647
786
|
return;
|
|
648
787
|
}
|
|
788
|
+
// Handle the command shutdown from Header
|
|
649
789
|
if (command === 'shutdown') {
|
|
650
790
|
await this.matterbridge.shutdownProcess();
|
|
651
791
|
res.json({ message: 'Command received' });
|
|
652
792
|
return;
|
|
653
793
|
}
|
|
794
|
+
// Handle the command restart from Header
|
|
654
795
|
if (command === 'restart') {
|
|
655
796
|
await this.matterbridge.restartProcess();
|
|
656
797
|
res.json({ message: 'Command received' });
|
|
657
798
|
return;
|
|
658
799
|
}
|
|
800
|
+
// Handle the command update from Header
|
|
659
801
|
if (command === 'update') {
|
|
660
802
|
await this.matterbridge.updateProcess();
|
|
661
803
|
this.wssSendRestartRequired();
|
|
662
804
|
res.json({ message: 'Command received' });
|
|
663
805
|
return;
|
|
664
806
|
}
|
|
807
|
+
// Handle the command saveconfig from Home
|
|
665
808
|
if (command === 'saveconfig') {
|
|
666
809
|
param = param.replace(/\*/g, '\\');
|
|
667
810
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
811
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
668
812
|
if (!this.matterbridge.plugins.has(param)) {
|
|
669
813
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
670
814
|
}
|
|
@@ -679,6 +823,7 @@ export class Frontend {
|
|
|
679
823
|
res.json({ message: 'Command received' });
|
|
680
824
|
return;
|
|
681
825
|
}
|
|
826
|
+
// Handle the command installplugin from Home
|
|
682
827
|
if (command === 'installplugin') {
|
|
683
828
|
param = param.replace(/\*/g, '\\');
|
|
684
829
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
@@ -687,6 +832,7 @@ export class Frontend {
|
|
|
687
832
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
688
833
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
689
834
|
this.wssSendSnackbarMessage(`Installed package ${param}`);
|
|
835
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
690
836
|
}
|
|
691
837
|
catch (error) {
|
|
692
838
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
@@ -695,16 +841,21 @@ export class Frontend {
|
|
|
695
841
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
696
842
|
this.wssSendRestartRequired();
|
|
697
843
|
param = param.split('@')[0];
|
|
844
|
+
// Also add the plugin to matterbridge so no return!
|
|
698
845
|
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
|
|
699
847
|
res.json({ message: 'Command received' });
|
|
700
848
|
return;
|
|
701
849
|
}
|
|
702
850
|
}
|
|
851
|
+
// Handle the command addplugin from Home
|
|
703
852
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
704
853
|
param = param.replace(/\*/g, '\\');
|
|
705
854
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
706
855
|
if (plugin) {
|
|
707
856
|
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
|
|
708
859
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
709
860
|
}
|
|
710
861
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
@@ -714,19 +865,21 @@ export class Frontend {
|
|
|
714
865
|
res.json({ message: 'Command received' });
|
|
715
866
|
return;
|
|
716
867
|
}
|
|
868
|
+
// Handle the command removeplugin from Home
|
|
717
869
|
if (command === 'removeplugin') {
|
|
718
870
|
if (!this.matterbridge.plugins.has(param)) {
|
|
719
871
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
720
872
|
}
|
|
721
873
|
else {
|
|
722
874
|
const plugin = this.matterbridge.plugins.get(param);
|
|
723
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
875
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
724
876
|
await this.matterbridge.plugins.remove(param);
|
|
725
877
|
}
|
|
726
878
|
res.json({ message: 'Command received' });
|
|
727
879
|
this.wssSendRefreshRequired();
|
|
728
880
|
return;
|
|
729
881
|
}
|
|
882
|
+
// Handle the command enableplugin from Home
|
|
730
883
|
if (command === 'enableplugin') {
|
|
731
884
|
if (!this.matterbridge.plugins.has(param)) {
|
|
732
885
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -744,6 +897,7 @@ export class Frontend {
|
|
|
744
897
|
plugin.addedDevices = undefined;
|
|
745
898
|
await this.matterbridge.plugins.enable(param);
|
|
746
899
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
900
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
747
901
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
748
902
|
}
|
|
749
903
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
|
|
@@ -755,6 +909,7 @@ export class Frontend {
|
|
|
755
909
|
this.wssSendRefreshRequired();
|
|
756
910
|
return;
|
|
757
911
|
}
|
|
912
|
+
// Handle the command disableplugin from Home
|
|
758
913
|
if (command === 'disableplugin') {
|
|
759
914
|
if (!this.matterbridge.plugins.has(param)) {
|
|
760
915
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -762,7 +917,7 @@ export class Frontend {
|
|
|
762
917
|
else {
|
|
763
918
|
const plugin = this.matterbridge.plugins.get(param);
|
|
764
919
|
if (plugin && plugin.enabled) {
|
|
765
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
920
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
766
921
|
await this.matterbridge.plugins.disable(param);
|
|
767
922
|
}
|
|
768
923
|
}
|
|
@@ -771,6 +926,7 @@ export class Frontend {
|
|
|
771
926
|
return;
|
|
772
927
|
}
|
|
773
928
|
});
|
|
929
|
+
// Fallback for routing (must be the last route)
|
|
774
930
|
this.expressApp.get('*', (req, res) => {
|
|
775
931
|
this.log.debug('The frontend sent:', req.url);
|
|
776
932
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -779,24 +935,29 @@ export class Frontend {
|
|
|
779
935
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
780
936
|
}
|
|
781
937
|
async stop() {
|
|
938
|
+
// Close the http server
|
|
782
939
|
if (this.httpServer) {
|
|
783
940
|
this.httpServer.close();
|
|
784
941
|
this.httpServer.removeAllListeners();
|
|
785
942
|
this.httpServer = undefined;
|
|
786
943
|
this.log.debug('Frontend http server closed successfully');
|
|
787
944
|
}
|
|
945
|
+
// Close the https server
|
|
788
946
|
if (this.httpsServer) {
|
|
789
947
|
this.httpsServer.close();
|
|
790
948
|
this.httpsServer.removeAllListeners();
|
|
791
949
|
this.httpsServer = undefined;
|
|
792
950
|
this.log.debug('Frontend https server closed successfully');
|
|
793
951
|
}
|
|
952
|
+
// Remove listeners from the express app
|
|
794
953
|
if (this.expressApp) {
|
|
795
954
|
this.expressApp.removeAllListeners();
|
|
796
955
|
this.expressApp = undefined;
|
|
797
956
|
this.log.debug('Frontend app closed successfully');
|
|
798
957
|
}
|
|
958
|
+
// Close the WebSocket server
|
|
799
959
|
if (this.webSocketServer) {
|
|
960
|
+
// Close all active connections
|
|
800
961
|
this.webSocketServer.clients.forEach((client) => {
|
|
801
962
|
if (client.readyState === WebSocket.OPEN) {
|
|
802
963
|
client.close();
|
|
@@ -813,6 +974,7 @@ export class Frontend {
|
|
|
813
974
|
this.webSocketServer = undefined;
|
|
814
975
|
}
|
|
815
976
|
}
|
|
977
|
+
// Function to format bytes to KB, MB, or GB
|
|
816
978
|
formatMemoryUsage = (bytes) => {
|
|
817
979
|
if (bytes >= 1024 ** 3) {
|
|
818
980
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -824,6 +986,7 @@ export class Frontend {
|
|
|
824
986
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
825
987
|
}
|
|
826
988
|
};
|
|
989
|
+
// Function to format system uptime with only the most significant unit
|
|
827
990
|
formatOsUpTime = (seconds) => {
|
|
828
991
|
if (seconds >= 86400) {
|
|
829
992
|
const days = Math.floor(seconds / 86400);
|
|
@@ -839,8 +1002,13 @@ export class Frontend {
|
|
|
839
1002
|
}
|
|
840
1003
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
841
1004
|
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Retrieves the api settings data.
|
|
1007
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
1008
|
+
*/
|
|
842
1009
|
async getApiSettings() {
|
|
843
1010
|
const { lastCpuUsage } = await import('./cli.js');
|
|
1011
|
+
// Update the system information
|
|
844
1012
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
845
1013
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
846
1014
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -849,6 +1017,7 @@ export class Frontend {
|
|
|
849
1017
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
850
1018
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
851
1019
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1020
|
+
// Update the matterbridge information
|
|
852
1021
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
853
1022
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
854
1023
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -867,7 +1036,14 @@ export class Frontend {
|
|
|
867
1036
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
868
1037
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
869
1038
|
}
|
|
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
|
+
*/
|
|
870
1044
|
getClusterTextFromDevice(device) {
|
|
1045
|
+
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1046
|
+
return '';
|
|
871
1047
|
const getAttribute = (device, cluster, attribute) => {
|
|
872
1048
|
let value = undefined;
|
|
873
1049
|
Object.entries(device.state)
|
|
@@ -905,6 +1081,7 @@ export class Frontend {
|
|
|
905
1081
|
};
|
|
906
1082
|
let attributes = '';
|
|
907
1083
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1084
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
908
1085
|
if (typeof attributeValue === 'undefined')
|
|
909
1086
|
return;
|
|
910
1087
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -982,8 +1159,13 @@ export class Frontend {
|
|
|
982
1159
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
983
1160
|
attributes += `${getUserLabel(device)} `;
|
|
984
1161
|
});
|
|
1162
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
985
1163
|
return attributes.trimStart().trimEnd();
|
|
986
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1167
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1168
|
+
*/
|
|
987
1169
|
getBaseRegisteredPlugins() {
|
|
988
1170
|
const baseRegisteredPlugins = [];
|
|
989
1171
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -1014,6 +1196,13 @@ export class Frontend {
|
|
|
1014
1196
|
}
|
|
1015
1197
|
return baseRegisteredPlugins;
|
|
1016
1198
|
}
|
|
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
|
+
*/
|
|
1017
1206
|
async wsMessageHandler(client, message) {
|
|
1018
1207
|
let data;
|
|
1019
1208
|
try {
|
|
@@ -1159,8 +1348,10 @@ export class Frontend {
|
|
|
1159
1348
|
else if (data.method === '/api/devices') {
|
|
1160
1349
|
const devices = [];
|
|
1161
1350
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1351
|
+
// Filter by pluginName if provided
|
|
1162
1352
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1163
1353
|
return;
|
|
1354
|
+
// Check if the device has the required properties
|
|
1164
1355
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1165
1356
|
return;
|
|
1166
1357
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1243,6 +1434,7 @@ export class Frontend {
|
|
|
1243
1434
|
});
|
|
1244
1435
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1245
1436
|
deviceTypes = [];
|
|
1437
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1246
1438
|
const name = childEndpoint.endpoint?.id;
|
|
1247
1439
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1248
1440
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1325,85 +1517,139 @@ export class Frontend {
|
|
|
1325
1517
|
return;
|
|
1326
1518
|
}
|
|
1327
1519
|
}
|
|
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
|
+
*/
|
|
1328
1528
|
wssSendMessage(level, time, name, message) {
|
|
1329
1529
|
if (!level || !time || !name || !message)
|
|
1330
1530
|
return;
|
|
1531
|
+
// Remove ANSI escape codes from the message
|
|
1532
|
+
// eslint-disable-next-line no-control-regex
|
|
1331
1533
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1534
|
+
// Remove leading asterisks from the message
|
|
1332
1535
|
message = message.replace(/^\*+/, '');
|
|
1536
|
+
// Replace all occurrences of \t and \n
|
|
1333
1537
|
message = message.replace(/[\t\n]/g, '');
|
|
1538
|
+
// Remove non-printable characters
|
|
1539
|
+
// eslint-disable-next-line no-control-regex
|
|
1334
1540
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1541
|
+
// Replace all occurrences of \" with "
|
|
1335
1542
|
message = message.replace(/\\"/g, '"');
|
|
1543
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1336
1544
|
const maxContinuousLength = 100;
|
|
1337
1545
|
const keepStartLength = 20;
|
|
1338
1546
|
const keepEndLength = 20;
|
|
1547
|
+
// Split the message into words
|
|
1339
1548
|
message = message
|
|
1340
1549
|
.split(' ')
|
|
1341
1550
|
.map((word) => {
|
|
1551
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1342
1552
|
if (word.length > maxContinuousLength) {
|
|
1343
1553
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1344
1554
|
}
|
|
1345
1555
|
return word;
|
|
1346
1556
|
})
|
|
1347
1557
|
.join(' ');
|
|
1558
|
+
// Send the message to all connected clients
|
|
1348
1559
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1349
1560
|
if (client.readyState === WebSocket.OPEN) {
|
|
1350
1561
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1351
1562
|
}
|
|
1352
1563
|
});
|
|
1353
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1567
|
+
*
|
|
1568
|
+
*/
|
|
1354
1569
|
wssSendRefreshRequired() {
|
|
1355
1570
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1356
1571
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1572
|
+
// Send the message to all connected clients
|
|
1357
1573
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1358
1574
|
if (client.readyState === WebSocket.OPEN) {
|
|
1359
1575
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1360
1576
|
}
|
|
1361
1577
|
});
|
|
1362
1578
|
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1581
|
+
*
|
|
1582
|
+
*/
|
|
1363
1583
|
wssSendRestartRequired() {
|
|
1364
1584
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1365
1585
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1366
1586
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1587
|
+
// Send the message to all connected clients
|
|
1367
1588
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1368
1589
|
if (client.readyState === WebSocket.OPEN) {
|
|
1369
1590
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1370
1591
|
}
|
|
1371
1592
|
});
|
|
1372
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Sends a memory update message to all connected clients.
|
|
1596
|
+
*
|
|
1597
|
+
*/
|
|
1373
1598
|
wssSendCpuUpdate(cpuUsage) {
|
|
1374
1599
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1600
|
+
// Send the message to all connected clients
|
|
1375
1601
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1376
1602
|
if (client.readyState === WebSocket.OPEN) {
|
|
1377
1603
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1378
1604
|
}
|
|
1379
1605
|
});
|
|
1380
1606
|
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Sends a cpu update message to all connected clients.
|
|
1609
|
+
*
|
|
1610
|
+
*/
|
|
1381
1611
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1382
1612
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1613
|
+
// Send the message to all connected clients
|
|
1383
1614
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1384
1615
|
if (client.readyState === WebSocket.OPEN) {
|
|
1385
1616
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1386
1617
|
}
|
|
1387
1618
|
});
|
|
1388
1619
|
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Sends a memory update message to all connected clients.
|
|
1622
|
+
*
|
|
1623
|
+
*/
|
|
1389
1624
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1390
1625
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1626
|
+
// Send the message to all connected clients
|
|
1391
1627
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1392
1628
|
if (client.readyState === WebSocket.OPEN) {
|
|
1393
1629
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1394
1630
|
}
|
|
1395
1631
|
});
|
|
1396
1632
|
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Sends a cpu update message to all connected clients.
|
|
1635
|
+
*
|
|
1636
|
+
*/
|
|
1397
1637
|
wssSendSnackbarMessage(message, timeout = 5) {
|
|
1398
1638
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1639
|
+
// Send the message to all connected clients
|
|
1399
1640
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1400
1641
|
if (client.readyState === WebSocket.OPEN) {
|
|
1401
1642
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
|
|
1402
1643
|
}
|
|
1403
1644
|
});
|
|
1404
1645
|
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Sends a message to all connected clients.
|
|
1648
|
+
*
|
|
1649
|
+
*/
|
|
1405
1650
|
wssBroadcastMessage(id, method, params) {
|
|
1406
1651
|
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
|
|
1407
1653
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1408
1654
|
if (client.readyState === WebSocket.OPEN) {
|
|
1409
1655
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1411,3 +1657,4 @@ export class Frontend {
|
|
|
1411
1657
|
});
|
|
1412
1658
|
}
|
|
1413
1659
|
}
|
|
1660
|
+
//# sourceMappingURL=frontend.js.map
|