matterbridge 2.1.4-dev.3 → 2.1.4
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 +4 -1
- package/README-DOCKER.md +11 -7
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- 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 +110 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +232 -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 -0
- 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 +409 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +749 -39
- 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 +834 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +691 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2264 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +105 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +159 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +122 -6
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +167 -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 +230 -3
- package/dist/pluginManager.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/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/export.d.ts +3 -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/utils.d.ts +231 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +307 -9
- package/dist/utils/utils.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.1
|
|
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 } from '@matter/main';
|
|
25
|
+
// Node modules
|
|
2
26
|
import { createServer } from 'http';
|
|
3
27
|
import https from 'https';
|
|
4
28
|
import express from 'express';
|
|
@@ -6,13 +30,32 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
6
30
|
import os from 'os';
|
|
7
31
|
import path from 'path';
|
|
8
32
|
import { promises as fs } from 'fs';
|
|
33
|
+
// AnsiLogger module
|
|
9
34
|
import { AnsiLogger, CYAN, db, debugStringify, er, nf, rs, stringify, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
|
|
35
|
+
// Matterbridge
|
|
10
36
|
import { createZip, getIntParameter, hasParameter, isValidNumber, isValidObject, isValidString } from './utils/utils.js';
|
|
11
37
|
import { plg } from './matterbridgeTypes.js';
|
|
12
38
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.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
|
+
* Initializes the frontend of Matterbridge.
|
|
56
|
+
*
|
|
57
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
58
|
+
*/
|
|
16
59
|
export class Frontend {
|
|
17
60
|
matterbridge;
|
|
18
61
|
log;
|
|
@@ -28,7 +71,7 @@ export class Frontend {
|
|
|
28
71
|
memoryTimeout;
|
|
29
72
|
constructor(matterbridge) {
|
|
30
73
|
this.matterbridge = matterbridge;
|
|
31
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
74
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
32
75
|
}
|
|
33
76
|
set logLevel(logLevel) {
|
|
34
77
|
this.log.logLevel = logLevel;
|
|
@@ -36,10 +79,21 @@ export class Frontend {
|
|
|
36
79
|
async start(port = 8283) {
|
|
37
80
|
this.port = port;
|
|
38
81
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
82
|
+
// Create the express app that serves the frontend
|
|
39
83
|
this.expressApp = express();
|
|
84
|
+
// Log all requests to the server for debugging
|
|
85
|
+
/*
|
|
86
|
+
this.expressApp.use((req, res, next) => {
|
|
87
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
88
|
+
next();
|
|
89
|
+
});
|
|
90
|
+
*/
|
|
91
|
+
// Serve static files from '/static' endpoint
|
|
40
92
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
41
93
|
if (!hasParameter('ssl')) {
|
|
94
|
+
// Create an HTTP server and attach the express app
|
|
42
95
|
this.httpServer = createServer(this.expressApp);
|
|
96
|
+
// Listen on the specified port
|
|
43
97
|
if (hasParameter('ingress')) {
|
|
44
98
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
45
99
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -53,6 +107,7 @@ export class Frontend {
|
|
|
53
107
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
54
108
|
});
|
|
55
109
|
}
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
111
|
this.httpServer.on('error', (error) => {
|
|
57
112
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
58
113
|
switch (error.code) {
|
|
@@ -68,6 +123,7 @@ export class Frontend {
|
|
|
68
123
|
});
|
|
69
124
|
}
|
|
70
125
|
else {
|
|
126
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
71
127
|
let cert;
|
|
72
128
|
try {
|
|
73
129
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -95,7 +151,9 @@ export class Frontend {
|
|
|
95
151
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
96
152
|
}
|
|
97
153
|
const serverOptions = { cert, key, ca };
|
|
154
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
98
155
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
156
|
+
// Listen on the specified port
|
|
99
157
|
if (hasParameter('ingress')) {
|
|
100
158
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
101
159
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -109,6 +167,7 @@ export class Frontend {
|
|
|
109
167
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
110
168
|
});
|
|
111
169
|
}
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
171
|
this.httpsServer.on('error', (error) => {
|
|
113
172
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
114
173
|
switch (error.code) {
|
|
@@ -125,12 +184,13 @@ export class Frontend {
|
|
|
125
184
|
}
|
|
126
185
|
if (this.initializeError)
|
|
127
186
|
return;
|
|
187
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
128
188
|
const wssPort = this.port;
|
|
129
189
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
130
190
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
131
191
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
132
192
|
const clientIp = request.socket.remoteAddress;
|
|
133
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
193
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
134
194
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
135
195
|
ws.on('message', (message) => {
|
|
136
196
|
this.wsMessageHandler(ws, message);
|
|
@@ -162,9 +222,11 @@ export class Frontend {
|
|
|
162
222
|
this.webSocketServer.on('error', (ws, error) => {
|
|
163
223
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
164
224
|
});
|
|
225
|
+
// Start the memory dump interval
|
|
165
226
|
if (hasParameter('memorydump')) {
|
|
166
227
|
this.startCpuMemoryDump();
|
|
167
228
|
}
|
|
229
|
+
// Endpoint to validate login code
|
|
168
230
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
169
231
|
const { password } = req.body;
|
|
170
232
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -183,23 +245,27 @@ export class Frontend {
|
|
|
183
245
|
this.log.warn('/api/login error wrong password');
|
|
184
246
|
res.json({ valid: false });
|
|
185
247
|
}
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
186
249
|
}
|
|
187
250
|
catch (error) {
|
|
188
251
|
this.log.error('/api/login error getting password');
|
|
189
252
|
res.json({ valid: false });
|
|
190
253
|
}
|
|
191
254
|
});
|
|
255
|
+
// Endpoint to provide health check
|
|
192
256
|
this.expressApp.get('/health', (req, res) => {
|
|
193
257
|
this.log.debug('Express received /health');
|
|
194
258
|
const healthStatus = {
|
|
195
|
-
status: 'ok',
|
|
196
|
-
uptime: process.uptime(),
|
|
197
|
-
timestamp: new Date().toISOString(),
|
|
259
|
+
status: 'ok', // Indicate service is healthy
|
|
260
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
261
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
198
262
|
};
|
|
199
263
|
res.status(200).json(healthStatus);
|
|
200
264
|
});
|
|
265
|
+
// Endpoint to provide memory usage details
|
|
201
266
|
this.expressApp.get('/memory', async (req, res) => {
|
|
202
267
|
this.log.debug('Express received /memory');
|
|
268
|
+
// Memory usage from process
|
|
203
269
|
const memoryUsageRaw = process.memoryUsage();
|
|
204
270
|
const memoryUsage = {
|
|
205
271
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -208,10 +274,13 @@ export class Frontend {
|
|
|
208
274
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
209
275
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
210
276
|
};
|
|
277
|
+
// V8 heap statistics
|
|
211
278
|
const { default: v8 } = await import('node:v8');
|
|
212
279
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
213
280
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
281
|
+
// Format heapStats
|
|
214
282
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
283
|
+
// Format heapSpaces
|
|
215
284
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
216
285
|
...space,
|
|
217
286
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -221,6 +290,23 @@ export class Frontend {
|
|
|
221
290
|
}));
|
|
222
291
|
const { default: module } = await import('module');
|
|
223
292
|
const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
|
|
293
|
+
/*
|
|
294
|
+
if (req.query.heapdump === 'true') {
|
|
295
|
+
const { default: heapdump } = await import('heapdump');
|
|
296
|
+
const filename = `heapdump-${Date.now()}.heapsnapshot`;
|
|
297
|
+
|
|
298
|
+
heapdump.writeSnapshot(filename, (err, dumpFilename) => {
|
|
299
|
+
if (err) {
|
|
300
|
+
this.log.error(`Heap dump error: ${err.message}`);
|
|
301
|
+
return res.status(500).json({ error: 'Heap dump failed', details: err.message });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
this.log.info(`Heap dump written to ${dumpFilename}`);
|
|
305
|
+
return res.status(200).json({ ...memoryReport, heapdump: dumpFilename });
|
|
306
|
+
});
|
|
307
|
+
return; // Exit early since heapdump response is handled inside callback
|
|
308
|
+
}
|
|
309
|
+
*/
|
|
224
310
|
const memoryReport = {
|
|
225
311
|
memoryUsage,
|
|
226
312
|
heapStats,
|
|
@@ -229,6 +315,7 @@ export class Frontend {
|
|
|
229
315
|
};
|
|
230
316
|
res.status(200).json(memoryReport);
|
|
231
317
|
});
|
|
318
|
+
// Endpoint to start advertising the server node
|
|
232
319
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
233
320
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
234
321
|
if (pairingCodes) {
|
|
@@ -239,19 +326,24 @@ export class Frontend {
|
|
|
239
326
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
240
327
|
}
|
|
241
328
|
});
|
|
329
|
+
// Endpoint to provide settings
|
|
242
330
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
243
331
|
this.log.debug('The frontend sent /api/settings');
|
|
244
332
|
res.json(await this.getApiSettings());
|
|
245
333
|
});
|
|
334
|
+
// Endpoint to provide plugins
|
|
246
335
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
247
336
|
this.log.debug('The frontend sent /api/plugins');
|
|
248
337
|
const response = this.getBaseRegisteredPlugins();
|
|
338
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
249
339
|
res.json(response);
|
|
250
340
|
});
|
|
341
|
+
// Endpoint to provide devices
|
|
251
342
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
252
343
|
this.log.debug('The frontend sent /api/devices');
|
|
253
344
|
const devices = [];
|
|
254
345
|
this.matterbridge.devices.forEach(async (device) => {
|
|
346
|
+
// Check if the device has the required properties
|
|
255
347
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
256
348
|
return;
|
|
257
349
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -267,8 +359,10 @@ export class Frontend {
|
|
|
267
359
|
cluster: cluster,
|
|
268
360
|
});
|
|
269
361
|
});
|
|
362
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
270
363
|
res.json(devices);
|
|
271
364
|
});
|
|
365
|
+
// Endpoint to provide the cluster servers of the devices
|
|
272
366
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
273
367
|
const selectedPluginName = req.params.selectedPluginName;
|
|
274
368
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -341,6 +435,7 @@ export class Frontend {
|
|
|
341
435
|
});
|
|
342
436
|
res.json(data);
|
|
343
437
|
});
|
|
438
|
+
// Endpoint to view the log
|
|
344
439
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
345
440
|
this.log.debug('The frontend sent /api/log');
|
|
346
441
|
try {
|
|
@@ -353,10 +448,12 @@ export class Frontend {
|
|
|
353
448
|
res.status(500).send('Error reading log file');
|
|
354
449
|
}
|
|
355
450
|
});
|
|
451
|
+
// Endpoint to download the matterbridge log
|
|
356
452
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
357
453
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
358
454
|
try {
|
|
359
455
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
456
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
360
457
|
}
|
|
361
458
|
catch (error) {
|
|
362
459
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -368,10 +465,12 @@ export class Frontend {
|
|
|
368
465
|
}
|
|
369
466
|
});
|
|
370
467
|
});
|
|
468
|
+
// Endpoint to download the matter log
|
|
371
469
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
372
470
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
373
471
|
try {
|
|
374
472
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
473
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
375
474
|
}
|
|
376
475
|
catch (error) {
|
|
377
476
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -383,6 +482,7 @@ export class Frontend {
|
|
|
383
482
|
}
|
|
384
483
|
});
|
|
385
484
|
});
|
|
485
|
+
// Endpoint to download the matter storage file
|
|
386
486
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
387
487
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
388
488
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -393,6 +493,7 @@ export class Frontend {
|
|
|
393
493
|
}
|
|
394
494
|
});
|
|
395
495
|
});
|
|
496
|
+
// Endpoint to download the matterbridge storage directory
|
|
396
497
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
397
498
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
398
499
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -403,6 +504,7 @@ export class Frontend {
|
|
|
403
504
|
}
|
|
404
505
|
});
|
|
405
506
|
});
|
|
507
|
+
// Endpoint to download the matterbridge plugin directory
|
|
406
508
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
407
509
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
408
510
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -413,9 +515,11 @@ export class Frontend {
|
|
|
413
515
|
}
|
|
414
516
|
});
|
|
415
517
|
});
|
|
518
|
+
// Endpoint to download the matterbridge plugin config files
|
|
416
519
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
417
520
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
418
521
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
522
|
+
// 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')));
|
|
419
523
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
420
524
|
if (error) {
|
|
421
525
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -423,6 +527,7 @@ export class Frontend {
|
|
|
423
527
|
}
|
|
424
528
|
});
|
|
425
529
|
});
|
|
530
|
+
// Endpoint to download the matterbridge plugin config files
|
|
426
531
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
427
532
|
this.log.debug('The frontend sent /api/download-backup');
|
|
428
533
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -432,6 +537,7 @@ export class Frontend {
|
|
|
432
537
|
}
|
|
433
538
|
});
|
|
434
539
|
});
|
|
540
|
+
// Endpoint to receive commands
|
|
435
541
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
436
542
|
const command = req.params.command;
|
|
437
543
|
let param = req.params.param;
|
|
@@ -441,13 +547,15 @@ export class Frontend {
|
|
|
441
547
|
return;
|
|
442
548
|
}
|
|
443
549
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
550
|
+
// Handle the command setpassword from Settings
|
|
444
551
|
if (command === 'setpassword') {
|
|
445
|
-
const password = param.slice(1, -1);
|
|
552
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
446
553
|
this.log.debug('setpassword', param, password);
|
|
447
554
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
448
555
|
res.json({ message: 'Command received' });
|
|
449
556
|
return;
|
|
450
557
|
}
|
|
558
|
+
// Handle the command setbridgemode from Settings
|
|
451
559
|
if (command === 'setbridgemode') {
|
|
452
560
|
this.log.debug(`setbridgemode: ${param}`);
|
|
453
561
|
this.wssSendRestartRequired();
|
|
@@ -455,6 +563,7 @@ export class Frontend {
|
|
|
455
563
|
res.json({ message: 'Command received' });
|
|
456
564
|
return;
|
|
457
565
|
}
|
|
566
|
+
// Handle the command backup from Settings
|
|
458
567
|
if (command === 'backup') {
|
|
459
568
|
this.log.notice(`Prepairing the backup...`);
|
|
460
569
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -462,25 +571,26 @@ export class Frontend {
|
|
|
462
571
|
res.json({ message: 'Command received' });
|
|
463
572
|
return;
|
|
464
573
|
}
|
|
574
|
+
// Handle the command setmbloglevel from Settings
|
|
465
575
|
if (command === 'setmbloglevel') {
|
|
466
576
|
this.log.debug('Matterbridge log level:', param);
|
|
467
577
|
if (param === 'Debug') {
|
|
468
|
-
this.log.logLevel = "debug"
|
|
578
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
469
579
|
}
|
|
470
580
|
else if (param === 'Info') {
|
|
471
|
-
this.log.logLevel = "info"
|
|
581
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
472
582
|
}
|
|
473
583
|
else if (param === 'Notice') {
|
|
474
|
-
this.log.logLevel = "notice"
|
|
584
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
475
585
|
}
|
|
476
586
|
else if (param === 'Warn') {
|
|
477
|
-
this.log.logLevel = "warn"
|
|
587
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
478
588
|
}
|
|
479
589
|
else if (param === 'Error') {
|
|
480
|
-
this.log.logLevel = "error"
|
|
590
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
481
591
|
}
|
|
482
592
|
else if (param === 'Fatal') {
|
|
483
|
-
this.log.logLevel = "fatal"
|
|
593
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
484
594
|
}
|
|
485
595
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
486
596
|
this.matterbridge.log.logLevel = this.log.logLevel;
|
|
@@ -490,12 +600,13 @@ export class Frontend {
|
|
|
490
600
|
for (const plugin of this.matterbridge.plugins) {
|
|
491
601
|
if (!plugin.platform || !plugin.platform.config)
|
|
492
602
|
continue;
|
|
493
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
494
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
603
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
604
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
495
605
|
}
|
|
496
606
|
res.json({ message: 'Command received' });
|
|
497
607
|
return;
|
|
498
608
|
}
|
|
609
|
+
// Handle the command setmbloglevel from Settings
|
|
499
610
|
if (command === 'setmjloglevel') {
|
|
500
611
|
this.log.debug('Matter.js log level:', param);
|
|
501
612
|
if (param === 'Debug') {
|
|
@@ -520,30 +631,34 @@ export class Frontend {
|
|
|
520
631
|
res.json({ message: 'Command received' });
|
|
521
632
|
return;
|
|
522
633
|
}
|
|
634
|
+
// Handle the command setmdnsinterface from Settings
|
|
523
635
|
if (command === 'setmdnsinterface') {
|
|
524
|
-
param = param.slice(1, -1);
|
|
636
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
525
637
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
526
638
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
527
639
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
528
640
|
res.json({ message: 'Command received' });
|
|
529
641
|
return;
|
|
530
642
|
}
|
|
643
|
+
// Handle the command setipv4address from Settings
|
|
531
644
|
if (command === 'setipv4address') {
|
|
532
|
-
param = param.slice(1, -1);
|
|
645
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
533
646
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
534
647
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
535
648
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
536
649
|
res.json({ message: 'Command received' });
|
|
537
650
|
return;
|
|
538
651
|
}
|
|
652
|
+
// Handle the command setipv6address from Settings
|
|
539
653
|
if (command === 'setipv6address') {
|
|
540
|
-
param = param.slice(1, -1);
|
|
654
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
541
655
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
542
656
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
543
657
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
544
658
|
res.json({ message: 'Command received' });
|
|
545
659
|
return;
|
|
546
660
|
}
|
|
661
|
+
// Handle the command setmatterport from Settings
|
|
547
662
|
if (command === 'setmatterport') {
|
|
548
663
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
549
664
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -552,6 +667,7 @@ export class Frontend {
|
|
|
552
667
|
res.json({ message: 'Command received' });
|
|
553
668
|
return;
|
|
554
669
|
}
|
|
670
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
555
671
|
if (command === 'setmatterdiscriminator') {
|
|
556
672
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
557
673
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -560,6 +676,7 @@ export class Frontend {
|
|
|
560
676
|
res.json({ message: 'Command received' });
|
|
561
677
|
return;
|
|
562
678
|
}
|
|
679
|
+
// Handle the command setmatterpasscode from Settings
|
|
563
680
|
if (command === 'setmatterpasscode') {
|
|
564
681
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
565
682
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -568,17 +685,20 @@ export class Frontend {
|
|
|
568
685
|
res.json({ message: 'Command received' });
|
|
569
686
|
return;
|
|
570
687
|
}
|
|
688
|
+
// Handle the command setmbloglevel from Settings
|
|
571
689
|
if (command === 'setmblogfile') {
|
|
572
690
|
this.log.debug('Matterbridge file log:', param);
|
|
573
691
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
574
692
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
693
|
+
// Create the file logger for matterbridge
|
|
575
694
|
if (param === 'true')
|
|
576
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
695
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
577
696
|
else
|
|
578
697
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
579
698
|
res.json({ message: 'Command received' });
|
|
580
699
|
return;
|
|
581
700
|
}
|
|
701
|
+
// Handle the command setmbloglevel from Settings
|
|
582
702
|
if (command === 'setmjlogfile') {
|
|
583
703
|
this.log.debug('Matter file log:', param);
|
|
584
704
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -605,40 +725,48 @@ export class Frontend {
|
|
|
605
725
|
res.json({ message: 'Command received' });
|
|
606
726
|
return;
|
|
607
727
|
}
|
|
728
|
+
// Handle the command unregister from Settings
|
|
608
729
|
if (command === 'unregister') {
|
|
609
730
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
610
731
|
res.json({ message: 'Command received' });
|
|
611
732
|
return;
|
|
612
733
|
}
|
|
734
|
+
// Handle the command reset from Settings
|
|
613
735
|
if (command === 'reset') {
|
|
614
736
|
await this.matterbridge.shutdownProcessAndReset();
|
|
615
737
|
res.json({ message: 'Command received' });
|
|
616
738
|
return;
|
|
617
739
|
}
|
|
740
|
+
// Handle the command factoryreset from Settings
|
|
618
741
|
if (command === 'factoryreset') {
|
|
619
742
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
620
743
|
res.json({ message: 'Command received' });
|
|
621
744
|
return;
|
|
622
745
|
}
|
|
746
|
+
// Handle the command shutdown from Header
|
|
623
747
|
if (command === 'shutdown') {
|
|
624
748
|
await this.matterbridge.shutdownProcess();
|
|
625
749
|
res.json({ message: 'Command received' });
|
|
626
750
|
return;
|
|
627
751
|
}
|
|
752
|
+
// Handle the command restart from Header
|
|
628
753
|
if (command === 'restart') {
|
|
629
754
|
await this.matterbridge.restartProcess();
|
|
630
755
|
res.json({ message: 'Command received' });
|
|
631
756
|
return;
|
|
632
757
|
}
|
|
758
|
+
// Handle the command update from Header
|
|
633
759
|
if (command === 'update') {
|
|
634
760
|
await this.matterbridge.updateProcess();
|
|
635
761
|
this.wssSendRestartRequired();
|
|
636
762
|
res.json({ message: 'Command received' });
|
|
637
763
|
return;
|
|
638
764
|
}
|
|
765
|
+
// Handle the command saveconfig from Home
|
|
639
766
|
if (command === 'saveconfig') {
|
|
640
767
|
param = param.replace(/\*/g, '\\');
|
|
641
768
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
769
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
642
770
|
if (!this.matterbridge.plugins.has(param)) {
|
|
643
771
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
644
772
|
}
|
|
@@ -652,49 +780,58 @@ export class Frontend {
|
|
|
652
780
|
res.json({ message: 'Command received' });
|
|
653
781
|
return;
|
|
654
782
|
}
|
|
783
|
+
// Handle the command installplugin from Home
|
|
655
784
|
if (command === 'installplugin') {
|
|
656
785
|
param = param.replace(/\*/g, '\\');
|
|
657
786
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
658
787
|
try {
|
|
659
788
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
660
789
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
790
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
661
791
|
}
|
|
662
792
|
catch (error) {
|
|
663
793
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
664
794
|
}
|
|
665
795
|
this.wssSendRestartRequired();
|
|
666
796
|
param = param.split('@')[0];
|
|
797
|
+
// Also add the plugin to matterbridge so no return!
|
|
667
798
|
if (param === 'matterbridge') {
|
|
799
|
+
// 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
|
|
668
800
|
res.json({ message: 'Command received' });
|
|
669
801
|
return;
|
|
670
802
|
}
|
|
671
803
|
}
|
|
804
|
+
// Handle the command addplugin from Home
|
|
672
805
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
673
806
|
param = param.replace(/\*/g, '\\');
|
|
674
807
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
675
808
|
if (plugin) {
|
|
676
809
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
810
|
+
// 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
|
|
811
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
677
812
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
678
813
|
}
|
|
679
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
814
|
+
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
680
815
|
}
|
|
681
816
|
res.json({ message: 'Command received' });
|
|
682
817
|
this.wssSendRefreshRequired();
|
|
683
818
|
return;
|
|
684
819
|
}
|
|
820
|
+
// Handle the command removeplugin from Home
|
|
685
821
|
if (command === 'removeplugin') {
|
|
686
822
|
if (!this.matterbridge.plugins.has(param)) {
|
|
687
823
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
688
824
|
}
|
|
689
825
|
else {
|
|
690
826
|
const plugin = this.matterbridge.plugins.get(param);
|
|
691
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
827
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
692
828
|
await this.matterbridge.plugins.remove(param);
|
|
693
829
|
}
|
|
694
830
|
res.json({ message: 'Command received' });
|
|
695
831
|
this.wssSendRefreshRequired();
|
|
696
832
|
return;
|
|
697
833
|
}
|
|
834
|
+
// Handle the command enableplugin from Home
|
|
698
835
|
if (command === 'enableplugin') {
|
|
699
836
|
if (!this.matterbridge.plugins.has(param)) {
|
|
700
837
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -712,15 +849,17 @@ export class Frontend {
|
|
|
712
849
|
plugin.addedDevices = undefined;
|
|
713
850
|
await this.matterbridge.plugins.enable(param);
|
|
714
851
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
852
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
715
853
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
716
854
|
}
|
|
717
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
855
|
+
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background since the server node is already started
|
|
718
856
|
}
|
|
719
857
|
}
|
|
720
858
|
res.json({ message: 'Command received' });
|
|
721
859
|
this.wssSendRefreshRequired();
|
|
722
860
|
return;
|
|
723
861
|
}
|
|
862
|
+
// Handle the command disableplugin from Home
|
|
724
863
|
if (command === 'disableplugin') {
|
|
725
864
|
if (!this.matterbridge.plugins.has(param)) {
|
|
726
865
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -728,7 +867,7 @@ export class Frontend {
|
|
|
728
867
|
else {
|
|
729
868
|
const plugin = this.matterbridge.plugins.get(param);
|
|
730
869
|
if (plugin && plugin.enabled) {
|
|
731
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
870
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
732
871
|
await this.matterbridge.plugins.disable(param);
|
|
733
872
|
}
|
|
734
873
|
}
|
|
@@ -737,6 +876,7 @@ export class Frontend {
|
|
|
737
876
|
return;
|
|
738
877
|
}
|
|
739
878
|
});
|
|
879
|
+
// Fallback for routing (must be the last route)
|
|
740
880
|
this.expressApp.get('*', (req, res) => {
|
|
741
881
|
this.log.debug('The frontend sent:', req.url);
|
|
742
882
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -745,6 +885,7 @@ export class Frontend {
|
|
|
745
885
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
746
886
|
}
|
|
747
887
|
async stop() {
|
|
888
|
+
// Start the memory check. This will not allow the process to exit but will log the memory usage for 5 minutes.
|
|
748
889
|
if (hasParameter('memorycheck')) {
|
|
749
890
|
await new Promise((resolve) => {
|
|
750
891
|
this.log.debug(`***Memory check started for ${getIntParameter('memorycheck') ?? 5 * 60 * 1000} ms`);
|
|
@@ -754,24 +895,29 @@ export class Frontend {
|
|
|
754
895
|
}, getIntParameter('memorycheck') ?? 5 * 60 * 1000);
|
|
755
896
|
});
|
|
756
897
|
}
|
|
898
|
+
// Close the http server
|
|
757
899
|
if (this.httpServer) {
|
|
758
900
|
this.httpServer.close();
|
|
759
901
|
this.httpServer.removeAllListeners();
|
|
760
902
|
this.httpServer = undefined;
|
|
761
903
|
this.log.debug('Frontend http server closed successfully');
|
|
762
904
|
}
|
|
905
|
+
// Close the https server
|
|
763
906
|
if (this.httpsServer) {
|
|
764
907
|
this.httpsServer.close();
|
|
765
908
|
this.httpsServer.removeAllListeners();
|
|
766
909
|
this.httpsServer = undefined;
|
|
767
910
|
this.log.debug('Frontend https server closed successfully');
|
|
768
911
|
}
|
|
912
|
+
// Remove listeners from the express app
|
|
769
913
|
if (this.expressApp) {
|
|
770
914
|
this.expressApp.removeAllListeners();
|
|
771
915
|
this.expressApp = undefined;
|
|
772
916
|
this.log.debug('Frontend app closed successfully');
|
|
773
917
|
}
|
|
918
|
+
// Close the WebSocket server
|
|
774
919
|
if (this.webSocketServer) {
|
|
920
|
+
// Close all active connections
|
|
775
921
|
this.webSocketServer.clients.forEach((client) => {
|
|
776
922
|
if (client.readyState === WebSocket.OPEN) {
|
|
777
923
|
client.close();
|
|
@@ -787,10 +933,12 @@ export class Frontend {
|
|
|
787
933
|
});
|
|
788
934
|
this.webSocketServer = undefined;
|
|
789
935
|
}
|
|
936
|
+
// Stop the memory dump interval
|
|
790
937
|
if (hasParameter('memorydump')) {
|
|
791
938
|
this.stopCpuMemoryDump();
|
|
792
939
|
}
|
|
793
940
|
}
|
|
941
|
+
// Function to format bytes to KB or MB
|
|
794
942
|
formatMemoryUsage = (bytes) => {
|
|
795
943
|
const kb = bytes / 1024;
|
|
796
944
|
const mb = kb / 1024;
|
|
@@ -802,9 +950,10 @@ export class Frontend {
|
|
|
802
950
|
const interval = () => {
|
|
803
951
|
const currCpus = os.cpus();
|
|
804
952
|
if (currCpus.length !== this.prevCpus.length) {
|
|
805
|
-
this.prevCpus = currCpus;
|
|
953
|
+
this.prevCpus = currCpus; // Reset the previous cpus if the number of cpu has changed
|
|
806
954
|
}
|
|
807
955
|
let totalIdle = 0, totalTick = 0;
|
|
956
|
+
// Get the cpu usage
|
|
808
957
|
this.prevCpus.forEach((prevCpu, i) => {
|
|
809
958
|
const currCpu = currCpus[i];
|
|
810
959
|
const idleDiff = currCpu.times.idle - prevCpu.times.idle;
|
|
@@ -814,6 +963,7 @@ export class Frontend {
|
|
|
814
963
|
});
|
|
815
964
|
const cpuUsage = (100 - (totalIdle / totalTick) * 100).toFixed(2);
|
|
816
965
|
this.prevCpus = currCpus;
|
|
966
|
+
// Get the memory usage
|
|
817
967
|
const memoryUsageRaw = process.memoryUsage();
|
|
818
968
|
this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
|
|
819
969
|
const memoryUsage = {
|
|
@@ -846,14 +996,21 @@ export class Frontend {
|
|
|
846
996
|
external: this.formatMemoryUsage(memory.external),
|
|
847
997
|
arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
|
|
848
998
|
};
|
|
999
|
+
// eslint-disable-next-line no-console
|
|
849
1000
|
console.log(`${YELLOW}Cpu usage:${db} ${CYAN}${memory.cpu.padStart(6, ' ')} %${db} - ${YELLOW}Memory usage:${db} rss ${CYAN}${memoryUsage.rss}${db} heapTotal ${CYAN}${memoryUsage.heapTotal}${db} heapUsed ${CYAN}${memoryUsage.heapUsed}${db} external ${memoryUsage.external} arrayBuffers ${memoryUsage.arrayBuffers}${rs}`);
|
|
850
1001
|
}
|
|
851
1002
|
this.memoryData = [];
|
|
852
1003
|
this.prevCpus = [];
|
|
853
1004
|
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Retrieves the api settings data.
|
|
1007
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
1008
|
+
*/
|
|
854
1009
|
async getApiSettings() {
|
|
1010
|
+
// Update the system information
|
|
855
1011
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
856
1012
|
this.matterbridge.systemInformation.heap = this.formatMemoryUsage(process.memoryUsage().heapUsed) + ' / ' + this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
1013
|
+
// Update the matterbridge information
|
|
857
1014
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
858
1015
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
859
1016
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -872,6 +1029,11 @@ export class Frontend {
|
|
|
872
1029
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
873
1030
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
874
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Retrieves the cluster text description from a given device.
|
|
1034
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1035
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1036
|
+
*/
|
|
875
1037
|
getClusterTextFromDevice(device) {
|
|
876
1038
|
const getAttribute = (device, cluster, attribute) => {
|
|
877
1039
|
let value = undefined;
|
|
@@ -910,6 +1072,7 @@ export class Frontend {
|
|
|
910
1072
|
};
|
|
911
1073
|
let attributes = '';
|
|
912
1074
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1075
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
913
1076
|
if (typeof attributeValue === 'undefined')
|
|
914
1077
|
return;
|
|
915
1078
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -987,8 +1150,13 @@ export class Frontend {
|
|
|
987
1150
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
988
1151
|
attributes += `${getUserLabel(device)} `;
|
|
989
1152
|
});
|
|
1153
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
990
1154
|
return attributes.trimStart().trimEnd();
|
|
991
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1158
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1159
|
+
*/
|
|
992
1160
|
getBaseRegisteredPlugins() {
|
|
993
1161
|
const baseRegisteredPlugins = [];
|
|
994
1162
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -1019,6 +1187,14 @@ export class Frontend {
|
|
|
1019
1187
|
}
|
|
1020
1188
|
return baseRegisteredPlugins;
|
|
1021
1189
|
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Handles incoming websocket messages for the Matterbridge.
|
|
1192
|
+
*
|
|
1193
|
+
* @param {Matterbridge} this - The Matterbridge instance.
|
|
1194
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1195
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1196
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1197
|
+
*/
|
|
1022
1198
|
async wsMessageHandler(client, message) {
|
|
1023
1199
|
let data;
|
|
1024
1200
|
try {
|
|
@@ -1106,8 +1282,10 @@ export class Frontend {
|
|
|
1106
1282
|
else if (data.method === '/api/devices') {
|
|
1107
1283
|
const devices = [];
|
|
1108
1284
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1285
|
+
// Filter by pluginName if provided
|
|
1109
1286
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1110
1287
|
return;
|
|
1288
|
+
// Check if the device has the required properties
|
|
1111
1289
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
1112
1290
|
return;
|
|
1113
1291
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1190,6 +1368,7 @@ export class Frontend {
|
|
|
1190
1368
|
});
|
|
1191
1369
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1192
1370
|
deviceTypes = [];
|
|
1371
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1193
1372
|
const name = childEndpoint.endpoint?.id;
|
|
1194
1373
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1195
1374
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1272,44 +1451,73 @@ export class Frontend {
|
|
|
1272
1451
|
return;
|
|
1273
1452
|
}
|
|
1274
1453
|
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1456
|
+
*
|
|
1457
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1458
|
+
* @param {string} time - The time string of the message
|
|
1459
|
+
* @param {string} name - The logger name of the message
|
|
1460
|
+
* @param {string} message - The content of the message.
|
|
1461
|
+
*/
|
|
1275
1462
|
wssSendMessage(level, time, name, message) {
|
|
1276
1463
|
if (!level || !time || !name || !message)
|
|
1277
1464
|
return;
|
|
1465
|
+
// Remove ANSI escape codes from the message
|
|
1466
|
+
// eslint-disable-next-line no-control-regex
|
|
1278
1467
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1468
|
+
// Remove leading asterisks from the message
|
|
1279
1469
|
message = message.replace(/^\*+/, '');
|
|
1470
|
+
// Replace all occurrences of \t and \n
|
|
1280
1471
|
message = message.replace(/[\t\n]/g, '');
|
|
1472
|
+
// Remove non-printable characters
|
|
1473
|
+
// eslint-disable-next-line no-control-regex
|
|
1281
1474
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1475
|
+
// Replace all occurrences of \" with "
|
|
1282
1476
|
message = message.replace(/\\"/g, '"');
|
|
1477
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1283
1478
|
const maxContinuousLength = 100;
|
|
1284
1479
|
const keepStartLength = 20;
|
|
1285
1480
|
const keepEndLength = 20;
|
|
1481
|
+
// Split the message into words
|
|
1286
1482
|
message = message
|
|
1287
1483
|
.split(' ')
|
|
1288
1484
|
.map((word) => {
|
|
1485
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1289
1486
|
if (word.length > maxContinuousLength) {
|
|
1290
1487
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1291
1488
|
}
|
|
1292
1489
|
return word;
|
|
1293
1490
|
})
|
|
1294
1491
|
.join(' ');
|
|
1492
|
+
// Send the message to all connected clients
|
|
1295
1493
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1296
1494
|
if (client.readyState === WebSocket.OPEN) {
|
|
1297
1495
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1298
1496
|
}
|
|
1299
1497
|
});
|
|
1300
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1501
|
+
*
|
|
1502
|
+
*/
|
|
1301
1503
|
wssSendRefreshRequired() {
|
|
1302
1504
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1303
1505
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1506
|
+
// Send the message to all connected clients
|
|
1304
1507
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1305
1508
|
if (client.readyState === WebSocket.OPEN) {
|
|
1306
1509
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1307
1510
|
}
|
|
1308
1511
|
});
|
|
1309
1512
|
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1515
|
+
*
|
|
1516
|
+
*/
|
|
1310
1517
|
wssSendRestartRequired() {
|
|
1311
1518
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1312
1519
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1520
|
+
// Send the message to all connected clients
|
|
1313
1521
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1314
1522
|
if (client.readyState === WebSocket.OPEN) {
|
|
1315
1523
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
@@ -1317,3 +1525,4 @@ export class Frontend {
|
|
|
1317
1525
|
});
|
|
1318
1526
|
}
|
|
1319
1527
|
}
|
|
1528
|
+
//# sourceMappingURL=frontend.js.map
|