matterbridge 3.3.6 → 3.3.7-dev-20251104-7c779b9
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 +21 -0
- package/dist/broadcastServer.js +1 -92
- package/dist/broadcastServerTypes.js +0 -24
- package/dist/cli.js +1 -97
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.js +0 -38
- package/dist/clusters/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -24
- package/dist/deviceManager.js +1 -124
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -42
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +55 -451
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +0 -53
- package/dist/index.js +0 -25
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterbridge.js +66 -841
- package/dist/matterbridgeAccessoryPlatform.js +0 -37
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.js +17 -638
- package/dist/matterbridgeDynamicPlatform.js +0 -37
- package/dist/matterbridgeEndpoint.js +52 -1408
- package/dist/matterbridgeEndpointHelpers.js +19 -464
- package/dist/matterbridgePlatform.js +1 -341
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +3 -319
- package/dist/shelly.js +7 -168
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -69
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -60
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -41
- package/dist/utils/export.js +0 -1
- package/dist/utils/format.js +0 -49
- package/dist/utils/hex.js +0 -124
- package/dist/utils/inspector.js +1 -69
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/jestHelpers.js +3 -153
- package/dist/utils/network.js +5 -96
- package/dist/utils/spawn.js +0 -71
- package/dist/utils/tracker.js +1 -64
- package/dist/utils/wait.js +8 -60
- package/frontend/build/assets/index.css +1 -1
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/broadcastServer.d.ts +0 -112
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts +0 -803
- package/dist/broadcastServerTypes.d.ts.map +0 -1
- package/dist/broadcastServerTypes.js.map +0 -1
- package/dist/cli.d.ts +0 -30
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts +0 -50
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts +0 -48
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -28
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -117
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts +0 -98
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts +0 -48
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts +0 -60
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts +0 -71
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts +0 -76
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts +0 -17
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts +0 -46
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts +0 -47
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts +0 -67
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts +0 -81
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts +0 -168
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts +0 -105
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts +0 -118
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts +0 -40
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts +0 -87
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts +0 -166
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts +0 -111
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/coap.d.ts +0 -205
- package/dist/dgram/coap.d.ts.map +0 -1
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -141
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js.map +0 -1
- package/dist/dgram/mb_coap.d.ts +0 -24
- package/dist/dgram/mb_coap.d.ts.map +0 -1
- package/dist/dgram/mb_coap.js.map +0 -1
- package/dist/dgram/mb_mdns.d.ts +0 -24
- package/dist/dgram/mb_mdns.d.ts.map +0 -1
- package/dist/dgram/mb_mdns.js.map +0 -1
- package/dist/dgram/mdns.d.ts +0 -290
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js.map +0 -1
- package/dist/dgram/multicast.d.ts +0 -67
- package/dist/dgram/multicast.d.ts.map +0 -1
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -56
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts +0 -236
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts +0 -529
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -33
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -476
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -2404
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -770
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1556
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -758
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -402
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -226
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -347
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -174
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -75
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -101
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -66
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -33
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -44
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js.map +0 -1
- package/dist/utils/export.d.ts +0 -13
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/format.d.ts +0 -53
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -89
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/inspector.d.ts +0 -87
- package/dist/utils/inspector.d.ts.map +0 -1
- package/dist/utils/inspector.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/jestHelpers.d.ts +0 -139
- package/dist/utils/jestHelpers.d.ts.map +0 -1
- package/dist/utils/jestHelpers.js.map +0 -1
- package/dist/utils/network.d.ts +0 -101
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -35
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/tracker.d.ts +0 -108
- package/dist/utils/tracker.d.ts.map +0 -1
- package/dist/utils/tracker.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -54
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js
CHANGED
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Frontend.
|
|
3
|
-
*
|
|
4
|
-
* @file frontend.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @created 2025-01-13
|
|
7
|
-
* @version 1.3.0
|
|
8
|
-
* @license Apache-2.0
|
|
9
|
-
*
|
|
10
|
-
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
-
*
|
|
12
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
-
* you may not use this file except in compliance with the License.
|
|
14
|
-
* You may obtain a copy of the License at
|
|
15
|
-
*
|
|
16
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
-
*
|
|
18
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
* See the License for the specific language governing permissions and
|
|
22
|
-
* limitations under the License.
|
|
23
|
-
*/
|
|
24
|
-
// eslint-disable-next-line no-console
|
|
25
1
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
26
2
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
|
-
// Node modules
|
|
28
3
|
import os from 'node:os';
|
|
29
4
|
import path from 'node:path';
|
|
30
5
|
import EventEmitter from 'node:events';
|
|
31
|
-
// AnsiLogger module
|
|
32
6
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
33
7
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
34
8
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
@@ -61,7 +35,7 @@ export class Frontend extends EventEmitter {
|
|
|
61
35
|
constructor(matterbridge) {
|
|
62
36
|
super();
|
|
63
37
|
this.matterbridge = matterbridge;
|
|
64
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
38
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
65
39
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
66
40
|
this.server = new BroadcastServer('frontend', this.log);
|
|
67
41
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -156,53 +130,23 @@ export class Frontend extends EventEmitter {
|
|
|
156
130
|
this.port = port;
|
|
157
131
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
158
132
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
159
|
-
// Initialize multer with the upload directory
|
|
160
133
|
const multer = await import('multer');
|
|
161
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
134
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
162
135
|
const upload = multer.default({ dest: uploadDir });
|
|
163
|
-
// Create the express app that serves the frontend
|
|
164
136
|
const express = await import('express');
|
|
165
137
|
this.expressApp = express.default();
|
|
166
|
-
|
|
167
|
-
/*
|
|
168
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
169
|
-
for (const method of methods) {
|
|
170
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
172
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
174
|
-
try {
|
|
175
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
176
|
-
return original(path, ...rest);
|
|
177
|
-
} catch (err) {
|
|
178
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
179
|
-
throw err;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
*/
|
|
184
|
-
// Log all requests to the server for debugging
|
|
185
|
-
/*
|
|
186
|
-
this.expressApp.use((req, res, next) => {
|
|
187
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
188
|
-
next();
|
|
189
|
-
});
|
|
190
|
-
*/
|
|
191
|
-
// Serve static files from 'frontend/build' directory
|
|
192
|
-
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
193
|
-
// Create a WebSocket server and attach it to the http or https server
|
|
138
|
+
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
194
139
|
this.log.debug(`Creating WebSocketServer...`);
|
|
195
140
|
const ws = await import('ws');
|
|
196
141
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
197
142
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
198
143
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
199
144
|
const clientIp = request.socket.remoteAddress;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
145
|
+
let callbackLogLevel = "notice";
|
|
146
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
147
|
+
callbackLogLevel = "info";
|
|
148
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
149
|
+
callbackLogLevel = "debug";
|
|
206
150
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
207
151
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
208
152
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -224,25 +168,16 @@ export class Frontend extends EventEmitter {
|
|
|
224
168
|
}
|
|
225
169
|
});
|
|
226
170
|
ws.on('error', (error) => {
|
|
227
|
-
// istanbul ignore next
|
|
228
171
|
this.log.error(`WebSocket client error: ${error}`);
|
|
229
172
|
});
|
|
230
173
|
});
|
|
231
174
|
this.webSocketServer.on('close', () => {
|
|
232
175
|
this.log.debug(`WebSocketServer closed`);
|
|
233
176
|
});
|
|
234
|
-
/* With { noServer: true } it never fires
|
|
235
|
-
this.webSocketServer.on('listening', () => {
|
|
236
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
237
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
238
|
-
});
|
|
239
|
-
*/
|
|
240
|
-
// istanbul ignore next
|
|
241
177
|
this.webSocketServer.on('error', (ws, error) => {
|
|
242
178
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
243
179
|
});
|
|
244
180
|
if (!hasParameter('ssl')) {
|
|
245
|
-
// Create an HTTP server and attach the express app
|
|
246
181
|
const http = await import('node:http');
|
|
247
182
|
try {
|
|
248
183
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -253,7 +188,6 @@ export class Frontend extends EventEmitter {
|
|
|
253
188
|
this.emit('server_error', error);
|
|
254
189
|
return;
|
|
255
190
|
}
|
|
256
|
-
// Listen on the specified port
|
|
257
191
|
if (hasParameter('ingress')) {
|
|
258
192
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
259
193
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -273,29 +207,23 @@ export class Frontend extends EventEmitter {
|
|
|
273
207
|
}
|
|
274
208
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
275
209
|
try {
|
|
276
|
-
// Only proceed for real WebSocket upgrades
|
|
277
|
-
// istanbul ignore next cause is only a safety check
|
|
278
210
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
279
211
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
280
212
|
return socket.destroy();
|
|
281
213
|
}
|
|
282
|
-
// Build a URL so we can read ?password=...
|
|
283
214
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
284
|
-
// Validate WebSocket password
|
|
285
215
|
const password = url.searchParams.get('password') ?? '';
|
|
286
216
|
if (password !== this.storedPassword) {
|
|
287
217
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
288
218
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
289
219
|
return socket.destroy();
|
|
290
220
|
}
|
|
291
|
-
// Complete the WebSocket handshake
|
|
292
221
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
293
222
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
294
223
|
this.webSocketServer?.emit('connection', ws, req);
|
|
295
224
|
});
|
|
296
225
|
}
|
|
297
226
|
catch (err) {
|
|
298
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
299
227
|
{
|
|
300
228
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
301
229
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -318,7 +246,6 @@ export class Frontend extends EventEmitter {
|
|
|
318
246
|
});
|
|
319
247
|
}
|
|
320
248
|
else {
|
|
321
|
-
// SSL is enabled, load the certificate and the private key
|
|
322
249
|
let cert;
|
|
323
250
|
let key;
|
|
324
251
|
let ca;
|
|
@@ -327,64 +254,61 @@ export class Frontend extends EventEmitter {
|
|
|
327
254
|
let passphrase;
|
|
328
255
|
let httpsServerOptions = {};
|
|
329
256
|
const fs = await import('node:fs');
|
|
330
|
-
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
331
|
-
// Load the p12 certificate and the passphrase
|
|
257
|
+
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
332
258
|
try {
|
|
333
|
-
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
334
|
-
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
259
|
+
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
260
|
+
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
335
261
|
}
|
|
336
262
|
catch (error) {
|
|
337
|
-
this.log.error(`Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
263
|
+
this.log.error(`Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}: ${error}`);
|
|
338
264
|
this.emit('server_error', error);
|
|
339
265
|
return;
|
|
340
266
|
}
|
|
341
267
|
try {
|
|
342
|
-
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
343
|
-
passphrase = passphrase.trim();
|
|
344
|
-
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
268
|
+
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
269
|
+
passphrase = passphrase.trim();
|
|
270
|
+
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
345
271
|
}
|
|
346
272
|
catch (error) {
|
|
347
|
-
this.log.error(`Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
273
|
+
this.log.error(`Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}: ${error}`);
|
|
348
274
|
this.emit('server_error', error);
|
|
349
275
|
return;
|
|
350
276
|
}
|
|
351
277
|
httpsServerOptions = { pfx, passphrase };
|
|
352
278
|
}
|
|
353
279
|
else {
|
|
354
|
-
// Load the SSL certificate, the private key and optionally the CA certificate. If the CA certificate is present, it will be used to create a full chain certificate.
|
|
355
280
|
try {
|
|
356
|
-
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
357
|
-
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
281
|
+
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
282
|
+
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
358
283
|
}
|
|
359
284
|
catch (error) {
|
|
360
|
-
this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
285
|
+
this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}: ${error}`);
|
|
361
286
|
this.emit('server_error', error);
|
|
362
287
|
return;
|
|
363
288
|
}
|
|
364
289
|
try {
|
|
365
|
-
key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
366
|
-
this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
290
|
+
key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem'), 'utf8');
|
|
291
|
+
this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`);
|
|
367
292
|
}
|
|
368
293
|
catch (error) {
|
|
369
|
-
this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
294
|
+
this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}: ${error}`);
|
|
370
295
|
this.emit('server_error', error);
|
|
371
296
|
return;
|
|
372
297
|
}
|
|
373
298
|
try {
|
|
374
|
-
ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
299
|
+
ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem'), 'utf8');
|
|
375
300
|
fullChain = `${cert}\n${ca}`;
|
|
376
|
-
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
301
|
+
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')}`);
|
|
377
302
|
}
|
|
378
303
|
catch (error) {
|
|
379
|
-
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs
|
|
304
|
+
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')} not loaded: ${error}`);
|
|
380
305
|
}
|
|
381
306
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
382
307
|
}
|
|
383
308
|
if (hasParameter('mtls')) {
|
|
384
|
-
httpsServerOptions.requestCert = true;
|
|
385
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
309
|
+
httpsServerOptions.requestCert = true;
|
|
310
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
386
311
|
}
|
|
387
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
388
312
|
const https = await import('node:https');
|
|
389
313
|
try {
|
|
390
314
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -395,7 +319,6 @@ export class Frontend extends EventEmitter {
|
|
|
395
319
|
this.emit('server_error', error);
|
|
396
320
|
return;
|
|
397
321
|
}
|
|
398
|
-
// Listen on the specified port
|
|
399
322
|
if (hasParameter('ingress')) {
|
|
400
323
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
401
324
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -415,29 +338,23 @@ export class Frontend extends EventEmitter {
|
|
|
415
338
|
}
|
|
416
339
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
417
340
|
try {
|
|
418
|
-
// Only proceed for real WebSocket upgrades
|
|
419
|
-
// istanbul ignore next cause is only a safety check
|
|
420
341
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
421
342
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
422
343
|
return socket.destroy();
|
|
423
344
|
}
|
|
424
|
-
// Build a URL so we can read ?password=...
|
|
425
345
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
426
|
-
// Validate WebSocket password
|
|
427
346
|
const password = url.searchParams.get('password') ?? '';
|
|
428
347
|
if (password !== this.storedPassword) {
|
|
429
348
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
430
349
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
431
350
|
return socket.destroy();
|
|
432
351
|
}
|
|
433
|
-
// Complete the WebSocket handshake
|
|
434
352
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
435
353
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
436
354
|
this.webSocketServer?.emit('connection', ws, req);
|
|
437
355
|
});
|
|
438
356
|
}
|
|
439
357
|
catch (err) {
|
|
440
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
441
358
|
{
|
|
442
359
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
443
360
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -459,7 +376,6 @@ export class Frontend extends EventEmitter {
|
|
|
459
376
|
return;
|
|
460
377
|
});
|
|
461
378
|
}
|
|
462
|
-
// Subscribe to cli events
|
|
463
379
|
cliEmitter.removeAllListeners();
|
|
464
380
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
465
381
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -470,8 +386,6 @@ export class Frontend extends EventEmitter {
|
|
|
470
386
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
471
387
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
472
388
|
});
|
|
473
|
-
// Endpoint to validate login code
|
|
474
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
475
389
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
476
390
|
const { password } = req.body;
|
|
477
391
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -484,20 +398,17 @@ export class Frontend extends EventEmitter {
|
|
|
484
398
|
res.json({ valid: false });
|
|
485
399
|
}
|
|
486
400
|
});
|
|
487
|
-
// Endpoint to provide health check for docker
|
|
488
401
|
this.expressApp.get('/health', (req, res) => {
|
|
489
402
|
this.log.debug('Express received /health');
|
|
490
403
|
const healthStatus = {
|
|
491
|
-
status: 'ok',
|
|
492
|
-
uptime: process.uptime(),
|
|
493
|
-
timestamp: new Date().toISOString(),
|
|
404
|
+
status: 'ok',
|
|
405
|
+
uptime: process.uptime(),
|
|
406
|
+
timestamp: new Date().toISOString(),
|
|
494
407
|
};
|
|
495
408
|
res.status(200).json(healthStatus);
|
|
496
409
|
});
|
|
497
|
-
// Endpoint to provide memory usage details
|
|
498
410
|
this.expressApp.get('/memory', async (req, res) => {
|
|
499
411
|
this.log.debug('Express received /memory');
|
|
500
|
-
// Memory usage from process
|
|
501
412
|
const memoryUsageRaw = process.memoryUsage();
|
|
502
413
|
const memoryUsage = {
|
|
503
414
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -506,13 +417,10 @@ export class Frontend extends EventEmitter {
|
|
|
506
417
|
external: formatBytes(memoryUsageRaw.external),
|
|
507
418
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
508
419
|
};
|
|
509
|
-
// V8 heap statistics
|
|
510
420
|
const { default: v8 } = await import('node:v8');
|
|
511
421
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
512
422
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
513
|
-
// Format heapStats
|
|
514
423
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
515
|
-
// Format heapSpaces
|
|
516
424
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
517
425
|
...space,
|
|
518
426
|
space_size: formatBytes(space.space_size),
|
|
@@ -531,22 +439,18 @@ export class Frontend extends EventEmitter {
|
|
|
531
439
|
};
|
|
532
440
|
res.status(200).json(memoryReport);
|
|
533
441
|
});
|
|
534
|
-
// Endpoint to provide settings
|
|
535
442
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
536
443
|
this.log.debug('The frontend sent /api/settings');
|
|
537
444
|
res.json(await this.getApiSettings());
|
|
538
445
|
});
|
|
539
|
-
// Endpoint to provide plugins
|
|
540
446
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
541
447
|
this.log.debug('The frontend sent /api/plugins');
|
|
542
448
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
543
449
|
});
|
|
544
|
-
// Endpoint to provide devices
|
|
545
450
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
546
451
|
this.log.debug('The frontend sent /api/devices');
|
|
547
452
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
548
453
|
});
|
|
549
|
-
// Endpoint to view the matterbridge log
|
|
550
454
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
551
455
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
552
456
|
try {
|
|
@@ -560,7 +464,6 @@ export class Frontend extends EventEmitter {
|
|
|
560
464
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
561
465
|
}
|
|
562
466
|
});
|
|
563
|
-
// Endpoint to view the matter.js log
|
|
564
467
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
565
468
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
566
469
|
try {
|
|
@@ -574,7 +477,6 @@ export class Frontend extends EventEmitter {
|
|
|
574
477
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
575
478
|
}
|
|
576
479
|
});
|
|
577
|
-
// Endpoint to view the diagnostic.log
|
|
578
480
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
579
481
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
580
482
|
await this.generateDiagnostic();
|
|
@@ -585,13 +487,10 @@ export class Frontend extends EventEmitter {
|
|
|
585
487
|
res.send(data.slice(29));
|
|
586
488
|
}
|
|
587
489
|
catch (error) {
|
|
588
|
-
// istanbul ignore next
|
|
589
490
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
590
|
-
// istanbul ignore next
|
|
591
491
|
res.status(500).send('Error reading diagnostic log file.');
|
|
592
492
|
}
|
|
593
493
|
});
|
|
594
|
-
// Endpoint to download the diagnostic.log
|
|
595
494
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
596
495
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
597
496
|
await this.generateDiagnostic();
|
|
@@ -602,19 +501,16 @@ export class Frontend extends EventEmitter {
|
|
|
602
501
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
603
502
|
}
|
|
604
503
|
catch (error) {
|
|
605
|
-
// istanbul ignore next
|
|
606
504
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
607
505
|
}
|
|
608
506
|
res.type('text/plain');
|
|
609
507
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
610
|
-
/* istanbul ignore if */
|
|
611
508
|
if (error) {
|
|
612
509
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
613
510
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
614
511
|
}
|
|
615
512
|
});
|
|
616
513
|
});
|
|
617
|
-
// Endpoint to view the history.html
|
|
618
514
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
619
515
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
620
516
|
try {
|
|
@@ -628,7 +524,6 @@ export class Frontend extends EventEmitter {
|
|
|
628
524
|
res.status(500).send('Error reading history file.');
|
|
629
525
|
}
|
|
630
526
|
});
|
|
631
|
-
// Endpoint to download the history.html
|
|
632
527
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
633
528
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
634
529
|
try {
|
|
@@ -638,7 +533,6 @@ export class Frontend extends EventEmitter {
|
|
|
638
533
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
639
534
|
res.type('text/plain');
|
|
640
535
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
641
|
-
/* istanbul ignore if */
|
|
642
536
|
if (error) {
|
|
643
537
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
644
538
|
res.status(500).send('Error downloading history file');
|
|
@@ -650,7 +544,6 @@ export class Frontend extends EventEmitter {
|
|
|
650
544
|
res.status(500).send('Error reading history file.');
|
|
651
545
|
}
|
|
652
546
|
});
|
|
653
|
-
// Endpoint to view the shelly log
|
|
654
547
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
655
548
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
656
549
|
try {
|
|
@@ -664,7 +557,6 @@ export class Frontend extends EventEmitter {
|
|
|
664
557
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
665
558
|
}
|
|
666
559
|
});
|
|
667
|
-
// Endpoint to download the matterbridge log
|
|
668
560
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
669
561
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
670
562
|
const fs = await import('node:fs');
|
|
@@ -679,14 +571,12 @@ export class Frontend extends EventEmitter {
|
|
|
679
571
|
}
|
|
680
572
|
res.type('text/plain');
|
|
681
573
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
682
|
-
/* istanbul ignore if */
|
|
683
574
|
if (error) {
|
|
684
575
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
685
576
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
686
577
|
}
|
|
687
578
|
});
|
|
688
579
|
});
|
|
689
|
-
// Endpoint to download the matter log
|
|
690
580
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
691
581
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
692
582
|
const fs = await import('node:fs');
|
|
@@ -701,14 +591,12 @@ export class Frontend extends EventEmitter {
|
|
|
701
591
|
}
|
|
702
592
|
res.type('text/plain');
|
|
703
593
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
704
|
-
/* istanbul ignore if */
|
|
705
594
|
if (error) {
|
|
706
595
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
707
596
|
res.status(500).send('Error downloading the matter log file');
|
|
708
597
|
}
|
|
709
598
|
});
|
|
710
599
|
});
|
|
711
|
-
// Endpoint to download the shelly log
|
|
712
600
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
713
601
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
714
602
|
const fs = await import('node:fs');
|
|
@@ -723,91 +611,75 @@ export class Frontend extends EventEmitter {
|
|
|
723
611
|
}
|
|
724
612
|
res.type('text/plain');
|
|
725
613
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
726
|
-
/* istanbul ignore if */
|
|
727
614
|
if (error) {
|
|
728
615
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
729
616
|
res.status(500).send('Error downloading Shelly system log file');
|
|
730
617
|
}
|
|
731
618
|
});
|
|
732
619
|
});
|
|
733
|
-
// Endpoint to download the matterbridge storage directory
|
|
734
620
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
735
621
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
736
622
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
737
623
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
738
|
-
/* istanbul ignore if */
|
|
739
624
|
if (error) {
|
|
740
625
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
741
626
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
742
627
|
}
|
|
743
628
|
});
|
|
744
629
|
});
|
|
745
|
-
// Endpoint to download the matter storage file
|
|
746
630
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
747
631
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
748
632
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
749
633
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
750
|
-
/* istanbul ignore if */
|
|
751
634
|
if (error) {
|
|
752
635
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
753
636
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
754
637
|
}
|
|
755
638
|
});
|
|
756
639
|
});
|
|
757
|
-
// Endpoint to download the matterbridge plugin directory
|
|
758
640
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
759
641
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
760
642
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
761
643
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
762
|
-
/* istanbul ignore if */
|
|
763
644
|
if (error) {
|
|
764
645
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
765
646
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
766
647
|
}
|
|
767
648
|
});
|
|
768
649
|
});
|
|
769
|
-
// Endpoint to download the matterbridge plugin config files
|
|
770
650
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
771
651
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
772
652
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
773
653
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
774
|
-
/* istanbul ignore if */
|
|
775
654
|
if (error) {
|
|
776
655
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
777
656
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
778
657
|
}
|
|
779
658
|
});
|
|
780
659
|
});
|
|
781
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
782
660
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
783
661
|
this.log.debug('The frontend sent /api/download-backup');
|
|
784
662
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
785
|
-
/* istanbul ignore if */
|
|
786
663
|
if (error) {
|
|
787
664
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
788
665
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
789
666
|
}
|
|
790
667
|
});
|
|
791
668
|
});
|
|
792
|
-
// Endpoint to upload a package
|
|
793
669
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
794
670
|
const { filename } = req.body;
|
|
795
671
|
const file = req.file;
|
|
796
|
-
/* istanbul ignore if */
|
|
797
672
|
if (!file || !filename) {
|
|
798
673
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
799
674
|
res.status(400).send('Invalid request: file and filename are required');
|
|
800
675
|
return;
|
|
801
676
|
}
|
|
802
677
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
803
|
-
// Define the path where the plugin file will be saved
|
|
804
678
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
805
679
|
try {
|
|
806
|
-
// Move the uploaded file to the specified path
|
|
807
680
|
const fs = await import('node:fs');
|
|
808
681
|
await fs.promises.rename(file.path, filePath);
|
|
809
682
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
810
|
-
// Install the plugin package
|
|
811
683
|
if (filename.endsWith('.tgz')) {
|
|
812
684
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
813
685
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -827,26 +699,23 @@ export class Frontend extends EventEmitter {
|
|
|
827
699
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
828
700
|
}
|
|
829
701
|
});
|
|
830
|
-
// Fallback for routing (must be the last route)
|
|
831
702
|
this.expressApp.use((req, res) => {
|
|
832
|
-
|
|
833
|
-
|
|
703
|
+
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
704
|
+
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
705
|
+
res.sendFile('index.html', { root: filePath });
|
|
834
706
|
});
|
|
835
|
-
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend
|
|
707
|
+
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend', 'build')}${UNDERLINEOFF}${rs}`);
|
|
836
708
|
}
|
|
837
709
|
async stop() {
|
|
838
710
|
this.log.debug('Stopping the frontend...');
|
|
839
711
|
const ws = await import('ws');
|
|
840
|
-
// Remove listeners from the express app
|
|
841
712
|
if (this.expressApp) {
|
|
842
713
|
this.expressApp.removeAllListeners();
|
|
843
714
|
this.expressApp = undefined;
|
|
844
715
|
this.log.debug('Frontend app closed successfully');
|
|
845
716
|
}
|
|
846
|
-
// Close the WebSocket server
|
|
847
717
|
if (this.webSocketServer) {
|
|
848
718
|
this.log.debug('Closing WebSocket server...');
|
|
849
|
-
// Close all active connections
|
|
850
719
|
this.webSocketServer.clients.forEach((client) => {
|
|
851
720
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
852
721
|
client.close();
|
|
@@ -855,7 +724,6 @@ export class Frontend extends EventEmitter {
|
|
|
855
724
|
await withTimeout(new Promise((resolve) => {
|
|
856
725
|
this.webSocketServer?.close((error) => {
|
|
857
726
|
if (error) {
|
|
858
|
-
// istanbul ignore next
|
|
859
727
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
860
728
|
}
|
|
861
729
|
else {
|
|
@@ -868,27 +736,8 @@ export class Frontend extends EventEmitter {
|
|
|
868
736
|
this.webSocketServer.removeAllListeners();
|
|
869
737
|
this.webSocketServer = undefined;
|
|
870
738
|
}
|
|
871
|
-
// Close the http server
|
|
872
739
|
if (this.httpServer) {
|
|
873
740
|
this.log.debug('Closing http server...');
|
|
874
|
-
/*
|
|
875
|
-
await withTimeout(
|
|
876
|
-
new Promise<void>((resolve) => {
|
|
877
|
-
this.httpServer?.close((error) => {
|
|
878
|
-
if (error) {
|
|
879
|
-
// istanbul ignore next
|
|
880
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
881
|
-
} else {
|
|
882
|
-
this.log.debug('Http server closed successfully');
|
|
883
|
-
this.emit('server_stopped');
|
|
884
|
-
}
|
|
885
|
-
resolve();
|
|
886
|
-
});
|
|
887
|
-
}),
|
|
888
|
-
5000,
|
|
889
|
-
false,
|
|
890
|
-
);
|
|
891
|
-
*/
|
|
892
741
|
this.httpServer.close();
|
|
893
742
|
this.log.debug('Http server closed successfully');
|
|
894
743
|
this.listening = false;
|
|
@@ -897,27 +746,8 @@ export class Frontend extends EventEmitter {
|
|
|
897
746
|
this.httpServer = undefined;
|
|
898
747
|
this.log.debug('Frontend http server closed successfully');
|
|
899
748
|
}
|
|
900
|
-
// Close the https server
|
|
901
749
|
if (this.httpsServer) {
|
|
902
750
|
this.log.debug('Closing https server...');
|
|
903
|
-
/*
|
|
904
|
-
await withTimeout(
|
|
905
|
-
new Promise<void>((resolve) => {
|
|
906
|
-
this.httpsServer?.close((error) => {
|
|
907
|
-
if (error) {
|
|
908
|
-
// istanbul ignore next
|
|
909
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
910
|
-
} else {
|
|
911
|
-
this.log.debug('Https server closed successfully');
|
|
912
|
-
this.emit('server_stopped');
|
|
913
|
-
}
|
|
914
|
-
resolve();
|
|
915
|
-
});
|
|
916
|
-
}),
|
|
917
|
-
5000,
|
|
918
|
-
false,
|
|
919
|
-
);
|
|
920
|
-
*/
|
|
921
751
|
this.httpsServer.close();
|
|
922
752
|
this.log.debug('Https server closed successfully');
|
|
923
753
|
this.listening = false;
|
|
@@ -928,13 +758,7 @@ export class Frontend extends EventEmitter {
|
|
|
928
758
|
}
|
|
929
759
|
this.log.debug('Frontend stopped successfully');
|
|
930
760
|
}
|
|
931
|
-
/**
|
|
932
|
-
* Retrieves the api settings data.
|
|
933
|
-
*
|
|
934
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
935
|
-
*/
|
|
936
761
|
async getApiSettings() {
|
|
937
|
-
// Update the variable system information properties
|
|
938
762
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
939
763
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
940
764
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -944,7 +768,6 @@ export class Frontend extends EventEmitter {
|
|
|
944
768
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
945
769
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
946
770
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
947
|
-
// Create the matterbridge information
|
|
948
771
|
const info = {
|
|
949
772
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
950
773
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -980,15 +803,9 @@ export class Frontend extends EventEmitter {
|
|
|
980
803
|
};
|
|
981
804
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
982
805
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Retrieves the reachable attribute.
|
|
985
|
-
*
|
|
986
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
987
|
-
* @returns {boolean} The reachable attribute.
|
|
988
|
-
*/
|
|
989
806
|
getReachability(device) {
|
|
990
807
|
if (this.matterbridge.hasCleanupStarted)
|
|
991
|
-
return false;
|
|
808
|
+
return false;
|
|
992
809
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
993
810
|
return false;
|
|
994
811
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -999,15 +816,9 @@ export class Frontend extends EventEmitter {
|
|
|
999
816
|
return true;
|
|
1000
817
|
return false;
|
|
1001
818
|
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Retrieves the power source attribute.
|
|
1004
|
-
*
|
|
1005
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1006
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1007
|
-
*/
|
|
1008
819
|
getPowerSource(endpoint) {
|
|
1009
820
|
if (this.matterbridge.hasCleanupStarted)
|
|
1010
|
-
return;
|
|
821
|
+
return;
|
|
1011
822
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1012
823
|
return undefined;
|
|
1013
824
|
const powerSource = (device) => {
|
|
@@ -1022,25 +833,16 @@ export class Frontend extends EventEmitter {
|
|
|
1022
833
|
}
|
|
1023
834
|
return;
|
|
1024
835
|
};
|
|
1025
|
-
// Root endpoint
|
|
1026
836
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1027
837
|
return powerSource(endpoint);
|
|
1028
|
-
// Child endpoints
|
|
1029
838
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1030
839
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1031
840
|
return powerSource(child);
|
|
1032
841
|
}
|
|
1033
842
|
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Retrieves the cluster text description from a given device.
|
|
1036
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1037
|
-
*
|
|
1038
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1039
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1040
|
-
*/
|
|
1041
843
|
getClusterTextFromDevice(device) {
|
|
1042
844
|
if (this.matterbridge.hasCleanupStarted)
|
|
1043
|
-
return '';
|
|
845
|
+
return '';
|
|
1044
846
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1045
847
|
return '';
|
|
1046
848
|
const getUserLabel = (device) => {
|
|
@@ -1050,7 +852,6 @@ export class Frontend extends EventEmitter {
|
|
|
1050
852
|
if (composed)
|
|
1051
853
|
return 'Composed: ' + composed.value;
|
|
1052
854
|
}
|
|
1053
|
-
// istanbul ignore next cause is not reachable
|
|
1054
855
|
return '';
|
|
1055
856
|
};
|
|
1056
857
|
const getFixedLabel = (device) => {
|
|
@@ -1060,13 +861,11 @@ export class Frontend extends EventEmitter {
|
|
|
1060
861
|
if (composed)
|
|
1061
862
|
return 'Composed: ' + composed.value;
|
|
1062
863
|
}
|
|
1063
|
-
// istanbul ignore next cause is not reacheable
|
|
1064
864
|
return '';
|
|
1065
865
|
};
|
|
1066
866
|
let attributes = '';
|
|
1067
867
|
let supportedModes = [];
|
|
1068
868
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1069
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1070
869
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1071
870
|
return;
|
|
1072
871
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1156,17 +955,11 @@ export class Frontend extends EventEmitter {
|
|
|
1156
955
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1157
956
|
attributes += `${getUserLabel(device)} `;
|
|
1158
957
|
});
|
|
1159
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1160
958
|
return attributes.trimStart().trimEnd();
|
|
1161
959
|
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1164
|
-
*
|
|
1165
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1166
|
-
*/
|
|
1167
960
|
getPlugins() {
|
|
1168
961
|
if (this.matterbridge.hasCleanupStarted)
|
|
1169
|
-
return [];
|
|
962
|
+
return [];
|
|
1170
963
|
const plugins = [];
|
|
1171
964
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1172
965
|
plugins.push({
|
|
@@ -1194,27 +987,18 @@ export class Frontend extends EventEmitter {
|
|
|
1194
987
|
schemaJson: plugin.schemaJson,
|
|
1195
988
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1196
989
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1197
|
-
// Childbridge mode specific data
|
|
1198
990
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1199
991
|
});
|
|
1200
992
|
}
|
|
1201
993
|
return plugins;
|
|
1202
994
|
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Retrieves the devices from Matterbridge.
|
|
1205
|
-
*
|
|
1206
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1207
|
-
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1208
|
-
*/
|
|
1209
995
|
getDevices(pluginName) {
|
|
1210
996
|
if (this.matterbridge.hasCleanupStarted)
|
|
1211
|
-
return [];
|
|
997
|
+
return [];
|
|
1212
998
|
const devices = [];
|
|
1213
999
|
for (const device of this.matterbridge.devices.array()) {
|
|
1214
|
-
// Filter by pluginName if provided
|
|
1215
1000
|
if (pluginName && pluginName !== device.plugin)
|
|
1216
1001
|
continue;
|
|
1217
|
-
// Check if the device has the required properties
|
|
1218
1002
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1219
1003
|
continue;
|
|
1220
1004
|
devices.push({
|
|
@@ -1234,39 +1018,24 @@ export class Frontend extends EventEmitter {
|
|
|
1234
1018
|
}
|
|
1235
1019
|
return devices;
|
|
1236
1020
|
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1239
|
-
*
|
|
1240
|
-
* Response for /api/clusters
|
|
1241
|
-
*
|
|
1242
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1243
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1244
|
-
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1245
|
-
*/
|
|
1246
1021
|
getClusters(pluginName, endpointNumber) {
|
|
1247
1022
|
if (this.matterbridge.hasCleanupStarted)
|
|
1248
|
-
return;
|
|
1023
|
+
return;
|
|
1249
1024
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1250
1025
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1251
1026
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1252
1027
|
return;
|
|
1253
1028
|
}
|
|
1254
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1255
|
-
// Get the device types from the main endpoint
|
|
1256
1029
|
const deviceTypes = [];
|
|
1257
1030
|
const clusters = [];
|
|
1258
1031
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1259
1032
|
deviceTypes.push(d.deviceType);
|
|
1260
1033
|
});
|
|
1261
|
-
// Get the clusters from the main endpoint
|
|
1262
1034
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1263
1035
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1264
1036
|
return;
|
|
1265
1037
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1266
1038
|
return;
|
|
1267
|
-
// console.log(
|
|
1268
|
-
// `${idn}${endpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
|
|
1269
|
-
// );
|
|
1270
1039
|
clusters.push({
|
|
1271
1040
|
endpoint: endpoint.number.toString(),
|
|
1272
1041
|
number: endpoint.number,
|
|
@@ -1280,19 +1049,12 @@ export class Frontend extends EventEmitter {
|
|
|
1280
1049
|
attributeLocalValue: attributeValue,
|
|
1281
1050
|
});
|
|
1282
1051
|
});
|
|
1283
|
-
// Get the child endpoints
|
|
1284
1052
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1285
|
-
// if (childEndpoints.length === 0) {
|
|
1286
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1287
|
-
// }
|
|
1288
1053
|
childEndpoints.forEach((childEndpoint) => {
|
|
1289
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1290
1054
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1291
1055
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1292
1056
|
return;
|
|
1293
1057
|
}
|
|
1294
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1295
|
-
// Get the device types of the child endpoint
|
|
1296
1058
|
const deviceTypes = [];
|
|
1297
1059
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1298
1060
|
deviceTypes.push(d.deviceType);
|
|
@@ -1302,9 +1064,6 @@ export class Frontend extends EventEmitter {
|
|
|
1302
1064
|
return;
|
|
1303
1065
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1304
1066
|
return;
|
|
1305
|
-
// console.log(
|
|
1306
|
-
// `${idn}${childEndpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
|
|
1307
|
-
// );
|
|
1308
1067
|
clusters.push({
|
|
1309
1068
|
endpoint: childEndpoint.number.toString(),
|
|
1310
1069
|
number: childEndpoint.number,
|
|
@@ -1324,7 +1083,6 @@ export class Frontend extends EventEmitter {
|
|
|
1324
1083
|
async generateDiagnostic() {
|
|
1325
1084
|
this.log.debug('Generating diagnostic...');
|
|
1326
1085
|
const serverNodes = [];
|
|
1327
|
-
// istanbul ignore else
|
|
1328
1086
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1329
1087
|
if (this.matterbridge.serverNode)
|
|
1330
1088
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1335,7 +1093,6 @@ export class Frontend extends EventEmitter {
|
|
|
1335
1093
|
serverNodes.push(plugin.serverNode);
|
|
1336
1094
|
}
|
|
1337
1095
|
}
|
|
1338
|
-
// istanbul ignore next
|
|
1339
1096
|
for (const device of this.matterbridge.devices.array()) {
|
|
1340
1097
|
if (device.serverNode)
|
|
1341
1098
|
serverNodes.push(device.serverNode);
|
|
@@ -1359,15 +1116,8 @@ export class Frontend extends EventEmitter {
|
|
|
1359
1116
|
values: [...serverNodes],
|
|
1360
1117
|
})));
|
|
1361
1118
|
delete Logger.destinations.diagnostic;
|
|
1362
|
-
await wait(500);
|
|
1119
|
+
await wait(500);
|
|
1363
1120
|
}
|
|
1364
|
-
/**
|
|
1365
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1366
|
-
*
|
|
1367
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1368
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1369
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1370
|
-
*/
|
|
1371
1121
|
async wsMessageHandler(client, message) {
|
|
1372
1122
|
let data;
|
|
1373
1123
|
const sendResponse = (data) => {
|
|
@@ -1387,7 +1137,7 @@ export class Frontend extends EventEmitter {
|
|
|
1387
1137
|
};
|
|
1388
1138
|
try {
|
|
1389
1139
|
data = JSON.parse(message.toString());
|
|
1390
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1140
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1391
1141
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1392
1142
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1393
1143
|
return;
|
|
@@ -1461,7 +1211,6 @@ export class Frontend extends EventEmitter {
|
|
|
1461
1211
|
return;
|
|
1462
1212
|
})
|
|
1463
1213
|
.catch((_error) => {
|
|
1464
|
-
//
|
|
1465
1214
|
});
|
|
1466
1215
|
}
|
|
1467
1216
|
else {
|
|
@@ -1509,7 +1258,6 @@ export class Frontend extends EventEmitter {
|
|
|
1509
1258
|
return;
|
|
1510
1259
|
})
|
|
1511
1260
|
.catch((_error) => {
|
|
1512
|
-
//
|
|
1513
1261
|
});
|
|
1514
1262
|
}
|
|
1515
1263
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1535,7 +1283,6 @@ export class Frontend extends EventEmitter {
|
|
|
1535
1283
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1536
1284
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1537
1285
|
if (plugin.serverNode) {
|
|
1538
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1539
1286
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1540
1287
|
plugin.serverNode = undefined;
|
|
1541
1288
|
}
|
|
@@ -1545,20 +1292,18 @@ export class Frontend extends EventEmitter {
|
|
|
1545
1292
|
this.matterbridge.devices.remove(device);
|
|
1546
1293
|
}
|
|
1547
1294
|
}
|
|
1548
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1549
1295
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1550
1296
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1551
1297
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1552
|
-
plugin.restartRequired = false;
|
|
1298
|
+
plugin.restartRequired = false;
|
|
1553
1299
|
let needRestart = 0;
|
|
1554
1300
|
for (const plugin of this.matterbridge.plugins) {
|
|
1555
1301
|
if (plugin.restartRequired)
|
|
1556
1302
|
needRestart++;
|
|
1557
1303
|
}
|
|
1558
1304
|
if (needRestart === 0) {
|
|
1559
|
-
this.wssSendRestartNotRequired(true);
|
|
1305
|
+
this.wssSendRestartNotRequired(true);
|
|
1560
1306
|
}
|
|
1561
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1562
1307
|
if (plugin.serverNode)
|
|
1563
1308
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1564
1309
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1824,22 +1569,22 @@ export class Frontend extends EventEmitter {
|
|
|
1824
1569
|
if (isValidString(data.params.value, 4)) {
|
|
1825
1570
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1826
1571
|
if (data.params.value === 'Debug') {
|
|
1827
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1572
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1828
1573
|
}
|
|
1829
1574
|
else if (data.params.value === 'Info') {
|
|
1830
|
-
await this.matterbridge.setLogLevel("info"
|
|
1575
|
+
await this.matterbridge.setLogLevel("info");
|
|
1831
1576
|
}
|
|
1832
1577
|
else if (data.params.value === 'Notice') {
|
|
1833
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1578
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1834
1579
|
}
|
|
1835
1580
|
else if (data.params.value === 'Warn') {
|
|
1836
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1581
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1837
1582
|
}
|
|
1838
1583
|
else if (data.params.value === 'Error') {
|
|
1839
|
-
await this.matterbridge.setLogLevel("error"
|
|
1584
|
+
await this.matterbridge.setLogLevel("error");
|
|
1840
1585
|
}
|
|
1841
1586
|
else if (data.params.value === 'Fatal') {
|
|
1842
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1587
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1843
1588
|
}
|
|
1844
1589
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1845
1590
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1850,7 +1595,6 @@ export class Frontend extends EventEmitter {
|
|
|
1850
1595
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1851
1596
|
this.matterbridge.fileLogger = data.params.value;
|
|
1852
1597
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1853
|
-
// Create the file logger for matterbridge
|
|
1854
1598
|
if (data.params.value)
|
|
1855
1599
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1856
1600
|
else
|
|
@@ -1879,12 +1623,11 @@ export class Frontend extends EventEmitter {
|
|
|
1879
1623
|
else if (data.params.value === 'Fatal') {
|
|
1880
1624
|
Logger.level = MatterLogLevel.FATAL;
|
|
1881
1625
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1626
|
+
let callbackLogLevel = "notice";
|
|
1627
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1628
|
+
callbackLogLevel = "info";
|
|
1629
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1630
|
+
callbackLogLevel = "debug";
|
|
1888
1631
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1889
1632
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1890
1633
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1936,7 +1679,6 @@ export class Frontend extends EventEmitter {
|
|
|
1936
1679
|
}
|
|
1937
1680
|
break;
|
|
1938
1681
|
case 'setmatterport':
|
|
1939
|
-
// eslint-disable-next-line no-case-declarations
|
|
1940
1682
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1941
1683
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1942
1684
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1956,7 +1698,6 @@ export class Frontend extends EventEmitter {
|
|
|
1956
1698
|
}
|
|
1957
1699
|
break;
|
|
1958
1700
|
case 'setmatterdiscriminator':
|
|
1959
|
-
// eslint-disable-next-line no-case-declarations
|
|
1960
1701
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1961
1702
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1962
1703
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1976,7 +1717,6 @@ export class Frontend extends EventEmitter {
|
|
|
1976
1717
|
}
|
|
1977
1718
|
break;
|
|
1978
1719
|
case 'setmatterpasscode':
|
|
1979
|
-
// eslint-disable-next-line no-case-declarations
|
|
1980
1720
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1981
1721
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1982
1722
|
this.matterbridge.passcode = passcode;
|
|
@@ -2022,19 +1762,15 @@ export class Frontend extends EventEmitter {
|
|
|
2022
1762
|
return;
|
|
2023
1763
|
}
|
|
2024
1764
|
const config = plugin.configJson;
|
|
2025
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2026
1765
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2027
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2028
1766
|
if (select === 'serial')
|
|
2029
1767
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2030
1768
|
if (select === 'name')
|
|
2031
1769
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2032
1770
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2033
|
-
// Remove postfix from the serial if it exists
|
|
2034
1771
|
if (config.postfix) {
|
|
2035
1772
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2036
1773
|
}
|
|
2037
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2038
1774
|
if (isValidArray(config.whiteList, 1)) {
|
|
2039
1775
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2040
1776
|
config.whiteList.push(data.params.serial);
|
|
@@ -2043,7 +1779,6 @@ export class Frontend extends EventEmitter {
|
|
|
2043
1779
|
config.whiteList.push(data.params.name);
|
|
2044
1780
|
}
|
|
2045
1781
|
}
|
|
2046
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2047
1782
|
if (isValidArray(config.blackList, 1)) {
|
|
2048
1783
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2049
1784
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2071,9 +1806,7 @@ export class Frontend extends EventEmitter {
|
|
|
2071
1806
|
return;
|
|
2072
1807
|
}
|
|
2073
1808
|
const config = plugin.configJson;
|
|
2074
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2075
1809
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2076
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2077
1810
|
if (select === 'serial')
|
|
2078
1811
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2079
1812
|
if (select === 'name')
|
|
@@ -2082,7 +1815,6 @@ export class Frontend extends EventEmitter {
|
|
|
2082
1815
|
if (config.postfix) {
|
|
2083
1816
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2084
1817
|
}
|
|
2085
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2086
1818
|
if (isValidArray(config.whiteList, 1)) {
|
|
2087
1819
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2088
1820
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2091,7 +1823,6 @@ export class Frontend extends EventEmitter {
|
|
|
2091
1823
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2092
1824
|
}
|
|
2093
1825
|
}
|
|
2094
|
-
// Add the serial to the blackList
|
|
2095
1826
|
if (isValidArray(config.blackList)) {
|
|
2096
1827
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2097
1828
|
config.blackList.push(data.params.serial);
|
|
@@ -2114,7 +1845,6 @@ export class Frontend extends EventEmitter {
|
|
|
2114
1845
|
}
|
|
2115
1846
|
}
|
|
2116
1847
|
else {
|
|
2117
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2118
1848
|
const localData = data;
|
|
2119
1849
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2120
1850
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2124,46 +1854,23 @@ export class Frontend extends EventEmitter {
|
|
|
2124
1854
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2125
1855
|
}
|
|
2126
1856
|
}
|
|
2127
|
-
/**
|
|
2128
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2129
|
-
*
|
|
2130
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2131
|
-
* @param {string} time - The time string of the message
|
|
2132
|
-
* @param {string} name - The logger name of the message
|
|
2133
|
-
* @param {string} message - The content of the message.
|
|
2134
|
-
*
|
|
2135
|
-
* @remarks
|
|
2136
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2137
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2138
|
-
* The function sends the message to all connected clients.
|
|
2139
|
-
*/
|
|
2140
1857
|
wssSendLogMessage(level, time, name, message) {
|
|
2141
1858
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2142
1859
|
return;
|
|
2143
1860
|
if (!level || !time || !name || !message)
|
|
2144
1861
|
return;
|
|
2145
|
-
// Remove ANSI escape codes from the message
|
|
2146
|
-
// eslint-disable-next-line no-control-regex
|
|
2147
1862
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2148
|
-
// Remove leading asterisks from the message
|
|
2149
1863
|
message = message.replace(/^\*+/, '');
|
|
2150
|
-
// Replace all occurrences of \t and \n
|
|
2151
1864
|
message = message.replace(/[\t\n]/g, '');
|
|
2152
|
-
// Remove non-printable characters
|
|
2153
|
-
// eslint-disable-next-line no-control-regex
|
|
2154
1865
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2155
|
-
// Replace all occurrences of \" with "
|
|
2156
1866
|
message = message.replace(/\\"/g, '"');
|
|
2157
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2158
1867
|
const maxContinuousLength = 100;
|
|
2159
1868
|
const keepStartLength = 20;
|
|
2160
1869
|
const keepEndLength = 20;
|
|
2161
|
-
// Split the message into words
|
|
2162
1870
|
if (level !== 'spawn') {
|
|
2163
1871
|
message = message
|
|
2164
1872
|
.split(' ')
|
|
2165
1873
|
.map((word) => {
|
|
2166
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2167
1874
|
if (word.length > maxContinuousLength) {
|
|
2168
1875
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2169
1876
|
}
|
|
@@ -2171,34 +1878,14 @@ export class Frontend extends EventEmitter {
|
|
|
2171
1878
|
})
|
|
2172
1879
|
.join(' ');
|
|
2173
1880
|
}
|
|
2174
|
-
// Send the message to all connected clients
|
|
2175
1881
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2176
1882
|
}
|
|
2177
|
-
/**
|
|
2178
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2179
|
-
*
|
|
2180
|
-
* @param {string} changed - The changed value.
|
|
2181
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2182
|
-
* possible values for changed:
|
|
2183
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2184
|
-
* - 'plugins'
|
|
2185
|
-
* - 'devices'
|
|
2186
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2187
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2188
|
-
*/
|
|
2189
1883
|
wssSendRefreshRequired(changed, params) {
|
|
2190
1884
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2191
1885
|
return;
|
|
2192
1886
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2193
|
-
// Send the message to all connected clients
|
|
2194
1887
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2195
1888
|
}
|
|
2196
|
-
/**
|
|
2197
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2198
|
-
*
|
|
2199
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2200
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2201
|
-
*/
|
|
2202
1889
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2203
1890
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2204
1891
|
return;
|
|
@@ -2207,14 +1894,8 @@ export class Frontend extends EventEmitter {
|
|
|
2207
1894
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2208
1895
|
if (snackbar === true)
|
|
2209
1896
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2210
|
-
// Send the message to all connected clients
|
|
2211
1897
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2212
1898
|
}
|
|
2213
|
-
/**
|
|
2214
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2215
|
-
*
|
|
2216
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2217
|
-
*/
|
|
2218
1899
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2219
1900
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2220
1901
|
return;
|
|
@@ -2222,133 +1903,57 @@ export class Frontend extends EventEmitter {
|
|
|
2222
1903
|
this.matterbridge.restartRequired = false;
|
|
2223
1904
|
if (snackbar === true)
|
|
2224
1905
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2225
|
-
// Send the message to all connected clients
|
|
2226
1906
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2227
1907
|
}
|
|
2228
|
-
/**
|
|
2229
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2230
|
-
*
|
|
2231
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2232
|
-
*/
|
|
2233
1908
|
wssSendUpdateRequired(devVersion = false) {
|
|
2234
1909
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2235
1910
|
return;
|
|
2236
1911
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2237
1912
|
this.matterbridge.updateRequired = true;
|
|
2238
|
-
// Send the message to all connected clients
|
|
2239
1913
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2240
1914
|
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Sends a cpu update message to all connected clients.
|
|
2243
|
-
*
|
|
2244
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2245
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2246
|
-
*/
|
|
2247
1915
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2248
1916
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2249
1917
|
return;
|
|
2250
1918
|
if (hasParameter('debug'))
|
|
2251
1919
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2252
|
-
// Send the message to all connected clients
|
|
2253
1920
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
|
|
2254
1921
|
}
|
|
2255
|
-
/**
|
|
2256
|
-
* Sends a memory update message to all connected clients.
|
|
2257
|
-
*
|
|
2258
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2259
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2260
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2261
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2262
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2263
|
-
* @param {string} external - The external memory in bytes.
|
|
2264
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2265
|
-
*/
|
|
2266
1922
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2267
1923
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2268
1924
|
return;
|
|
2269
1925
|
if (hasParameter('debug'))
|
|
2270
1926
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2271
|
-
// Send the message to all connected clients
|
|
2272
1927
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2273
1928
|
}
|
|
2274
|
-
/**
|
|
2275
|
-
* Sends an uptime update message to all connected clients.
|
|
2276
|
-
*
|
|
2277
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2278
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2279
|
-
*/
|
|
2280
1929
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2281
1930
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2282
1931
|
return;
|
|
2283
1932
|
if (hasParameter('debug'))
|
|
2284
1933
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2285
|
-
// Send the message to all connected clients
|
|
2286
1934
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2287
1935
|
}
|
|
2288
|
-
/**
|
|
2289
|
-
* Sends an open snackbar message to all connected clients.
|
|
2290
|
-
*
|
|
2291
|
-
* @param {string} message - The message to send.
|
|
2292
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2293
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2294
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2295
|
-
*
|
|
2296
|
-
* @remarks
|
|
2297
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2298
|
-
*/
|
|
2299
1936
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2300
1937
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2301
1938
|
return;
|
|
2302
1939
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2303
|
-
// Send the message to all connected clients
|
|
2304
1940
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2305
1941
|
}
|
|
2306
|
-
/**
|
|
2307
|
-
* Sends a close snackbar message to all connected clients.
|
|
2308
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2309
|
-
*
|
|
2310
|
-
* @param {string} message - The message to send.
|
|
2311
|
-
*/
|
|
2312
1942
|
wssSendCloseSnackbarMessage(message) {
|
|
2313
1943
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2314
1944
|
return;
|
|
2315
1945
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2316
|
-
// Send the message to all connected clients
|
|
2317
1946
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2318
1947
|
}
|
|
2319
|
-
/**
|
|
2320
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2321
|
-
*
|
|
2322
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2323
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2324
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2325
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2326
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2327
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2328
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2329
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2330
|
-
*
|
|
2331
|
-
* @remarks
|
|
2332
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2333
|
-
* with the updated attribute information.
|
|
2334
|
-
*/
|
|
2335
1948
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2336
1949
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2337
1950
|
return;
|
|
2338
1951
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2339
|
-
// Send the message to all connected clients
|
|
2340
1952
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2341
1953
|
}
|
|
2342
|
-
/**
|
|
2343
|
-
* Sends a message to all connected clients.
|
|
2344
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2345
|
-
*
|
|
2346
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2347
|
-
*/
|
|
2348
1954
|
wssBroadcastMessage(msg) {
|
|
2349
1955
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2350
1956
|
return;
|
|
2351
|
-
// Send the message to all connected clients
|
|
2352
1957
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2353
1958
|
if (msg.method !== 'log')
|
|
2354
1959
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2359,4 +1964,3 @@ export class Frontend extends EventEmitter {
|
|
|
2359
1964
|
});
|
|
2360
1965
|
}
|
|
2361
1966
|
}
|
|
2362
|
-
//# sourceMappingURL=frontend.js.map
|