matterbridge 3.3.7 → 3.3.8-dev-20251115-920acfc
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 +37 -0
- package/README-SERVICE-OPT.md +4 -4
- package/README.md +4 -0
- package/dist/broadcastServer.js +1 -93
- 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 -105
- package/dist/devices/airConditioner.js +1 -58
- package/dist/devices/batteryStorage.js +2 -49
- package/dist/devices/cooktop.js +1 -56
- package/dist/devices/dishwasher.js +1 -58
- package/dist/devices/evse.js +11 -75
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +1 -43
- package/dist/devices/heatPump.js +3 -51
- package/dist/devices/laundryDryer.js +4 -63
- package/dist/devices/laundryWasher.js +5 -71
- package/dist/devices/microwaveOven.js +6 -89
- package/dist/devices/oven.js +1 -86
- package/dist/devices/refrigerator.js +2 -104
- package/dist/devices/roboticVacuumCleaner.js +10 -101
- package/dist/devices/solarPower.js +1 -39
- package/dist/devices/speaker.js +1 -85
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.js +3 -83
- 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 +40 -452
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +0 -53
- package/dist/index.js +1 -25
- package/dist/jestutils/export.js +1 -0
- package/dist/{utils → jestutils}/jestHelpers.js +167 -175
- 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 +50 -838
- package/dist/matterbridgeAccessoryPlatform.js +0 -37
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.js +27 -653
- package/dist/matterbridgeDynamicPlatform.js +0 -37
- package/dist/matterbridgeEndpoint.js +74 -1429
- package/dist/matterbridgeEndpointHelpers.js +42 -475
- package/dist/matterbridgeEndpointTypes.js +3 -0
- package/dist/matterbridgePlatform.js +18 -341
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +5 -340
- 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 +2 -43
- 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/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/npm-shrinkwrap.json +8 -8
- package/package.json +5 -2
- package/scripts/fetch-chip.mjs +100 -0
- package/dist/broadcastServer.d.ts +0 -115
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts +0 -806
- 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 -128
- 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 -238
- 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 -478
- 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 -239
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -371
- 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';
|
|
@@ -63,7 +37,7 @@ export class Frontend extends EventEmitter {
|
|
|
63
37
|
constructor(matterbridge) {
|
|
64
38
|
super();
|
|
65
39
|
this.matterbridge = matterbridge;
|
|
66
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
40
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
67
41
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
68
42
|
this.server = new BroadcastServer('frontend', this.log);
|
|
69
43
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -162,53 +136,23 @@ export class Frontend extends EventEmitter {
|
|
|
162
136
|
this.port = port;
|
|
163
137
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
164
138
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
165
|
-
// Initialize multer with the upload directory
|
|
166
139
|
const multer = await import('multer');
|
|
167
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
140
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
168
141
|
const upload = multer.default({ dest: uploadDir });
|
|
169
|
-
// Create the express app that serves the frontend
|
|
170
142
|
const express = await import('express');
|
|
171
143
|
this.expressApp = express.default();
|
|
172
|
-
// Inject logging/debug wrapper for route/middleware registration
|
|
173
|
-
/*
|
|
174
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
175
|
-
for (const method of methods) {
|
|
176
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
178
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
180
|
-
try {
|
|
181
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
182
|
-
return original(path, ...rest);
|
|
183
|
-
} catch (err) {
|
|
184
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
185
|
-
throw err;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
*/
|
|
190
|
-
// Log all requests to the server for debugging
|
|
191
|
-
/*
|
|
192
|
-
this.expressApp.use((req, res, next) => {
|
|
193
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
194
|
-
next();
|
|
195
|
-
});
|
|
196
|
-
*/
|
|
197
|
-
// Serve static files from 'frontend/build' directory
|
|
198
144
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
199
|
-
// Create a WebSocket server and attach it to the http or https server
|
|
200
145
|
this.log.debug(`Creating WebSocketServer...`);
|
|
201
146
|
const ws = await import('ws');
|
|
202
147
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
203
148
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
204
149
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
205
150
|
const clientIp = request.socket.remoteAddress;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
151
|
+
let callbackLogLevel = "notice";
|
|
152
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
153
|
+
callbackLogLevel = "info";
|
|
154
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
155
|
+
callbackLogLevel = "debug";
|
|
212
156
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
213
157
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
214
158
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -230,25 +174,16 @@ export class Frontend extends EventEmitter {
|
|
|
230
174
|
}
|
|
231
175
|
});
|
|
232
176
|
ws.on('error', (error) => {
|
|
233
|
-
// istanbul ignore next
|
|
234
177
|
this.log.error(`WebSocket client error: ${error}`);
|
|
235
178
|
});
|
|
236
179
|
});
|
|
237
180
|
this.webSocketServer.on('close', () => {
|
|
238
181
|
this.log.debug(`WebSocketServer closed`);
|
|
239
182
|
});
|
|
240
|
-
/* With { noServer: true } it never fires
|
|
241
|
-
this.webSocketServer.on('listening', () => {
|
|
242
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
243
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
244
|
-
});
|
|
245
|
-
*/
|
|
246
|
-
// istanbul ignore next
|
|
247
183
|
this.webSocketServer.on('error', (ws, error) => {
|
|
248
184
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
249
185
|
});
|
|
250
186
|
if (!hasParameter('ssl')) {
|
|
251
|
-
// Create an HTTP server and attach the express app
|
|
252
187
|
const http = await import('node:http');
|
|
253
188
|
try {
|
|
254
189
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -259,7 +194,6 @@ export class Frontend extends EventEmitter {
|
|
|
259
194
|
this.emit('server_error', error);
|
|
260
195
|
return;
|
|
261
196
|
}
|
|
262
|
-
// Listen on the specified port
|
|
263
197
|
if (hasParameter('ingress')) {
|
|
264
198
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
265
199
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -279,29 +213,23 @@ export class Frontend extends EventEmitter {
|
|
|
279
213
|
}
|
|
280
214
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
281
215
|
try {
|
|
282
|
-
// Only proceed for real WebSocket upgrades
|
|
283
|
-
// istanbul ignore next cause is only a safety check
|
|
284
216
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
285
217
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
286
218
|
return socket.destroy();
|
|
287
219
|
}
|
|
288
|
-
// Build a URL so we can read ?password=...
|
|
289
220
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
290
|
-
// Validate WebSocket password
|
|
291
221
|
const password = url.searchParams.get('password') ?? '';
|
|
292
222
|
if (password !== this.storedPassword) {
|
|
293
223
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
294
224
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
295
225
|
return socket.destroy();
|
|
296
226
|
}
|
|
297
|
-
// Complete the WebSocket handshake
|
|
298
227
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
299
228
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
300
229
|
this.webSocketServer?.emit('connection', ws, req);
|
|
301
230
|
});
|
|
302
231
|
}
|
|
303
232
|
catch (err) {
|
|
304
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
305
233
|
{
|
|
306
234
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
307
235
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -324,7 +252,6 @@ export class Frontend extends EventEmitter {
|
|
|
324
252
|
});
|
|
325
253
|
}
|
|
326
254
|
else {
|
|
327
|
-
// SSL is enabled, load the certificate and the private key
|
|
328
255
|
let cert;
|
|
329
256
|
let key;
|
|
330
257
|
let ca;
|
|
@@ -334,7 +261,6 @@ export class Frontend extends EventEmitter {
|
|
|
334
261
|
let httpsServerOptions = {};
|
|
335
262
|
const fs = await import('node:fs');
|
|
336
263
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
337
|
-
// Load the p12 certificate and the passphrase
|
|
338
264
|
try {
|
|
339
265
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
340
266
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -346,7 +272,7 @@ export class Frontend extends EventEmitter {
|
|
|
346
272
|
}
|
|
347
273
|
try {
|
|
348
274
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
349
|
-
passphrase = passphrase.trim();
|
|
275
|
+
passphrase = passphrase.trim();
|
|
350
276
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
351
277
|
}
|
|
352
278
|
catch (error) {
|
|
@@ -357,7 +283,6 @@ export class Frontend extends EventEmitter {
|
|
|
357
283
|
httpsServerOptions = { pfx, passphrase };
|
|
358
284
|
}
|
|
359
285
|
else {
|
|
360
|
-
// 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.
|
|
361
286
|
try {
|
|
362
287
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
363
288
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -387,10 +312,9 @@ export class Frontend extends EventEmitter {
|
|
|
387
312
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
388
313
|
}
|
|
389
314
|
if (hasParameter('mtls')) {
|
|
390
|
-
httpsServerOptions.requestCert = true;
|
|
391
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
315
|
+
httpsServerOptions.requestCert = true;
|
|
316
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
392
317
|
}
|
|
393
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
394
318
|
const https = await import('node:https');
|
|
395
319
|
try {
|
|
396
320
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -401,7 +325,6 @@ export class Frontend extends EventEmitter {
|
|
|
401
325
|
this.emit('server_error', error);
|
|
402
326
|
return;
|
|
403
327
|
}
|
|
404
|
-
// Listen on the specified port
|
|
405
328
|
if (hasParameter('ingress')) {
|
|
406
329
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
407
330
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -421,29 +344,23 @@ export class Frontend extends EventEmitter {
|
|
|
421
344
|
}
|
|
422
345
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
423
346
|
try {
|
|
424
|
-
// Only proceed for real WebSocket upgrades
|
|
425
|
-
// istanbul ignore next cause is only a safety check
|
|
426
347
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
427
348
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
428
349
|
return socket.destroy();
|
|
429
350
|
}
|
|
430
|
-
// Build a URL so we can read ?password=...
|
|
431
351
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
432
|
-
// Validate WebSocket password
|
|
433
352
|
const password = url.searchParams.get('password') ?? '';
|
|
434
353
|
if (password !== this.storedPassword) {
|
|
435
354
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
436
355
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
437
356
|
return socket.destroy();
|
|
438
357
|
}
|
|
439
|
-
// Complete the WebSocket handshake
|
|
440
358
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
441
359
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
442
360
|
this.webSocketServer?.emit('connection', ws, req);
|
|
443
361
|
});
|
|
444
362
|
}
|
|
445
363
|
catch (err) {
|
|
446
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
447
364
|
{
|
|
448
365
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
449
366
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -465,7 +382,6 @@ export class Frontend extends EventEmitter {
|
|
|
465
382
|
return;
|
|
466
383
|
});
|
|
467
384
|
}
|
|
468
|
-
// Subscribe to cli events
|
|
469
385
|
cliEmitter.removeAllListeners();
|
|
470
386
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
471
387
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -476,8 +392,6 @@ export class Frontend extends EventEmitter {
|
|
|
476
392
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
477
393
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
478
394
|
});
|
|
479
|
-
// Endpoint to validate login code
|
|
480
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
481
395
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
482
396
|
const { password } = req.body;
|
|
483
397
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -490,20 +404,17 @@ export class Frontend extends EventEmitter {
|
|
|
490
404
|
res.json({ valid: false });
|
|
491
405
|
}
|
|
492
406
|
});
|
|
493
|
-
// Endpoint to provide health check for docker
|
|
494
407
|
this.expressApp.get('/health', (req, res) => {
|
|
495
408
|
this.log.debug('Express received /health');
|
|
496
409
|
const healthStatus = {
|
|
497
|
-
status: 'ok',
|
|
498
|
-
uptime: process.uptime(),
|
|
499
|
-
timestamp: new Date().toISOString(),
|
|
410
|
+
status: 'ok',
|
|
411
|
+
uptime: process.uptime(),
|
|
412
|
+
timestamp: new Date().toISOString(),
|
|
500
413
|
};
|
|
501
414
|
res.status(200).json(healthStatus);
|
|
502
415
|
});
|
|
503
|
-
// Endpoint to provide memory usage details
|
|
504
416
|
this.expressApp.get('/memory', async (req, res) => {
|
|
505
417
|
this.log.debug('Express received /memory');
|
|
506
|
-
// Memory usage from process
|
|
507
418
|
const memoryUsageRaw = process.memoryUsage();
|
|
508
419
|
const memoryUsage = {
|
|
509
420
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -512,13 +423,10 @@ export class Frontend extends EventEmitter {
|
|
|
512
423
|
external: formatBytes(memoryUsageRaw.external),
|
|
513
424
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
514
425
|
};
|
|
515
|
-
// V8 heap statistics
|
|
516
426
|
const { default: v8 } = await import('node:v8');
|
|
517
427
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
518
428
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
519
|
-
// Format heapStats
|
|
520
429
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
521
|
-
// Format heapSpaces
|
|
522
430
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
523
431
|
...space,
|
|
524
432
|
space_size: formatBytes(space.space_size),
|
|
@@ -537,22 +445,18 @@ export class Frontend extends EventEmitter {
|
|
|
537
445
|
};
|
|
538
446
|
res.status(200).json(memoryReport);
|
|
539
447
|
});
|
|
540
|
-
// Endpoint to provide settings
|
|
541
448
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
542
449
|
this.log.debug('The frontend sent /api/settings');
|
|
543
450
|
res.json(await this.getApiSettings());
|
|
544
451
|
});
|
|
545
|
-
// Endpoint to provide plugins
|
|
546
452
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
547
453
|
this.log.debug('The frontend sent /api/plugins');
|
|
548
454
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
549
455
|
});
|
|
550
|
-
// Endpoint to provide devices
|
|
551
456
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
552
457
|
this.log.debug('The frontend sent /api/devices');
|
|
553
458
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
554
459
|
});
|
|
555
|
-
// Endpoint to view the matterbridge log
|
|
556
460
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
557
461
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
558
462
|
try {
|
|
@@ -566,7 +470,6 @@ export class Frontend extends EventEmitter {
|
|
|
566
470
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
567
471
|
}
|
|
568
472
|
});
|
|
569
|
-
// Endpoint to view the matter.js log
|
|
570
473
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
571
474
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
572
475
|
try {
|
|
@@ -580,7 +483,6 @@ export class Frontend extends EventEmitter {
|
|
|
580
483
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
581
484
|
}
|
|
582
485
|
});
|
|
583
|
-
// Endpoint to view the diagnostic.log
|
|
584
486
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
585
487
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
586
488
|
await this.generateDiagnostic();
|
|
@@ -591,13 +493,10 @@ export class Frontend extends EventEmitter {
|
|
|
591
493
|
res.send(data.slice(29));
|
|
592
494
|
}
|
|
593
495
|
catch (error) {
|
|
594
|
-
// istanbul ignore next
|
|
595
496
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
596
|
-
// istanbul ignore next
|
|
597
497
|
res.status(500).send('Error reading diagnostic log file.');
|
|
598
498
|
}
|
|
599
499
|
});
|
|
600
|
-
// Endpoint to download the diagnostic.log
|
|
601
500
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
602
501
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
603
502
|
await this.generateDiagnostic();
|
|
@@ -608,19 +507,16 @@ export class Frontend extends EventEmitter {
|
|
|
608
507
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
609
508
|
}
|
|
610
509
|
catch (error) {
|
|
611
|
-
// istanbul ignore next
|
|
612
510
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
613
511
|
}
|
|
614
512
|
res.type('text/plain');
|
|
615
513
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
616
|
-
/* istanbul ignore if */
|
|
617
514
|
if (error) {
|
|
618
515
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
619
516
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
620
517
|
}
|
|
621
518
|
});
|
|
622
519
|
});
|
|
623
|
-
// Endpoint to view the history.html
|
|
624
520
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
625
521
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
626
522
|
try {
|
|
@@ -634,7 +530,6 @@ export class Frontend extends EventEmitter {
|
|
|
634
530
|
res.status(500).send('Error reading history file.');
|
|
635
531
|
}
|
|
636
532
|
});
|
|
637
|
-
// Endpoint to download the history.html
|
|
638
533
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
639
534
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
640
535
|
try {
|
|
@@ -644,7 +539,6 @@ export class Frontend extends EventEmitter {
|
|
|
644
539
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
645
540
|
res.type('text/plain');
|
|
646
541
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
647
|
-
/* istanbul ignore if */
|
|
648
542
|
if (error) {
|
|
649
543
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
650
544
|
res.status(500).send('Error downloading history file');
|
|
@@ -656,7 +550,6 @@ export class Frontend extends EventEmitter {
|
|
|
656
550
|
res.status(500).send('Error reading history file.');
|
|
657
551
|
}
|
|
658
552
|
});
|
|
659
|
-
// Endpoint to view the shelly log
|
|
660
553
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
661
554
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
662
555
|
try {
|
|
@@ -670,7 +563,6 @@ export class Frontend extends EventEmitter {
|
|
|
670
563
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
671
564
|
}
|
|
672
565
|
});
|
|
673
|
-
// Endpoint to download the matterbridge log
|
|
674
566
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
675
567
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
676
568
|
const fs = await import('node:fs');
|
|
@@ -685,14 +577,12 @@ export class Frontend extends EventEmitter {
|
|
|
685
577
|
}
|
|
686
578
|
res.type('text/plain');
|
|
687
579
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
688
|
-
/* istanbul ignore if */
|
|
689
580
|
if (error) {
|
|
690
581
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
691
582
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
692
583
|
}
|
|
693
584
|
});
|
|
694
585
|
});
|
|
695
|
-
// Endpoint to download the matter log
|
|
696
586
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
697
587
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
698
588
|
const fs = await import('node:fs');
|
|
@@ -707,14 +597,12 @@ export class Frontend extends EventEmitter {
|
|
|
707
597
|
}
|
|
708
598
|
res.type('text/plain');
|
|
709
599
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
710
|
-
/* istanbul ignore if */
|
|
711
600
|
if (error) {
|
|
712
601
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
713
602
|
res.status(500).send('Error downloading the matter log file');
|
|
714
603
|
}
|
|
715
604
|
});
|
|
716
605
|
});
|
|
717
|
-
// Endpoint to download the shelly log
|
|
718
606
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
719
607
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
720
608
|
const fs = await import('node:fs');
|
|
@@ -729,91 +617,75 @@ export class Frontend extends EventEmitter {
|
|
|
729
617
|
}
|
|
730
618
|
res.type('text/plain');
|
|
731
619
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
732
|
-
/* istanbul ignore if */
|
|
733
620
|
if (error) {
|
|
734
621
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
735
622
|
res.status(500).send('Error downloading Shelly system log file');
|
|
736
623
|
}
|
|
737
624
|
});
|
|
738
625
|
});
|
|
739
|
-
// Endpoint to download the matterbridge storage directory
|
|
740
626
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
741
627
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
742
628
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
743
629
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
744
|
-
/* istanbul ignore if */
|
|
745
630
|
if (error) {
|
|
746
631
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
747
632
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
748
633
|
}
|
|
749
634
|
});
|
|
750
635
|
});
|
|
751
|
-
// Endpoint to download the matter storage file
|
|
752
636
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
753
637
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
754
638
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
755
639
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
756
|
-
/* istanbul ignore if */
|
|
757
640
|
if (error) {
|
|
758
641
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
759
642
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
760
643
|
}
|
|
761
644
|
});
|
|
762
645
|
});
|
|
763
|
-
// Endpoint to download the matterbridge plugin directory
|
|
764
646
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
765
647
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
766
648
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
767
649
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
768
|
-
/* istanbul ignore if */
|
|
769
650
|
if (error) {
|
|
770
651
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
771
652
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
772
653
|
}
|
|
773
654
|
});
|
|
774
655
|
});
|
|
775
|
-
// Endpoint to download the matterbridge plugin config files
|
|
776
656
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
777
657
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
778
658
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
779
659
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
780
|
-
/* istanbul ignore if */
|
|
781
660
|
if (error) {
|
|
782
661
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
783
662
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
784
663
|
}
|
|
785
664
|
});
|
|
786
665
|
});
|
|
787
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
788
666
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
789
667
|
this.log.debug('The frontend sent /api/download-backup');
|
|
790
668
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
791
|
-
/* istanbul ignore if */
|
|
792
669
|
if (error) {
|
|
793
670
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
794
671
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
795
672
|
}
|
|
796
673
|
});
|
|
797
674
|
});
|
|
798
|
-
// Endpoint to upload a package
|
|
799
675
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
800
676
|
const { filename } = req.body;
|
|
801
677
|
const file = req.file;
|
|
802
|
-
/* istanbul ignore if */
|
|
803
678
|
if (!file || !filename) {
|
|
804
679
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
805
680
|
res.status(400).send('Invalid request: file and filename are required');
|
|
806
681
|
return;
|
|
807
682
|
}
|
|
808
683
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
809
|
-
// Define the path where the plugin file will be saved
|
|
810
684
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
811
685
|
try {
|
|
812
|
-
// Move the uploaded file to the specified path
|
|
813
686
|
const fs = await import('node:fs');
|
|
814
687
|
await fs.promises.rename(file.path, filePath);
|
|
815
688
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
816
|
-
// Install the plugin package
|
|
817
689
|
if (filename.endsWith('.tgz')) {
|
|
818
690
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
819
691
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -833,7 +705,6 @@ export class Frontend extends EventEmitter {
|
|
|
833
705
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
834
706
|
}
|
|
835
707
|
});
|
|
836
|
-
// Fallback for routing (must be the last route)
|
|
837
708
|
this.expressApp.use((req, res) => {
|
|
838
709
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
839
710
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -844,16 +715,13 @@ export class Frontend extends EventEmitter {
|
|
|
844
715
|
async stop() {
|
|
845
716
|
this.log.debug('Stopping the frontend...');
|
|
846
717
|
const ws = await import('ws');
|
|
847
|
-
// Remove listeners from the express app
|
|
848
718
|
if (this.expressApp) {
|
|
849
719
|
this.expressApp.removeAllListeners();
|
|
850
720
|
this.expressApp = undefined;
|
|
851
721
|
this.log.debug('Frontend app closed successfully');
|
|
852
722
|
}
|
|
853
|
-
// Close the WebSocket server
|
|
854
723
|
if (this.webSocketServer) {
|
|
855
724
|
this.log.debug('Closing WebSocket server...');
|
|
856
|
-
// Close all active connections
|
|
857
725
|
this.webSocketServer.clients.forEach((client) => {
|
|
858
726
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
859
727
|
client.close();
|
|
@@ -862,7 +730,6 @@ export class Frontend extends EventEmitter {
|
|
|
862
730
|
await withTimeout(new Promise((resolve) => {
|
|
863
731
|
this.webSocketServer?.close((error) => {
|
|
864
732
|
if (error) {
|
|
865
|
-
// istanbul ignore next
|
|
866
733
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
867
734
|
}
|
|
868
735
|
else {
|
|
@@ -875,27 +742,8 @@ export class Frontend extends EventEmitter {
|
|
|
875
742
|
this.webSocketServer.removeAllListeners();
|
|
876
743
|
this.webSocketServer = undefined;
|
|
877
744
|
}
|
|
878
|
-
// Close the http server
|
|
879
745
|
if (this.httpServer) {
|
|
880
746
|
this.log.debug('Closing http server...');
|
|
881
|
-
/*
|
|
882
|
-
await withTimeout(
|
|
883
|
-
new Promise<void>((resolve) => {
|
|
884
|
-
this.httpServer?.close((error) => {
|
|
885
|
-
if (error) {
|
|
886
|
-
// istanbul ignore next
|
|
887
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
888
|
-
} else {
|
|
889
|
-
this.log.debug('Http server closed successfully');
|
|
890
|
-
this.emit('server_stopped');
|
|
891
|
-
}
|
|
892
|
-
resolve();
|
|
893
|
-
});
|
|
894
|
-
}),
|
|
895
|
-
5000,
|
|
896
|
-
false,
|
|
897
|
-
);
|
|
898
|
-
*/
|
|
899
747
|
this.httpServer.close();
|
|
900
748
|
this.log.debug('Http server closed successfully');
|
|
901
749
|
this.listening = false;
|
|
@@ -904,27 +752,8 @@ export class Frontend extends EventEmitter {
|
|
|
904
752
|
this.httpServer = undefined;
|
|
905
753
|
this.log.debug('Frontend http server closed successfully');
|
|
906
754
|
}
|
|
907
|
-
// Close the https server
|
|
908
755
|
if (this.httpsServer) {
|
|
909
756
|
this.log.debug('Closing https server...');
|
|
910
|
-
/*
|
|
911
|
-
await withTimeout(
|
|
912
|
-
new Promise<void>((resolve) => {
|
|
913
|
-
this.httpsServer?.close((error) => {
|
|
914
|
-
if (error) {
|
|
915
|
-
// istanbul ignore next
|
|
916
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
917
|
-
} else {
|
|
918
|
-
this.log.debug('Https server closed successfully');
|
|
919
|
-
this.emit('server_stopped');
|
|
920
|
-
}
|
|
921
|
-
resolve();
|
|
922
|
-
});
|
|
923
|
-
}),
|
|
924
|
-
5000,
|
|
925
|
-
false,
|
|
926
|
-
);
|
|
927
|
-
*/
|
|
928
757
|
this.httpsServer.close();
|
|
929
758
|
this.log.debug('Https server closed successfully');
|
|
930
759
|
this.listening = false;
|
|
@@ -935,13 +764,7 @@ export class Frontend extends EventEmitter {
|
|
|
935
764
|
}
|
|
936
765
|
this.log.debug('Frontend stopped successfully');
|
|
937
766
|
}
|
|
938
|
-
/**
|
|
939
|
-
* Retrieves the api settings data.
|
|
940
|
-
*
|
|
941
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
942
|
-
*/
|
|
943
767
|
async getApiSettings() {
|
|
944
|
-
// Update the variable system information properties
|
|
945
768
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
946
769
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
947
770
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -951,7 +774,6 @@ export class Frontend extends EventEmitter {
|
|
|
951
774
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
952
775
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
953
776
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
954
|
-
// Create the matterbridge information
|
|
955
777
|
const info = {
|
|
956
778
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
957
779
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -987,15 +809,9 @@ export class Frontend extends EventEmitter {
|
|
|
987
809
|
};
|
|
988
810
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
989
811
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Retrieves the reachable attribute.
|
|
992
|
-
*
|
|
993
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
994
|
-
* @returns {boolean} The reachable attribute.
|
|
995
|
-
*/
|
|
996
812
|
getReachability(device) {
|
|
997
813
|
if (this.matterbridge.hasCleanupStarted)
|
|
998
|
-
return false;
|
|
814
|
+
return false;
|
|
999
815
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1000
816
|
return false;
|
|
1001
817
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -1006,15 +822,9 @@ export class Frontend extends EventEmitter {
|
|
|
1006
822
|
return true;
|
|
1007
823
|
return false;
|
|
1008
824
|
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Retrieves the power source attribute.
|
|
1011
|
-
*
|
|
1012
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1013
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1014
|
-
*/
|
|
1015
825
|
getPowerSource(endpoint) {
|
|
1016
826
|
if (this.matterbridge.hasCleanupStarted)
|
|
1017
|
-
return;
|
|
827
|
+
return;
|
|
1018
828
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1019
829
|
return undefined;
|
|
1020
830
|
const powerSource = (device) => {
|
|
@@ -1029,25 +839,16 @@ export class Frontend extends EventEmitter {
|
|
|
1029
839
|
}
|
|
1030
840
|
return;
|
|
1031
841
|
};
|
|
1032
|
-
// Root endpoint
|
|
1033
842
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1034
843
|
return powerSource(endpoint);
|
|
1035
|
-
// Child endpoints
|
|
1036
844
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1037
845
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1038
846
|
return powerSource(child);
|
|
1039
847
|
}
|
|
1040
848
|
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Retrieves the cluster text description from a given device.
|
|
1043
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1044
|
-
*
|
|
1045
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1046
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1047
|
-
*/
|
|
1048
849
|
getClusterTextFromDevice(device) {
|
|
1049
850
|
if (this.matterbridge.hasCleanupStarted)
|
|
1050
|
-
return '';
|
|
851
|
+
return '';
|
|
1051
852
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1052
853
|
return '';
|
|
1053
854
|
const getUserLabel = (device) => {
|
|
@@ -1057,7 +858,6 @@ export class Frontend extends EventEmitter {
|
|
|
1057
858
|
if (composed)
|
|
1058
859
|
return 'Composed: ' + composed.value;
|
|
1059
860
|
}
|
|
1060
|
-
// istanbul ignore next cause is not reachable
|
|
1061
861
|
return '';
|
|
1062
862
|
};
|
|
1063
863
|
const getFixedLabel = (device) => {
|
|
@@ -1067,13 +867,11 @@ export class Frontend extends EventEmitter {
|
|
|
1067
867
|
if (composed)
|
|
1068
868
|
return 'Composed: ' + composed.value;
|
|
1069
869
|
}
|
|
1070
|
-
// istanbul ignore next cause is not reacheable
|
|
1071
870
|
return '';
|
|
1072
871
|
};
|
|
1073
872
|
let attributes = '';
|
|
1074
873
|
let supportedModes = [];
|
|
1075
874
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1076
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1077
875
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1078
876
|
return;
|
|
1079
877
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1163,17 +961,11 @@ export class Frontend extends EventEmitter {
|
|
|
1163
961
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1164
962
|
attributes += `${getUserLabel(device)} `;
|
|
1165
963
|
});
|
|
1166
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1167
964
|
return attributes.trimStart().trimEnd();
|
|
1168
965
|
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1171
|
-
*
|
|
1172
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1173
|
-
*/
|
|
1174
966
|
getPlugins() {
|
|
1175
967
|
if (this.matterbridge.hasCleanupStarted)
|
|
1176
|
-
return [];
|
|
968
|
+
return [];
|
|
1177
969
|
const plugins = [];
|
|
1178
970
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1179
971
|
plugins.push({
|
|
@@ -1201,27 +993,18 @@ export class Frontend extends EventEmitter {
|
|
|
1201
993
|
schemaJson: plugin.schemaJson,
|
|
1202
994
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1203
995
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1204
|
-
// Childbridge mode specific data
|
|
1205
996
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1206
997
|
});
|
|
1207
998
|
}
|
|
1208
999
|
return plugins;
|
|
1209
1000
|
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Retrieves the devices from Matterbridge.
|
|
1212
|
-
*
|
|
1213
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1214
|
-
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1215
|
-
*/
|
|
1216
1001
|
getDevices(pluginName) {
|
|
1217
1002
|
if (this.matterbridge.hasCleanupStarted)
|
|
1218
|
-
return [];
|
|
1003
|
+
return [];
|
|
1219
1004
|
const devices = [];
|
|
1220
1005
|
for (const device of this.matterbridge.devices.array()) {
|
|
1221
|
-
// Filter by pluginName if provided
|
|
1222
1006
|
if (pluginName && pluginName !== device.plugin)
|
|
1223
1007
|
continue;
|
|
1224
|
-
// Check if the device has the required properties
|
|
1225
1008
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1226
1009
|
continue;
|
|
1227
1010
|
devices.push({
|
|
@@ -1241,39 +1024,24 @@ export class Frontend extends EventEmitter {
|
|
|
1241
1024
|
}
|
|
1242
1025
|
return devices;
|
|
1243
1026
|
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1246
|
-
*
|
|
1247
|
-
* Response for /api/clusters
|
|
1248
|
-
*
|
|
1249
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1250
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1251
|
-
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1252
|
-
*/
|
|
1253
1027
|
getClusters(pluginName, endpointNumber) {
|
|
1254
1028
|
if (this.matterbridge.hasCleanupStarted)
|
|
1255
|
-
return;
|
|
1029
|
+
return;
|
|
1256
1030
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1257
1031
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1258
1032
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1259
1033
|
return;
|
|
1260
1034
|
}
|
|
1261
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1262
|
-
// Get the device types from the main endpoint
|
|
1263
1035
|
const deviceTypes = [];
|
|
1264
1036
|
const clusters = [];
|
|
1265
1037
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1266
1038
|
deviceTypes.push(d.deviceType);
|
|
1267
1039
|
});
|
|
1268
|
-
// Get the clusters from the main endpoint
|
|
1269
1040
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1270
1041
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1271
1042
|
return;
|
|
1272
1043
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1273
1044
|
return;
|
|
1274
|
-
// console.log(
|
|
1275
|
-
// `${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}`,
|
|
1276
|
-
// );
|
|
1277
1045
|
clusters.push({
|
|
1278
1046
|
endpoint: endpoint.number.toString(),
|
|
1279
1047
|
number: endpoint.number,
|
|
@@ -1287,19 +1055,12 @@ export class Frontend extends EventEmitter {
|
|
|
1287
1055
|
attributeLocalValue: attributeValue,
|
|
1288
1056
|
});
|
|
1289
1057
|
});
|
|
1290
|
-
// Get the child endpoints
|
|
1291
1058
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1292
|
-
// if (childEndpoints.length === 0) {
|
|
1293
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1294
|
-
// }
|
|
1295
1059
|
childEndpoints.forEach((childEndpoint) => {
|
|
1296
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1297
1060
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1298
1061
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1299
1062
|
return;
|
|
1300
1063
|
}
|
|
1301
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1302
|
-
// Get the device types of the child endpoint
|
|
1303
1064
|
const deviceTypes = [];
|
|
1304
1065
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1305
1066
|
deviceTypes.push(d.deviceType);
|
|
@@ -1309,9 +1070,6 @@ export class Frontend extends EventEmitter {
|
|
|
1309
1070
|
return;
|
|
1310
1071
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1311
1072
|
return;
|
|
1312
|
-
// console.log(
|
|
1313
|
-
// `${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}`,
|
|
1314
|
-
// );
|
|
1315
1073
|
clusters.push({
|
|
1316
1074
|
endpoint: childEndpoint.number.toString(),
|
|
1317
1075
|
number: childEndpoint.number,
|
|
@@ -1331,7 +1089,6 @@ export class Frontend extends EventEmitter {
|
|
|
1331
1089
|
async generateDiagnostic() {
|
|
1332
1090
|
this.log.debug('Generating diagnostic...');
|
|
1333
1091
|
const serverNodes = [];
|
|
1334
|
-
// istanbul ignore else
|
|
1335
1092
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1336
1093
|
if (this.matterbridge.serverNode)
|
|
1337
1094
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1342,7 +1099,6 @@ export class Frontend extends EventEmitter {
|
|
|
1342
1099
|
serverNodes.push(plugin.serverNode);
|
|
1343
1100
|
}
|
|
1344
1101
|
}
|
|
1345
|
-
// istanbul ignore next
|
|
1346
1102
|
for (const device of this.matterbridge.devices.array()) {
|
|
1347
1103
|
if (device.serverNode)
|
|
1348
1104
|
serverNodes.push(device.serverNode);
|
|
@@ -1366,15 +1122,8 @@ export class Frontend extends EventEmitter {
|
|
|
1366
1122
|
values: [...serverNodes],
|
|
1367
1123
|
})));
|
|
1368
1124
|
delete Logger.destinations.diagnostic;
|
|
1369
|
-
await wait(500);
|
|
1125
|
+
await wait(500);
|
|
1370
1126
|
}
|
|
1371
|
-
/**
|
|
1372
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1373
|
-
*
|
|
1374
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1375
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1376
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1377
|
-
*/
|
|
1378
1127
|
async wsMessageHandler(client, message) {
|
|
1379
1128
|
let data;
|
|
1380
1129
|
const sendResponse = (data) => {
|
|
@@ -1394,7 +1143,7 @@ export class Frontend extends EventEmitter {
|
|
|
1394
1143
|
};
|
|
1395
1144
|
try {
|
|
1396
1145
|
data = JSON.parse(message.toString());
|
|
1397
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1146
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1398
1147
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1399
1148
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1400
1149
|
return;
|
|
@@ -1451,22 +1200,7 @@ export class Frontend extends EventEmitter {
|
|
|
1451
1200
|
}
|
|
1452
1201
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1453
1202
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1454
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1455
|
-
/*
|
|
1456
|
-
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1457
|
-
if (plugin) {
|
|
1458
|
-
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1459
|
-
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1460
|
-
this.wssSendRestartRequired();
|
|
1461
|
-
this.wssSendRefreshRequired('plugins');
|
|
1462
|
-
this.wssSendRefreshRequired('devices');
|
|
1463
|
-
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1464
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1465
|
-
} else {
|
|
1466
|
-
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1467
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1468
|
-
}
|
|
1469
|
-
*/
|
|
1203
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1470
1204
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1471
1205
|
if (plugin) {
|
|
1472
1206
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1480,7 +1214,6 @@ export class Frontend extends EventEmitter {
|
|
|
1480
1214
|
return;
|
|
1481
1215
|
})
|
|
1482
1216
|
.catch((_error) => {
|
|
1483
|
-
//
|
|
1484
1217
|
});
|
|
1485
1218
|
}
|
|
1486
1219
|
else {
|
|
@@ -1495,10 +1228,6 @@ export class Frontend extends EventEmitter {
|
|
|
1495
1228
|
}
|
|
1496
1229
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1497
1230
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1498
|
-
/*
|
|
1499
|
-
await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been removed.', removeAllDevices: true } }, 5000);
|
|
1500
|
-
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1501
|
-
*/
|
|
1502
1231
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1503
1232
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1504
1233
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1534,7 +1263,6 @@ export class Frontend extends EventEmitter {
|
|
|
1534
1263
|
return;
|
|
1535
1264
|
})
|
|
1536
1265
|
.catch((_error) => {
|
|
1537
|
-
//
|
|
1538
1266
|
});
|
|
1539
1267
|
}
|
|
1540
1268
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1560,7 +1288,6 @@ export class Frontend extends EventEmitter {
|
|
|
1560
1288
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1561
1289
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1562
1290
|
if (plugin.serverNode) {
|
|
1563
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1564
1291
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1565
1292
|
plugin.serverNode = undefined;
|
|
1566
1293
|
}
|
|
@@ -1570,20 +1297,18 @@ export class Frontend extends EventEmitter {
|
|
|
1570
1297
|
this.matterbridge.devices.remove(device);
|
|
1571
1298
|
}
|
|
1572
1299
|
}
|
|
1573
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1574
1300
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1575
1301
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1576
1302
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1577
|
-
plugin.restartRequired = false;
|
|
1303
|
+
plugin.restartRequired = false;
|
|
1578
1304
|
let needRestart = 0;
|
|
1579
1305
|
for (const plugin of this.matterbridge.plugins) {
|
|
1580
1306
|
if (plugin.restartRequired)
|
|
1581
1307
|
needRestart++;
|
|
1582
1308
|
}
|
|
1583
1309
|
if (needRestart === 0) {
|
|
1584
|
-
this.wssSendRestartNotRequired(true);
|
|
1310
|
+
this.wssSendRestartNotRequired(true);
|
|
1585
1311
|
}
|
|
1586
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1587
1312
|
if (plugin.serverNode)
|
|
1588
1313
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1589
1314
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1728,7 +1453,11 @@ export class Frontend extends EventEmitter {
|
|
|
1728
1453
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1729
1454
|
}
|
|
1730
1455
|
if (data.params.advertise) {
|
|
1731
|
-
|
|
1456
|
+
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1457
|
+
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1458
|
+
await advertiser.advertise(true);
|
|
1459
|
+
if (advertiser && advertiser.restartAdvertisement && typeof advertiser.restartAdvertisement === 'function')
|
|
1460
|
+
advertiser.restartAdvertisement();
|
|
1732
1461
|
this.log.debug(`*Advertising has been sent for node ${data.params.id}`);
|
|
1733
1462
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertising: true } });
|
|
1734
1463
|
}
|
|
@@ -1849,22 +1578,22 @@ export class Frontend extends EventEmitter {
|
|
|
1849
1578
|
if (isValidString(data.params.value, 4)) {
|
|
1850
1579
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1851
1580
|
if (data.params.value === 'Debug') {
|
|
1852
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1581
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1853
1582
|
}
|
|
1854
1583
|
else if (data.params.value === 'Info') {
|
|
1855
|
-
await this.matterbridge.setLogLevel("info"
|
|
1584
|
+
await this.matterbridge.setLogLevel("info");
|
|
1856
1585
|
}
|
|
1857
1586
|
else if (data.params.value === 'Notice') {
|
|
1858
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1587
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1859
1588
|
}
|
|
1860
1589
|
else if (data.params.value === 'Warn') {
|
|
1861
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1590
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1862
1591
|
}
|
|
1863
1592
|
else if (data.params.value === 'Error') {
|
|
1864
|
-
await this.matterbridge.setLogLevel("error"
|
|
1593
|
+
await this.matterbridge.setLogLevel("error");
|
|
1865
1594
|
}
|
|
1866
1595
|
else if (data.params.value === 'Fatal') {
|
|
1867
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1596
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1868
1597
|
}
|
|
1869
1598
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1870
1599
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1875,7 +1604,6 @@ export class Frontend extends EventEmitter {
|
|
|
1875
1604
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1876
1605
|
this.matterbridge.fileLogger = data.params.value;
|
|
1877
1606
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1878
|
-
// Create the file logger for matterbridge
|
|
1879
1607
|
if (data.params.value)
|
|
1880
1608
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1881
1609
|
else
|
|
@@ -1904,12 +1632,11 @@ export class Frontend extends EventEmitter {
|
|
|
1904
1632
|
else if (data.params.value === 'Fatal') {
|
|
1905
1633
|
Logger.level = MatterLogLevel.FATAL;
|
|
1906
1634
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1635
|
+
let callbackLogLevel = "notice";
|
|
1636
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1637
|
+
callbackLogLevel = "info";
|
|
1638
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1639
|
+
callbackLogLevel = "debug";
|
|
1913
1640
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1914
1641
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1915
1642
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1961,7 +1688,6 @@ export class Frontend extends EventEmitter {
|
|
|
1961
1688
|
}
|
|
1962
1689
|
break;
|
|
1963
1690
|
case 'setmatterport':
|
|
1964
|
-
// eslint-disable-next-line no-case-declarations
|
|
1965
1691
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1966
1692
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1967
1693
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1981,7 +1707,6 @@ export class Frontend extends EventEmitter {
|
|
|
1981
1707
|
}
|
|
1982
1708
|
break;
|
|
1983
1709
|
case 'setmatterdiscriminator':
|
|
1984
|
-
// eslint-disable-next-line no-case-declarations
|
|
1985
1710
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1986
1711
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1987
1712
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -2001,7 +1726,6 @@ export class Frontend extends EventEmitter {
|
|
|
2001
1726
|
}
|
|
2002
1727
|
break;
|
|
2003
1728
|
case 'setmatterpasscode':
|
|
2004
|
-
// eslint-disable-next-line no-case-declarations
|
|
2005
1729
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2006
1730
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
2007
1731
|
this.matterbridge.passcode = passcode;
|
|
@@ -2047,19 +1771,15 @@ export class Frontend extends EventEmitter {
|
|
|
2047
1771
|
return;
|
|
2048
1772
|
}
|
|
2049
1773
|
const config = plugin.configJson;
|
|
2050
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2051
1774
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2052
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2053
1775
|
if (select === 'serial')
|
|
2054
1776
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2055
1777
|
if (select === 'name')
|
|
2056
1778
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2057
1779
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2058
|
-
// Remove postfix from the serial if it exists
|
|
2059
1780
|
if (config.postfix) {
|
|
2060
1781
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2061
1782
|
}
|
|
2062
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2063
1783
|
if (isValidArray(config.whiteList, 1)) {
|
|
2064
1784
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2065
1785
|
config.whiteList.push(data.params.serial);
|
|
@@ -2068,7 +1788,6 @@ export class Frontend extends EventEmitter {
|
|
|
2068
1788
|
config.whiteList.push(data.params.name);
|
|
2069
1789
|
}
|
|
2070
1790
|
}
|
|
2071
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2072
1791
|
if (isValidArray(config.blackList, 1)) {
|
|
2073
1792
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2074
1793
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2096,9 +1815,7 @@ export class Frontend extends EventEmitter {
|
|
|
2096
1815
|
return;
|
|
2097
1816
|
}
|
|
2098
1817
|
const config = plugin.configJson;
|
|
2099
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2100
1818
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2101
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2102
1819
|
if (select === 'serial')
|
|
2103
1820
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2104
1821
|
if (select === 'name')
|
|
@@ -2107,7 +1824,6 @@ export class Frontend extends EventEmitter {
|
|
|
2107
1824
|
if (config.postfix) {
|
|
2108
1825
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2109
1826
|
}
|
|
2110
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2111
1827
|
if (isValidArray(config.whiteList, 1)) {
|
|
2112
1828
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2113
1829
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2116,7 +1832,6 @@ export class Frontend extends EventEmitter {
|
|
|
2116
1832
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2117
1833
|
}
|
|
2118
1834
|
}
|
|
2119
|
-
// Add the serial to the blackList
|
|
2120
1835
|
if (isValidArray(config.blackList)) {
|
|
2121
1836
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2122
1837
|
config.blackList.push(data.params.serial);
|
|
@@ -2139,7 +1854,6 @@ export class Frontend extends EventEmitter {
|
|
|
2139
1854
|
}
|
|
2140
1855
|
}
|
|
2141
1856
|
else {
|
|
2142
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2143
1857
|
const localData = data;
|
|
2144
1858
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2145
1859
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2149,46 +1863,23 @@ export class Frontend extends EventEmitter {
|
|
|
2149
1863
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2150
1864
|
}
|
|
2151
1865
|
}
|
|
2152
|
-
/**
|
|
2153
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2154
|
-
*
|
|
2155
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2156
|
-
* @param {string} time - The time string of the message
|
|
2157
|
-
* @param {string} name - The logger name of the message
|
|
2158
|
-
* @param {string} message - The content of the message.
|
|
2159
|
-
*
|
|
2160
|
-
* @remarks
|
|
2161
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2162
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2163
|
-
* The function sends the message to all connected clients.
|
|
2164
|
-
*/
|
|
2165
1866
|
wssSendLogMessage(level, time, name, message) {
|
|
2166
1867
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2167
1868
|
return;
|
|
2168
1869
|
if (!level || !time || !name || !message)
|
|
2169
1870
|
return;
|
|
2170
|
-
// Remove ANSI escape codes from the message
|
|
2171
|
-
// eslint-disable-next-line no-control-regex
|
|
2172
1871
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2173
|
-
// Remove leading asterisks from the message
|
|
2174
1872
|
message = message.replace(/^\*+/, '');
|
|
2175
|
-
// Replace all occurrences of \t and \n
|
|
2176
1873
|
message = message.replace(/[\t\n]/g, '');
|
|
2177
|
-
// Remove non-printable characters
|
|
2178
|
-
// eslint-disable-next-line no-control-regex
|
|
2179
1874
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2180
|
-
// Replace all occurrences of \" with "
|
|
2181
1875
|
message = message.replace(/\\"/g, '"');
|
|
2182
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2183
1876
|
const maxContinuousLength = 100;
|
|
2184
1877
|
const keepStartLength = 20;
|
|
2185
1878
|
const keepEndLength = 20;
|
|
2186
|
-
// Split the message into words
|
|
2187
1879
|
if (level !== 'spawn') {
|
|
2188
1880
|
message = message
|
|
2189
1881
|
.split(' ')
|
|
2190
1882
|
.map((word) => {
|
|
2191
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2192
1883
|
if (word.length > maxContinuousLength) {
|
|
2193
1884
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2194
1885
|
}
|
|
@@ -2196,34 +1887,14 @@ export class Frontend extends EventEmitter {
|
|
|
2196
1887
|
})
|
|
2197
1888
|
.join(' ');
|
|
2198
1889
|
}
|
|
2199
|
-
// Send the message to all connected clients
|
|
2200
1890
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2201
1891
|
}
|
|
2202
|
-
/**
|
|
2203
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2204
|
-
*
|
|
2205
|
-
* @param {string} changed - The changed value.
|
|
2206
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2207
|
-
* possible values for changed:
|
|
2208
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2209
|
-
* - 'plugins'
|
|
2210
|
-
* - 'devices'
|
|
2211
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2212
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2213
|
-
*/
|
|
2214
1892
|
wssSendRefreshRequired(changed, params) {
|
|
2215
1893
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2216
1894
|
return;
|
|
2217
1895
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2218
|
-
// Send the message to all connected clients
|
|
2219
1896
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2220
1897
|
}
|
|
2221
|
-
/**
|
|
2222
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2223
|
-
*
|
|
2224
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2225
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2226
|
-
*/
|
|
2227
1898
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2228
1899
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2229
1900
|
return;
|
|
@@ -2232,14 +1903,8 @@ export class Frontend extends EventEmitter {
|
|
|
2232
1903
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2233
1904
|
if (snackbar === true)
|
|
2234
1905
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2235
|
-
// Send the message to all connected clients
|
|
2236
1906
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2237
1907
|
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2240
|
-
*
|
|
2241
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2242
|
-
*/
|
|
2243
1908
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2244
1909
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2245
1910
|
return;
|
|
@@ -2247,133 +1912,57 @@ export class Frontend extends EventEmitter {
|
|
|
2247
1912
|
this.matterbridge.restartRequired = false;
|
|
2248
1913
|
if (snackbar === true)
|
|
2249
1914
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2250
|
-
// Send the message to all connected clients
|
|
2251
1915
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2252
1916
|
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2255
|
-
*
|
|
2256
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2257
|
-
*/
|
|
2258
1917
|
wssSendUpdateRequired(devVersion = false) {
|
|
2259
1918
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2260
1919
|
return;
|
|
2261
1920
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2262
1921
|
this.matterbridge.updateRequired = true;
|
|
2263
|
-
// Send the message to all connected clients
|
|
2264
1922
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2265
1923
|
}
|
|
2266
|
-
/**
|
|
2267
|
-
* Sends a cpu update message to all connected clients.
|
|
2268
|
-
*
|
|
2269
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2270
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2271
|
-
*/
|
|
2272
1924
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2273
1925
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2274
1926
|
return;
|
|
2275
1927
|
if (hasParameter('debug'))
|
|
2276
1928
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2277
|
-
// Send the message to all connected clients
|
|
2278
1929
|
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 } });
|
|
2279
1930
|
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Sends a memory update message to all connected clients.
|
|
2282
|
-
*
|
|
2283
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2284
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2285
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2286
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2287
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2288
|
-
* @param {string} external - The external memory in bytes.
|
|
2289
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2290
|
-
*/
|
|
2291
1931
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2292
1932
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2293
1933
|
return;
|
|
2294
1934
|
if (hasParameter('debug'))
|
|
2295
1935
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2296
|
-
// Send the message to all connected clients
|
|
2297
1936
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2298
1937
|
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Sends an uptime update message to all connected clients.
|
|
2301
|
-
*
|
|
2302
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2303
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2304
|
-
*/
|
|
2305
1938
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2306
1939
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2307
1940
|
return;
|
|
2308
1941
|
if (hasParameter('debug'))
|
|
2309
1942
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2310
|
-
// Send the message to all connected clients
|
|
2311
1943
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2312
1944
|
}
|
|
2313
|
-
/**
|
|
2314
|
-
* Sends an open snackbar message to all connected clients.
|
|
2315
|
-
*
|
|
2316
|
-
* @param {string} message - The message to send.
|
|
2317
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2318
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2319
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2320
|
-
*
|
|
2321
|
-
* @remarks
|
|
2322
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2323
|
-
*/
|
|
2324
1945
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2325
1946
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2326
1947
|
return;
|
|
2327
1948
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2328
|
-
// Send the message to all connected clients
|
|
2329
1949
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2330
1950
|
}
|
|
2331
|
-
/**
|
|
2332
|
-
* Sends a close snackbar message to all connected clients.
|
|
2333
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2334
|
-
*
|
|
2335
|
-
* @param {string} message - The message to send.
|
|
2336
|
-
*/
|
|
2337
1951
|
wssSendCloseSnackbarMessage(message) {
|
|
2338
1952
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2339
1953
|
return;
|
|
2340
1954
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2341
|
-
// Send the message to all connected clients
|
|
2342
1955
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2343
1956
|
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2348
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2349
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2350
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2351
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2352
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2353
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2354
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2355
|
-
*
|
|
2356
|
-
* @remarks
|
|
2357
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2358
|
-
* with the updated attribute information.
|
|
2359
|
-
*/
|
|
2360
1957
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2361
1958
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2362
1959
|
return;
|
|
2363
1960
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2364
|
-
// Send the message to all connected clients
|
|
2365
1961
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2366
1962
|
}
|
|
2367
|
-
/**
|
|
2368
|
-
* Sends a message to all connected clients.
|
|
2369
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2370
|
-
*
|
|
2371
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2372
|
-
*/
|
|
2373
1963
|
wssBroadcastMessage(msg) {
|
|
2374
1964
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2375
1965
|
return;
|
|
2376
|
-
// Send the message to all connected clients
|
|
2377
1966
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2378
1967
|
if (msg.method !== 'log')
|
|
2379
1968
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2384,4 +1973,3 @@ export class Frontend extends EventEmitter {
|
|
|
2384
1973
|
});
|
|
2385
1974
|
}
|
|
2386
1975
|
}
|
|
2387
|
-
//# sourceMappingURL=frontend.js.map
|