matterbridge 2.1.5-dev.8 → 2.1.5
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 -2
- package/README-DOCKER.md +6 -0
- 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 +143 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +268 -28
- 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 -41
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +117 -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 +121 -5
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +169 -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 +264 -10
- 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.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// @matter
|
|
1
24
|
import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat } 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,16 +30,47 @@ 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, deepCopy, 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
|
+
* Websocket message ID indicating a cpu update.
|
|
56
|
+
* @constant {number}
|
|
57
|
+
*/
|
|
16
58
|
export const WS_ID_CPU_UPDATE = 3;
|
|
59
|
+
/**
|
|
60
|
+
* Websocket message ID indicating a memory update.
|
|
61
|
+
* @constant {number}
|
|
62
|
+
*/
|
|
17
63
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
64
|
+
/**
|
|
65
|
+
* Websocket message ID indicating a memory update.
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
18
68
|
export const WS_ID_SNACKBAR = 5;
|
|
69
|
+
/**
|
|
70
|
+
* Initializes the frontend of Matterbridge.
|
|
71
|
+
*
|
|
72
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
73
|
+
*/
|
|
19
74
|
export class Frontend {
|
|
20
75
|
matterbridge;
|
|
21
76
|
log;
|
|
@@ -32,7 +87,7 @@ export class Frontend {
|
|
|
32
87
|
memoryTimeout;
|
|
33
88
|
constructor(matterbridge) {
|
|
34
89
|
this.matterbridge = matterbridge;
|
|
35
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
90
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
36
91
|
}
|
|
37
92
|
set logLevel(logLevel) {
|
|
38
93
|
this.log.logLevel = logLevel;
|
|
@@ -40,10 +95,21 @@ export class Frontend {
|
|
|
40
95
|
async start(port = 8283) {
|
|
41
96
|
this.port = port;
|
|
42
97
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
98
|
+
// Create the express app that serves the frontend
|
|
43
99
|
this.expressApp = express();
|
|
100
|
+
// Log all requests to the server for debugging
|
|
101
|
+
/*
|
|
102
|
+
this.expressApp.use((req, res, next) => {
|
|
103
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
104
|
+
next();
|
|
105
|
+
});
|
|
106
|
+
*/
|
|
107
|
+
// Serve static files from '/static' endpoint
|
|
44
108
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
45
109
|
if (!hasParameter('ssl')) {
|
|
110
|
+
// Create an HTTP server and attach the express app
|
|
46
111
|
this.httpServer = createServer(this.expressApp);
|
|
112
|
+
// Listen on the specified port
|
|
47
113
|
if (hasParameter('ingress')) {
|
|
48
114
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
49
115
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -57,6 +123,7 @@ export class Frontend {
|
|
|
57
123
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
58
124
|
});
|
|
59
125
|
}
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
127
|
this.httpServer.on('error', (error) => {
|
|
61
128
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
62
129
|
switch (error.code) {
|
|
@@ -72,6 +139,7 @@ export class Frontend {
|
|
|
72
139
|
});
|
|
73
140
|
}
|
|
74
141
|
else {
|
|
142
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
75
143
|
let cert;
|
|
76
144
|
try {
|
|
77
145
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -99,7 +167,9 @@ export class Frontend {
|
|
|
99
167
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
100
168
|
}
|
|
101
169
|
const serverOptions = { cert, key, ca };
|
|
170
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
102
171
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
172
|
+
// Listen on the specified port
|
|
103
173
|
if (hasParameter('ingress')) {
|
|
104
174
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
105
175
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -113,6 +183,7 @@ export class Frontend {
|
|
|
113
183
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
114
184
|
});
|
|
115
185
|
}
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
187
|
this.httpsServer.on('error', (error) => {
|
|
117
188
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
118
189
|
switch (error.code) {
|
|
@@ -129,16 +200,18 @@ export class Frontend {
|
|
|
129
200
|
}
|
|
130
201
|
if (this.initializeError)
|
|
131
202
|
return;
|
|
203
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
132
204
|
const wssPort = this.port;
|
|
133
205
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
134
206
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
135
207
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
136
208
|
const clientIp = request.socket.remoteAddress;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
209
|
+
// Set the global logger callback for the WebSocketServer
|
|
210
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
211
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
212
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
213
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
214
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
142
215
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
143
216
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
144
217
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -172,9 +245,11 @@ export class Frontend {
|
|
|
172
245
|
this.webSocketServer.on('error', (ws, error) => {
|
|
173
246
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
174
247
|
});
|
|
248
|
+
// Start the memory dump interval
|
|
175
249
|
if (hasParameter('memorydump')) {
|
|
176
250
|
this.startCpuMemoryDump();
|
|
177
251
|
}
|
|
252
|
+
// Endpoint to validate login code
|
|
178
253
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
179
254
|
const { password } = req.body;
|
|
180
255
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -193,23 +268,27 @@ export class Frontend {
|
|
|
193
268
|
this.log.warn('/api/login error wrong password');
|
|
194
269
|
res.json({ valid: false });
|
|
195
270
|
}
|
|
271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
196
272
|
}
|
|
197
273
|
catch (error) {
|
|
198
274
|
this.log.error('/api/login error getting password');
|
|
199
275
|
res.json({ valid: false });
|
|
200
276
|
}
|
|
201
277
|
});
|
|
278
|
+
// Endpoint to provide health check
|
|
202
279
|
this.expressApp.get('/health', (req, res) => {
|
|
203
280
|
this.log.debug('Express received /health');
|
|
204
281
|
const healthStatus = {
|
|
205
|
-
status: 'ok',
|
|
206
|
-
uptime: process.uptime(),
|
|
207
|
-
timestamp: new Date().toISOString(),
|
|
282
|
+
status: 'ok', // Indicate service is healthy
|
|
283
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
284
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
208
285
|
};
|
|
209
286
|
res.status(200).json(healthStatus);
|
|
210
287
|
});
|
|
288
|
+
// Endpoint to provide memory usage details
|
|
211
289
|
this.expressApp.get('/memory', async (req, res) => {
|
|
212
290
|
this.log.debug('Express received /memory');
|
|
291
|
+
// Memory usage from process
|
|
213
292
|
const memoryUsageRaw = process.memoryUsage();
|
|
214
293
|
const memoryUsage = {
|
|
215
294
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -218,10 +297,13 @@ export class Frontend {
|
|
|
218
297
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
219
298
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
220
299
|
};
|
|
300
|
+
// V8 heap statistics
|
|
221
301
|
const { default: v8 } = await import('node:v8');
|
|
222
302
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
223
303
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
304
|
+
// Format heapStats
|
|
224
305
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
306
|
+
// Format heapSpaces
|
|
225
307
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
226
308
|
...space,
|
|
227
309
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -231,6 +313,23 @@ export class Frontend {
|
|
|
231
313
|
}));
|
|
232
314
|
const { default: module } = await import('module');
|
|
233
315
|
const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
|
|
316
|
+
/*
|
|
317
|
+
if (req.query.heapdump === 'true') {
|
|
318
|
+
const { default: heapdump } = await import('heapdump');
|
|
319
|
+
const filename = `heapdump-${Date.now()}.heapsnapshot`;
|
|
320
|
+
|
|
321
|
+
heapdump.writeSnapshot(filename, (err, dumpFilename) => {
|
|
322
|
+
if (err) {
|
|
323
|
+
this.log.error(`Heap dump error: ${err.message}`);
|
|
324
|
+
return res.status(500).json({ error: 'Heap dump failed', details: err.message });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.log.info(`Heap dump written to ${dumpFilename}`);
|
|
328
|
+
return res.status(200).json({ ...memoryReport, heapdump: dumpFilename });
|
|
329
|
+
});
|
|
330
|
+
return; // Exit early since heapdump response is handled inside callback
|
|
331
|
+
}
|
|
332
|
+
*/
|
|
234
333
|
const memoryReport = {
|
|
235
334
|
memoryUsage,
|
|
236
335
|
heapStats,
|
|
@@ -239,6 +338,7 @@ export class Frontend {
|
|
|
239
338
|
};
|
|
240
339
|
res.status(200).json(memoryReport);
|
|
241
340
|
});
|
|
341
|
+
// Endpoint to start advertising the server node
|
|
242
342
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
243
343
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
244
344
|
if (pairingCodes) {
|
|
@@ -249,19 +349,24 @@ export class Frontend {
|
|
|
249
349
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
250
350
|
}
|
|
251
351
|
});
|
|
352
|
+
// Endpoint to provide settings
|
|
252
353
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
253
354
|
this.log.debug('The frontend sent /api/settings');
|
|
254
355
|
res.json(await this.getApiSettings());
|
|
255
356
|
});
|
|
357
|
+
// Endpoint to provide plugins
|
|
256
358
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
257
359
|
this.log.debug('The frontend sent /api/plugins');
|
|
258
360
|
const response = this.getBaseRegisteredPlugins();
|
|
361
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
259
362
|
res.json(response);
|
|
260
363
|
});
|
|
364
|
+
// Endpoint to provide devices
|
|
261
365
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
262
366
|
this.log.debug('The frontend sent /api/devices');
|
|
263
367
|
const devices = [];
|
|
264
368
|
this.matterbridge.devices.forEach(async (device) => {
|
|
369
|
+
// Check if the device has the required properties
|
|
265
370
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
266
371
|
return;
|
|
267
372
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -277,8 +382,10 @@ export class Frontend {
|
|
|
277
382
|
cluster: cluster,
|
|
278
383
|
});
|
|
279
384
|
});
|
|
385
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
280
386
|
res.json(devices);
|
|
281
387
|
});
|
|
388
|
+
// Endpoint to provide the cluster servers of the devices
|
|
282
389
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
283
390
|
const selectedPluginName = req.params.selectedPluginName;
|
|
284
391
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -351,6 +458,7 @@ export class Frontend {
|
|
|
351
458
|
});
|
|
352
459
|
res.json(data);
|
|
353
460
|
});
|
|
461
|
+
// Endpoint to view the log
|
|
354
462
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
355
463
|
this.log.debug('The frontend sent /api/log');
|
|
356
464
|
try {
|
|
@@ -363,10 +471,12 @@ export class Frontend {
|
|
|
363
471
|
res.status(500).send('Error reading log file');
|
|
364
472
|
}
|
|
365
473
|
});
|
|
474
|
+
// Endpoint to download the matterbridge log
|
|
366
475
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
367
476
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
368
477
|
try {
|
|
369
478
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
479
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
370
480
|
}
|
|
371
481
|
catch (error) {
|
|
372
482
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -378,10 +488,12 @@ export class Frontend {
|
|
|
378
488
|
}
|
|
379
489
|
});
|
|
380
490
|
});
|
|
491
|
+
// Endpoint to download the matter log
|
|
381
492
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
382
493
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
383
494
|
try {
|
|
384
495
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
496
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
385
497
|
}
|
|
386
498
|
catch (error) {
|
|
387
499
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -393,6 +505,7 @@ export class Frontend {
|
|
|
393
505
|
}
|
|
394
506
|
});
|
|
395
507
|
});
|
|
508
|
+
// Endpoint to download the matter storage file
|
|
396
509
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
397
510
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
398
511
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -403,6 +516,7 @@ export class Frontend {
|
|
|
403
516
|
}
|
|
404
517
|
});
|
|
405
518
|
});
|
|
519
|
+
// Endpoint to download the matterbridge storage directory
|
|
406
520
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
407
521
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
408
522
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -413,6 +527,7 @@ export class Frontend {
|
|
|
413
527
|
}
|
|
414
528
|
});
|
|
415
529
|
});
|
|
530
|
+
// Endpoint to download the matterbridge plugin directory
|
|
416
531
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
417
532
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
418
533
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -423,9 +538,11 @@ export class Frontend {
|
|
|
423
538
|
}
|
|
424
539
|
});
|
|
425
540
|
});
|
|
541
|
+
// Endpoint to download the matterbridge plugin config files
|
|
426
542
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
427
543
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
428
544
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
545
|
+
// 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')));
|
|
429
546
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
430
547
|
if (error) {
|
|
431
548
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -433,6 +550,7 @@ export class Frontend {
|
|
|
433
550
|
}
|
|
434
551
|
});
|
|
435
552
|
});
|
|
553
|
+
// Endpoint to download the matterbridge plugin config files
|
|
436
554
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
437
555
|
this.log.debug('The frontend sent /api/download-backup');
|
|
438
556
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -442,6 +560,7 @@ export class Frontend {
|
|
|
442
560
|
}
|
|
443
561
|
});
|
|
444
562
|
});
|
|
563
|
+
// Endpoint to receive commands
|
|
445
564
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
446
565
|
const command = req.params.command;
|
|
447
566
|
let param = req.params.param;
|
|
@@ -451,13 +570,15 @@ export class Frontend {
|
|
|
451
570
|
return;
|
|
452
571
|
}
|
|
453
572
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
573
|
+
// Handle the command setpassword from Settings
|
|
454
574
|
if (command === 'setpassword') {
|
|
455
|
-
const password = param.slice(1, -1);
|
|
575
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
456
576
|
this.log.debug('setpassword', param, password);
|
|
457
577
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
458
578
|
res.json({ message: 'Command received' });
|
|
459
579
|
return;
|
|
460
580
|
}
|
|
581
|
+
// Handle the command setbridgemode from Settings
|
|
461
582
|
if (command === 'setbridgemode') {
|
|
462
583
|
this.log.debug(`setbridgemode: ${param}`);
|
|
463
584
|
this.wssSendRestartRequired();
|
|
@@ -465,6 +586,7 @@ export class Frontend {
|
|
|
465
586
|
res.json({ message: 'Command received' });
|
|
466
587
|
return;
|
|
467
588
|
}
|
|
589
|
+
// Handle the command backup from Settings
|
|
468
590
|
if (command === 'backup') {
|
|
469
591
|
this.log.notice(`Prepairing the backup...`);
|
|
470
592
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -473,25 +595,26 @@ export class Frontend {
|
|
|
473
595
|
res.json({ message: 'Command received' });
|
|
474
596
|
return;
|
|
475
597
|
}
|
|
598
|
+
// Handle the command setmbloglevel from Settings
|
|
476
599
|
if (command === 'setmbloglevel') {
|
|
477
600
|
this.log.debug('Matterbridge log level:', param);
|
|
478
601
|
if (param === 'Debug') {
|
|
479
|
-
this.log.logLevel = "debug"
|
|
602
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
480
603
|
}
|
|
481
604
|
else if (param === 'Info') {
|
|
482
|
-
this.log.logLevel = "info"
|
|
605
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
483
606
|
}
|
|
484
607
|
else if (param === 'Notice') {
|
|
485
|
-
this.log.logLevel = "notice"
|
|
608
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
486
609
|
}
|
|
487
610
|
else if (param === 'Warn') {
|
|
488
|
-
this.log.logLevel = "warn"
|
|
611
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
489
612
|
}
|
|
490
613
|
else if (param === 'Error') {
|
|
491
|
-
this.log.logLevel = "error"
|
|
614
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
492
615
|
}
|
|
493
616
|
else if (param === 'Fatal') {
|
|
494
|
-
this.log.logLevel = "fatal"
|
|
617
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
495
618
|
}
|
|
496
619
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
497
620
|
this.matterbridge.log.logLevel = this.log.logLevel;
|
|
@@ -501,12 +624,13 @@ export class Frontend {
|
|
|
501
624
|
for (const plugin of this.matterbridge.plugins) {
|
|
502
625
|
if (!plugin.platform || !plugin.platform.config)
|
|
503
626
|
continue;
|
|
504
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
505
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
627
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
628
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
506
629
|
}
|
|
507
630
|
res.json({ message: 'Command received' });
|
|
508
631
|
return;
|
|
509
632
|
}
|
|
633
|
+
// Handle the command setmbloglevel from Settings
|
|
510
634
|
if (command === 'setmjloglevel') {
|
|
511
635
|
this.log.debug('Matter.js log level:', param);
|
|
512
636
|
if (param === 'Debug') {
|
|
@@ -531,30 +655,34 @@ export class Frontend {
|
|
|
531
655
|
res.json({ message: 'Command received' });
|
|
532
656
|
return;
|
|
533
657
|
}
|
|
658
|
+
// Handle the command setmdnsinterface from Settings
|
|
534
659
|
if (command === 'setmdnsinterface') {
|
|
535
|
-
param = param.slice(1, -1);
|
|
660
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
536
661
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
537
662
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
538
663
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
539
664
|
res.json({ message: 'Command received' });
|
|
540
665
|
return;
|
|
541
666
|
}
|
|
667
|
+
// Handle the command setipv4address from Settings
|
|
542
668
|
if (command === 'setipv4address') {
|
|
543
|
-
param = param.slice(1, -1);
|
|
669
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
544
670
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
545
671
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
546
672
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
547
673
|
res.json({ message: 'Command received' });
|
|
548
674
|
return;
|
|
549
675
|
}
|
|
676
|
+
// Handle the command setipv6address from Settings
|
|
550
677
|
if (command === 'setipv6address') {
|
|
551
|
-
param = param.slice(1, -1);
|
|
678
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
552
679
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
553
680
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
554
681
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
555
682
|
res.json({ message: 'Command received' });
|
|
556
683
|
return;
|
|
557
684
|
}
|
|
685
|
+
// Handle the command setmatterport from Settings
|
|
558
686
|
if (command === 'setmatterport') {
|
|
559
687
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
560
688
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -563,6 +691,7 @@ export class Frontend {
|
|
|
563
691
|
res.json({ message: 'Command received' });
|
|
564
692
|
return;
|
|
565
693
|
}
|
|
694
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
566
695
|
if (command === 'setmatterdiscriminator') {
|
|
567
696
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
568
697
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -571,6 +700,7 @@ export class Frontend {
|
|
|
571
700
|
res.json({ message: 'Command received' });
|
|
572
701
|
return;
|
|
573
702
|
}
|
|
703
|
+
// Handle the command setmatterpasscode from Settings
|
|
574
704
|
if (command === 'setmatterpasscode') {
|
|
575
705
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
576
706
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -579,17 +709,20 @@ export class Frontend {
|
|
|
579
709
|
res.json({ message: 'Command received' });
|
|
580
710
|
return;
|
|
581
711
|
}
|
|
712
|
+
// Handle the command setmbloglevel from Settings
|
|
582
713
|
if (command === 'setmblogfile') {
|
|
583
714
|
this.log.debug('Matterbridge file log:', param);
|
|
584
715
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
585
716
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
717
|
+
// Create the file logger for matterbridge
|
|
586
718
|
if (param === 'true')
|
|
587
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
719
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
588
720
|
else
|
|
589
721
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
590
722
|
res.json({ message: 'Command received' });
|
|
591
723
|
return;
|
|
592
724
|
}
|
|
725
|
+
// Handle the command setmbloglevel from Settings
|
|
593
726
|
if (command === 'setmjlogfile') {
|
|
594
727
|
this.log.debug('Matter file log:', param);
|
|
595
728
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -616,40 +749,48 @@ export class Frontend {
|
|
|
616
749
|
res.json({ message: 'Command received' });
|
|
617
750
|
return;
|
|
618
751
|
}
|
|
752
|
+
// Handle the command unregister from Settings
|
|
619
753
|
if (command === 'unregister') {
|
|
620
754
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
621
755
|
res.json({ message: 'Command received' });
|
|
622
756
|
return;
|
|
623
757
|
}
|
|
758
|
+
// Handle the command reset from Settings
|
|
624
759
|
if (command === 'reset') {
|
|
625
760
|
await this.matterbridge.shutdownProcessAndReset();
|
|
626
761
|
res.json({ message: 'Command received' });
|
|
627
762
|
return;
|
|
628
763
|
}
|
|
764
|
+
// Handle the command factoryreset from Settings
|
|
629
765
|
if (command === 'factoryreset') {
|
|
630
766
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
631
767
|
res.json({ message: 'Command received' });
|
|
632
768
|
return;
|
|
633
769
|
}
|
|
770
|
+
// Handle the command shutdown from Header
|
|
634
771
|
if (command === 'shutdown') {
|
|
635
772
|
await this.matterbridge.shutdownProcess();
|
|
636
773
|
res.json({ message: 'Command received' });
|
|
637
774
|
return;
|
|
638
775
|
}
|
|
776
|
+
// Handle the command restart from Header
|
|
639
777
|
if (command === 'restart') {
|
|
640
778
|
await this.matterbridge.restartProcess();
|
|
641
779
|
res.json({ message: 'Command received' });
|
|
642
780
|
return;
|
|
643
781
|
}
|
|
782
|
+
// Handle the command update from Header
|
|
644
783
|
if (command === 'update') {
|
|
645
784
|
await this.matterbridge.updateProcess();
|
|
646
785
|
this.wssSendRestartRequired();
|
|
647
786
|
res.json({ message: 'Command received' });
|
|
648
787
|
return;
|
|
649
788
|
}
|
|
789
|
+
// Handle the command saveconfig from Home
|
|
650
790
|
if (command === 'saveconfig') {
|
|
651
791
|
param = param.replace(/\*/g, '\\');
|
|
652
792
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
793
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
653
794
|
if (!this.matterbridge.plugins.has(param)) {
|
|
654
795
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
655
796
|
}
|
|
@@ -663,49 +804,58 @@ export class Frontend {
|
|
|
663
804
|
res.json({ message: 'Command received' });
|
|
664
805
|
return;
|
|
665
806
|
}
|
|
807
|
+
// Handle the command installplugin from Home
|
|
666
808
|
if (command === 'installplugin') {
|
|
667
809
|
param = param.replace(/\*/g, '\\');
|
|
668
810
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
669
811
|
try {
|
|
670
812
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
671
813
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
814
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
672
815
|
}
|
|
673
816
|
catch (error) {
|
|
674
817
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
675
818
|
}
|
|
676
819
|
this.wssSendRestartRequired();
|
|
677
820
|
param = param.split('@')[0];
|
|
821
|
+
// Also add the plugin to matterbridge so no return!
|
|
678
822
|
if (param === 'matterbridge') {
|
|
823
|
+
// 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
|
|
679
824
|
res.json({ message: 'Command received' });
|
|
680
825
|
return;
|
|
681
826
|
}
|
|
682
827
|
}
|
|
828
|
+
// Handle the command addplugin from Home
|
|
683
829
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
684
830
|
param = param.replace(/\*/g, '\\');
|
|
685
831
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
686
832
|
if (plugin) {
|
|
687
833
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
834
|
+
// 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
|
|
835
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
688
836
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
689
837
|
}
|
|
690
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
838
|
+
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
691
839
|
}
|
|
692
840
|
res.json({ message: 'Command received' });
|
|
693
841
|
this.wssSendRefreshRequired();
|
|
694
842
|
return;
|
|
695
843
|
}
|
|
844
|
+
// Handle the command removeplugin from Home
|
|
696
845
|
if (command === 'removeplugin') {
|
|
697
846
|
if (!this.matterbridge.plugins.has(param)) {
|
|
698
847
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
699
848
|
}
|
|
700
849
|
else {
|
|
701
850
|
const plugin = this.matterbridge.plugins.get(param);
|
|
702
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
851
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
703
852
|
await this.matterbridge.plugins.remove(param);
|
|
704
853
|
}
|
|
705
854
|
res.json({ message: 'Command received' });
|
|
706
855
|
this.wssSendRefreshRequired();
|
|
707
856
|
return;
|
|
708
857
|
}
|
|
858
|
+
// Handle the command enableplugin from Home
|
|
709
859
|
if (command === 'enableplugin') {
|
|
710
860
|
if (!this.matterbridge.plugins.has(param)) {
|
|
711
861
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -723,15 +873,17 @@ export class Frontend {
|
|
|
723
873
|
plugin.addedDevices = undefined;
|
|
724
874
|
await this.matterbridge.plugins.enable(param);
|
|
725
875
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
876
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
726
877
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
727
878
|
}
|
|
728
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
879
|
+
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
|
|
729
880
|
}
|
|
730
881
|
}
|
|
731
882
|
res.json({ message: 'Command received' });
|
|
732
883
|
this.wssSendRefreshRequired();
|
|
733
884
|
return;
|
|
734
885
|
}
|
|
886
|
+
// Handle the command disableplugin from Home
|
|
735
887
|
if (command === 'disableplugin') {
|
|
736
888
|
if (!this.matterbridge.plugins.has(param)) {
|
|
737
889
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -739,7 +891,7 @@ export class Frontend {
|
|
|
739
891
|
else {
|
|
740
892
|
const plugin = this.matterbridge.plugins.get(param);
|
|
741
893
|
if (plugin && plugin.enabled) {
|
|
742
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
894
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
743
895
|
await this.matterbridge.plugins.disable(param);
|
|
744
896
|
}
|
|
745
897
|
}
|
|
@@ -748,6 +900,7 @@ export class Frontend {
|
|
|
748
900
|
return;
|
|
749
901
|
}
|
|
750
902
|
});
|
|
903
|
+
// Fallback for routing (must be the last route)
|
|
751
904
|
this.expressApp.get('*', (req, res) => {
|
|
752
905
|
this.log.debug('The frontend sent:', req.url);
|
|
753
906
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -756,6 +909,7 @@ export class Frontend {
|
|
|
756
909
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
757
910
|
}
|
|
758
911
|
async stop() {
|
|
912
|
+
// Start the memory check. This will not allow the process to exit but will log the memory usage for 5 minutes.
|
|
759
913
|
if (hasParameter('memorycheck')) {
|
|
760
914
|
this.wssSendSnackbarMessage('Memory check started', getIntParameter('memorycheck') ?? 5 * 60 * 1000);
|
|
761
915
|
await new Promise((resolve) => {
|
|
@@ -767,24 +921,29 @@ export class Frontend {
|
|
|
767
921
|
}, getIntParameter('memorycheck') ?? 5 * 60 * 1000);
|
|
768
922
|
});
|
|
769
923
|
}
|
|
924
|
+
// Close the http server
|
|
770
925
|
if (this.httpServer) {
|
|
771
926
|
this.httpServer.close();
|
|
772
927
|
this.httpServer.removeAllListeners();
|
|
773
928
|
this.httpServer = undefined;
|
|
774
929
|
this.log.debug('Frontend http server closed successfully');
|
|
775
930
|
}
|
|
931
|
+
// Close the https server
|
|
776
932
|
if (this.httpsServer) {
|
|
777
933
|
this.httpsServer.close();
|
|
778
934
|
this.httpsServer.removeAllListeners();
|
|
779
935
|
this.httpsServer = undefined;
|
|
780
936
|
this.log.debug('Frontend https server closed successfully');
|
|
781
937
|
}
|
|
938
|
+
// Remove listeners from the express app
|
|
782
939
|
if (this.expressApp) {
|
|
783
940
|
this.expressApp.removeAllListeners();
|
|
784
941
|
this.expressApp = undefined;
|
|
785
942
|
this.log.debug('Frontend app closed successfully');
|
|
786
943
|
}
|
|
944
|
+
// Close the WebSocket server
|
|
787
945
|
if (this.webSocketServer) {
|
|
946
|
+
// Close all active connections
|
|
788
947
|
this.webSocketServer.clients.forEach((client) => {
|
|
789
948
|
if (client.readyState === WebSocket.OPEN) {
|
|
790
949
|
client.close();
|
|
@@ -800,10 +959,12 @@ export class Frontend {
|
|
|
800
959
|
});
|
|
801
960
|
this.webSocketServer = undefined;
|
|
802
961
|
}
|
|
962
|
+
// Stop the memory dump interval
|
|
803
963
|
if (hasParameter('memorydump')) {
|
|
804
964
|
this.stopCpuMemoryDump();
|
|
805
965
|
}
|
|
806
966
|
}
|
|
967
|
+
// Function to format bytes to KB, MB, or GB
|
|
807
968
|
formatMemoryUsage = (bytes) => {
|
|
808
969
|
if (bytes >= 1024 ** 3) {
|
|
809
970
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -815,6 +976,7 @@ export class Frontend {
|
|
|
815
976
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
816
977
|
}
|
|
817
978
|
};
|
|
979
|
+
// Function to format system uptime with only the most significant unit
|
|
818
980
|
formatOsUpTime = () => {
|
|
819
981
|
const seconds = os.uptime();
|
|
820
982
|
if (seconds >= 86400) {
|
|
@@ -834,11 +996,12 @@ export class Frontend {
|
|
|
834
996
|
getCpuUsage = () => {
|
|
835
997
|
const currCpus = os.cpus();
|
|
836
998
|
if (currCpus.length !== this.prevCpus.length) {
|
|
837
|
-
this.prevCpus = deepCopy(currCpus);
|
|
999
|
+
this.prevCpus = deepCopy(currCpus); // Reset the previous cpus
|
|
838
1000
|
this.log.debug(`***Cpu usage reset. Current cpus: ${currCpus.length}. Previous cpus: ${this.prevCpus.length}.`);
|
|
839
1001
|
return this.lastCpuUsage.toFixed(2);
|
|
840
1002
|
}
|
|
841
1003
|
let totalIdle = 0, totalTick = 0;
|
|
1004
|
+
// Get the cpu usage
|
|
842
1005
|
this.prevCpus.forEach((prevCpu, i) => {
|
|
843
1006
|
const currCpu = currCpus[i];
|
|
844
1007
|
const idleDiff = currCpu.times.idle - prevCpu.times.idle;
|
|
@@ -859,7 +1022,9 @@ export class Frontend {
|
|
|
859
1022
|
clearInterval(this.memoryInterval);
|
|
860
1023
|
clearTimeout(this.memoryTimeout);
|
|
861
1024
|
const interval = () => {
|
|
1025
|
+
// Get the cpu usage
|
|
862
1026
|
const cpuUsage = this.getCpuUsage();
|
|
1027
|
+
// Get the memory usage
|
|
863
1028
|
const memoryUsageRaw = process.memoryUsage();
|
|
864
1029
|
this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
|
|
865
1030
|
const memoryUsage = {
|
|
@@ -870,6 +1035,7 @@ export class Frontend {
|
|
|
870
1035
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
871
1036
|
};
|
|
872
1037
|
this.log.debug(`***Cpu usage: ${CYAN}${cpuUsage.padStart(6, ' ')} %${db} - Memory usage: rss ${CYAN}${memoryUsage.rss}${db} heapTotal ${CYAN}${memoryUsage.heapTotal}${db} heapUsed ${CYAN}${memoryUsage.heapUsed}${db} external ${memoryUsage.external} arrayBuffers ${memoryUsage.arrayBuffers}`);
|
|
1038
|
+
// Update the system information
|
|
873
1039
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
874
1040
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
875
1041
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
|
|
@@ -881,7 +1047,7 @@ export class Frontend {
|
|
|
881
1047
|
this.wssSendMemoryUpdate(this.matterbridge.systemInformation.freeMemory, this.matterbridge.systemInformation.totalMemory, this.matterbridge.systemInformation.systemUptime, this.matterbridge.systemInformation.rss, this.matterbridge.systemInformation.heapUsed, this.matterbridge.systemInformation.heapTotal);
|
|
882
1048
|
};
|
|
883
1049
|
interval();
|
|
884
|
-
this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000);
|
|
1050
|
+
this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000); // 1 second
|
|
885
1051
|
this.memoryInterval.unref();
|
|
886
1052
|
this.memoryTimeout = setTimeout(() => {
|
|
887
1053
|
this.stopCpuMemoryDump();
|
|
@@ -901,12 +1067,18 @@ export class Frontend {
|
|
|
901
1067
|
external: this.formatMemoryUsage(memory.external),
|
|
902
1068
|
arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
|
|
903
1069
|
};
|
|
1070
|
+
// eslint-disable-next-line no-console
|
|
904
1071
|
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}`);
|
|
905
1072
|
}
|
|
906
1073
|
this.memoryData = [];
|
|
907
1074
|
this.prevCpus = [];
|
|
908
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Retrieves the api settings data.
|
|
1078
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
1079
|
+
*/
|
|
909
1080
|
async getApiSettings() {
|
|
1081
|
+
// Update the system information
|
|
910
1082
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
911
1083
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
912
1084
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
|
|
@@ -914,6 +1086,7 @@ export class Frontend {
|
|
|
914
1086
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
915
1087
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
916
1088
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1089
|
+
// Update the matterbridge information
|
|
917
1090
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
918
1091
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
919
1092
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -932,6 +1105,11 @@ export class Frontend {
|
|
|
932
1105
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
933
1106
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
934
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Retrieves the cluster text description from a given device.
|
|
1110
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1111
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1112
|
+
*/
|
|
935
1113
|
getClusterTextFromDevice(device) {
|
|
936
1114
|
const getAttribute = (device, cluster, attribute) => {
|
|
937
1115
|
let value = undefined;
|
|
@@ -970,6 +1148,7 @@ export class Frontend {
|
|
|
970
1148
|
};
|
|
971
1149
|
let attributes = '';
|
|
972
1150
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1151
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
973
1152
|
if (typeof attributeValue === 'undefined')
|
|
974
1153
|
return;
|
|
975
1154
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1047,8 +1226,13 @@ export class Frontend {
|
|
|
1047
1226
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1048
1227
|
attributes += `${getUserLabel(device)} `;
|
|
1049
1228
|
});
|
|
1229
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1050
1230
|
return attributes.trimStart().trimEnd();
|
|
1051
1231
|
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1234
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1235
|
+
*/
|
|
1052
1236
|
getBaseRegisteredPlugins() {
|
|
1053
1237
|
const baseRegisteredPlugins = [];
|
|
1054
1238
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -1079,6 +1263,14 @@ export class Frontend {
|
|
|
1079
1263
|
}
|
|
1080
1264
|
return baseRegisteredPlugins;
|
|
1081
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Handles incoming websocket messages for the Matterbridge.
|
|
1268
|
+
*
|
|
1269
|
+
* @param {Matterbridge} this - The Matterbridge instance.
|
|
1270
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1271
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1272
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1273
|
+
*/
|
|
1082
1274
|
async wsMessageHandler(client, message) {
|
|
1083
1275
|
let data;
|
|
1084
1276
|
try {
|
|
@@ -1166,8 +1358,10 @@ export class Frontend {
|
|
|
1166
1358
|
else if (data.method === '/api/devices') {
|
|
1167
1359
|
const devices = [];
|
|
1168
1360
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1361
|
+
// Filter by pluginName if provided
|
|
1169
1362
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1170
1363
|
return;
|
|
1364
|
+
// Check if the device has the required properties
|
|
1171
1365
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
1172
1366
|
return;
|
|
1173
1367
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1250,6 +1444,7 @@ export class Frontend {
|
|
|
1250
1444
|
});
|
|
1251
1445
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1252
1446
|
deviceTypes = [];
|
|
1447
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1253
1448
|
const name = childEndpoint.endpoint?.id;
|
|
1254
1449
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1255
1450
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1332,70 +1527,114 @@ export class Frontend {
|
|
|
1332
1527
|
return;
|
|
1333
1528
|
}
|
|
1334
1529
|
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1532
|
+
*
|
|
1533
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1534
|
+
* @param {string} time - The time string of the message
|
|
1535
|
+
* @param {string} name - The logger name of the message
|
|
1536
|
+
* @param {string} message - The content of the message.
|
|
1537
|
+
*/
|
|
1335
1538
|
wssSendMessage(level, time, name, message) {
|
|
1336
1539
|
if (!level || !time || !name || !message)
|
|
1337
1540
|
return;
|
|
1541
|
+
// Remove ANSI escape codes from the message
|
|
1542
|
+
// eslint-disable-next-line no-control-regex
|
|
1338
1543
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1544
|
+
// Remove leading asterisks from the message
|
|
1339
1545
|
message = message.replace(/^\*+/, '');
|
|
1546
|
+
// Replace all occurrences of \t and \n
|
|
1340
1547
|
message = message.replace(/[\t\n]/g, '');
|
|
1548
|
+
// Remove non-printable characters
|
|
1549
|
+
// eslint-disable-next-line no-control-regex
|
|
1341
1550
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1551
|
+
// Replace all occurrences of \" with "
|
|
1342
1552
|
message = message.replace(/\\"/g, '"');
|
|
1553
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1343
1554
|
const maxContinuousLength = 100;
|
|
1344
1555
|
const keepStartLength = 20;
|
|
1345
1556
|
const keepEndLength = 20;
|
|
1557
|
+
// Split the message into words
|
|
1346
1558
|
message = message
|
|
1347
1559
|
.split(' ')
|
|
1348
1560
|
.map((word) => {
|
|
1561
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1349
1562
|
if (word.length > maxContinuousLength) {
|
|
1350
1563
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1351
1564
|
}
|
|
1352
1565
|
return word;
|
|
1353
1566
|
})
|
|
1354
1567
|
.join(' ');
|
|
1568
|
+
// Send the message to all connected clients
|
|
1355
1569
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1356
1570
|
if (client.readyState === WebSocket.OPEN) {
|
|
1357
1571
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1358
1572
|
}
|
|
1359
1573
|
});
|
|
1360
1574
|
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1577
|
+
*
|
|
1578
|
+
*/
|
|
1361
1579
|
wssSendRefreshRequired() {
|
|
1362
1580
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1363
1581
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1582
|
+
// Send the message to all connected clients
|
|
1364
1583
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1365
1584
|
if (client.readyState === WebSocket.OPEN) {
|
|
1366
1585
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1367
1586
|
}
|
|
1368
1587
|
});
|
|
1369
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1591
|
+
*
|
|
1592
|
+
*/
|
|
1370
1593
|
wssSendRestartRequired() {
|
|
1371
1594
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1372
1595
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1596
|
+
// Send the message to all connected clients
|
|
1373
1597
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1374
1598
|
if (client.readyState === WebSocket.OPEN) {
|
|
1375
1599
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1376
1600
|
}
|
|
1377
1601
|
});
|
|
1378
1602
|
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Sends a memory update message to all connected clients.
|
|
1605
|
+
*
|
|
1606
|
+
*/
|
|
1379
1607
|
wssSendCpuUpdate(cpuUsed) {
|
|
1380
1608
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1381
1609
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1610
|
+
// Send the message to all connected clients
|
|
1382
1611
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1383
1612
|
if (client.readyState === WebSocket.OPEN) {
|
|
1384
1613
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsed } }));
|
|
1385
1614
|
}
|
|
1386
1615
|
});
|
|
1387
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Sends a cpu update message to all connected clients.
|
|
1619
|
+
*
|
|
1620
|
+
*/
|
|
1388
1621
|
wssSendMemoryUpdate(freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal) {
|
|
1389
1622
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1390
1623
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1624
|
+
// Send the message to all connected clients
|
|
1391
1625
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1392
1626
|
if (client.readyState === WebSocket.OPEN) {
|
|
1393
1627
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal } }));
|
|
1394
1628
|
}
|
|
1395
1629
|
});
|
|
1396
1630
|
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Sends a cpu update message to all connected clients.
|
|
1633
|
+
*
|
|
1634
|
+
*/
|
|
1397
1635
|
wssSendSnackbarMessage(message, timeout = 5) {
|
|
1398
1636
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1637
|
+
// Send the message to all connected clients
|
|
1399
1638
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1400
1639
|
if (client.readyState === WebSocket.OPEN) {
|
|
1401
1640
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
|
|
@@ -1403,3 +1642,4 @@ export class Frontend {
|
|
|
1403
1642
|
});
|
|
1404
1643
|
}
|
|
1405
1644
|
}
|
|
1645
|
+
//# sourceMappingURL=frontend.js.map
|