matterbridge 2.1.3-dev.1 → 2.1.3
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 +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 +231 -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 +748 -40
- 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 +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2262 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +96 -0
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +152 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +111 -3
- 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 +221 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +251 -7
- 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,24 +885,29 @@ 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
|
+
// Close the http server
|
|
748
889
|
if (this.httpServer) {
|
|
749
890
|
this.httpServer.close();
|
|
750
891
|
this.httpServer.removeAllListeners();
|
|
751
892
|
this.httpServer = undefined;
|
|
752
893
|
this.log.debug('Frontend http server closed successfully');
|
|
753
894
|
}
|
|
895
|
+
// Close the https server
|
|
754
896
|
if (this.httpsServer) {
|
|
755
897
|
this.httpsServer.close();
|
|
756
898
|
this.httpsServer.removeAllListeners();
|
|
757
899
|
this.httpsServer = undefined;
|
|
758
900
|
this.log.debug('Frontend https server closed successfully');
|
|
759
901
|
}
|
|
902
|
+
// Remove listeners from the express app
|
|
760
903
|
if (this.expressApp) {
|
|
761
904
|
this.expressApp.removeAllListeners();
|
|
762
905
|
this.expressApp = undefined;
|
|
763
906
|
this.log.debug('Frontend app closed successfully');
|
|
764
907
|
}
|
|
908
|
+
// Close the WebSocket server
|
|
765
909
|
if (this.webSocketServer) {
|
|
910
|
+
// Close all active connections
|
|
766
911
|
this.webSocketServer.clients.forEach((client) => {
|
|
767
912
|
if (client.readyState === WebSocket.OPEN) {
|
|
768
913
|
client.close();
|
|
@@ -778,10 +923,12 @@ export class Frontend {
|
|
|
778
923
|
});
|
|
779
924
|
this.webSocketServer = undefined;
|
|
780
925
|
}
|
|
926
|
+
// Stop the memory dump interval
|
|
781
927
|
if (hasParameter('memorydump')) {
|
|
782
928
|
this.stopCpuMemoryDump();
|
|
783
929
|
}
|
|
784
930
|
}
|
|
931
|
+
// Function to format bytes to KB or MB
|
|
785
932
|
formatMemoryUsage = (bytes) => {
|
|
786
933
|
const kb = bytes / 1024;
|
|
787
934
|
const mb = kb / 1024;
|
|
@@ -793,9 +940,10 @@ export class Frontend {
|
|
|
793
940
|
const interval = () => {
|
|
794
941
|
const currCpus = os.cpus();
|
|
795
942
|
if (currCpus.length !== this.prevCpus.length) {
|
|
796
|
-
this.prevCpus = currCpus;
|
|
943
|
+
this.prevCpus = currCpus; // Reset the previous cpus if the number of cpu has changed
|
|
797
944
|
}
|
|
798
945
|
let totalIdle = 0, totalTick = 0;
|
|
946
|
+
// Get the cpu usage
|
|
799
947
|
this.prevCpus.forEach((prevCpu, i) => {
|
|
800
948
|
const currCpu = currCpus[i];
|
|
801
949
|
const idleDiff = currCpu.times.idle - prevCpu.times.idle;
|
|
@@ -805,6 +953,7 @@ export class Frontend {
|
|
|
805
953
|
});
|
|
806
954
|
const cpuUsage = (100 - (totalIdle / totalTick) * 100).toFixed(2);
|
|
807
955
|
this.prevCpus = currCpus;
|
|
956
|
+
// Get the memory usage
|
|
808
957
|
const memoryUsageRaw = process.memoryUsage();
|
|
809
958
|
this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
|
|
810
959
|
const memoryUsage = {
|
|
@@ -837,14 +986,21 @@ export class Frontend {
|
|
|
837
986
|
external: this.formatMemoryUsage(memory.external),
|
|
838
987
|
arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
|
|
839
988
|
};
|
|
989
|
+
// eslint-disable-next-line no-console
|
|
840
990
|
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}`);
|
|
841
991
|
}
|
|
842
992
|
this.memoryData = [];
|
|
843
993
|
this.prevCpus = [];
|
|
844
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* Retrieves the api settings data.
|
|
997
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
998
|
+
*/
|
|
845
999
|
async getApiSettings() {
|
|
1000
|
+
// Update the system information
|
|
846
1001
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
847
1002
|
this.matterbridge.systemInformation.heap = this.formatMemoryUsage(process.memoryUsage().heapUsed) + ' / ' + this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
1003
|
+
// Update the matterbridge information
|
|
848
1004
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
849
1005
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
850
1006
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -863,6 +1019,11 @@ export class Frontend {
|
|
|
863
1019
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
864
1020
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
865
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Retrieves the cluster text description from a given device.
|
|
1024
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1025
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1026
|
+
*/
|
|
866
1027
|
getClusterTextFromDevice(device) {
|
|
867
1028
|
const getAttribute = (device, cluster, attribute) => {
|
|
868
1029
|
let value = undefined;
|
|
@@ -901,6 +1062,7 @@ export class Frontend {
|
|
|
901
1062
|
};
|
|
902
1063
|
let attributes = '';
|
|
903
1064
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1065
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
904
1066
|
if (typeof attributeValue === 'undefined')
|
|
905
1067
|
return;
|
|
906
1068
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -978,8 +1140,13 @@ export class Frontend {
|
|
|
978
1140
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
979
1141
|
attributes += `${getUserLabel(device)} `;
|
|
980
1142
|
});
|
|
1143
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
981
1144
|
return attributes.trimStart().trimEnd();
|
|
982
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1148
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1149
|
+
*/
|
|
983
1150
|
getBaseRegisteredPlugins() {
|
|
984
1151
|
const baseRegisteredPlugins = [];
|
|
985
1152
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -1010,6 +1177,14 @@ export class Frontend {
|
|
|
1010
1177
|
}
|
|
1011
1178
|
return baseRegisteredPlugins;
|
|
1012
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Handles incoming websocket messages for the Matterbridge.
|
|
1182
|
+
*
|
|
1183
|
+
* @param {Matterbridge} this - The Matterbridge instance.
|
|
1184
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1185
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1186
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1187
|
+
*/
|
|
1013
1188
|
async wsMessageHandler(client, message) {
|
|
1014
1189
|
let data;
|
|
1015
1190
|
try {
|
|
@@ -1097,8 +1272,10 @@ export class Frontend {
|
|
|
1097
1272
|
else if (data.method === '/api/devices') {
|
|
1098
1273
|
const devices = [];
|
|
1099
1274
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1275
|
+
// Filter by pluginName if provided
|
|
1100
1276
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1101
1277
|
return;
|
|
1278
|
+
// Check if the device has the required properties
|
|
1102
1279
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
1103
1280
|
return;
|
|
1104
1281
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1181,6 +1358,7 @@ export class Frontend {
|
|
|
1181
1358
|
});
|
|
1182
1359
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1183
1360
|
deviceTypes = [];
|
|
1361
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1184
1362
|
const name = childEndpoint.endpoint?.id;
|
|
1185
1363
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1186
1364
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1263,44 +1441,73 @@ export class Frontend {
|
|
|
1263
1441
|
return;
|
|
1264
1442
|
}
|
|
1265
1443
|
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1446
|
+
*
|
|
1447
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1448
|
+
* @param {string} time - The time string of the message
|
|
1449
|
+
* @param {string} name - The logger name of the message
|
|
1450
|
+
* @param {string} message - The content of the message.
|
|
1451
|
+
*/
|
|
1266
1452
|
wssSendMessage(level, time, name, message) {
|
|
1267
1453
|
if (!level || !time || !name || !message)
|
|
1268
1454
|
return;
|
|
1455
|
+
// Remove ANSI escape codes from the message
|
|
1456
|
+
// eslint-disable-next-line no-control-regex
|
|
1269
1457
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1458
|
+
// Remove leading asterisks from the message
|
|
1270
1459
|
message = message.replace(/^\*+/, '');
|
|
1460
|
+
// Replace all occurrences of \t and \n
|
|
1271
1461
|
message = message.replace(/[\t\n]/g, '');
|
|
1462
|
+
// Remove non-printable characters
|
|
1463
|
+
// eslint-disable-next-line no-control-regex
|
|
1272
1464
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1465
|
+
// Replace all occurrences of \" with "
|
|
1273
1466
|
message = message.replace(/\\"/g, '"');
|
|
1467
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1274
1468
|
const maxContinuousLength = 100;
|
|
1275
1469
|
const keepStartLength = 20;
|
|
1276
1470
|
const keepEndLength = 20;
|
|
1471
|
+
// Split the message into words
|
|
1277
1472
|
message = message
|
|
1278
1473
|
.split(' ')
|
|
1279
1474
|
.map((word) => {
|
|
1475
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1280
1476
|
if (word.length > maxContinuousLength) {
|
|
1281
1477
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1282
1478
|
}
|
|
1283
1479
|
return word;
|
|
1284
1480
|
})
|
|
1285
1481
|
.join(' ');
|
|
1482
|
+
// Send the message to all connected clients
|
|
1286
1483
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1287
1484
|
if (client.readyState === WebSocket.OPEN) {
|
|
1288
1485
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1289
1486
|
}
|
|
1290
1487
|
});
|
|
1291
1488
|
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1491
|
+
*
|
|
1492
|
+
*/
|
|
1292
1493
|
wssSendRefreshRequired() {
|
|
1293
1494
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1294
1495
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1496
|
+
// Send the message to all connected clients
|
|
1295
1497
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1296
1498
|
if (client.readyState === WebSocket.OPEN) {
|
|
1297
1499
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1298
1500
|
}
|
|
1299
1501
|
});
|
|
1300
1502
|
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1505
|
+
*
|
|
1506
|
+
*/
|
|
1301
1507
|
wssSendRestartRequired() {
|
|
1302
1508
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1303
1509
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1510
|
+
// Send the message to all connected clients
|
|
1304
1511
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1305
1512
|
if (client.readyState === WebSocket.OPEN) {
|
|
1306
1513
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
@@ -1308,3 +1515,4 @@ export class Frontend {
|
|
|
1308
1515
|
});
|
|
1309
1516
|
}
|
|
1310
1517
|
}
|
|
1518
|
+
//# sourceMappingURL=frontend.js.map
|