matterbridge 2.0.0-edge.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +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 +98 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +239 -22
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -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/export.d.ts +10 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +4 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matterbridge.d.ts +356 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +687 -38
- 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 +963 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +31 -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 +10254 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1174 -10
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointDefault.d.ts +2 -0
- package/dist/matterbridgeEndpointDefault.d.ts.map +1 -0
- package/dist/matterbridgeEndpointDefault.js +55 -0
- package/dist/matterbridgeEndpointDefault.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 +167 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -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 +253 -9
- package/dist/utils/utils.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2025-01-13
|
|
7
|
+
* @version 1.0.0
|
|
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, 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;
|
|
@@ -24,15 +67,26 @@ export class Frontend {
|
|
|
24
67
|
webSocketServer;
|
|
25
68
|
constructor(matterbridge) {
|
|
26
69
|
this.matterbridge = matterbridge;
|
|
27
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
70
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
28
71
|
}
|
|
29
72
|
async start(port = 8283) {
|
|
30
73
|
this.port = port;
|
|
31
74
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
75
|
+
// Create the express app that serves the frontend
|
|
32
76
|
this.expressApp = express();
|
|
77
|
+
// Log all requests to the server for debugging
|
|
78
|
+
/*
|
|
79
|
+
this.expressApp.use((req, res, next) => {
|
|
80
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
81
|
+
next();
|
|
82
|
+
});
|
|
83
|
+
*/
|
|
84
|
+
// Serve static files from '/static' endpoint
|
|
33
85
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
34
86
|
if (!hasParameter('ssl')) {
|
|
87
|
+
// Create an HTTP server and attach the express app
|
|
35
88
|
this.httpServer = createServer(this.expressApp);
|
|
89
|
+
// Listen on the specified port
|
|
36
90
|
if (hasParameter('ingress')) {
|
|
37
91
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
38
92
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -46,6 +100,7 @@ export class Frontend {
|
|
|
46
100
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
47
101
|
});
|
|
48
102
|
}
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
104
|
this.httpServer.on('error', (error) => {
|
|
50
105
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
51
106
|
switch (error.code) {
|
|
@@ -61,6 +116,7 @@ export class Frontend {
|
|
|
61
116
|
});
|
|
62
117
|
}
|
|
63
118
|
else {
|
|
119
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
64
120
|
let cert;
|
|
65
121
|
try {
|
|
66
122
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -88,7 +144,9 @@ export class Frontend {
|
|
|
88
144
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
89
145
|
}
|
|
90
146
|
const serverOptions = { cert, key, ca };
|
|
147
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
91
148
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
149
|
+
// Listen on the specified port
|
|
92
150
|
if (hasParameter('ingress')) {
|
|
93
151
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
94
152
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -102,6 +160,7 @@ export class Frontend {
|
|
|
102
160
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
103
161
|
});
|
|
104
162
|
}
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
164
|
this.httpsServer.on('error', (error) => {
|
|
106
165
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
107
166
|
switch (error.code) {
|
|
@@ -118,12 +177,13 @@ export class Frontend {
|
|
|
118
177
|
}
|
|
119
178
|
if (this.initializeError)
|
|
120
179
|
return;
|
|
180
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
121
181
|
const wssPort = this.port;
|
|
122
182
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
123
183
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
124
184
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
125
185
|
const clientIp = request.socket.remoteAddress;
|
|
126
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
186
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
127
187
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
128
188
|
ws.on('message', (message) => {
|
|
129
189
|
this.wsMessageHandler(ws, message);
|
|
@@ -155,6 +215,7 @@ export class Frontend {
|
|
|
155
215
|
this.webSocketServer.on('error', (ws, error) => {
|
|
156
216
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
157
217
|
});
|
|
218
|
+
// Endpoint to validate login code
|
|
158
219
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
159
220
|
const { password } = req.body;
|
|
160
221
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -173,21 +234,66 @@ export class Frontend {
|
|
|
173
234
|
this.log.warn('/api/login error wrong password');
|
|
174
235
|
res.json({ valid: false });
|
|
175
236
|
}
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
176
238
|
}
|
|
177
239
|
catch (error) {
|
|
178
240
|
this.log.error('/api/login error getting password');
|
|
179
241
|
res.json({ valid: false });
|
|
180
242
|
}
|
|
181
243
|
});
|
|
244
|
+
// Endpoint to provide health check
|
|
182
245
|
this.expressApp.get('/health', (req, res) => {
|
|
183
246
|
this.log.debug('Express received /health');
|
|
184
247
|
const healthStatus = {
|
|
185
|
-
status: 'ok',
|
|
186
|
-
uptime: process.uptime(),
|
|
187
|
-
timestamp: new Date().toISOString(),
|
|
248
|
+
status: 'ok', // Indicate service is healthy
|
|
249
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
250
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
188
251
|
};
|
|
189
252
|
res.status(200).json(healthStatus);
|
|
190
253
|
});
|
|
254
|
+
// Endpoint to provide memory usage details
|
|
255
|
+
this.expressApp.get('/memory', async (req, res) => {
|
|
256
|
+
this.log.debug('Express received /memory');
|
|
257
|
+
// Function to format bytes to KB or MB
|
|
258
|
+
const formatMemoryUsage = (bytes) => {
|
|
259
|
+
const kb = bytes / 1024;
|
|
260
|
+
const mb = kb / 1024;
|
|
261
|
+
return mb >= 1 ? `${mb.toFixed(2)} MB` : `${kb.toFixed(2)} KB`;
|
|
262
|
+
};
|
|
263
|
+
// Memory usage from process
|
|
264
|
+
const memoryUsageRaw = process.memoryUsage();
|
|
265
|
+
const memoryUsage = {
|
|
266
|
+
rss: formatMemoryUsage(memoryUsageRaw.rss),
|
|
267
|
+
heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
|
|
268
|
+
heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
|
|
269
|
+
external: formatMemoryUsage(memoryUsageRaw.external),
|
|
270
|
+
arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
271
|
+
};
|
|
272
|
+
// V8 heap statistics
|
|
273
|
+
const { default: v8 } = await import('node:v8');
|
|
274
|
+
const heapStatsRaw = v8.getHeapStatistics();
|
|
275
|
+
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
276
|
+
// Format heapStats
|
|
277
|
+
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
|
|
278
|
+
// Format heapSpaces
|
|
279
|
+
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
280
|
+
...space,
|
|
281
|
+
space_size: formatMemoryUsage(space.space_size),
|
|
282
|
+
space_used_size: formatMemoryUsage(space.space_used_size),
|
|
283
|
+
space_available_size: formatMemoryUsage(space.space_available_size),
|
|
284
|
+
physical_space_size: formatMemoryUsage(space.physical_space_size),
|
|
285
|
+
}));
|
|
286
|
+
const { default: module } = await import('module');
|
|
287
|
+
const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
|
|
288
|
+
const memoryReport = {
|
|
289
|
+
memoryUsage,
|
|
290
|
+
heapStats,
|
|
291
|
+
heapSpaces,
|
|
292
|
+
loadedModules,
|
|
293
|
+
};
|
|
294
|
+
res.status(200).json(memoryReport);
|
|
295
|
+
});
|
|
296
|
+
// Endpoint to provide settings
|
|
191
297
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
192
298
|
this.log.debug('The frontend sent /api/settings');
|
|
193
299
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
@@ -207,17 +313,22 @@ export class Frontend {
|
|
|
207
313
|
this.matterbridge.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridge.matterbridgeSessionInformations.values());
|
|
208
314
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
209
315
|
const response = { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
316
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
210
317
|
res.json(response);
|
|
211
318
|
});
|
|
319
|
+
// Endpoint to provide plugins
|
|
212
320
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
213
321
|
this.log.debug('The frontend sent /api/plugins');
|
|
214
322
|
const response = await this.getBaseRegisteredPlugins();
|
|
323
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
215
324
|
res.json(response);
|
|
216
325
|
});
|
|
326
|
+
// Endpoint to provide devices
|
|
217
327
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
218
328
|
this.log.debug('The frontend sent /api/devices');
|
|
219
329
|
const devices = [];
|
|
220
330
|
this.matterbridge.devices.forEach(async (device) => {
|
|
331
|
+
// Check if the device has the required properties
|
|
221
332
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
222
333
|
return;
|
|
223
334
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -233,8 +344,10 @@ export class Frontend {
|
|
|
233
344
|
cluster: cluster,
|
|
234
345
|
});
|
|
235
346
|
});
|
|
347
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
236
348
|
res.json(devices);
|
|
237
349
|
});
|
|
350
|
+
// Endpoint to provide the cluster servers of the devices
|
|
238
351
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
239
352
|
const selectedPluginName = req.params.selectedPluginName;
|
|
240
353
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -307,6 +420,7 @@ export class Frontend {
|
|
|
307
420
|
});
|
|
308
421
|
res.json(data);
|
|
309
422
|
});
|
|
423
|
+
// Endpoint to view the log
|
|
310
424
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
311
425
|
this.log.debug('The frontend sent /api/log');
|
|
312
426
|
try {
|
|
@@ -319,10 +433,12 @@ export class Frontend {
|
|
|
319
433
|
res.status(500).send('Error reading log file');
|
|
320
434
|
}
|
|
321
435
|
});
|
|
436
|
+
// Endpoint to download the matterbridge log
|
|
322
437
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
323
438
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
324
439
|
try {
|
|
325
440
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
441
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
326
442
|
}
|
|
327
443
|
catch (error) {
|
|
328
444
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -334,10 +450,12 @@ export class Frontend {
|
|
|
334
450
|
}
|
|
335
451
|
});
|
|
336
452
|
});
|
|
453
|
+
// Endpoint to download the matter log
|
|
337
454
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
338
455
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
339
456
|
try {
|
|
340
457
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
341
459
|
}
|
|
342
460
|
catch (error) {
|
|
343
461
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -349,6 +467,7 @@ export class Frontend {
|
|
|
349
467
|
}
|
|
350
468
|
});
|
|
351
469
|
});
|
|
470
|
+
// Endpoint to download the matter storage file
|
|
352
471
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
353
472
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
354
473
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -359,6 +478,7 @@ export class Frontend {
|
|
|
359
478
|
}
|
|
360
479
|
});
|
|
361
480
|
});
|
|
481
|
+
// Endpoint to download the matterbridge storage directory
|
|
362
482
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
363
483
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
364
484
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -369,6 +489,7 @@ export class Frontend {
|
|
|
369
489
|
}
|
|
370
490
|
});
|
|
371
491
|
});
|
|
492
|
+
// Endpoint to download the matterbridge plugin directory
|
|
372
493
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
373
494
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
374
495
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -379,9 +500,11 @@ export class Frontend {
|
|
|
379
500
|
}
|
|
380
501
|
});
|
|
381
502
|
});
|
|
503
|
+
// Endpoint to download the matterbridge plugin config files
|
|
382
504
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
383
505
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
384
506
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
507
|
+
// 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')));
|
|
385
508
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
386
509
|
if (error) {
|
|
387
510
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -389,6 +512,7 @@ export class Frontend {
|
|
|
389
512
|
}
|
|
390
513
|
});
|
|
391
514
|
});
|
|
515
|
+
// Endpoint to download the matterbridge plugin config files
|
|
392
516
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
393
517
|
this.log.debug('The frontend sent /api/download-backup');
|
|
394
518
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -398,6 +522,7 @@ export class Frontend {
|
|
|
398
522
|
}
|
|
399
523
|
});
|
|
400
524
|
});
|
|
525
|
+
// Endpoint to receive commands
|
|
401
526
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
402
527
|
const command = req.params.command;
|
|
403
528
|
let param = req.params.param;
|
|
@@ -407,13 +532,15 @@ export class Frontend {
|
|
|
407
532
|
return;
|
|
408
533
|
}
|
|
409
534
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
535
|
+
// Handle the command setpassword from Settings
|
|
410
536
|
if (command === 'setpassword') {
|
|
411
|
-
const password = param.slice(1, -1);
|
|
537
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
412
538
|
this.log.debug('setpassword', param, password);
|
|
413
539
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
414
540
|
res.json({ message: 'Command received' });
|
|
415
541
|
return;
|
|
416
542
|
}
|
|
543
|
+
// Handle the command setbridgemode from Settings
|
|
417
544
|
if (command === 'setbridgemode') {
|
|
418
545
|
this.log.debug(`setbridgemode: ${param}`);
|
|
419
546
|
this.wssSendRestartRequired();
|
|
@@ -421,6 +548,7 @@ export class Frontend {
|
|
|
421
548
|
res.json({ message: 'Command received' });
|
|
422
549
|
return;
|
|
423
550
|
}
|
|
551
|
+
// Handle the command backup from Settings
|
|
424
552
|
if (command === 'backup') {
|
|
425
553
|
this.log.notice(`Prepairing the backup...`);
|
|
426
554
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -428,25 +556,26 @@ export class Frontend {
|
|
|
428
556
|
res.json({ message: 'Command received' });
|
|
429
557
|
return;
|
|
430
558
|
}
|
|
559
|
+
// Handle the command setmbloglevel from Settings
|
|
431
560
|
if (command === 'setmbloglevel') {
|
|
432
561
|
this.log.debug('Matterbridge log level:', param);
|
|
433
562
|
if (param === 'Debug') {
|
|
434
|
-
this.log.logLevel = "debug"
|
|
563
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
435
564
|
}
|
|
436
565
|
else if (param === 'Info') {
|
|
437
|
-
this.log.logLevel = "info"
|
|
566
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
438
567
|
}
|
|
439
568
|
else if (param === 'Notice') {
|
|
440
|
-
this.log.logLevel = "notice"
|
|
569
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
441
570
|
}
|
|
442
571
|
else if (param === 'Warn') {
|
|
443
|
-
this.log.logLevel = "warn"
|
|
572
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
444
573
|
}
|
|
445
574
|
else if (param === 'Error') {
|
|
446
|
-
this.log.logLevel = "error"
|
|
575
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
447
576
|
}
|
|
448
577
|
else if (param === 'Fatal') {
|
|
449
|
-
this.log.logLevel = "fatal"
|
|
578
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
450
579
|
}
|
|
451
580
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
452
581
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
@@ -454,12 +583,13 @@ export class Frontend {
|
|
|
454
583
|
for (const plugin of this.matterbridge.plugins) {
|
|
455
584
|
if (!plugin.platform || !plugin.platform.config)
|
|
456
585
|
continue;
|
|
457
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
458
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
586
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
587
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
459
588
|
}
|
|
460
589
|
res.json({ message: 'Command received' });
|
|
461
590
|
return;
|
|
462
591
|
}
|
|
592
|
+
// Handle the command setmbloglevel from Settings
|
|
463
593
|
if (command === 'setmjloglevel') {
|
|
464
594
|
this.log.debug('Matter.js log level:', param);
|
|
465
595
|
if (param === 'Debug') {
|
|
@@ -484,30 +614,34 @@ export class Frontend {
|
|
|
484
614
|
res.json({ message: 'Command received' });
|
|
485
615
|
return;
|
|
486
616
|
}
|
|
617
|
+
// Handle the command setmdnsinterface from Settings
|
|
487
618
|
if (command === 'setmdnsinterface') {
|
|
488
|
-
param = param.slice(1, -1);
|
|
619
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
489
620
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
490
621
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
491
622
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
492
623
|
res.json({ message: 'Command received' });
|
|
493
624
|
return;
|
|
494
625
|
}
|
|
626
|
+
// Handle the command setipv4address from Settings
|
|
495
627
|
if (command === 'setipv4address') {
|
|
496
|
-
param = param.slice(1, -1);
|
|
628
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
497
629
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
498
630
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
499
631
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
500
632
|
res.json({ message: 'Command received' });
|
|
501
633
|
return;
|
|
502
634
|
}
|
|
635
|
+
// Handle the command setipv6address from Settings
|
|
503
636
|
if (command === 'setipv6address') {
|
|
504
|
-
param = param.slice(1, -1);
|
|
637
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
505
638
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
506
639
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
507
640
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
508
641
|
res.json({ message: 'Command received' });
|
|
509
642
|
return;
|
|
510
643
|
}
|
|
644
|
+
// Handle the command setmatterport from Settings
|
|
511
645
|
if (command === 'setmatterport') {
|
|
512
646
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
513
647
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -516,6 +650,7 @@ export class Frontend {
|
|
|
516
650
|
res.json({ message: 'Command received' });
|
|
517
651
|
return;
|
|
518
652
|
}
|
|
653
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
519
654
|
if (command === 'setmatterdiscriminator') {
|
|
520
655
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
521
656
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -524,6 +659,7 @@ export class Frontend {
|
|
|
524
659
|
res.json({ message: 'Command received' });
|
|
525
660
|
return;
|
|
526
661
|
}
|
|
662
|
+
// Handle the command setmatterpasscode from Settings
|
|
527
663
|
if (command === 'setmatterpasscode') {
|
|
528
664
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
529
665
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -532,17 +668,20 @@ export class Frontend {
|
|
|
532
668
|
res.json({ message: 'Command received' });
|
|
533
669
|
return;
|
|
534
670
|
}
|
|
671
|
+
// Handle the command setmbloglevel from Settings
|
|
535
672
|
if (command === 'setmblogfile') {
|
|
536
673
|
this.log.debug('Matterbridge file log:', param);
|
|
537
674
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
538
675
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
676
|
+
// Create the file logger for matterbridge
|
|
539
677
|
if (param === 'true')
|
|
540
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
678
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
541
679
|
else
|
|
542
680
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
543
681
|
res.json({ message: 'Command received' });
|
|
544
682
|
return;
|
|
545
683
|
}
|
|
684
|
+
// Handle the command setmbloglevel from Settings
|
|
546
685
|
if (command === 'setmjlogfile') {
|
|
547
686
|
this.log.debug('Matter file log:', param);
|
|
548
687
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -569,36 +708,43 @@ export class Frontend {
|
|
|
569
708
|
res.json({ message: 'Command received' });
|
|
570
709
|
return;
|
|
571
710
|
}
|
|
711
|
+
// Handle the command unregister from Settings
|
|
572
712
|
if (command === 'unregister') {
|
|
573
713
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
574
714
|
res.json({ message: 'Command received' });
|
|
575
715
|
return;
|
|
576
716
|
}
|
|
717
|
+
// Handle the command reset from Settings
|
|
577
718
|
if (command === 'reset') {
|
|
578
719
|
await this.matterbridge.shutdownProcessAndReset();
|
|
579
720
|
res.json({ message: 'Command received' });
|
|
580
721
|
return;
|
|
581
722
|
}
|
|
723
|
+
// Handle the command factoryreset from Settings
|
|
582
724
|
if (command === 'factoryreset') {
|
|
583
725
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
584
726
|
res.json({ message: 'Command received' });
|
|
585
727
|
return;
|
|
586
728
|
}
|
|
729
|
+
// Handle the command shutdown from Header
|
|
587
730
|
if (command === 'shutdown') {
|
|
588
731
|
await this.matterbridge.shutdownProcess();
|
|
589
732
|
res.json({ message: 'Command received' });
|
|
590
733
|
return;
|
|
591
734
|
}
|
|
735
|
+
// Handle the command restart from Header
|
|
592
736
|
if (command === 'restart') {
|
|
593
737
|
await this.matterbridge.restartProcess();
|
|
594
738
|
res.json({ message: 'Command received' });
|
|
595
739
|
return;
|
|
596
740
|
}
|
|
741
|
+
// Handle the command update from Header
|
|
597
742
|
if (command === 'update') {
|
|
598
743
|
this.log.info('Updating matterbridge...');
|
|
599
744
|
try {
|
|
600
745
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
601
746
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
747
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
602
748
|
}
|
|
603
749
|
catch (error) {
|
|
604
750
|
this.log.error('Error updating matterbridge');
|
|
@@ -608,9 +754,11 @@ export class Frontend {
|
|
|
608
754
|
res.json({ message: 'Command received' });
|
|
609
755
|
return;
|
|
610
756
|
}
|
|
757
|
+
// Handle the command saveconfig from Home
|
|
611
758
|
if (command === 'saveconfig') {
|
|
612
759
|
param = param.replace(/\*/g, '\\');
|
|
613
760
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
761
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
614
762
|
if (!this.matterbridge.plugins.has(param)) {
|
|
615
763
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
616
764
|
}
|
|
@@ -624,49 +772,58 @@ export class Frontend {
|
|
|
624
772
|
res.json({ message: 'Command received' });
|
|
625
773
|
return;
|
|
626
774
|
}
|
|
775
|
+
// Handle the command installplugin from Home
|
|
627
776
|
if (command === 'installplugin') {
|
|
628
777
|
param = param.replace(/\*/g, '\\');
|
|
629
778
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
630
779
|
try {
|
|
631
780
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
632
781
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
782
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
633
783
|
}
|
|
634
784
|
catch (error) {
|
|
635
785
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
636
786
|
}
|
|
637
787
|
this.wssSendRestartRequired();
|
|
638
788
|
param = param.split('@')[0];
|
|
789
|
+
// Also add the plugin to matterbridge so no return!
|
|
639
790
|
if (param === 'matterbridge') {
|
|
791
|
+
// 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
|
|
640
792
|
res.json({ message: 'Command received' });
|
|
641
793
|
return;
|
|
642
794
|
}
|
|
643
795
|
}
|
|
796
|
+
// Handle the command addplugin from Home
|
|
644
797
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
645
798
|
param = param.replace(/\*/g, '\\');
|
|
646
799
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
647
800
|
if (plugin) {
|
|
648
801
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
802
|
+
// 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
|
|
803
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
649
804
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
650
805
|
}
|
|
651
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
806
|
+
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
652
807
|
}
|
|
653
808
|
res.json({ message: 'Command received' });
|
|
654
809
|
this.wssSendRefreshRequired();
|
|
655
810
|
return;
|
|
656
811
|
}
|
|
812
|
+
// Handle the command removeplugin from Home
|
|
657
813
|
if (command === 'removeplugin') {
|
|
658
814
|
if (!this.matterbridge.plugins.has(param)) {
|
|
659
815
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
660
816
|
}
|
|
661
817
|
else {
|
|
662
818
|
const plugin = this.matterbridge.plugins.get(param);
|
|
663
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
819
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
664
820
|
await this.matterbridge.plugins.remove(param);
|
|
665
821
|
}
|
|
666
822
|
res.json({ message: 'Command received' });
|
|
667
823
|
this.wssSendRefreshRequired();
|
|
668
824
|
return;
|
|
669
825
|
}
|
|
826
|
+
// Handle the command enableplugin from Home
|
|
670
827
|
if (command === 'enableplugin') {
|
|
671
828
|
if (!this.matterbridge.plugins.has(param)) {
|
|
672
829
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -684,15 +841,17 @@ export class Frontend {
|
|
|
684
841
|
plugin.addedDevices = undefined;
|
|
685
842
|
await this.matterbridge.plugins.enable(param);
|
|
686
843
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
844
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
687
845
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
688
846
|
}
|
|
689
|
-
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
847
|
+
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
|
|
690
848
|
}
|
|
691
849
|
}
|
|
692
850
|
res.json({ message: 'Command received' });
|
|
693
851
|
this.wssSendRefreshRequired();
|
|
694
852
|
return;
|
|
695
853
|
}
|
|
854
|
+
// Handle the command disableplugin from Home
|
|
696
855
|
if (command === 'disableplugin') {
|
|
697
856
|
if (!this.matterbridge.plugins.has(param)) {
|
|
698
857
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -700,7 +859,7 @@ export class Frontend {
|
|
|
700
859
|
else {
|
|
701
860
|
const plugin = this.matterbridge.plugins.get(param);
|
|
702
861
|
if (plugin && plugin.enabled) {
|
|
703
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
862
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
704
863
|
await this.matterbridge.plugins.disable(param);
|
|
705
864
|
}
|
|
706
865
|
}
|
|
@@ -709,6 +868,7 @@ export class Frontend {
|
|
|
709
868
|
return;
|
|
710
869
|
}
|
|
711
870
|
});
|
|
871
|
+
// Fallback for routing (must be the last route)
|
|
712
872
|
this.expressApp.get('*', (req, res) => {
|
|
713
873
|
this.log.debug('The frontend sent:', req.url);
|
|
714
874
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -717,24 +877,29 @@ export class Frontend {
|
|
|
717
877
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
718
878
|
}
|
|
719
879
|
async stop() {
|
|
880
|
+
// Close the http server
|
|
720
881
|
if (this.httpServer) {
|
|
721
882
|
this.httpServer.close();
|
|
722
883
|
this.httpServer.removeAllListeners();
|
|
723
884
|
this.httpServer = undefined;
|
|
724
885
|
this.log.debug('Frontend http server closed successfully');
|
|
725
886
|
}
|
|
887
|
+
// Close the https server
|
|
726
888
|
if (this.httpsServer) {
|
|
727
889
|
this.httpsServer.close();
|
|
728
890
|
this.httpsServer.removeAllListeners();
|
|
729
891
|
this.httpsServer = undefined;
|
|
730
892
|
this.log.debug('Frontend https server closed successfully');
|
|
731
893
|
}
|
|
894
|
+
// Remove listeners from the express app
|
|
732
895
|
if (this.expressApp) {
|
|
733
896
|
this.expressApp.removeAllListeners();
|
|
734
897
|
this.expressApp = undefined;
|
|
735
898
|
this.log.debug('Frontend app closed successfully');
|
|
736
899
|
}
|
|
900
|
+
// Close the WebSocket server
|
|
737
901
|
if (this.webSocketServer) {
|
|
902
|
+
// Close all active connections
|
|
738
903
|
this.webSocketServer.clients.forEach((client) => {
|
|
739
904
|
if (client.readyState === WebSocket.OPEN) {
|
|
740
905
|
client.close();
|
|
@@ -751,6 +916,11 @@ export class Frontend {
|
|
|
751
916
|
this.webSocketServer = undefined;
|
|
752
917
|
}
|
|
753
918
|
}
|
|
919
|
+
/**
|
|
920
|
+
* Retrieves the cluster text description from a given device.
|
|
921
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
922
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
923
|
+
*/
|
|
754
924
|
getClusterTextFromDevice(device) {
|
|
755
925
|
const getAttribute = (device, cluster, attribute) => {
|
|
756
926
|
let value = undefined;
|
|
@@ -788,8 +958,10 @@ export class Frontend {
|
|
|
788
958
|
return '';
|
|
789
959
|
};
|
|
790
960
|
let attributes = '';
|
|
961
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
791
962
|
Object.entries(device.state).forEach(([clusterName, clusterAttributes]) => {
|
|
792
963
|
Object.entries(clusterAttributes).forEach(([attributeName, attributeValue]) => {
|
|
964
|
+
// console.log(`Cluster: ${clusterName} Attribute: ${attributeName} Value: ${attributeValue}`);
|
|
793
965
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
794
966
|
attributes += `OnOff: ${attributeValue} `;
|
|
795
967
|
if (clusterName === 'switch' && attributeName === 'currentPosition')
|
|
@@ -860,6 +1032,10 @@ export class Frontend {
|
|
|
860
1032
|
});
|
|
861
1033
|
return attributes.trimStart().trimEnd();
|
|
862
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1037
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
1038
|
+
*/
|
|
863
1039
|
async getBaseRegisteredPlugins() {
|
|
864
1040
|
const baseRegisteredPlugins = [];
|
|
865
1041
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -890,6 +1066,14 @@ export class Frontend {
|
|
|
890
1066
|
}
|
|
891
1067
|
return baseRegisteredPlugins;
|
|
892
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Handles incoming websocket messages for the Matterbridge.
|
|
1071
|
+
*
|
|
1072
|
+
* @param {Matterbridge} this - The Matterbridge instance.
|
|
1073
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1074
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1075
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1076
|
+
*/
|
|
893
1077
|
async wsMessageHandler(client, message) {
|
|
894
1078
|
let data;
|
|
895
1079
|
try {
|
|
@@ -989,8 +1173,10 @@ export class Frontend {
|
|
|
989
1173
|
else if (data.method === '/api/devices') {
|
|
990
1174
|
const devices = [];
|
|
991
1175
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1176
|
+
// Filter by pluginName if provided
|
|
992
1177
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
993
1178
|
return;
|
|
1179
|
+
// Check if the device has the required properties
|
|
994
1180
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
|
|
995
1181
|
return;
|
|
996
1182
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1070,6 +1256,7 @@ export class Frontend {
|
|
|
1070
1256
|
});
|
|
1071
1257
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1072
1258
|
deviceTypes = [];
|
|
1259
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1073
1260
|
const name = childEndpoint.endpoint?.id;
|
|
1074
1261
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1075
1262
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1152,44 +1339,73 @@ export class Frontend {
|
|
|
1152
1339
|
return;
|
|
1153
1340
|
}
|
|
1154
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1344
|
+
*
|
|
1345
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1346
|
+
* @param {string} time - The time string of the message
|
|
1347
|
+
* @param {string} name - The logger name of the message
|
|
1348
|
+
* @param {string} message - The content of the message.
|
|
1349
|
+
*/
|
|
1155
1350
|
wssSendMessage(level, time, name, message) {
|
|
1156
1351
|
if (!level || !time || !name || !message)
|
|
1157
1352
|
return;
|
|
1353
|
+
// Remove ANSI escape codes from the message
|
|
1354
|
+
// eslint-disable-next-line no-control-regex
|
|
1158
1355
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1356
|
+
// Remove leading asterisks from the message
|
|
1159
1357
|
message = message.replace(/^\*+/, '');
|
|
1358
|
+
// Replace all occurrences of \t and \n
|
|
1160
1359
|
message = message.replace(/[\t\n]/g, '');
|
|
1360
|
+
// Remove non-printable characters
|
|
1361
|
+
// eslint-disable-next-line no-control-regex
|
|
1161
1362
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1363
|
+
// Replace all occurrences of \" with "
|
|
1162
1364
|
message = message.replace(/\\"/g, '"');
|
|
1365
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1163
1366
|
const maxContinuousLength = 100;
|
|
1164
1367
|
const keepStartLength = 20;
|
|
1165
1368
|
const keepEndLength = 20;
|
|
1369
|
+
// Split the message into words
|
|
1166
1370
|
message = message
|
|
1167
1371
|
.split(' ')
|
|
1168
1372
|
.map((word) => {
|
|
1373
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1169
1374
|
if (word.length > maxContinuousLength) {
|
|
1170
1375
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1171
1376
|
}
|
|
1172
1377
|
return word;
|
|
1173
1378
|
})
|
|
1174
1379
|
.join(' ');
|
|
1380
|
+
// Send the message to all connected clients
|
|
1175
1381
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1176
1382
|
if (client.readyState === WebSocket.OPEN) {
|
|
1177
1383
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1178
1384
|
}
|
|
1179
1385
|
});
|
|
1180
1386
|
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1389
|
+
*
|
|
1390
|
+
*/
|
|
1181
1391
|
wssSendRefreshRequired() {
|
|
1182
1392
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1183
1393
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1394
|
+
// Send the message to all connected clients
|
|
1184
1395
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1185
1396
|
if (client.readyState === WebSocket.OPEN) {
|
|
1186
1397
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1187
1398
|
}
|
|
1188
1399
|
});
|
|
1189
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1403
|
+
*
|
|
1404
|
+
*/
|
|
1190
1405
|
wssSendRestartRequired() {
|
|
1191
1406
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1192
1407
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1408
|
+
// Send the message to all connected clients
|
|
1193
1409
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1194
1410
|
if (client.readyState === WebSocket.OPEN) {
|
|
1195
1411
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
@@ -1197,3 +1413,4 @@ export class Frontend {
|
|
|
1197
1413
|
});
|
|
1198
1414
|
}
|
|
1199
1415
|
}
|
|
1416
|
+
//# sourceMappingURL=frontend.js.map
|