matterbridge 2.1.1-dev.1 → 2.1.1
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 +3 -2
- 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 +46 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +26 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +109 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +227 -22
- 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 +4 -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 +755 -43
- 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 +164 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +123 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +165 -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 +238 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +240 -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;
|
|
@@ -27,7 +70,7 @@ export class Frontend {
|
|
|
27
70
|
memoryTimeout;
|
|
28
71
|
constructor(matterbridge) {
|
|
29
72
|
this.matterbridge = matterbridge;
|
|
30
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
73
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
31
74
|
}
|
|
32
75
|
set logLevel(logLevel) {
|
|
33
76
|
this.log.logLevel = logLevel;
|
|
@@ -35,10 +78,21 @@ export class Frontend {
|
|
|
35
78
|
async start(port = 8283) {
|
|
36
79
|
this.port = port;
|
|
37
80
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
81
|
+
// Create the express app that serves the frontend
|
|
38
82
|
this.expressApp = express();
|
|
83
|
+
// Log all requests to the server for debugging
|
|
84
|
+
/*
|
|
85
|
+
this.expressApp.use((req, res, next) => {
|
|
86
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
87
|
+
next();
|
|
88
|
+
});
|
|
89
|
+
*/
|
|
90
|
+
// Serve static files from '/static' endpoint
|
|
39
91
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
40
92
|
if (!hasParameter('ssl')) {
|
|
93
|
+
// Create an HTTP server and attach the express app
|
|
41
94
|
this.httpServer = createServer(this.expressApp);
|
|
95
|
+
// Listen on the specified port
|
|
42
96
|
if (hasParameter('ingress')) {
|
|
43
97
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
44
98
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -52,6 +106,7 @@ export class Frontend {
|
|
|
52
106
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
53
107
|
});
|
|
54
108
|
}
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
110
|
this.httpServer.on('error', (error) => {
|
|
56
111
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
57
112
|
switch (error.code) {
|
|
@@ -67,6 +122,7 @@ export class Frontend {
|
|
|
67
122
|
});
|
|
68
123
|
}
|
|
69
124
|
else {
|
|
125
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
70
126
|
let cert;
|
|
71
127
|
try {
|
|
72
128
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -94,7 +150,9 @@ export class Frontend {
|
|
|
94
150
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
95
151
|
}
|
|
96
152
|
const serverOptions = { cert, key, ca };
|
|
153
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
97
154
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
155
|
+
// Listen on the specified port
|
|
98
156
|
if (hasParameter('ingress')) {
|
|
99
157
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
100
158
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -108,6 +166,7 @@ export class Frontend {
|
|
|
108
166
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
109
167
|
});
|
|
110
168
|
}
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
170
|
this.httpsServer.on('error', (error) => {
|
|
112
171
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
113
172
|
switch (error.code) {
|
|
@@ -124,12 +183,13 @@ export class Frontend {
|
|
|
124
183
|
}
|
|
125
184
|
if (this.initializeError)
|
|
126
185
|
return;
|
|
186
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
127
187
|
const wssPort = this.port;
|
|
128
188
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
129
189
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
130
190
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
131
191
|
const clientIp = request.socket.remoteAddress;
|
|
132
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
192
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
133
193
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
134
194
|
ws.on('message', (message) => {
|
|
135
195
|
this.wsMessageHandler(ws, message);
|
|
@@ -161,9 +221,11 @@ export class Frontend {
|
|
|
161
221
|
this.webSocketServer.on('error', (ws, error) => {
|
|
162
222
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
163
223
|
});
|
|
224
|
+
// Start the memory dump interval
|
|
164
225
|
if (hasParameter('memorydump')) {
|
|
165
226
|
this.startMemoryDump();
|
|
166
227
|
}
|
|
228
|
+
// Endpoint to validate login code
|
|
167
229
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
168
230
|
const { password } = req.body;
|
|
169
231
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -182,23 +244,27 @@ export class Frontend {
|
|
|
182
244
|
this.log.warn('/api/login error wrong password');
|
|
183
245
|
res.json({ valid: false });
|
|
184
246
|
}
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
185
248
|
}
|
|
186
249
|
catch (error) {
|
|
187
250
|
this.log.error('/api/login error getting password');
|
|
188
251
|
res.json({ valid: false });
|
|
189
252
|
}
|
|
190
253
|
});
|
|
254
|
+
// Endpoint to provide health check
|
|
191
255
|
this.expressApp.get('/health', (req, res) => {
|
|
192
256
|
this.log.debug('Express received /health');
|
|
193
257
|
const healthStatus = {
|
|
194
|
-
status: 'ok',
|
|
195
|
-
uptime: process.uptime(),
|
|
196
|
-
timestamp: new Date().toISOString(),
|
|
258
|
+
status: 'ok', // Indicate service is healthy
|
|
259
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
260
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
197
261
|
};
|
|
198
262
|
res.status(200).json(healthStatus);
|
|
199
263
|
});
|
|
264
|
+
// Endpoint to provide memory usage details
|
|
200
265
|
this.expressApp.get('/memory', async (req, res) => {
|
|
201
266
|
this.log.debug('Express received /memory');
|
|
267
|
+
// Memory usage from process
|
|
202
268
|
const memoryUsageRaw = process.memoryUsage();
|
|
203
269
|
const memoryUsage = {
|
|
204
270
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -207,10 +273,13 @@ export class Frontend {
|
|
|
207
273
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
208
274
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
209
275
|
};
|
|
276
|
+
// V8 heap statistics
|
|
210
277
|
const { default: v8 } = await import('node:v8');
|
|
211
278
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
212
279
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
280
|
+
// Format heapStats
|
|
213
281
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
282
|
+
// Format heapSpaces
|
|
214
283
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
215
284
|
...space,
|
|
216
285
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -220,6 +289,23 @@ export class Frontend {
|
|
|
220
289
|
}));
|
|
221
290
|
const { default: module } = await import('module');
|
|
222
291
|
const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
|
|
292
|
+
/*
|
|
293
|
+
if (req.query.heapdump === 'true') {
|
|
294
|
+
const { default: heapdump } = await import('heapdump');
|
|
295
|
+
const filename = `heapdump-${Date.now()}.heapsnapshot`;
|
|
296
|
+
|
|
297
|
+
heapdump.writeSnapshot(filename, (err, dumpFilename) => {
|
|
298
|
+
if (err) {
|
|
299
|
+
this.log.error(`Heap dump error: ${err.message}`);
|
|
300
|
+
return res.status(500).json({ error: 'Heap dump failed', details: err.message });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.log.info(`Heap dump written to ${dumpFilename}`);
|
|
304
|
+
return res.status(200).json({ ...memoryReport, heapdump: dumpFilename });
|
|
305
|
+
});
|
|
306
|
+
return; // Exit early since heapdump response is handled inside callback
|
|
307
|
+
}
|
|
308
|
+
*/
|
|
223
309
|
const memoryReport = {
|
|
224
310
|
memoryUsage,
|
|
225
311
|
heapStats,
|
|
@@ -228,6 +314,7 @@ export class Frontend {
|
|
|
228
314
|
};
|
|
229
315
|
res.status(200).json(memoryReport);
|
|
230
316
|
});
|
|
317
|
+
// Endpoint to start advertising the server node
|
|
231
318
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
232
319
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
233
320
|
if (pairingCodes) {
|
|
@@ -238,19 +325,24 @@ export class Frontend {
|
|
|
238
325
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
239
326
|
}
|
|
240
327
|
});
|
|
328
|
+
// Endpoint to provide settings
|
|
241
329
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
242
330
|
this.log.debug('The frontend sent /api/settings');
|
|
243
331
|
res.json(await this.getApiSettings());
|
|
244
332
|
});
|
|
333
|
+
// Endpoint to provide plugins
|
|
245
334
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
246
335
|
this.log.debug('The frontend sent /api/plugins');
|
|
247
336
|
const response = this.getBaseRegisteredPlugins();
|
|
337
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
248
338
|
res.json(response);
|
|
249
339
|
});
|
|
340
|
+
// Endpoint to provide devices
|
|
250
341
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
251
342
|
this.log.debug('The frontend sent /api/devices');
|
|
252
343
|
const devices = [];
|
|
253
344
|
this.matterbridge.devices.forEach(async (device) => {
|
|
345
|
+
// Check if the device has the required properties
|
|
254
346
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
255
347
|
return;
|
|
256
348
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -266,8 +358,10 @@ export class Frontend {
|
|
|
266
358
|
cluster: cluster,
|
|
267
359
|
});
|
|
268
360
|
});
|
|
361
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
269
362
|
res.json(devices);
|
|
270
363
|
});
|
|
364
|
+
// Endpoint to provide the cluster servers of the devices
|
|
271
365
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
272
366
|
const selectedPluginName = req.params.selectedPluginName;
|
|
273
367
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -340,6 +434,7 @@ export class Frontend {
|
|
|
340
434
|
});
|
|
341
435
|
res.json(data);
|
|
342
436
|
});
|
|
437
|
+
// Endpoint to view the log
|
|
343
438
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
344
439
|
this.log.debug('The frontend sent /api/log');
|
|
345
440
|
try {
|
|
@@ -352,10 +447,12 @@ export class Frontend {
|
|
|
352
447
|
res.status(500).send('Error reading log file');
|
|
353
448
|
}
|
|
354
449
|
});
|
|
450
|
+
// Endpoint to download the matterbridge log
|
|
355
451
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
356
452
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
357
453
|
try {
|
|
358
454
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
455
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
359
456
|
}
|
|
360
457
|
catch (error) {
|
|
361
458
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -367,10 +464,12 @@ export class Frontend {
|
|
|
367
464
|
}
|
|
368
465
|
});
|
|
369
466
|
});
|
|
467
|
+
// Endpoint to download the matter log
|
|
370
468
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
371
469
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
372
470
|
try {
|
|
373
471
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
374
473
|
}
|
|
375
474
|
catch (error) {
|
|
376
475
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -382,6 +481,7 @@ export class Frontend {
|
|
|
382
481
|
}
|
|
383
482
|
});
|
|
384
483
|
});
|
|
484
|
+
// Endpoint to download the matter storage file
|
|
385
485
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
386
486
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
387
487
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -392,6 +492,7 @@ export class Frontend {
|
|
|
392
492
|
}
|
|
393
493
|
});
|
|
394
494
|
});
|
|
495
|
+
// Endpoint to download the matterbridge storage directory
|
|
395
496
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
396
497
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
397
498
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -402,6 +503,7 @@ export class Frontend {
|
|
|
402
503
|
}
|
|
403
504
|
});
|
|
404
505
|
});
|
|
506
|
+
// Endpoint to download the matterbridge plugin directory
|
|
405
507
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
406
508
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
407
509
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -412,9 +514,11 @@ export class Frontend {
|
|
|
412
514
|
}
|
|
413
515
|
});
|
|
414
516
|
});
|
|
517
|
+
// Endpoint to download the matterbridge plugin config files
|
|
415
518
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
416
519
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
417
520
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
521
|
+
// 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')));
|
|
418
522
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
419
523
|
if (error) {
|
|
420
524
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -422,6 +526,7 @@ export class Frontend {
|
|
|
422
526
|
}
|
|
423
527
|
});
|
|
424
528
|
});
|
|
529
|
+
// Endpoint to download the matterbridge plugin config files
|
|
425
530
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
426
531
|
this.log.debug('The frontend sent /api/download-backup');
|
|
427
532
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -431,6 +536,7 @@ export class Frontend {
|
|
|
431
536
|
}
|
|
432
537
|
});
|
|
433
538
|
});
|
|
539
|
+
// Endpoint to receive commands
|
|
434
540
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
435
541
|
const command = req.params.command;
|
|
436
542
|
let param = req.params.param;
|
|
@@ -440,13 +546,15 @@ export class Frontend {
|
|
|
440
546
|
return;
|
|
441
547
|
}
|
|
442
548
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
549
|
+
// Handle the command setpassword from Settings
|
|
443
550
|
if (command === 'setpassword') {
|
|
444
|
-
const password = param.slice(1, -1);
|
|
551
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
445
552
|
this.log.debug('setpassword', param, password);
|
|
446
553
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
447
554
|
res.json({ message: 'Command received' });
|
|
448
555
|
return;
|
|
449
556
|
}
|
|
557
|
+
// Handle the command setbridgemode from Settings
|
|
450
558
|
if (command === 'setbridgemode') {
|
|
451
559
|
this.log.debug(`setbridgemode: ${param}`);
|
|
452
560
|
this.wssSendRestartRequired();
|
|
@@ -454,6 +562,7 @@ export class Frontend {
|
|
|
454
562
|
res.json({ message: 'Command received' });
|
|
455
563
|
return;
|
|
456
564
|
}
|
|
565
|
+
// Handle the command backup from Settings
|
|
457
566
|
if (command === 'backup') {
|
|
458
567
|
this.log.notice(`Prepairing the backup...`);
|
|
459
568
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -461,25 +570,26 @@ export class Frontend {
|
|
|
461
570
|
res.json({ message: 'Command received' });
|
|
462
571
|
return;
|
|
463
572
|
}
|
|
573
|
+
// Handle the command setmbloglevel from Settings
|
|
464
574
|
if (command === 'setmbloglevel') {
|
|
465
575
|
this.log.debug('Matterbridge log level:', param);
|
|
466
576
|
if (param === 'Debug') {
|
|
467
|
-
this.log.logLevel = "debug"
|
|
577
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
468
578
|
}
|
|
469
579
|
else if (param === 'Info') {
|
|
470
|
-
this.log.logLevel = "info"
|
|
580
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
471
581
|
}
|
|
472
582
|
else if (param === 'Notice') {
|
|
473
|
-
this.log.logLevel = "notice"
|
|
583
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
474
584
|
}
|
|
475
585
|
else if (param === 'Warn') {
|
|
476
|
-
this.log.logLevel = "warn"
|
|
586
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
477
587
|
}
|
|
478
588
|
else if (param === 'Error') {
|
|
479
|
-
this.log.logLevel = "error"
|
|
589
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
480
590
|
}
|
|
481
591
|
else if (param === 'Fatal') {
|
|
482
|
-
this.log.logLevel = "fatal"
|
|
592
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
483
593
|
}
|
|
484
594
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
485
595
|
this.matterbridge.log.logLevel = this.log.logLevel;
|
|
@@ -489,12 +599,13 @@ export class Frontend {
|
|
|
489
599
|
for (const plugin of this.matterbridge.plugins) {
|
|
490
600
|
if (!plugin.platform || !plugin.platform.config)
|
|
491
601
|
continue;
|
|
492
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
493
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
602
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
603
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
494
604
|
}
|
|
495
605
|
res.json({ message: 'Command received' });
|
|
496
606
|
return;
|
|
497
607
|
}
|
|
608
|
+
// Handle the command setmbloglevel from Settings
|
|
498
609
|
if (command === 'setmjloglevel') {
|
|
499
610
|
this.log.debug('Matter.js log level:', param);
|
|
500
611
|
if (param === 'Debug') {
|
|
@@ -519,30 +630,34 @@ export class Frontend {
|
|
|
519
630
|
res.json({ message: 'Command received' });
|
|
520
631
|
return;
|
|
521
632
|
}
|
|
633
|
+
// Handle the command setmdnsinterface from Settings
|
|
522
634
|
if (command === 'setmdnsinterface') {
|
|
523
|
-
param = param.slice(1, -1);
|
|
635
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
524
636
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
525
637
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
526
638
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
527
639
|
res.json({ message: 'Command received' });
|
|
528
640
|
return;
|
|
529
641
|
}
|
|
642
|
+
// Handle the command setipv4address from Settings
|
|
530
643
|
if (command === 'setipv4address') {
|
|
531
|
-
param = param.slice(1, -1);
|
|
644
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
532
645
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
533
646
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
534
647
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
535
648
|
res.json({ message: 'Command received' });
|
|
536
649
|
return;
|
|
537
650
|
}
|
|
651
|
+
// Handle the command setipv6address from Settings
|
|
538
652
|
if (command === 'setipv6address') {
|
|
539
|
-
param = param.slice(1, -1);
|
|
653
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
540
654
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
541
655
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
542
656
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
543
657
|
res.json({ message: 'Command received' });
|
|
544
658
|
return;
|
|
545
659
|
}
|
|
660
|
+
// Handle the command setmatterport from Settings
|
|
546
661
|
if (command === 'setmatterport') {
|
|
547
662
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
548
663
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -551,6 +666,7 @@ export class Frontend {
|
|
|
551
666
|
res.json({ message: 'Command received' });
|
|
552
667
|
return;
|
|
553
668
|
}
|
|
669
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
554
670
|
if (command === 'setmatterdiscriminator') {
|
|
555
671
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
556
672
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -559,6 +675,7 @@ export class Frontend {
|
|
|
559
675
|
res.json({ message: 'Command received' });
|
|
560
676
|
return;
|
|
561
677
|
}
|
|
678
|
+
// Handle the command setmatterpasscode from Settings
|
|
562
679
|
if (command === 'setmatterpasscode') {
|
|
563
680
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
564
681
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -567,17 +684,20 @@ export class Frontend {
|
|
|
567
684
|
res.json({ message: 'Command received' });
|
|
568
685
|
return;
|
|
569
686
|
}
|
|
687
|
+
// Handle the command setmbloglevel from Settings
|
|
570
688
|
if (command === 'setmblogfile') {
|
|
571
689
|
this.log.debug('Matterbridge file log:', param);
|
|
572
690
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
573
691
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
692
|
+
// Create the file logger for matterbridge
|
|
574
693
|
if (param === 'true')
|
|
575
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
694
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
576
695
|
else
|
|
577
696
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
578
697
|
res.json({ message: 'Command received' });
|
|
579
698
|
return;
|
|
580
699
|
}
|
|
700
|
+
// Handle the command setmbloglevel from Settings
|
|
581
701
|
if (command === 'setmjlogfile') {
|
|
582
702
|
this.log.debug('Matter file log:', param);
|
|
583
703
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -604,36 +724,43 @@ export class Frontend {
|
|
|
604
724
|
res.json({ message: 'Command received' });
|
|
605
725
|
return;
|
|
606
726
|
}
|
|
727
|
+
// Handle the command unregister from Settings
|
|
607
728
|
if (command === 'unregister') {
|
|
608
729
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
609
730
|
res.json({ message: 'Command received' });
|
|
610
731
|
return;
|
|
611
732
|
}
|
|
733
|
+
// Handle the command reset from Settings
|
|
612
734
|
if (command === 'reset') {
|
|
613
735
|
await this.matterbridge.shutdownProcessAndReset();
|
|
614
736
|
res.json({ message: 'Command received' });
|
|
615
737
|
return;
|
|
616
738
|
}
|
|
739
|
+
// Handle the command factoryreset from Settings
|
|
617
740
|
if (command === 'factoryreset') {
|
|
618
741
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
619
742
|
res.json({ message: 'Command received' });
|
|
620
743
|
return;
|
|
621
744
|
}
|
|
745
|
+
// Handle the command shutdown from Header
|
|
622
746
|
if (command === 'shutdown') {
|
|
623
747
|
await this.matterbridge.shutdownProcess();
|
|
624
748
|
res.json({ message: 'Command received' });
|
|
625
749
|
return;
|
|
626
750
|
}
|
|
751
|
+
// Handle the command restart from Header
|
|
627
752
|
if (command === 'restart') {
|
|
628
753
|
await this.matterbridge.restartProcess();
|
|
629
754
|
res.json({ message: 'Command received' });
|
|
630
755
|
return;
|
|
631
756
|
}
|
|
757
|
+
// Handle the command update from Header
|
|
632
758
|
if (command === 'update') {
|
|
633
759
|
this.log.info('Updating matterbridge...');
|
|
634
760
|
try {
|
|
635
761
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
636
762
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
763
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
637
764
|
}
|
|
638
765
|
catch (error) {
|
|
639
766
|
this.log.error('Error updating matterbridge');
|
|
@@ -643,9 +770,11 @@ export class Frontend {
|
|
|
643
770
|
res.json({ message: 'Command received' });
|
|
644
771
|
return;
|
|
645
772
|
}
|
|
773
|
+
// Handle the command saveconfig from Home
|
|
646
774
|
if (command === 'saveconfig') {
|
|
647
775
|
param = param.replace(/\*/g, '\\');
|
|
648
776
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
777
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
649
778
|
if (!this.matterbridge.plugins.has(param)) {
|
|
650
779
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
651
780
|
}
|
|
@@ -659,49 +788,58 @@ export class Frontend {
|
|
|
659
788
|
res.json({ message: 'Command received' });
|
|
660
789
|
return;
|
|
661
790
|
}
|
|
791
|
+
// Handle the command installplugin from Home
|
|
662
792
|
if (command === 'installplugin') {
|
|
663
793
|
param = param.replace(/\*/g, '\\');
|
|
664
794
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
665
795
|
try {
|
|
666
796
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
667
797
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
798
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
668
799
|
}
|
|
669
800
|
catch (error) {
|
|
670
801
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
671
802
|
}
|
|
672
803
|
this.wssSendRestartRequired();
|
|
673
804
|
param = param.split('@')[0];
|
|
805
|
+
// Also add the plugin to matterbridge so no return!
|
|
674
806
|
if (param === 'matterbridge') {
|
|
807
|
+
// 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
|
|
675
808
|
res.json({ message: 'Command received' });
|
|
676
809
|
return;
|
|
677
810
|
}
|
|
678
811
|
}
|
|
812
|
+
// Handle the command addplugin from Home
|
|
679
813
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
680
814
|
param = param.replace(/\*/g, '\\');
|
|
681
815
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
682
816
|
if (plugin) {
|
|
683
817
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
818
|
+
// 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
|
|
819
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
684
820
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
685
821
|
}
|
|
686
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
822
|
+
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
687
823
|
}
|
|
688
824
|
res.json({ message: 'Command received' });
|
|
689
825
|
this.wssSendRefreshRequired();
|
|
690
826
|
return;
|
|
691
827
|
}
|
|
828
|
+
// Handle the command removeplugin from Home
|
|
692
829
|
if (command === 'removeplugin') {
|
|
693
830
|
if (!this.matterbridge.plugins.has(param)) {
|
|
694
831
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
695
832
|
}
|
|
696
833
|
else {
|
|
697
834
|
const plugin = this.matterbridge.plugins.get(param);
|
|
698
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
835
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
699
836
|
await this.matterbridge.plugins.remove(param);
|
|
700
837
|
}
|
|
701
838
|
res.json({ message: 'Command received' });
|
|
702
839
|
this.wssSendRefreshRequired();
|
|
703
840
|
return;
|
|
704
841
|
}
|
|
842
|
+
// Handle the command enableplugin from Home
|
|
705
843
|
if (command === 'enableplugin') {
|
|
706
844
|
if (!this.matterbridge.plugins.has(param)) {
|
|
707
845
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -719,15 +857,17 @@ export class Frontend {
|
|
|
719
857
|
plugin.addedDevices = undefined;
|
|
720
858
|
await this.matterbridge.plugins.enable(param);
|
|
721
859
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
860
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
722
861
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
723
862
|
}
|
|
724
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
863
|
+
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
|
|
725
864
|
}
|
|
726
865
|
}
|
|
727
866
|
res.json({ message: 'Command received' });
|
|
728
867
|
this.wssSendRefreshRequired();
|
|
729
868
|
return;
|
|
730
869
|
}
|
|
870
|
+
// Handle the command disableplugin from Home
|
|
731
871
|
if (command === 'disableplugin') {
|
|
732
872
|
if (!this.matterbridge.plugins.has(param)) {
|
|
733
873
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -735,7 +875,7 @@ export class Frontend {
|
|
|
735
875
|
else {
|
|
736
876
|
const plugin = this.matterbridge.plugins.get(param);
|
|
737
877
|
if (plugin && plugin.enabled) {
|
|
738
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
878
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
739
879
|
await this.matterbridge.plugins.disable(param);
|
|
740
880
|
}
|
|
741
881
|
}
|
|
@@ -744,6 +884,7 @@ export class Frontend {
|
|
|
744
884
|
return;
|
|
745
885
|
}
|
|
746
886
|
});
|
|
887
|
+
// Fallback for routing (must be the last route)
|
|
747
888
|
this.expressApp.get('*', (req, res) => {
|
|
748
889
|
this.log.debug('The frontend sent:', req.url);
|
|
749
890
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -752,24 +893,29 @@ export class Frontend {
|
|
|
752
893
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
753
894
|
}
|
|
754
895
|
async stop() {
|
|
896
|
+
// Close the http server
|
|
755
897
|
if (this.httpServer) {
|
|
756
898
|
this.httpServer.close();
|
|
757
899
|
this.httpServer.removeAllListeners();
|
|
758
900
|
this.httpServer = undefined;
|
|
759
901
|
this.log.debug('Frontend http server closed successfully');
|
|
760
902
|
}
|
|
903
|
+
// Close the https server
|
|
761
904
|
if (this.httpsServer) {
|
|
762
905
|
this.httpsServer.close();
|
|
763
906
|
this.httpsServer.removeAllListeners();
|
|
764
907
|
this.httpsServer = undefined;
|
|
765
908
|
this.log.debug('Frontend https server closed successfully');
|
|
766
909
|
}
|
|
910
|
+
// Remove listeners from the express app
|
|
767
911
|
if (this.expressApp) {
|
|
768
912
|
this.expressApp.removeAllListeners();
|
|
769
913
|
this.expressApp = undefined;
|
|
770
914
|
this.log.debug('Frontend app closed successfully');
|
|
771
915
|
}
|
|
916
|
+
// Close the WebSocket server
|
|
772
917
|
if (this.webSocketServer) {
|
|
918
|
+
// Close all active connections
|
|
773
919
|
this.webSocketServer.clients.forEach((client) => {
|
|
774
920
|
if (client.readyState === WebSocket.OPEN) {
|
|
775
921
|
client.close();
|
|
@@ -785,10 +931,12 @@ export class Frontend {
|
|
|
785
931
|
});
|
|
786
932
|
this.webSocketServer = undefined;
|
|
787
933
|
}
|
|
934
|
+
// Stop the memory dump interval
|
|
788
935
|
if (hasParameter('memorydump')) {
|
|
789
936
|
this.stopMemoryDump();
|
|
790
937
|
}
|
|
791
938
|
}
|
|
939
|
+
// Function to format bytes to KB or MB
|
|
792
940
|
formatMemoryUsage = (bytes) => {
|
|
793
941
|
const kb = bytes / 1024;
|
|
794
942
|
const mb = kb / 1024;
|
|
@@ -830,9 +978,14 @@ export class Frontend {
|
|
|
830
978
|
external: this.formatMemoryUsage(memory.external),
|
|
831
979
|
arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
|
|
832
980
|
};
|
|
981
|
+
// eslint-disable-next-line no-console
|
|
833
982
|
console.log(`${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}`);
|
|
834
983
|
}
|
|
835
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Retrieves the api settings.
|
|
987
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
988
|
+
*/
|
|
836
989
|
async getApiSettings() {
|
|
837
990
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
838
991
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
@@ -852,6 +1005,11 @@ export class Frontend {
|
|
|
852
1005
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
853
1006
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
854
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Retrieves the cluster text description from a given device.
|
|
1010
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1011
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1012
|
+
*/
|
|
855
1013
|
getClusterTextFromDevice(device) {
|
|
856
1014
|
const getAttribute = (device, cluster, attribute) => {
|
|
857
1015
|
let value = undefined;
|
|
@@ -890,6 +1048,7 @@ export class Frontend {
|
|
|
890
1048
|
};
|
|
891
1049
|
let attributes = '';
|
|
892
1050
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1051
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
893
1052
|
if (typeof attributeValue === 'undefined')
|
|
894
1053
|
return;
|
|
895
1054
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -967,8 +1126,13 @@ export class Frontend {
|
|
|
967
1126
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
968
1127
|
attributes += `${getUserLabel(device)} `;
|
|
969
1128
|
});
|
|
1129
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
970
1130
|
return attributes.trimStart().trimEnd();
|
|
971
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1134
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1135
|
+
*/
|
|
972
1136
|
getBaseRegisteredPlugins() {
|
|
973
1137
|
const baseRegisteredPlugins = [];
|
|
974
1138
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -999,6 +1163,14 @@ export class Frontend {
|
|
|
999
1163
|
}
|
|
1000
1164
|
return baseRegisteredPlugins;
|
|
1001
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Handles incoming websocket messages for the Matterbridge.
|
|
1168
|
+
*
|
|
1169
|
+
* @param {Matterbridge} this - The Matterbridge instance.
|
|
1170
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1171
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1172
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1173
|
+
*/
|
|
1002
1174
|
async wsMessageHandler(client, message) {
|
|
1003
1175
|
let data;
|
|
1004
1176
|
try {
|
|
@@ -1086,8 +1258,10 @@ export class Frontend {
|
|
|
1086
1258
|
else if (data.method === '/api/devices') {
|
|
1087
1259
|
const devices = [];
|
|
1088
1260
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1261
|
+
// Filter by pluginName if provided
|
|
1089
1262
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1090
1263
|
return;
|
|
1264
|
+
// Check if the device has the required properties
|
|
1091
1265
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
1092
1266
|
return;
|
|
1093
1267
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1170,6 +1344,7 @@ export class Frontend {
|
|
|
1170
1344
|
});
|
|
1171
1345
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1172
1346
|
deviceTypes = [];
|
|
1347
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1173
1348
|
const name = childEndpoint.endpoint?.id;
|
|
1174
1349
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1175
1350
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1252,44 +1427,73 @@ export class Frontend {
|
|
|
1252
1427
|
return;
|
|
1253
1428
|
}
|
|
1254
1429
|
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1432
|
+
*
|
|
1433
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1434
|
+
* @param {string} time - The time string of the message
|
|
1435
|
+
* @param {string} name - The logger name of the message
|
|
1436
|
+
* @param {string} message - The content of the message.
|
|
1437
|
+
*/
|
|
1255
1438
|
wssSendMessage(level, time, name, message) {
|
|
1256
1439
|
if (!level || !time || !name || !message)
|
|
1257
1440
|
return;
|
|
1441
|
+
// Remove ANSI escape codes from the message
|
|
1442
|
+
// eslint-disable-next-line no-control-regex
|
|
1258
1443
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1444
|
+
// Remove leading asterisks from the message
|
|
1259
1445
|
message = message.replace(/^\*+/, '');
|
|
1446
|
+
// Replace all occurrences of \t and \n
|
|
1260
1447
|
message = message.replace(/[\t\n]/g, '');
|
|
1448
|
+
// Remove non-printable characters
|
|
1449
|
+
// eslint-disable-next-line no-control-regex
|
|
1261
1450
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1451
|
+
// Replace all occurrences of \" with "
|
|
1262
1452
|
message = message.replace(/\\"/g, '"');
|
|
1453
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1263
1454
|
const maxContinuousLength = 100;
|
|
1264
1455
|
const keepStartLength = 20;
|
|
1265
1456
|
const keepEndLength = 20;
|
|
1457
|
+
// Split the message into words
|
|
1266
1458
|
message = message
|
|
1267
1459
|
.split(' ')
|
|
1268
1460
|
.map((word) => {
|
|
1461
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1269
1462
|
if (word.length > maxContinuousLength) {
|
|
1270
1463
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1271
1464
|
}
|
|
1272
1465
|
return word;
|
|
1273
1466
|
})
|
|
1274
1467
|
.join(' ');
|
|
1468
|
+
// Send the message to all connected clients
|
|
1275
1469
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1276
1470
|
if (client.readyState === WebSocket.OPEN) {
|
|
1277
1471
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1278
1472
|
}
|
|
1279
1473
|
});
|
|
1280
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1477
|
+
*
|
|
1478
|
+
*/
|
|
1281
1479
|
wssSendRefreshRequired() {
|
|
1282
1480
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1283
1481
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1482
|
+
// Send the message to all connected clients
|
|
1284
1483
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1285
1484
|
if (client.readyState === WebSocket.OPEN) {
|
|
1286
1485
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1287
1486
|
}
|
|
1288
1487
|
});
|
|
1289
1488
|
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1491
|
+
*
|
|
1492
|
+
*/
|
|
1290
1493
|
wssSendRestartRequired() {
|
|
1291
1494
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1292
1495
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1496
|
+
// Send the message to all connected clients
|
|
1293
1497
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1294
1498
|
if (client.readyState === WebSocket.OPEN) {
|
|
1295
1499
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
@@ -1297,3 +1501,4 @@ export class Frontend {
|
|
|
1297
1501
|
});
|
|
1298
1502
|
}
|
|
1299
1503
|
}
|
|
1504
|
+
//# sourceMappingURL=frontend.js.map
|