matterbridge 3.3.8 → 3.3.9-dev-20251118-930cfdb
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 +18 -0
- package/README-DEV.md +20 -25
- 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 +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -43
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +39 -454
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +0 -53
- package/dist/index.js +0 -25
- package/dist/jestutils/export.js +0 -1
- package/dist/jestutils/jestHelpers.js +104 -278
- 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 +51 -792
- package/dist/matterbridgeAccessoryPlatform.js +0 -38
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.js +14 -635
- package/dist/matterbridgeDynamicPlatform.js +0 -38
- package/dist/matterbridgeEndpoint.js +82 -1445
- package/dist/matterbridgeEndpointHelpers.js +21 -483
- package/dist/matterbridgeEndpointTypes.js +0 -25
- package/dist/matterbridgePlatform.js +1 -354
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +11 -346
- package/dist/shelly.js +7 -168
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -69
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -60
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -41
- package/dist/utils/export.js +0 -1
- package/dist/utils/format.js +0 -49
- package/dist/utils/hex.js +0 -124
- package/dist/utils/inspector.js +1 -69
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/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 +2 -2
- package/package.json +1 -2
- 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 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jestutils/export.d.ts +0 -2
- package/dist/jestutils/export.d.ts.map +0 -1
- package/dist/jestutils/export.js.map +0 -1
- package/dist/jestutils/jestHelpers.d.ts +0 -250
- package/dist/jestutils/jestHelpers.d.ts.map +0 -1
- package/dist/jestutils/jestHelpers.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 -469
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -41
- 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 -698
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -41
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1490
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -787
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgeEndpointTypes.d.ts +0 -197
- package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
- package/dist/matterbridgeEndpointTypes.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -415
- 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/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,24 @@ 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') {
|
|
217
|
+
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
285
218
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
286
219
|
return socket.destroy();
|
|
287
220
|
}
|
|
288
|
-
// Build a URL so we can read ?password=...
|
|
289
221
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
290
|
-
// Validate WebSocket password
|
|
291
222
|
const password = url.searchParams.get('password') ?? '';
|
|
292
223
|
if (password !== this.storedPassword) {
|
|
293
224
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
294
225
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
295
226
|
return socket.destroy();
|
|
296
227
|
}
|
|
297
|
-
// Complete the WebSocket handshake
|
|
298
228
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
299
229
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
300
230
|
this.webSocketServer?.emit('connection', ws, req);
|
|
301
231
|
});
|
|
302
232
|
}
|
|
303
233
|
catch (err) {
|
|
304
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
305
234
|
{
|
|
306
235
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
307
236
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -324,7 +253,6 @@ export class Frontend extends EventEmitter {
|
|
|
324
253
|
});
|
|
325
254
|
}
|
|
326
255
|
else {
|
|
327
|
-
// SSL is enabled, load the certificate and the private key
|
|
328
256
|
let cert;
|
|
329
257
|
let key;
|
|
330
258
|
let ca;
|
|
@@ -334,7 +262,6 @@ export class Frontend extends EventEmitter {
|
|
|
334
262
|
let httpsServerOptions = {};
|
|
335
263
|
const fs = await import('node:fs');
|
|
336
264
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
337
|
-
// Load the p12 certificate and the passphrase
|
|
338
265
|
try {
|
|
339
266
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
340
267
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -346,7 +273,7 @@ export class Frontend extends EventEmitter {
|
|
|
346
273
|
}
|
|
347
274
|
try {
|
|
348
275
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
349
|
-
passphrase = passphrase.trim();
|
|
276
|
+
passphrase = passphrase.trim();
|
|
350
277
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
351
278
|
}
|
|
352
279
|
catch (error) {
|
|
@@ -357,7 +284,6 @@ export class Frontend extends EventEmitter {
|
|
|
357
284
|
httpsServerOptions = { pfx, passphrase };
|
|
358
285
|
}
|
|
359
286
|
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
287
|
try {
|
|
362
288
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
363
289
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -387,10 +313,9 @@ export class Frontend extends EventEmitter {
|
|
|
387
313
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
388
314
|
}
|
|
389
315
|
if (hasParameter('mtls')) {
|
|
390
|
-
httpsServerOptions.requestCert = true;
|
|
391
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
316
|
+
httpsServerOptions.requestCert = true;
|
|
317
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
392
318
|
}
|
|
393
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
394
319
|
const https = await import('node:https');
|
|
395
320
|
try {
|
|
396
321
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -401,7 +326,6 @@ export class Frontend extends EventEmitter {
|
|
|
401
326
|
this.emit('server_error', error);
|
|
402
327
|
return;
|
|
403
328
|
}
|
|
404
|
-
// Listen on the specified port
|
|
405
329
|
if (hasParameter('ingress')) {
|
|
406
330
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
407
331
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -421,29 +345,23 @@ export class Frontend extends EventEmitter {
|
|
|
421
345
|
}
|
|
422
346
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
423
347
|
try {
|
|
424
|
-
// Only proceed for real WebSocket upgrades
|
|
425
|
-
// istanbul ignore next cause is only a safety check
|
|
426
348
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
427
349
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
428
350
|
return socket.destroy();
|
|
429
351
|
}
|
|
430
|
-
// Build a URL so we can read ?password=...
|
|
431
352
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
432
|
-
// Validate WebSocket password
|
|
433
353
|
const password = url.searchParams.get('password') ?? '';
|
|
434
354
|
if (password !== this.storedPassword) {
|
|
435
355
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
436
356
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
437
357
|
return socket.destroy();
|
|
438
358
|
}
|
|
439
|
-
// Complete the WebSocket handshake
|
|
440
359
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
441
360
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
442
361
|
this.webSocketServer?.emit('connection', ws, req);
|
|
443
362
|
});
|
|
444
363
|
}
|
|
445
364
|
catch (err) {
|
|
446
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
447
365
|
{
|
|
448
366
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
449
367
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -465,7 +383,6 @@ export class Frontend extends EventEmitter {
|
|
|
465
383
|
return;
|
|
466
384
|
});
|
|
467
385
|
}
|
|
468
|
-
// Subscribe to cli events
|
|
469
386
|
cliEmitter.removeAllListeners();
|
|
470
387
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
471
388
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -476,8 +393,6 @@ export class Frontend extends EventEmitter {
|
|
|
476
393
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
477
394
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
478
395
|
});
|
|
479
|
-
// Endpoint to validate login code
|
|
480
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
481
396
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
482
397
|
const { password } = req.body;
|
|
483
398
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -490,20 +405,17 @@ export class Frontend extends EventEmitter {
|
|
|
490
405
|
res.json({ valid: false });
|
|
491
406
|
}
|
|
492
407
|
});
|
|
493
|
-
// Endpoint to provide health check for docker
|
|
494
408
|
this.expressApp.get('/health', (req, res) => {
|
|
495
409
|
this.log.debug('Express received /health');
|
|
496
410
|
const healthStatus = {
|
|
497
|
-
status: 'ok',
|
|
498
|
-
uptime: process.uptime(),
|
|
499
|
-
timestamp: new Date().toISOString(),
|
|
411
|
+
status: 'ok',
|
|
412
|
+
uptime: process.uptime(),
|
|
413
|
+
timestamp: new Date().toISOString(),
|
|
500
414
|
};
|
|
501
415
|
res.status(200).json(healthStatus);
|
|
502
416
|
});
|
|
503
|
-
// Endpoint to provide memory usage details
|
|
504
417
|
this.expressApp.get('/memory', async (req, res) => {
|
|
505
418
|
this.log.debug('Express received /memory');
|
|
506
|
-
// Memory usage from process
|
|
507
419
|
const memoryUsageRaw = process.memoryUsage();
|
|
508
420
|
const memoryUsage = {
|
|
509
421
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -512,13 +424,10 @@ export class Frontend extends EventEmitter {
|
|
|
512
424
|
external: formatBytes(memoryUsageRaw.external),
|
|
513
425
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
514
426
|
};
|
|
515
|
-
// V8 heap statistics
|
|
516
427
|
const { default: v8 } = await import('node:v8');
|
|
517
428
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
518
429
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
519
|
-
// Format heapStats
|
|
520
430
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
521
|
-
// Format heapSpaces
|
|
522
431
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
523
432
|
...space,
|
|
524
433
|
space_size: formatBytes(space.space_size),
|
|
@@ -537,22 +446,18 @@ export class Frontend extends EventEmitter {
|
|
|
537
446
|
};
|
|
538
447
|
res.status(200).json(memoryReport);
|
|
539
448
|
});
|
|
540
|
-
// Endpoint to provide settings
|
|
541
449
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
542
450
|
this.log.debug('The frontend sent /api/settings');
|
|
543
451
|
res.json(await this.getApiSettings());
|
|
544
452
|
});
|
|
545
|
-
// Endpoint to provide plugins
|
|
546
453
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
547
454
|
this.log.debug('The frontend sent /api/plugins');
|
|
548
455
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
549
456
|
});
|
|
550
|
-
// Endpoint to provide devices
|
|
551
457
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
552
458
|
this.log.debug('The frontend sent /api/devices');
|
|
553
459
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
554
460
|
});
|
|
555
|
-
// Endpoint to view the matterbridge log
|
|
556
461
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
557
462
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
558
463
|
try {
|
|
@@ -566,7 +471,6 @@ export class Frontend extends EventEmitter {
|
|
|
566
471
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
567
472
|
}
|
|
568
473
|
});
|
|
569
|
-
// Endpoint to view the matter.js log
|
|
570
474
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
571
475
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
572
476
|
try {
|
|
@@ -580,7 +484,6 @@ export class Frontend extends EventEmitter {
|
|
|
580
484
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
581
485
|
}
|
|
582
486
|
});
|
|
583
|
-
// Endpoint to view the diagnostic.log
|
|
584
487
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
585
488
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
586
489
|
await this.generateDiagnostic();
|
|
@@ -591,13 +494,10 @@ export class Frontend extends EventEmitter {
|
|
|
591
494
|
res.send(data.slice(29));
|
|
592
495
|
}
|
|
593
496
|
catch (error) {
|
|
594
|
-
// istanbul ignore next
|
|
595
497
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
596
|
-
// istanbul ignore next
|
|
597
498
|
res.status(500).send('Error reading diagnostic log file.');
|
|
598
499
|
}
|
|
599
500
|
});
|
|
600
|
-
// Endpoint to download the diagnostic.log
|
|
601
501
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
602
502
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
603
503
|
await this.generateDiagnostic();
|
|
@@ -608,19 +508,16 @@ export class Frontend extends EventEmitter {
|
|
|
608
508
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
609
509
|
}
|
|
610
510
|
catch (error) {
|
|
611
|
-
// istanbul ignore next
|
|
612
511
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
613
512
|
}
|
|
614
513
|
res.type('text/plain');
|
|
615
514
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
616
|
-
/* istanbul ignore if */
|
|
617
515
|
if (error) {
|
|
618
516
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
619
517
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
620
518
|
}
|
|
621
519
|
});
|
|
622
520
|
});
|
|
623
|
-
// Endpoint to view the history.html
|
|
624
521
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
625
522
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
626
523
|
try {
|
|
@@ -634,7 +531,6 @@ export class Frontend extends EventEmitter {
|
|
|
634
531
|
res.status(500).send('Error reading history file.');
|
|
635
532
|
}
|
|
636
533
|
});
|
|
637
|
-
// Endpoint to download the history.html
|
|
638
534
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
639
535
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
640
536
|
try {
|
|
@@ -644,7 +540,6 @@ export class Frontend extends EventEmitter {
|
|
|
644
540
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
645
541
|
res.type('text/plain');
|
|
646
542
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
647
|
-
/* istanbul ignore if */
|
|
648
543
|
if (error) {
|
|
649
544
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
650
545
|
res.status(500).send('Error downloading history file');
|
|
@@ -656,7 +551,6 @@ export class Frontend extends EventEmitter {
|
|
|
656
551
|
res.status(500).send('Error reading history file.');
|
|
657
552
|
}
|
|
658
553
|
});
|
|
659
|
-
// Endpoint to view the shelly log
|
|
660
554
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
661
555
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
662
556
|
try {
|
|
@@ -670,7 +564,6 @@ export class Frontend extends EventEmitter {
|
|
|
670
564
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
671
565
|
}
|
|
672
566
|
});
|
|
673
|
-
// Endpoint to download the matterbridge log
|
|
674
567
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
675
568
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
676
569
|
const fs = await import('node:fs');
|
|
@@ -685,14 +578,12 @@ export class Frontend extends EventEmitter {
|
|
|
685
578
|
}
|
|
686
579
|
res.type('text/plain');
|
|
687
580
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
688
|
-
/* istanbul ignore if */
|
|
689
581
|
if (error) {
|
|
690
582
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
691
583
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
692
584
|
}
|
|
693
585
|
});
|
|
694
586
|
});
|
|
695
|
-
// Endpoint to download the matter log
|
|
696
587
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
697
588
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
698
589
|
const fs = await import('node:fs');
|
|
@@ -707,14 +598,12 @@ export class Frontend extends EventEmitter {
|
|
|
707
598
|
}
|
|
708
599
|
res.type('text/plain');
|
|
709
600
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
710
|
-
/* istanbul ignore if */
|
|
711
601
|
if (error) {
|
|
712
602
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
713
603
|
res.status(500).send('Error downloading the matter log file');
|
|
714
604
|
}
|
|
715
605
|
});
|
|
716
606
|
});
|
|
717
|
-
// Endpoint to download the shelly log
|
|
718
607
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
719
608
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
720
609
|
const fs = await import('node:fs');
|
|
@@ -729,91 +618,75 @@ export class Frontend extends EventEmitter {
|
|
|
729
618
|
}
|
|
730
619
|
res.type('text/plain');
|
|
731
620
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
732
|
-
/* istanbul ignore if */
|
|
733
621
|
if (error) {
|
|
734
622
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
735
623
|
res.status(500).send('Error downloading Shelly system log file');
|
|
736
624
|
}
|
|
737
625
|
});
|
|
738
626
|
});
|
|
739
|
-
// Endpoint to download the matterbridge storage directory
|
|
740
627
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
741
628
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
742
629
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
743
630
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
744
|
-
/* istanbul ignore if */
|
|
745
631
|
if (error) {
|
|
746
632
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
747
633
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
748
634
|
}
|
|
749
635
|
});
|
|
750
636
|
});
|
|
751
|
-
// Endpoint to download the matter storage file
|
|
752
637
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
753
638
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
754
639
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
755
640
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
756
|
-
/* istanbul ignore if */
|
|
757
641
|
if (error) {
|
|
758
642
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
759
643
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
760
644
|
}
|
|
761
645
|
});
|
|
762
646
|
});
|
|
763
|
-
// Endpoint to download the matterbridge plugin directory
|
|
764
647
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
765
648
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
766
649
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
767
650
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
768
|
-
/* istanbul ignore if */
|
|
769
651
|
if (error) {
|
|
770
652
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
771
653
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
772
654
|
}
|
|
773
655
|
});
|
|
774
656
|
});
|
|
775
|
-
// Endpoint to download the matterbridge plugin config files
|
|
776
657
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
777
658
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
778
659
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
779
660
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
780
|
-
/* istanbul ignore if */
|
|
781
661
|
if (error) {
|
|
782
662
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
783
663
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
784
664
|
}
|
|
785
665
|
});
|
|
786
666
|
});
|
|
787
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
788
667
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
789
668
|
this.log.debug('The frontend sent /api/download-backup');
|
|
790
669
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
791
|
-
/* istanbul ignore if */
|
|
792
670
|
if (error) {
|
|
793
671
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
794
672
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
795
673
|
}
|
|
796
674
|
});
|
|
797
675
|
});
|
|
798
|
-
// Endpoint to upload a package
|
|
799
676
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
800
677
|
const { filename } = req.body;
|
|
801
678
|
const file = req.file;
|
|
802
|
-
/* istanbul ignore if */
|
|
803
679
|
if (!file || !filename) {
|
|
804
680
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
805
681
|
res.status(400).send('Invalid request: file and filename are required');
|
|
806
682
|
return;
|
|
807
683
|
}
|
|
808
684
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
809
|
-
// Define the path where the plugin file will be saved
|
|
810
685
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
811
686
|
try {
|
|
812
|
-
// Move the uploaded file to the specified path
|
|
813
687
|
const fs = await import('node:fs');
|
|
814
688
|
await fs.promises.rename(file.path, filePath);
|
|
815
689
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
816
|
-
// Install the plugin package
|
|
817
690
|
if (filename.endsWith('.tgz')) {
|
|
818
691
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
819
692
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -833,7 +706,6 @@ export class Frontend extends EventEmitter {
|
|
|
833
706
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
834
707
|
}
|
|
835
708
|
});
|
|
836
|
-
// Fallback for routing (must be the last route)
|
|
837
709
|
this.expressApp.use((req, res) => {
|
|
838
710
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
839
711
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -844,16 +716,13 @@ export class Frontend extends EventEmitter {
|
|
|
844
716
|
async stop() {
|
|
845
717
|
this.log.debug('Stopping the frontend...');
|
|
846
718
|
const ws = await import('ws');
|
|
847
|
-
// Remove listeners from the express app
|
|
848
719
|
if (this.expressApp) {
|
|
849
720
|
this.expressApp.removeAllListeners();
|
|
850
721
|
this.expressApp = undefined;
|
|
851
722
|
this.log.debug('Frontend app closed successfully');
|
|
852
723
|
}
|
|
853
|
-
// Close the WebSocket server
|
|
854
724
|
if (this.webSocketServer) {
|
|
855
725
|
this.log.debug('Closing WebSocket server...');
|
|
856
|
-
// Close all active connections
|
|
857
726
|
this.webSocketServer.clients.forEach((client) => {
|
|
858
727
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
859
728
|
client.close();
|
|
@@ -862,7 +731,6 @@ export class Frontend extends EventEmitter {
|
|
|
862
731
|
await withTimeout(new Promise((resolve) => {
|
|
863
732
|
this.webSocketServer?.close((error) => {
|
|
864
733
|
if (error) {
|
|
865
|
-
// istanbul ignore next
|
|
866
734
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
867
735
|
}
|
|
868
736
|
else {
|
|
@@ -875,27 +743,8 @@ export class Frontend extends EventEmitter {
|
|
|
875
743
|
this.webSocketServer.removeAllListeners();
|
|
876
744
|
this.webSocketServer = undefined;
|
|
877
745
|
}
|
|
878
|
-
// Close the http server
|
|
879
746
|
if (this.httpServer) {
|
|
880
747
|
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
748
|
this.httpServer.close();
|
|
900
749
|
this.log.debug('Http server closed successfully');
|
|
901
750
|
this.listening = false;
|
|
@@ -904,27 +753,8 @@ export class Frontend extends EventEmitter {
|
|
|
904
753
|
this.httpServer = undefined;
|
|
905
754
|
this.log.debug('Frontend http server closed successfully');
|
|
906
755
|
}
|
|
907
|
-
// Close the https server
|
|
908
756
|
if (this.httpsServer) {
|
|
909
757
|
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
758
|
this.httpsServer.close();
|
|
929
759
|
this.log.debug('Https server closed successfully');
|
|
930
760
|
this.listening = false;
|
|
@@ -935,13 +765,7 @@ export class Frontend extends EventEmitter {
|
|
|
935
765
|
}
|
|
936
766
|
this.log.debug('Frontend stopped successfully');
|
|
937
767
|
}
|
|
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
768
|
async getApiSettings() {
|
|
944
|
-
// Update the variable system information properties
|
|
945
769
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
946
770
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
947
771
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -951,7 +775,6 @@ export class Frontend extends EventEmitter {
|
|
|
951
775
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
952
776
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
953
777
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
954
|
-
// Create the matterbridge information
|
|
955
778
|
const info = {
|
|
956
779
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
957
780
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -987,15 +810,9 @@ export class Frontend extends EventEmitter {
|
|
|
987
810
|
};
|
|
988
811
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
989
812
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Retrieves the reachable attribute.
|
|
992
|
-
*
|
|
993
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
994
|
-
* @returns {boolean} The reachable attribute.
|
|
995
|
-
*/
|
|
996
813
|
getReachability(device) {
|
|
997
814
|
if (this.matterbridge.hasCleanupStarted)
|
|
998
|
-
return false;
|
|
815
|
+
return false;
|
|
999
816
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1000
817
|
return false;
|
|
1001
818
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -1006,15 +823,9 @@ export class Frontend extends EventEmitter {
|
|
|
1006
823
|
return true;
|
|
1007
824
|
return false;
|
|
1008
825
|
}
|
|
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
826
|
getPowerSource(endpoint) {
|
|
1016
827
|
if (this.matterbridge.hasCleanupStarted)
|
|
1017
|
-
return;
|
|
828
|
+
return;
|
|
1018
829
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1019
830
|
return undefined;
|
|
1020
831
|
const powerSource = (device) => {
|
|
@@ -1029,25 +840,16 @@ export class Frontend extends EventEmitter {
|
|
|
1029
840
|
}
|
|
1030
841
|
return;
|
|
1031
842
|
};
|
|
1032
|
-
// Root endpoint
|
|
1033
843
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1034
844
|
return powerSource(endpoint);
|
|
1035
|
-
// Child endpoints
|
|
1036
845
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1037
846
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1038
847
|
return powerSource(child);
|
|
1039
848
|
}
|
|
1040
849
|
}
|
|
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
850
|
getClusterTextFromDevice(device) {
|
|
1049
851
|
if (this.matterbridge.hasCleanupStarted)
|
|
1050
|
-
return '';
|
|
852
|
+
return '';
|
|
1051
853
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1052
854
|
return '';
|
|
1053
855
|
const getUserLabel = (device) => {
|
|
@@ -1057,7 +859,6 @@ export class Frontend extends EventEmitter {
|
|
|
1057
859
|
if (composed)
|
|
1058
860
|
return 'Composed: ' + composed.value;
|
|
1059
861
|
}
|
|
1060
|
-
// istanbul ignore next cause is not reachable
|
|
1061
862
|
return '';
|
|
1062
863
|
};
|
|
1063
864
|
const getFixedLabel = (device) => {
|
|
@@ -1067,13 +868,11 @@ export class Frontend extends EventEmitter {
|
|
|
1067
868
|
if (composed)
|
|
1068
869
|
return 'Composed: ' + composed.value;
|
|
1069
870
|
}
|
|
1070
|
-
// istanbul ignore next cause is not reacheable
|
|
1071
871
|
return '';
|
|
1072
872
|
};
|
|
1073
873
|
let attributes = '';
|
|
1074
874
|
let supportedModes = [];
|
|
1075
875
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1076
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1077
876
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1078
877
|
return;
|
|
1079
878
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1163,17 +962,11 @@ export class Frontend extends EventEmitter {
|
|
|
1163
962
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1164
963
|
attributes += `${getUserLabel(device)} `;
|
|
1165
964
|
});
|
|
1166
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1167
965
|
return attributes.trimStart().trimEnd();
|
|
1168
966
|
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1171
|
-
*
|
|
1172
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1173
|
-
*/
|
|
1174
967
|
getPlugins() {
|
|
1175
968
|
if (this.matterbridge.hasCleanupStarted)
|
|
1176
|
-
return [];
|
|
969
|
+
return [];
|
|
1177
970
|
const plugins = [];
|
|
1178
971
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1179
972
|
plugins.push({
|
|
@@ -1201,27 +994,18 @@ export class Frontend extends EventEmitter {
|
|
|
1201
994
|
schemaJson: plugin.schemaJson,
|
|
1202
995
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1203
996
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1204
|
-
// Childbridge mode specific data
|
|
1205
997
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1206
998
|
});
|
|
1207
999
|
}
|
|
1208
1000
|
return plugins;
|
|
1209
1001
|
}
|
|
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
1002
|
getDevices(pluginName) {
|
|
1217
1003
|
if (this.matterbridge.hasCleanupStarted)
|
|
1218
|
-
return [];
|
|
1004
|
+
return [];
|
|
1219
1005
|
const devices = [];
|
|
1220
1006
|
for (const device of this.matterbridge.devices.array()) {
|
|
1221
|
-
// Filter by pluginName if provided
|
|
1222
1007
|
if (pluginName && pluginName !== device.plugin)
|
|
1223
1008
|
continue;
|
|
1224
|
-
// Check if the device has the required properties
|
|
1225
1009
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1226
1010
|
continue;
|
|
1227
1011
|
devices.push({
|
|
@@ -1241,39 +1025,24 @@ export class Frontend extends EventEmitter {
|
|
|
1241
1025
|
}
|
|
1242
1026
|
return devices;
|
|
1243
1027
|
}
|
|
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
1028
|
getClusters(pluginName, endpointNumber) {
|
|
1254
1029
|
if (this.matterbridge.hasCleanupStarted)
|
|
1255
|
-
return;
|
|
1030
|
+
return;
|
|
1256
1031
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1257
1032
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1258
1033
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1259
1034
|
return;
|
|
1260
1035
|
}
|
|
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
1036
|
const deviceTypes = [];
|
|
1264
1037
|
const clusters = [];
|
|
1265
1038
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1266
1039
|
deviceTypes.push(d.deviceType);
|
|
1267
1040
|
});
|
|
1268
|
-
// Get the clusters from the main endpoint
|
|
1269
1041
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1270
1042
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1271
1043
|
return;
|
|
1272
1044
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1273
1045
|
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
1046
|
clusters.push({
|
|
1278
1047
|
endpoint: endpoint.number.toString(),
|
|
1279
1048
|
number: endpoint.number,
|
|
@@ -1287,19 +1056,12 @@ export class Frontend extends EventEmitter {
|
|
|
1287
1056
|
attributeLocalValue: attributeValue,
|
|
1288
1057
|
});
|
|
1289
1058
|
});
|
|
1290
|
-
// Get the child endpoints
|
|
1291
1059
|
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
1060
|
childEndpoints.forEach((childEndpoint) => {
|
|
1296
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1297
1061
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1298
1062
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1299
1063
|
return;
|
|
1300
1064
|
}
|
|
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
1065
|
const deviceTypes = [];
|
|
1304
1066
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1305
1067
|
deviceTypes.push(d.deviceType);
|
|
@@ -1309,9 +1071,6 @@ export class Frontend extends EventEmitter {
|
|
|
1309
1071
|
return;
|
|
1310
1072
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1311
1073
|
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
1074
|
clusters.push({
|
|
1316
1075
|
endpoint: childEndpoint.number.toString(),
|
|
1317
1076
|
number: childEndpoint.number,
|
|
@@ -1331,7 +1090,6 @@ export class Frontend extends EventEmitter {
|
|
|
1331
1090
|
async generateDiagnostic() {
|
|
1332
1091
|
this.log.debug('Generating diagnostic...');
|
|
1333
1092
|
const serverNodes = [];
|
|
1334
|
-
// istanbul ignore else
|
|
1335
1093
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1336
1094
|
if (this.matterbridge.serverNode)
|
|
1337
1095
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1342,7 +1100,6 @@ export class Frontend extends EventEmitter {
|
|
|
1342
1100
|
serverNodes.push(plugin.serverNode);
|
|
1343
1101
|
}
|
|
1344
1102
|
}
|
|
1345
|
-
// istanbul ignore next
|
|
1346
1103
|
for (const device of this.matterbridge.devices.array()) {
|
|
1347
1104
|
if (device.serverNode)
|
|
1348
1105
|
serverNodes.push(device.serverNode);
|
|
@@ -1366,15 +1123,8 @@ export class Frontend extends EventEmitter {
|
|
|
1366
1123
|
values: [...serverNodes],
|
|
1367
1124
|
})));
|
|
1368
1125
|
delete Logger.destinations.diagnostic;
|
|
1369
|
-
await wait(500);
|
|
1126
|
+
await wait(500);
|
|
1370
1127
|
}
|
|
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
1128
|
async wsMessageHandler(client, message) {
|
|
1379
1129
|
let data;
|
|
1380
1130
|
const sendResponse = (data) => {
|
|
@@ -1391,10 +1141,13 @@ export class Frontend extends EventEmitter {
|
|
|
1391
1141
|
}
|
|
1392
1142
|
client.send(JSON.stringify(data));
|
|
1393
1143
|
}
|
|
1144
|
+
else {
|
|
1145
|
+
this.log.error('Cannot send api response, client not connected');
|
|
1146
|
+
}
|
|
1394
1147
|
};
|
|
1395
1148
|
try {
|
|
1396
1149
|
data = JSON.parse(message.toString());
|
|
1397
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1150
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1398
1151
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1399
1152
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1400
1153
|
return;
|
|
@@ -1451,22 +1204,7 @@ export class Frontend extends EventEmitter {
|
|
|
1451
1204
|
}
|
|
1452
1205
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1453
1206
|
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
|
-
*/
|
|
1207
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1470
1208
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1471
1209
|
if (plugin) {
|
|
1472
1210
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1480,7 +1218,6 @@ export class Frontend extends EventEmitter {
|
|
|
1480
1218
|
return;
|
|
1481
1219
|
})
|
|
1482
1220
|
.catch((_error) => {
|
|
1483
|
-
//
|
|
1484
1221
|
});
|
|
1485
1222
|
}
|
|
1486
1223
|
else {
|
|
@@ -1495,10 +1232,6 @@ export class Frontend extends EventEmitter {
|
|
|
1495
1232
|
}
|
|
1496
1233
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1497
1234
|
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
1235
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1503
1236
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1504
1237
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1534,7 +1267,6 @@ export class Frontend extends EventEmitter {
|
|
|
1534
1267
|
return;
|
|
1535
1268
|
})
|
|
1536
1269
|
.catch((_error) => {
|
|
1537
|
-
//
|
|
1538
1270
|
});
|
|
1539
1271
|
}
|
|
1540
1272
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1560,7 +1292,6 @@ export class Frontend extends EventEmitter {
|
|
|
1560
1292
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1561
1293
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1562
1294
|
if (plugin.serverNode) {
|
|
1563
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1564
1295
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1565
1296
|
plugin.serverNode = undefined;
|
|
1566
1297
|
}
|
|
@@ -1570,20 +1301,18 @@ export class Frontend extends EventEmitter {
|
|
|
1570
1301
|
this.matterbridge.devices.remove(device);
|
|
1571
1302
|
}
|
|
1572
1303
|
}
|
|
1573
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1574
1304
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1575
1305
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1576
1306
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1577
|
-
plugin.restartRequired = false;
|
|
1307
|
+
plugin.restartRequired = false;
|
|
1578
1308
|
let needRestart = 0;
|
|
1579
1309
|
for (const plugin of this.matterbridge.plugins) {
|
|
1580
1310
|
if (plugin.restartRequired)
|
|
1581
1311
|
needRestart++;
|
|
1582
1312
|
}
|
|
1583
1313
|
if (needRestart === 0) {
|
|
1584
|
-
this.wssSendRestartNotRequired(true);
|
|
1314
|
+
this.wssSendRestartNotRequired(true);
|
|
1585
1315
|
}
|
|
1586
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1587
1316
|
if (plugin.serverNode)
|
|
1588
1317
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1589
1318
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1728,9 +1457,6 @@ export class Frontend extends EventEmitter {
|
|
|
1728
1457
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1729
1458
|
}
|
|
1730
1459
|
if (data.params.advertise) {
|
|
1731
|
-
// TODO: matter.js 0.16.0
|
|
1732
|
-
// await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
|
|
1733
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1734
1460
|
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1735
1461
|
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1736
1462
|
await advertiser.advertise(true);
|
|
@@ -1856,22 +1582,22 @@ export class Frontend extends EventEmitter {
|
|
|
1856
1582
|
if (isValidString(data.params.value, 4)) {
|
|
1857
1583
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1858
1584
|
if (data.params.value === 'Debug') {
|
|
1859
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1585
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1860
1586
|
}
|
|
1861
1587
|
else if (data.params.value === 'Info') {
|
|
1862
|
-
await this.matterbridge.setLogLevel("info"
|
|
1588
|
+
await this.matterbridge.setLogLevel("info");
|
|
1863
1589
|
}
|
|
1864
1590
|
else if (data.params.value === 'Notice') {
|
|
1865
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1591
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1866
1592
|
}
|
|
1867
1593
|
else if (data.params.value === 'Warn') {
|
|
1868
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1594
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1869
1595
|
}
|
|
1870
1596
|
else if (data.params.value === 'Error') {
|
|
1871
|
-
await this.matterbridge.setLogLevel("error"
|
|
1597
|
+
await this.matterbridge.setLogLevel("error");
|
|
1872
1598
|
}
|
|
1873
1599
|
else if (data.params.value === 'Fatal') {
|
|
1874
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1600
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1875
1601
|
}
|
|
1876
1602
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1877
1603
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1882,7 +1608,6 @@ export class Frontend extends EventEmitter {
|
|
|
1882
1608
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1883
1609
|
this.matterbridge.fileLogger = data.params.value;
|
|
1884
1610
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1885
|
-
// Create the file logger for matterbridge
|
|
1886
1611
|
if (data.params.value)
|
|
1887
1612
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1888
1613
|
else
|
|
@@ -1911,12 +1636,11 @@ export class Frontend extends EventEmitter {
|
|
|
1911
1636
|
else if (data.params.value === 'Fatal') {
|
|
1912
1637
|
Logger.level = MatterLogLevel.FATAL;
|
|
1913
1638
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1639
|
+
let callbackLogLevel = "notice";
|
|
1640
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1641
|
+
callbackLogLevel = "info";
|
|
1642
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1643
|
+
callbackLogLevel = "debug";
|
|
1920
1644
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1921
1645
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1922
1646
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1968,7 +1692,6 @@ export class Frontend extends EventEmitter {
|
|
|
1968
1692
|
}
|
|
1969
1693
|
break;
|
|
1970
1694
|
case 'setmatterport':
|
|
1971
|
-
// eslint-disable-next-line no-case-declarations
|
|
1972
1695
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1973
1696
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1974
1697
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1988,7 +1711,6 @@ export class Frontend extends EventEmitter {
|
|
|
1988
1711
|
}
|
|
1989
1712
|
break;
|
|
1990
1713
|
case 'setmatterdiscriminator':
|
|
1991
|
-
// eslint-disable-next-line no-case-declarations
|
|
1992
1714
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1993
1715
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1994
1716
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -2008,7 +1730,6 @@ export class Frontend extends EventEmitter {
|
|
|
2008
1730
|
}
|
|
2009
1731
|
break;
|
|
2010
1732
|
case 'setmatterpasscode':
|
|
2011
|
-
// eslint-disable-next-line no-case-declarations
|
|
2012
1733
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2013
1734
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
2014
1735
|
this.matterbridge.passcode = passcode;
|
|
@@ -2054,19 +1775,15 @@ export class Frontend extends EventEmitter {
|
|
|
2054
1775
|
return;
|
|
2055
1776
|
}
|
|
2056
1777
|
const config = plugin.configJson;
|
|
2057
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2058
1778
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2059
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2060
1779
|
if (select === 'serial')
|
|
2061
1780
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2062
1781
|
if (select === 'name')
|
|
2063
1782
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2064
1783
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2065
|
-
// Remove postfix from the serial if it exists
|
|
2066
1784
|
if (config.postfix) {
|
|
2067
1785
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2068
1786
|
}
|
|
2069
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2070
1787
|
if (isValidArray(config.whiteList, 1)) {
|
|
2071
1788
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2072
1789
|
config.whiteList.push(data.params.serial);
|
|
@@ -2075,7 +1792,6 @@ export class Frontend extends EventEmitter {
|
|
|
2075
1792
|
config.whiteList.push(data.params.name);
|
|
2076
1793
|
}
|
|
2077
1794
|
}
|
|
2078
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2079
1795
|
if (isValidArray(config.blackList, 1)) {
|
|
2080
1796
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2081
1797
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2103,9 +1819,7 @@ export class Frontend extends EventEmitter {
|
|
|
2103
1819
|
return;
|
|
2104
1820
|
}
|
|
2105
1821
|
const config = plugin.configJson;
|
|
2106
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2107
1822
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2108
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2109
1823
|
if (select === 'serial')
|
|
2110
1824
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2111
1825
|
if (select === 'name')
|
|
@@ -2114,7 +1828,6 @@ export class Frontend extends EventEmitter {
|
|
|
2114
1828
|
if (config.postfix) {
|
|
2115
1829
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2116
1830
|
}
|
|
2117
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2118
1831
|
if (isValidArray(config.whiteList, 1)) {
|
|
2119
1832
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2120
1833
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2123,7 +1836,6 @@ export class Frontend extends EventEmitter {
|
|
|
2123
1836
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2124
1837
|
}
|
|
2125
1838
|
}
|
|
2126
|
-
// Add the serial to the blackList
|
|
2127
1839
|
if (isValidArray(config.blackList)) {
|
|
2128
1840
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2129
1841
|
config.blackList.push(data.params.serial);
|
|
@@ -2146,7 +1858,6 @@ export class Frontend extends EventEmitter {
|
|
|
2146
1858
|
}
|
|
2147
1859
|
}
|
|
2148
1860
|
else {
|
|
2149
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2150
1861
|
const localData = data;
|
|
2151
1862
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2152
1863
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2156,46 +1867,23 @@ export class Frontend extends EventEmitter {
|
|
|
2156
1867
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2157
1868
|
}
|
|
2158
1869
|
}
|
|
2159
|
-
/**
|
|
2160
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2161
|
-
*
|
|
2162
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2163
|
-
* @param {string} time - The time string of the message
|
|
2164
|
-
* @param {string} name - The logger name of the message
|
|
2165
|
-
* @param {string} message - The content of the message.
|
|
2166
|
-
*
|
|
2167
|
-
* @remarks
|
|
2168
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2169
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2170
|
-
* The function sends the message to all connected clients.
|
|
2171
|
-
*/
|
|
2172
1870
|
wssSendLogMessage(level, time, name, message) {
|
|
2173
1871
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2174
1872
|
return;
|
|
2175
1873
|
if (!level || !time || !name || !message)
|
|
2176
1874
|
return;
|
|
2177
|
-
// Remove ANSI escape codes from the message
|
|
2178
|
-
// eslint-disable-next-line no-control-regex
|
|
2179
1875
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2180
|
-
// Remove leading asterisks from the message
|
|
2181
1876
|
message = message.replace(/^\*+/, '');
|
|
2182
|
-
// Replace all occurrences of \t and \n
|
|
2183
1877
|
message = message.replace(/[\t\n]/g, '');
|
|
2184
|
-
// Remove non-printable characters
|
|
2185
|
-
// eslint-disable-next-line no-control-regex
|
|
2186
1878
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2187
|
-
// Replace all occurrences of \" with "
|
|
2188
1879
|
message = message.replace(/\\"/g, '"');
|
|
2189
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2190
1880
|
const maxContinuousLength = 100;
|
|
2191
1881
|
const keepStartLength = 20;
|
|
2192
1882
|
const keepEndLength = 20;
|
|
2193
|
-
// Split the message into words
|
|
2194
1883
|
if (level !== 'spawn') {
|
|
2195
1884
|
message = message
|
|
2196
1885
|
.split(' ')
|
|
2197
1886
|
.map((word) => {
|
|
2198
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2199
1887
|
if (word.length > maxContinuousLength) {
|
|
2200
1888
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2201
1889
|
}
|
|
@@ -2203,34 +1891,14 @@ export class Frontend extends EventEmitter {
|
|
|
2203
1891
|
})
|
|
2204
1892
|
.join(' ');
|
|
2205
1893
|
}
|
|
2206
|
-
// Send the message to all connected clients
|
|
2207
1894
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2208
1895
|
}
|
|
2209
|
-
/**
|
|
2210
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2211
|
-
*
|
|
2212
|
-
* @param {string} changed - The changed value.
|
|
2213
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2214
|
-
* possible values for changed:
|
|
2215
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2216
|
-
* - 'plugins'
|
|
2217
|
-
* - 'devices'
|
|
2218
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2219
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2220
|
-
*/
|
|
2221
1896
|
wssSendRefreshRequired(changed, params) {
|
|
2222
1897
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2223
1898
|
return;
|
|
2224
1899
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2225
|
-
// Send the message to all connected clients
|
|
2226
1900
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2227
1901
|
}
|
|
2228
|
-
/**
|
|
2229
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2230
|
-
*
|
|
2231
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2232
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2233
|
-
*/
|
|
2234
1902
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2235
1903
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2236
1904
|
return;
|
|
@@ -2239,14 +1907,8 @@ export class Frontend extends EventEmitter {
|
|
|
2239
1907
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2240
1908
|
if (snackbar === true)
|
|
2241
1909
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2242
|
-
// Send the message to all connected clients
|
|
2243
1910
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2244
1911
|
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2247
|
-
*
|
|
2248
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2249
|
-
*/
|
|
2250
1912
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2251
1913
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2252
1914
|
return;
|
|
@@ -2254,133 +1916,57 @@ export class Frontend extends EventEmitter {
|
|
|
2254
1916
|
this.matterbridge.restartRequired = false;
|
|
2255
1917
|
if (snackbar === true)
|
|
2256
1918
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2257
|
-
// Send the message to all connected clients
|
|
2258
1919
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2259
1920
|
}
|
|
2260
|
-
/**
|
|
2261
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2262
|
-
*
|
|
2263
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2264
|
-
*/
|
|
2265
1921
|
wssSendUpdateRequired(devVersion = false) {
|
|
2266
1922
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2267
1923
|
return;
|
|
2268
1924
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2269
1925
|
this.matterbridge.updateRequired = true;
|
|
2270
|
-
// Send the message to all connected clients
|
|
2271
1926
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2272
1927
|
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Sends a cpu update message to all connected clients.
|
|
2275
|
-
*
|
|
2276
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2277
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2278
|
-
*/
|
|
2279
1928
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2280
1929
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2281
1930
|
return;
|
|
2282
1931
|
if (hasParameter('debug'))
|
|
2283
1932
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2284
|
-
// Send the message to all connected clients
|
|
2285
1933
|
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 } });
|
|
2286
1934
|
}
|
|
2287
|
-
/**
|
|
2288
|
-
* Sends a memory update message to all connected clients.
|
|
2289
|
-
*
|
|
2290
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2291
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2292
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2293
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2294
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2295
|
-
* @param {string} external - The external memory in bytes.
|
|
2296
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2297
|
-
*/
|
|
2298
1935
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2299
1936
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2300
1937
|
return;
|
|
2301
1938
|
if (hasParameter('debug'))
|
|
2302
1939
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2303
|
-
// Send the message to all connected clients
|
|
2304
1940
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2305
1941
|
}
|
|
2306
|
-
/**
|
|
2307
|
-
* Sends an uptime update message to all connected clients.
|
|
2308
|
-
*
|
|
2309
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2310
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2311
|
-
*/
|
|
2312
1942
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2313
1943
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2314
1944
|
return;
|
|
2315
1945
|
if (hasParameter('debug'))
|
|
2316
1946
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2317
|
-
// Send the message to all connected clients
|
|
2318
1947
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2319
1948
|
}
|
|
2320
|
-
/**
|
|
2321
|
-
* Sends an open snackbar message to all connected clients.
|
|
2322
|
-
*
|
|
2323
|
-
* @param {string} message - The message to send.
|
|
2324
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2325
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2326
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2327
|
-
*
|
|
2328
|
-
* @remarks
|
|
2329
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2330
|
-
*/
|
|
2331
1949
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2332
1950
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2333
1951
|
return;
|
|
2334
1952
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2335
|
-
// Send the message to all connected clients
|
|
2336
1953
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2337
1954
|
}
|
|
2338
|
-
/**
|
|
2339
|
-
* Sends a close snackbar message to all connected clients.
|
|
2340
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2341
|
-
*
|
|
2342
|
-
* @param {string} message - The message to send.
|
|
2343
|
-
*/
|
|
2344
1955
|
wssSendCloseSnackbarMessage(message) {
|
|
2345
1956
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2346
1957
|
return;
|
|
2347
1958
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2348
|
-
// Send the message to all connected clients
|
|
2349
1959
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2350
1960
|
}
|
|
2351
|
-
/**
|
|
2352
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2353
|
-
*
|
|
2354
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2355
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2356
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2357
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2358
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2359
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2360
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2361
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2362
|
-
*
|
|
2363
|
-
* @remarks
|
|
2364
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2365
|
-
* with the updated attribute information.
|
|
2366
|
-
*/
|
|
2367
1961
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2368
1962
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2369
1963
|
return;
|
|
2370
1964
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2371
|
-
// Send the message to all connected clients
|
|
2372
1965
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2373
1966
|
}
|
|
2374
|
-
/**
|
|
2375
|
-
* Sends a message to all connected clients.
|
|
2376
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2377
|
-
*
|
|
2378
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2379
|
-
*/
|
|
2380
1967
|
wssBroadcastMessage(msg) {
|
|
2381
1968
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2382
1969
|
return;
|
|
2383
|
-
// Send the message to all connected clients
|
|
2384
1970
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2385
1971
|
if (msg.method !== 'log')
|
|
2386
1972
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2391,4 +1977,3 @@ export class Frontend extends EventEmitter {
|
|
|
2391
1977
|
});
|
|
2392
1978
|
}
|
|
2393
1979
|
}
|
|
2394
|
-
//# sourceMappingURL=frontend.js.map
|