matterbridge 3.5.2 → 3.5.3-dev-20260202-e19e9b6
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 +30 -1
- package/README-DOCKER.md +2 -2
- package/README.md +14 -9
- package/dist/broadcastServer.d.ts +0 -115
- package/dist/broadcastServer.js +0 -117
- package/dist/broadcastServerTypes.d.ts +0 -43
- package/dist/broadcastServerTypes.js +0 -24
- package/dist/checkUpdates.d.ts +0 -75
- package/dist/checkUpdates.js +1 -91
- package/dist/cli.d.ts +0 -24
- package/dist/cli.js +1 -97
- package/dist/cliEmitter.d.ts +0 -36
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.d.ts +0 -42
- package/dist/cliHistory.js +0 -38
- package/dist/clusters/export.d.ts +0 -1
- package/dist/clusters/export.js +0 -2
- package/dist/deviceManager.d.ts +0 -108
- package/dist/deviceManager.js +1 -114
- package/dist/devices/airConditioner.d.ts +0 -75
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/basicVideoPlayer.d.ts +0 -58
- package/dist/devices/basicVideoPlayer.js +1 -56
- package/dist/devices/batteryStorage.d.ts +0 -43
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/castingVideoPlayer.d.ts +0 -63
- package/dist/devices/castingVideoPlayer.js +2 -65
- package/dist/devices/cooktop.d.ts +0 -55
- package/dist/devices/cooktop.js +0 -56
- package/dist/devices/dishwasher.d.ts +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.d.ts +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.d.ts +0 -1
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.d.ts +0 -41
- package/dist/devices/extractorHood.js +0 -43
- package/dist/devices/heatPump.d.ts +0 -43
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.d.ts +0 -58
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.d.ts +0 -64
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.d.ts +1 -77
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.d.ts +0 -82
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.d.ts +0 -100
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.d.ts +0 -36
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.d.ts +0 -79
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.d.ts +0 -21
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.d.ts +0 -74
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/export.d.ts +0 -1
- package/dist/dgram/export.js +0 -1
- package/dist/frontend.d.ts +0 -187
- package/dist/frontend.js +37 -498
- package/dist/frontendTypes.d.ts +0 -57
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.d.ts +0 -43
- package/dist/helpers.js +0 -54
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -25
- package/dist/jestutils/export.d.ts +0 -1
- package/dist/jestutils/export.js +0 -1
- package/dist/jestutils/jestHelpers.d.ts +0 -255
- package/dist/jestutils/jestHelpers.js +15 -371
- package/dist/logger/export.d.ts +0 -1
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.d.ts +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.d.ts +0 -1
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.d.ts +0 -1
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.d.ts +0 -1
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.d.ts +0 -1
- package/dist/matter/export.js +0 -2
- package/dist/matter/types.d.ts +0 -1
- package/dist/matter/types.js +0 -2
- package/dist/matterNode.d.ts +0 -258
- package/dist/matterNode.js +8 -359
- package/dist/matterbridge.d.ts +0 -373
- package/dist/matterbridge.js +46 -854
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.js +0 -50
- package/dist/matterbridgeBehaviors.d.ts +0 -24
- package/dist/matterbridgeBehaviors.js +5 -65
- package/dist/matterbridgeDeviceTypes.d.ts +0 -649
- package/dist/matterbridgeDeviceTypes.js +6 -673
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.js +0 -50
- package/dist/matterbridgeEndpoint.d.ts +0 -1369
- package/dist/matterbridgeEndpoint.js +54 -1507
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
- package/dist/matterbridgeEndpointHelpers.js +20 -482
- package/dist/matterbridgeEndpointTypes.d.ts +0 -70
- package/dist/matterbridgeEndpointTypes.js +0 -25
- package/dist/matterbridgePlatform.d.ts +0 -434
- package/dist/matterbridgePlatform.js +1 -472
- package/dist/matterbridgePlatformTypes.d.ts +0 -29
- package/dist/matterbridgePlatformTypes.js +0 -24
- package/dist/matterbridgeTypes.d.ts +0 -46
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/mb_coap.d.ts +0 -23
- package/dist/mb_coap.js +3 -41
- package/dist/mb_health.d.ts +0 -67
- package/dist/mb_health.js +0 -70
- package/dist/mb_mdns.d.ts +0 -23
- package/dist/mb_mdns.js +36 -94
- package/dist/pluginManager.d.ts +0 -305
- package/dist/pluginManager.js +5 -342
- package/dist/shelly.d.ts +0 -157
- package/dist/shelly.js +7 -178
- package/dist/spawn.d.ts +0 -32
- package/dist/spawn.js +1 -71
- package/dist/storage/export.d.ts +0 -1
- package/dist/storage/export.js +0 -1
- package/dist/utils/export.d.ts +0 -1
- package/dist/utils/export.js +0 -1
- package/dist/worker.d.ts +0 -61
- package/dist/worker.js +4 -65
- package/dist/workerCheckUpdates.d.ts +0 -24
- package/dist/workerCheckUpdates.js +5 -36
- package/dist/workerGlobalPrefix.d.ts +0 -24
- package/dist/workerGlobalPrefix.js +5 -36
- package/dist/workerTypes.d.ts +0 -25
- package/dist/workerTypes.js +0 -24
- package/frontend/build/assets/index.js +4 -4
- package/frontend/build/assets/vendor_emotion.js +1 -1
- package/frontend/build/assets/vendor_lodash.js +1 -1
- package/frontend/build/assets/vendor_mdi.js +1 -1
- package/frontend/build/assets/vendor_mui.js +22 -22
- package/frontend/build/assets/vendor_node_modules.js +20 -20
- package/frontend/build/assets/vendor_notistack.js +2 -2
- package/frontend/build/assets/vendor_qrcode.js +1 -1
- package/frontend/build/assets/vendor_rjsf.js +8 -8
- package/frontend/build/index.html +1 -1
- package/frontend/package.json +48 -47
- package/npm-shrinkwrap.json +77 -47
- package/package.json +7 -7
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts.map +0 -1
- package/dist/broadcastServerTypes.js.map +0 -1
- package/dist/checkUpdates.d.ts.map +0 -1
- package/dist/checkUpdates.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/basicVideoPlayer.d.ts.map +0 -1
- package/dist/devices/basicVideoPlayer.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/castingVideoPlayer.d.ts.map +0 -1
- package/dist/devices/castingVideoPlayer.js.map +0 -1
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/export.d.ts.map +0 -1
- package/dist/dgram/export.js.map +0 -1
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jestutils/export.d.ts.map +0 -1
- package/dist/jestutils/export.js.map +0 -1
- package/dist/jestutils/jestHelpers.d.ts.map +0 -1
- package/dist/jestutils/jestHelpers.js.map +0 -1
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterNode.d.ts.map +0 -1
- package/dist/matterNode.js.map +0 -1
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
- package/dist/matterbridgeEndpointTypes.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgePlatformTypes.d.ts.map +0 -1
- package/dist/matterbridgePlatformTypes.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/mb_coap.d.ts.map +0 -1
- package/dist/mb_coap.js.map +0 -1
- package/dist/mb_health.d.ts.map +0 -1
- package/dist/mb_health.js.map +0 -1
- package/dist/mb_mdns.d.ts.map +0 -1
- package/dist/mb_mdns.js.map +0 -1
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/spawn.d.ts.map +0 -1
- package/dist/spawn.js.map +0 -1
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/worker.d.ts.map +0 -1
- package/dist/worker.js.map +0 -1
- package/dist/workerCheckUpdates.d.ts.map +0 -1
- package/dist/workerCheckUpdates.js.map +0 -1
- package/dist/workerGlobalPrefix.d.ts.map +0 -1
- package/dist/workerGlobalPrefix.js.map +0 -1
- package/dist/workerTypes.d.ts.map +0 -1
- package/dist/workerTypes.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.3
|
|
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 */ /* istanbul ignore next */
|
|
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';
|
|
@@ -36,7 +10,6 @@ import { FabricIndex } from '@matter/types/datatype';
|
|
|
36
10
|
import { CommissioningOptions } from '@matter/types/commissioning';
|
|
37
11
|
import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
|
|
38
12
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
39
|
-
// @matterbridge
|
|
40
13
|
import { createZip, formatBytes, formatPercent, formatUptime, getParameter, hasParameter, inspectError, isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, wait, withTimeout } from '@matterbridge/utils';
|
|
41
14
|
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
|
|
42
15
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
@@ -60,7 +33,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
33
|
constructor(matterbridge) {
|
|
61
34
|
super();
|
|
62
35
|
this.matterbridge = matterbridge;
|
|
63
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
36
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
64
37
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
65
38
|
this.server = new BroadcastServer('frontend', this.log);
|
|
66
39
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -71,7 +44,6 @@ export class Frontend extends EventEmitter {
|
|
|
71
44
|
}
|
|
72
45
|
async msgHandler(msg) {
|
|
73
46
|
if (this.server.isWorkerRequest(msg)) {
|
|
74
|
-
// istanbul ignore else
|
|
75
47
|
if (this.verbose)
|
|
76
48
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
77
49
|
switch (msg.type) {
|
|
@@ -123,13 +95,11 @@ export class Frontend extends EventEmitter {
|
|
|
123
95
|
this.server.respond({ ...msg, result: { success: true } });
|
|
124
96
|
break;
|
|
125
97
|
default:
|
|
126
|
-
// istanbul ignore next
|
|
127
98
|
if (this.verbose)
|
|
128
99
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
129
100
|
}
|
|
130
101
|
}
|
|
131
102
|
if (this.server.isWorkerResponse(msg) && msg.result) {
|
|
132
|
-
// istanbul ignore next
|
|
133
103
|
if (this.verbose)
|
|
134
104
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
135
105
|
switch (msg.type) {
|
|
@@ -173,55 +143,23 @@ export class Frontend extends EventEmitter {
|
|
|
173
143
|
this.port = port;
|
|
174
144
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
175
145
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
176
|
-
// Initialize multer with the upload directory
|
|
177
146
|
const multer = await import('multer');
|
|
178
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
147
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
179
148
|
const upload = multer.default({ dest: uploadDir });
|
|
180
|
-
// Create the express app that serves the frontend
|
|
181
149
|
const express = await import('express');
|
|
182
150
|
this.expressApp = express.default();
|
|
183
|
-
// Inject logging/debug wrapper for route/middleware registration
|
|
184
|
-
/*
|
|
185
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
186
|
-
for (const method of methods) {
|
|
187
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
191
|
-
try {
|
|
192
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
193
|
-
return original(path, ...rest);
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
196
|
-
throw err;
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
*/
|
|
201
|
-
// Log all requests to the server for debugging
|
|
202
|
-
/*
|
|
203
|
-
this.expressApp.use((req, res, next) => {
|
|
204
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
205
|
-
next();
|
|
206
|
-
});
|
|
207
|
-
*/
|
|
208
|
-
// Serve static files from 'frontend/build' directory
|
|
209
151
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
210
|
-
// Create a WebSocket server and attach it to the http or https server
|
|
211
152
|
this.log.debug(`Creating WebSocketServer...`);
|
|
212
153
|
const ws = await import('ws');
|
|
213
154
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
214
155
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
215
156
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
216
157
|
const clientIp = request.socket.remoteAddress;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (this.matterbridge.getLogLevel() === "
|
|
221
|
-
callbackLogLevel = "
|
|
222
|
-
// istanbul ignore else
|
|
223
|
-
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
224
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
158
|
+
let callbackLogLevel = "notice";
|
|
159
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
160
|
+
callbackLogLevel = "info";
|
|
161
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
162
|
+
callbackLogLevel = "debug";
|
|
225
163
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
226
164
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
227
165
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -237,34 +175,23 @@ export class Frontend extends EventEmitter {
|
|
|
237
175
|
});
|
|
238
176
|
ws.on('close', () => {
|
|
239
177
|
this.log.info('WebSocket client disconnected');
|
|
240
|
-
// istanbul ignore else
|
|
241
178
|
if (this.webSocketServer?.clients.size === 0) {
|
|
242
179
|
AnsiLogger.setGlobalCallback(undefined);
|
|
243
180
|
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
244
181
|
this.authClients = [];
|
|
245
182
|
}
|
|
246
183
|
});
|
|
247
|
-
// istanbul ignore next
|
|
248
184
|
ws.on('error', (error) => {
|
|
249
|
-
// istanbul ignore next
|
|
250
185
|
this.log.error(`WebSocket client error: ${error}`);
|
|
251
186
|
});
|
|
252
187
|
});
|
|
253
188
|
this.webSocketServer.on('close', () => {
|
|
254
189
|
this.log.debug(`WebSocketServer closed`);
|
|
255
190
|
});
|
|
256
|
-
/* With { noServer: true } it never fires
|
|
257
|
-
this.webSocketServer.on('listening', () => {
|
|
258
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
259
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
260
|
-
});
|
|
261
|
-
*/
|
|
262
|
-
// istanbul ignore next
|
|
263
191
|
this.webSocketServer.on('error', (ws, error) => {
|
|
264
192
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
265
193
|
});
|
|
266
194
|
if (!hasParameter('ssl')) {
|
|
267
|
-
// Create an HTTP server and attach the express app
|
|
268
195
|
const http = await import('node:http');
|
|
269
196
|
try {
|
|
270
197
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -275,9 +202,7 @@ export class Frontend extends EventEmitter {
|
|
|
275
202
|
this.emit('server_error', error);
|
|
276
203
|
return;
|
|
277
204
|
}
|
|
278
|
-
// Listen on the specified port
|
|
279
205
|
if (hasParameter('ingress')) {
|
|
280
|
-
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
281
206
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
282
207
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
283
208
|
this.listening = true;
|
|
@@ -285,17 +210,13 @@ export class Frontend extends EventEmitter {
|
|
|
285
210
|
});
|
|
286
211
|
}
|
|
287
212
|
else {
|
|
288
|
-
// We listen to all available addresses
|
|
289
213
|
this.httpServer.listen(this.port, getParameter('bind'), () => {
|
|
290
214
|
const addr = this.httpServer?.address();
|
|
291
|
-
// istanbul ignore else
|
|
292
215
|
if (addr && typeof addr !== 'string') {
|
|
293
216
|
this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
294
217
|
}
|
|
295
|
-
// istanbul ignore else
|
|
296
218
|
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
297
219
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
298
|
-
// istanbul ignore else
|
|
299
220
|
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
300
221
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
301
222
|
this.listening = true;
|
|
@@ -304,25 +225,19 @@ export class Frontend extends EventEmitter {
|
|
|
304
225
|
}
|
|
305
226
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
306
227
|
try {
|
|
307
|
-
// Only proceed for real WebSocket upgrades
|
|
308
|
-
// istanbul ignore next cause is only a safety check
|
|
309
228
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
310
229
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
311
230
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
312
231
|
return socket.destroy();
|
|
313
232
|
}
|
|
314
|
-
// Build a URL so we can read ?password=...
|
|
315
233
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
316
|
-
// Validate WebSocket password
|
|
317
234
|
const password = url.searchParams.get('password') ?? '';
|
|
318
235
|
if (password !== this.storedPassword) {
|
|
319
236
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
320
237
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
321
238
|
return socket.destroy();
|
|
322
239
|
}
|
|
323
|
-
// Complete the WebSocket handshake
|
|
324
240
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
325
|
-
// istanbul ignore else
|
|
326
241
|
if (req.socket.remoteAddress)
|
|
327
242
|
this.authClients.push(req.socket.remoteAddress);
|
|
328
243
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -330,7 +245,6 @@ export class Frontend extends EventEmitter {
|
|
|
330
245
|
});
|
|
331
246
|
}
|
|
332
247
|
catch (err) {
|
|
333
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
334
248
|
{
|
|
335
249
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
336
250
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -353,7 +267,6 @@ export class Frontend extends EventEmitter {
|
|
|
353
267
|
});
|
|
354
268
|
}
|
|
355
269
|
else {
|
|
356
|
-
// SSL is enabled, load the certificate and the private key
|
|
357
270
|
let cert;
|
|
358
271
|
let key;
|
|
359
272
|
let ca;
|
|
@@ -363,7 +276,6 @@ export class Frontend extends EventEmitter {
|
|
|
363
276
|
let httpsServerOptions = {};
|
|
364
277
|
const fs = await import('node:fs');
|
|
365
278
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
366
|
-
// Load the p12 certificate and the passphrase
|
|
367
279
|
try {
|
|
368
280
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
369
281
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -375,7 +287,7 @@ export class Frontend extends EventEmitter {
|
|
|
375
287
|
}
|
|
376
288
|
try {
|
|
377
289
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
378
|
-
passphrase = passphrase.trim();
|
|
290
|
+
passphrase = passphrase.trim();
|
|
379
291
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
380
292
|
}
|
|
381
293
|
catch (error) {
|
|
@@ -386,7 +298,6 @@ export class Frontend extends EventEmitter {
|
|
|
386
298
|
httpsServerOptions = { pfx, passphrase };
|
|
387
299
|
}
|
|
388
300
|
else {
|
|
389
|
-
// 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.
|
|
390
301
|
try {
|
|
391
302
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
392
303
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -416,10 +327,9 @@ export class Frontend extends EventEmitter {
|
|
|
416
327
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
417
328
|
}
|
|
418
329
|
if (hasParameter('mtls')) {
|
|
419
|
-
httpsServerOptions.requestCert = true;
|
|
420
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
330
|
+
httpsServerOptions.requestCert = true;
|
|
331
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
421
332
|
}
|
|
422
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
423
333
|
const https = await import('node:https');
|
|
424
334
|
try {
|
|
425
335
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -430,9 +340,7 @@ export class Frontend extends EventEmitter {
|
|
|
430
340
|
this.emit('server_error', error);
|
|
431
341
|
return;
|
|
432
342
|
}
|
|
433
|
-
// Listen on the specified port
|
|
434
343
|
if (hasParameter('ingress')) {
|
|
435
|
-
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
436
344
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
437
345
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
438
346
|
this.listening = true;
|
|
@@ -440,17 +348,13 @@ export class Frontend extends EventEmitter {
|
|
|
440
348
|
});
|
|
441
349
|
}
|
|
442
350
|
else {
|
|
443
|
-
// We listen to all available addresses
|
|
444
351
|
this.httpsServer.listen(this.port, getParameter('bind'), () => {
|
|
445
352
|
const addr = this.httpsServer?.address();
|
|
446
|
-
// istanbul ignore else
|
|
447
353
|
if (addr && typeof addr !== 'string') {
|
|
448
354
|
this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
449
355
|
}
|
|
450
|
-
// istanbul ignore else
|
|
451
356
|
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
452
357
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
453
|
-
// istanbul ignore else
|
|
454
358
|
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
455
359
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
456
360
|
this.listening = true;
|
|
@@ -459,24 +363,18 @@ export class Frontend extends EventEmitter {
|
|
|
459
363
|
}
|
|
460
364
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
461
365
|
try {
|
|
462
|
-
// Only proceed for real WebSocket upgrades
|
|
463
|
-
// istanbul ignore next cause is only a safety check
|
|
464
366
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
465
367
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
466
368
|
return socket.destroy();
|
|
467
369
|
}
|
|
468
|
-
// Build a URL so we can read ?password=...
|
|
469
370
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
470
|
-
// Validate WebSocket password
|
|
471
371
|
const password = url.searchParams.get('password') ?? '';
|
|
472
372
|
if (password !== this.storedPassword) {
|
|
473
373
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
474
374
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
475
375
|
return socket.destroy();
|
|
476
376
|
}
|
|
477
|
-
// Complete the WebSocket handshake
|
|
478
377
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
479
|
-
// istanbul ignore else
|
|
480
378
|
if (req.socket.remoteAddress)
|
|
481
379
|
this.authClients.push(req.socket.remoteAddress);
|
|
482
380
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -484,7 +382,6 @@ export class Frontend extends EventEmitter {
|
|
|
484
382
|
});
|
|
485
383
|
}
|
|
486
384
|
catch (err) {
|
|
487
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
488
385
|
{
|
|
489
386
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
490
387
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -506,7 +403,6 @@ export class Frontend extends EventEmitter {
|
|
|
506
403
|
return;
|
|
507
404
|
});
|
|
508
405
|
}
|
|
509
|
-
// Subscribe to cli events
|
|
510
406
|
cliEmitter.removeAllListeners();
|
|
511
407
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
512
408
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -517,8 +413,6 @@ export class Frontend extends EventEmitter {
|
|
|
517
413
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
518
414
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
519
415
|
});
|
|
520
|
-
// Endpoint to validate login code
|
|
521
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
522
416
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
523
417
|
const { password } = req.body;
|
|
524
418
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -533,20 +427,17 @@ export class Frontend extends EventEmitter {
|
|
|
533
427
|
res.json({ valid: false });
|
|
534
428
|
}
|
|
535
429
|
});
|
|
536
|
-
// Endpoint to provide health check for docker
|
|
537
430
|
this.expressApp.get('/health', (req, res) => {
|
|
538
431
|
this.log.debug('Express received /health');
|
|
539
432
|
const healthStatus = {
|
|
540
|
-
status: 'ok',
|
|
541
|
-
uptime: process.uptime(),
|
|
542
|
-
timestamp: new Date().toISOString(),
|
|
433
|
+
status: 'ok',
|
|
434
|
+
uptime: process.uptime(),
|
|
435
|
+
timestamp: new Date().toISOString(),
|
|
543
436
|
};
|
|
544
437
|
res.status(200).json(healthStatus);
|
|
545
438
|
});
|
|
546
|
-
// Endpoint to provide memory usage details
|
|
547
439
|
this.expressApp.get('/memory', async (req, res) => {
|
|
548
440
|
this.log.debug('Express received /memory');
|
|
549
|
-
// Memory usage from process
|
|
550
441
|
const memoryUsageRaw = process.memoryUsage();
|
|
551
442
|
const memoryUsage = {
|
|
552
443
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -555,13 +446,10 @@ export class Frontend extends EventEmitter {
|
|
|
555
446
|
external: formatBytes(memoryUsageRaw.external),
|
|
556
447
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
557
448
|
};
|
|
558
|
-
// V8 heap statistics
|
|
559
449
|
const { default: v8 } = await import('node:v8');
|
|
560
450
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
561
451
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
562
|
-
// Format heapStats
|
|
563
452
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
564
|
-
// Format heapSpaces
|
|
565
453
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
566
454
|
...space,
|
|
567
455
|
space_size: formatBytes(space.space_size),
|
|
@@ -580,28 +468,24 @@ export class Frontend extends EventEmitter {
|
|
|
580
468
|
};
|
|
581
469
|
res.status(200).json(memoryReport);
|
|
582
470
|
});
|
|
583
|
-
// Endpoint to provide settings
|
|
584
471
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
585
472
|
this.log.debug('The frontend sent /api/settings');
|
|
586
473
|
if (!this.validateReq(req, res))
|
|
587
474
|
return;
|
|
588
475
|
res.json(await this.getApiSettings());
|
|
589
476
|
});
|
|
590
|
-
// Endpoint to provide plugins
|
|
591
477
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
592
478
|
this.log.debug('The frontend sent /api/plugins');
|
|
593
479
|
if (!this.validateReq(req, res))
|
|
594
480
|
return;
|
|
595
481
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
596
482
|
});
|
|
597
|
-
// Endpoint to provide devices
|
|
598
483
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
599
484
|
this.log.debug('The frontend sent /api/devices');
|
|
600
485
|
if (!this.validateReq(req, res))
|
|
601
486
|
return;
|
|
602
487
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
603
488
|
});
|
|
604
|
-
// Endpoint to view the matterbridge log
|
|
605
489
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
606
490
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
607
491
|
if (!this.validateReq(req, res))
|
|
@@ -617,7 +501,6 @@ export class Frontend extends EventEmitter {
|
|
|
617
501
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
618
502
|
}
|
|
619
503
|
});
|
|
620
|
-
// Endpoint to view the matter.js log
|
|
621
504
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
622
505
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
623
506
|
if (!this.validateReq(req, res))
|
|
@@ -633,7 +516,6 @@ export class Frontend extends EventEmitter {
|
|
|
633
516
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
634
517
|
}
|
|
635
518
|
});
|
|
636
|
-
// Endpoint to view the diagnostic.log
|
|
637
519
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
638
520
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
639
521
|
if (!this.validateReq(req, res))
|
|
@@ -646,13 +528,10 @@ export class Frontend extends EventEmitter {
|
|
|
646
528
|
res.send(data.slice(29));
|
|
647
529
|
}
|
|
648
530
|
catch (error) {
|
|
649
|
-
// istanbul ignore next
|
|
650
531
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
651
|
-
// istanbul ignore next
|
|
652
532
|
res.status(500).send('Error reading diagnostic log file.');
|
|
653
533
|
}
|
|
654
534
|
});
|
|
655
|
-
// Endpoint to download the diagnostic.log
|
|
656
535
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
657
536
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
658
537
|
if (!this.validateReq(req, res))
|
|
@@ -665,19 +544,16 @@ export class Frontend extends EventEmitter {
|
|
|
665
544
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
666
545
|
}
|
|
667
546
|
catch (error) {
|
|
668
|
-
// istanbul ignore next
|
|
669
547
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
670
548
|
}
|
|
671
549
|
res.type('text/plain');
|
|
672
550
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
673
|
-
/* istanbul ignore if */
|
|
674
551
|
if (error) {
|
|
675
552
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
676
553
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
677
554
|
}
|
|
678
555
|
});
|
|
679
556
|
});
|
|
680
|
-
// Endpoint to view the history.html
|
|
681
557
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
682
558
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
683
559
|
if (!this.validateReq(req, res))
|
|
@@ -693,7 +569,6 @@ export class Frontend extends EventEmitter {
|
|
|
693
569
|
res.status(500).send('Error reading history file.');
|
|
694
570
|
}
|
|
695
571
|
});
|
|
696
|
-
// Endpoint to download the history.html
|
|
697
572
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
698
573
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
699
574
|
if (!this.validateReq(req, res))
|
|
@@ -705,7 +580,6 @@ export class Frontend extends EventEmitter {
|
|
|
705
580
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
706
581
|
res.type('text/plain');
|
|
707
582
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
708
|
-
/* istanbul ignore if */
|
|
709
583
|
if (error) {
|
|
710
584
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
711
585
|
res.status(500).send('Error downloading history file');
|
|
@@ -717,7 +591,6 @@ export class Frontend extends EventEmitter {
|
|
|
717
591
|
res.status(500).send('Error reading history file.');
|
|
718
592
|
}
|
|
719
593
|
});
|
|
720
|
-
// Endpoint to view the shelly log
|
|
721
594
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
722
595
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
723
596
|
if (!this.validateReq(req, res))
|
|
@@ -733,7 +606,6 @@ export class Frontend extends EventEmitter {
|
|
|
733
606
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
734
607
|
}
|
|
735
608
|
});
|
|
736
|
-
// Endpoint to download the matterbridge log
|
|
737
609
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
738
610
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
739
611
|
if (!this.validateReq(req, res))
|
|
@@ -750,14 +622,12 @@ export class Frontend extends EventEmitter {
|
|
|
750
622
|
}
|
|
751
623
|
res.type('text/plain');
|
|
752
624
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
753
|
-
/* istanbul ignore if */
|
|
754
625
|
if (error) {
|
|
755
626
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
756
627
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
757
628
|
}
|
|
758
629
|
});
|
|
759
630
|
});
|
|
760
|
-
// Endpoint to download the matter log
|
|
761
631
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
762
632
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
763
633
|
if (!this.validateReq(req, res))
|
|
@@ -774,14 +644,12 @@ export class Frontend extends EventEmitter {
|
|
|
774
644
|
}
|
|
775
645
|
res.type('text/plain');
|
|
776
646
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
777
|
-
/* istanbul ignore if */
|
|
778
647
|
if (error) {
|
|
779
648
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
780
649
|
res.status(500).send('Error downloading the matter log file');
|
|
781
650
|
}
|
|
782
651
|
});
|
|
783
652
|
});
|
|
784
|
-
// Endpoint to download the shelly log
|
|
785
653
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
786
654
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
787
655
|
if (!this.validateReq(req, res))
|
|
@@ -798,103 +666,87 @@ export class Frontend extends EventEmitter {
|
|
|
798
666
|
}
|
|
799
667
|
res.type('text/plain');
|
|
800
668
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
801
|
-
/* istanbul ignore if */
|
|
802
669
|
if (error) {
|
|
803
670
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
804
671
|
res.status(500).send('Error downloading Shelly system log file');
|
|
805
672
|
}
|
|
806
673
|
});
|
|
807
674
|
});
|
|
808
|
-
// Endpoint to download the matterbridge storage directory
|
|
809
675
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
810
676
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
811
677
|
if (!this.validateReq(req, res))
|
|
812
678
|
return;
|
|
813
679
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
814
680
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
815
|
-
/* istanbul ignore if */
|
|
816
681
|
if (error) {
|
|
817
682
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
818
683
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
819
684
|
}
|
|
820
685
|
});
|
|
821
686
|
});
|
|
822
|
-
// Endpoint to download the matter storage file
|
|
823
687
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
824
688
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
825
689
|
if (!this.validateReq(req, res))
|
|
826
690
|
return;
|
|
827
691
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
828
692
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
829
|
-
/* istanbul ignore if */
|
|
830
693
|
if (error) {
|
|
831
694
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
832
695
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
833
696
|
}
|
|
834
697
|
});
|
|
835
698
|
});
|
|
836
|
-
// Endpoint to download the matterbridge plugin directory
|
|
837
699
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
838
700
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
839
701
|
if (!this.validateReq(req, res))
|
|
840
702
|
return;
|
|
841
703
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
842
704
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
843
|
-
/* istanbul ignore if */
|
|
844
705
|
if (error) {
|
|
845
706
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
846
707
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
847
708
|
}
|
|
848
709
|
});
|
|
849
710
|
});
|
|
850
|
-
// Endpoint to download the matterbridge plugin config files
|
|
851
711
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
852
712
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
853
713
|
if (!this.validateReq(req, res))
|
|
854
714
|
return;
|
|
855
715
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
856
716
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
857
|
-
/* istanbul ignore if */
|
|
858
717
|
if (error) {
|
|
859
718
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
860
719
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
861
720
|
}
|
|
862
721
|
});
|
|
863
722
|
});
|
|
864
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
865
723
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
866
724
|
this.log.debug('The frontend sent /api/download-backup');
|
|
867
725
|
if (!this.validateReq(req, res))
|
|
868
726
|
return;
|
|
869
727
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
870
|
-
/* istanbul ignore if */
|
|
871
728
|
if (error) {
|
|
872
729
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
873
730
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
874
731
|
}
|
|
875
732
|
});
|
|
876
733
|
});
|
|
877
|
-
// Endpoint to upload a package
|
|
878
734
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
879
735
|
if (!this.validateReq(req, res))
|
|
880
736
|
return;
|
|
881
737
|
const { filename } = req.body;
|
|
882
738
|
const file = req.file;
|
|
883
|
-
/* istanbul ignore if */
|
|
884
739
|
if (!file || !filename) {
|
|
885
740
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
886
741
|
res.status(400).send('Invalid request: file and filename are required');
|
|
887
742
|
return;
|
|
888
743
|
}
|
|
889
744
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
890
|
-
// Define the path where the plugin file will be saved
|
|
891
745
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
892
746
|
try {
|
|
893
|
-
// Move the uploaded file to the specified path
|
|
894
747
|
const fs = await import('node:fs');
|
|
895
748
|
await fs.promises.rename(file.path, filePath);
|
|
896
749
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
897
|
-
// Install the plugin package
|
|
898
750
|
if (filename.endsWith('.tgz')) {
|
|
899
751
|
const { spawnCommand } = await import('./spawn.js');
|
|
900
752
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
@@ -922,7 +774,6 @@ export class Frontend extends EventEmitter {
|
|
|
922
774
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
923
775
|
}
|
|
924
776
|
});
|
|
925
|
-
// Fallback for routing (must be the last route)
|
|
926
777
|
this.expressApp.use((req, res) => {
|
|
927
778
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
928
779
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -933,16 +784,13 @@ export class Frontend extends EventEmitter {
|
|
|
933
784
|
async stop() {
|
|
934
785
|
this.log.debug('Stopping the frontend...');
|
|
935
786
|
const ws = await import('ws');
|
|
936
|
-
// Remove listeners from the express app
|
|
937
787
|
if (this.expressApp) {
|
|
938
788
|
this.expressApp.removeAllListeners();
|
|
939
789
|
this.expressApp = undefined;
|
|
940
790
|
this.log.debug('Frontend app closed successfully');
|
|
941
791
|
}
|
|
942
|
-
// Close the WebSocket server
|
|
943
792
|
if (this.webSocketServer) {
|
|
944
793
|
this.log.debug('Closing WebSocket server...');
|
|
945
|
-
// Close all active connections
|
|
946
794
|
this.webSocketServer.clients.forEach((client) => {
|
|
947
795
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
948
796
|
client.close();
|
|
@@ -950,9 +798,7 @@ export class Frontend extends EventEmitter {
|
|
|
950
798
|
});
|
|
951
799
|
await withTimeout(new Promise((resolve) => {
|
|
952
800
|
this.webSocketServer?.close((error) => {
|
|
953
|
-
// istanbul ignore if
|
|
954
801
|
if (error) {
|
|
955
|
-
// istanbul ignore next
|
|
956
802
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
957
803
|
}
|
|
958
804
|
else {
|
|
@@ -965,27 +811,8 @@ export class Frontend extends EventEmitter {
|
|
|
965
811
|
this.webSocketServer.removeAllListeners();
|
|
966
812
|
this.webSocketServer = undefined;
|
|
967
813
|
}
|
|
968
|
-
// Close the http server
|
|
969
814
|
if (this.httpServer) {
|
|
970
815
|
this.log.debug('Closing http server...');
|
|
971
|
-
/*
|
|
972
|
-
await withTimeout(
|
|
973
|
-
new Promise<void>((resolve) => {
|
|
974
|
-
this.httpServer?.close((error) => {
|
|
975
|
-
if (error) {
|
|
976
|
-
// istanbul ignore next
|
|
977
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
978
|
-
} else {
|
|
979
|
-
this.log.debug('Http server closed successfully');
|
|
980
|
-
this.emit('server_stopped');
|
|
981
|
-
}
|
|
982
|
-
resolve();
|
|
983
|
-
});
|
|
984
|
-
}),
|
|
985
|
-
5000,
|
|
986
|
-
false,
|
|
987
|
-
);
|
|
988
|
-
*/
|
|
989
816
|
this.httpServer.close();
|
|
990
817
|
this.log.debug('Http server closed successfully');
|
|
991
818
|
this.listening = false;
|
|
@@ -994,27 +821,8 @@ export class Frontend extends EventEmitter {
|
|
|
994
821
|
this.httpServer = undefined;
|
|
995
822
|
this.log.debug('Frontend http server closed successfully');
|
|
996
823
|
}
|
|
997
|
-
// Close the https server
|
|
998
824
|
if (this.httpsServer) {
|
|
999
825
|
this.log.debug('Closing https server...');
|
|
1000
|
-
/*
|
|
1001
|
-
await withTimeout(
|
|
1002
|
-
new Promise<void>((resolve) => {
|
|
1003
|
-
this.httpsServer?.close((error) => {
|
|
1004
|
-
if (error) {
|
|
1005
|
-
// istanbul ignore next
|
|
1006
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
1007
|
-
} else {
|
|
1008
|
-
this.log.debug('Https server closed successfully');
|
|
1009
|
-
this.emit('server_stopped');
|
|
1010
|
-
}
|
|
1011
|
-
resolve();
|
|
1012
|
-
});
|
|
1013
|
-
}),
|
|
1014
|
-
5000,
|
|
1015
|
-
false,
|
|
1016
|
-
);
|
|
1017
|
-
*/
|
|
1018
826
|
this.httpsServer.close();
|
|
1019
827
|
this.log.debug('Https server closed successfully');
|
|
1020
828
|
this.listening = false;
|
|
@@ -1025,13 +833,7 @@ export class Frontend extends EventEmitter {
|
|
|
1025
833
|
}
|
|
1026
834
|
this.log.debug('Frontend stopped successfully');
|
|
1027
835
|
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Retrieves the api settings data.
|
|
1030
|
-
*
|
|
1031
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
1032
|
-
*/
|
|
1033
836
|
async getApiSettings() {
|
|
1034
|
-
// Update the variable system information properties
|
|
1035
837
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
1036
838
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
1037
839
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -1041,7 +843,6 @@ export class Frontend extends EventEmitter {
|
|
|
1041
843
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
1042
844
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
1043
845
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
1044
|
-
// Create the matterbridge information
|
|
1045
846
|
const info = {
|
|
1046
847
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
1047
848
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -1077,15 +878,9 @@ export class Frontend extends EventEmitter {
|
|
|
1077
878
|
};
|
|
1078
879
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
1079
880
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Retrieves the reachable attribute.
|
|
1082
|
-
*
|
|
1083
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1084
|
-
* @returns {boolean} The reachable attribute.
|
|
1085
|
-
*/
|
|
1086
881
|
getReachability(device) {
|
|
1087
882
|
if (this.matterbridge.hasCleanupStarted)
|
|
1088
|
-
return false;
|
|
883
|
+
return false;
|
|
1089
884
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1090
885
|
return false;
|
|
1091
886
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -1096,15 +891,9 @@ export class Frontend extends EventEmitter {
|
|
|
1096
891
|
return true;
|
|
1097
892
|
return false;
|
|
1098
893
|
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Retrieves the power source attribute.
|
|
1101
|
-
*
|
|
1102
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1103
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1104
|
-
*/
|
|
1105
894
|
getPowerSource(endpoint) {
|
|
1106
895
|
if (this.matterbridge.hasCleanupStarted)
|
|
1107
|
-
return undefined;
|
|
896
|
+
return undefined;
|
|
1108
897
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1109
898
|
return undefined;
|
|
1110
899
|
const powerSource = (device) => {
|
|
@@ -1119,25 +908,16 @@ export class Frontend extends EventEmitter {
|
|
|
1119
908
|
}
|
|
1120
909
|
return;
|
|
1121
910
|
};
|
|
1122
|
-
// Root endpoint
|
|
1123
911
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1124
912
|
return powerSource(endpoint);
|
|
1125
|
-
// Child endpoints
|
|
1126
913
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1127
|
-
// istanbul ignore else
|
|
1128
914
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1129
915
|
return powerSource(child);
|
|
1130
916
|
}
|
|
1131
917
|
}
|
|
1132
|
-
/**
|
|
1133
|
-
* Retrieves the battery level attribute.
|
|
1134
|
-
*
|
|
1135
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1136
|
-
* @returns {number | undefined} The battery level attribute.
|
|
1137
|
-
*/
|
|
1138
918
|
getBatteryLevel(endpoint) {
|
|
1139
919
|
if (this.matterbridge.hasCleanupStarted)
|
|
1140
|
-
return undefined;
|
|
920
|
+
return undefined;
|
|
1141
921
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1142
922
|
return undefined;
|
|
1143
923
|
const batteryLevel = (device) => {
|
|
@@ -1148,27 +928,16 @@ export class Frontend extends EventEmitter {
|
|
|
1148
928
|
}
|
|
1149
929
|
return undefined;
|
|
1150
930
|
};
|
|
1151
|
-
// Root endpoint
|
|
1152
931
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1153
932
|
return batteryLevel(endpoint);
|
|
1154
|
-
// Child endpoints
|
|
1155
933
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1156
|
-
// istanbul ignore else
|
|
1157
934
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1158
935
|
return batteryLevel(child);
|
|
1159
936
|
}
|
|
1160
937
|
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Retrieves the cluster text description from a given device.
|
|
1163
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1164
|
-
*
|
|
1165
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1166
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1167
|
-
*/
|
|
1168
938
|
getClusterTextFromDevice(device) {
|
|
1169
939
|
if (this.matterbridge.hasCleanupStarted)
|
|
1170
|
-
return '';
|
|
1171
|
-
// istanbul ignore else
|
|
940
|
+
return '';
|
|
1172
941
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1173
942
|
return '';
|
|
1174
943
|
const getUserLabel = (device) => {
|
|
@@ -1178,7 +947,6 @@ export class Frontend extends EventEmitter {
|
|
|
1178
947
|
if (composed)
|
|
1179
948
|
return 'Composed: ' + composed.value;
|
|
1180
949
|
}
|
|
1181
|
-
// istanbul ignore next cause is not reachable
|
|
1182
950
|
return '';
|
|
1183
951
|
};
|
|
1184
952
|
const getFixedLabel = (device) => {
|
|
@@ -1188,13 +956,11 @@ export class Frontend extends EventEmitter {
|
|
|
1188
956
|
if (composed)
|
|
1189
957
|
return 'Composed: ' + composed.value;
|
|
1190
958
|
}
|
|
1191
|
-
// istanbul ignore next cause is not reacheable
|
|
1192
959
|
return '';
|
|
1193
960
|
};
|
|
1194
961
|
let attributes = '';
|
|
1195
962
|
let supportedModes = [];
|
|
1196
963
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1197
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1198
964
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1199
965
|
return;
|
|
1200
966
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1284,17 +1050,11 @@ export class Frontend extends EventEmitter {
|
|
|
1284
1050
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1285
1051
|
attributes += `${getUserLabel(device)} `;
|
|
1286
1052
|
});
|
|
1287
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1288
1053
|
return attributes.trimStart().trimEnd();
|
|
1289
1054
|
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1292
|
-
*
|
|
1293
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1294
|
-
*/
|
|
1295
1055
|
getPlugins() {
|
|
1296
1056
|
if (this.matterbridge.hasCleanupStarted)
|
|
1297
|
-
return [];
|
|
1057
|
+
return [];
|
|
1298
1058
|
const plugins = [];
|
|
1299
1059
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1300
1060
|
plugins.push({
|
|
@@ -1322,27 +1082,18 @@ export class Frontend extends EventEmitter {
|
|
|
1322
1082
|
schemaJson: plugin.schemaJson,
|
|
1323
1083
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1324
1084
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1325
|
-
// Childbridge mode specific data
|
|
1326
1085
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1327
1086
|
});
|
|
1328
1087
|
}
|
|
1329
1088
|
return plugins;
|
|
1330
1089
|
}
|
|
1331
|
-
/**
|
|
1332
|
-
* Retrieves the devices from Matterbridge.
|
|
1333
|
-
*
|
|
1334
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1335
|
-
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1336
|
-
*/
|
|
1337
1090
|
getDevices(pluginName) {
|
|
1338
1091
|
if (this.matterbridge.hasCleanupStarted)
|
|
1339
|
-
return [];
|
|
1092
|
+
return [];
|
|
1340
1093
|
const devices = [];
|
|
1341
1094
|
for (const device of this.matterbridge.devices.array()) {
|
|
1342
|
-
// Filter by pluginName if provided
|
|
1343
1095
|
if (pluginName && pluginName !== device.plugin)
|
|
1344
1096
|
continue;
|
|
1345
|
-
// Check if the device has the required properties
|
|
1346
1097
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1347
1098
|
continue;
|
|
1348
1099
|
devices.push({
|
|
@@ -1363,39 +1114,24 @@ export class Frontend extends EventEmitter {
|
|
|
1363
1114
|
}
|
|
1364
1115
|
return devices;
|
|
1365
1116
|
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1368
|
-
*
|
|
1369
|
-
* Response for /api/clusters
|
|
1370
|
-
*
|
|
1371
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1372
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1373
|
-
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1374
|
-
*/
|
|
1375
1117
|
getClusters(pluginName, endpointNumber) {
|
|
1376
1118
|
if (this.matterbridge.hasCleanupStarted)
|
|
1377
|
-
return;
|
|
1119
|
+
return;
|
|
1378
1120
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1379
1121
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1380
1122
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1381
1123
|
return;
|
|
1382
1124
|
}
|
|
1383
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1384
|
-
// Get the device types from the main endpoint
|
|
1385
1125
|
const deviceTypes = [];
|
|
1386
1126
|
const clusters = [];
|
|
1387
1127
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1388
1128
|
deviceTypes.push(d.deviceType);
|
|
1389
1129
|
});
|
|
1390
|
-
// Get the clusters from the main endpoint
|
|
1391
1130
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1392
1131
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1393
1132
|
return;
|
|
1394
1133
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1395
1134
|
return;
|
|
1396
|
-
// console.log(
|
|
1397
|
-
// `${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}`,
|
|
1398
|
-
// );
|
|
1399
1135
|
clusters.push({
|
|
1400
1136
|
endpoint: endpoint.number.toString(),
|
|
1401
1137
|
number: endpoint.number,
|
|
@@ -1409,19 +1145,12 @@ export class Frontend extends EventEmitter {
|
|
|
1409
1145
|
attributeLocalValue: attributeValue,
|
|
1410
1146
|
});
|
|
1411
1147
|
});
|
|
1412
|
-
// Get the child endpoints
|
|
1413
1148
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1414
|
-
// if (childEndpoints.length === 0) {
|
|
1415
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1416
|
-
// }
|
|
1417
1149
|
childEndpoints.forEach((childEndpoint) => {
|
|
1418
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1419
1150
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1420
1151
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1421
1152
|
return;
|
|
1422
1153
|
}
|
|
1423
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1424
|
-
// Get the device types of the child endpoint
|
|
1425
1154
|
const deviceTypes = [];
|
|
1426
1155
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1427
1156
|
deviceTypes.push(d.deviceType);
|
|
@@ -1431,9 +1160,6 @@ export class Frontend extends EventEmitter {
|
|
|
1431
1160
|
return;
|
|
1432
1161
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1433
1162
|
return;
|
|
1434
|
-
// console.log(
|
|
1435
|
-
// `${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}`,
|
|
1436
|
-
// );
|
|
1437
1163
|
clusters.push({
|
|
1438
1164
|
endpoint: childEndpoint.number.toString(),
|
|
1439
1165
|
number: childEndpoint.number,
|
|
@@ -1453,7 +1179,6 @@ export class Frontend extends EventEmitter {
|
|
|
1453
1179
|
async generateDiagnostic() {
|
|
1454
1180
|
this.log.debug('Generating diagnostic...');
|
|
1455
1181
|
const serverNodes = [];
|
|
1456
|
-
// istanbul ignore else
|
|
1457
1182
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1458
1183
|
if (this.matterbridge.serverNode)
|
|
1459
1184
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1464,7 +1189,6 @@ export class Frontend extends EventEmitter {
|
|
|
1464
1189
|
serverNodes.push(plugin.serverNode);
|
|
1465
1190
|
}
|
|
1466
1191
|
}
|
|
1467
|
-
// istanbul ignore next
|
|
1468
1192
|
for (const device of this.matterbridge.devices.array()) {
|
|
1469
1193
|
if (device.serverNode)
|
|
1470
1194
|
serverNodes.push(device.serverNode);
|
|
@@ -1488,15 +1212,8 @@ export class Frontend extends EventEmitter {
|
|
|
1488
1212
|
values: [...serverNodes],
|
|
1489
1213
|
})));
|
|
1490
1214
|
delete Logger.destinations.diagnostic;
|
|
1491
|
-
await wait(500);
|
|
1215
|
+
await wait(500);
|
|
1492
1216
|
}
|
|
1493
|
-
/**
|
|
1494
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1495
|
-
*
|
|
1496
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1497
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1498
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1499
|
-
*/
|
|
1500
1217
|
async wsMessageHandler(client, message) {
|
|
1501
1218
|
let data;
|
|
1502
1219
|
const sendResponse = (data) => {
|
|
@@ -1514,13 +1231,12 @@ export class Frontend extends EventEmitter {
|
|
|
1514
1231
|
client.send(JSON.stringify(data));
|
|
1515
1232
|
}
|
|
1516
1233
|
else {
|
|
1517
|
-
// istanbul ignore next cause is only a safety check
|
|
1518
1234
|
this.log.error('Cannot send api response, client not connected');
|
|
1519
1235
|
}
|
|
1520
1236
|
};
|
|
1521
1237
|
try {
|
|
1522
1238
|
data = JSON.parse(message.toString());
|
|
1523
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1239
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1524
1240
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1525
1241
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1526
1242
|
return;
|
|
@@ -1577,22 +1293,7 @@ export class Frontend extends EventEmitter {
|
|
|
1577
1293
|
}
|
|
1578
1294
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1579
1295
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1580
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1581
|
-
/*
|
|
1582
|
-
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1583
|
-
if (plugin) {
|
|
1584
|
-
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1585
|
-
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1586
|
-
this.wssSendRestartRequired();
|
|
1587
|
-
this.wssSendRefreshRequired('plugins');
|
|
1588
|
-
this.wssSendRefreshRequired('devices');
|
|
1589
|
-
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1590
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1591
|
-
} else {
|
|
1592
|
-
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1593
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1594
|
-
}
|
|
1595
|
-
*/
|
|
1296
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1596
1297
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1597
1298
|
if (plugin) {
|
|
1598
1299
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1605,7 +1306,7 @@ export class Frontend extends EventEmitter {
|
|
|
1605
1306
|
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1606
1307
|
return;
|
|
1607
1308
|
})
|
|
1608
|
-
.catch(
|
|
1309
|
+
.catch((_error) => { });
|
|
1609
1310
|
}
|
|
1610
1311
|
else {
|
|
1611
1312
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
@@ -1619,10 +1320,6 @@ export class Frontend extends EventEmitter {
|
|
|
1619
1320
|
}
|
|
1620
1321
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1621
1322
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1622
|
-
/*
|
|
1623
|
-
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);
|
|
1624
|
-
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1625
|
-
*/
|
|
1626
1323
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1627
1324
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1628
1325
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1650,10 +1347,8 @@ export class Frontend extends EventEmitter {
|
|
|
1650
1347
|
this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
|
|
1651
1348
|
setImmediate(async () => {
|
|
1652
1349
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
1653
|
-
// @ts-expect-error Accessing private method
|
|
1654
1350
|
if (plugin.serverNode)
|
|
1655
1351
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1656
|
-
// @ts-expect-error Accessing private method
|
|
1657
1352
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1658
1353
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1659
1354
|
this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
|
|
@@ -1669,11 +1364,9 @@ export class Frontend extends EventEmitter {
|
|
|
1669
1364
|
}
|
|
1670
1365
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1671
1366
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
|
|
1672
|
-
// @ts-expect-error Accessing private method
|
|
1673
1367
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1674
1368
|
device.serverNode = undefined;
|
|
1675
1369
|
}
|
|
1676
|
-
// @ts-expect-error Accessing private method
|
|
1677
1370
|
if (plugin.serverNode)
|
|
1678
1371
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1679
1372
|
plugin.serverNode = undefined;
|
|
@@ -1692,37 +1385,30 @@ export class Frontend extends EventEmitter {
|
|
|
1692
1385
|
this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
|
|
1693
1386
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1694
1387
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1695
|
-
// Stop server nodes
|
|
1696
1388
|
if (plugin.serverNode) {
|
|
1697
|
-
// @ts-expect-error Accessing private method
|
|
1698
1389
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1699
1390
|
plugin.serverNode = undefined;
|
|
1700
1391
|
}
|
|
1701
1392
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
|
|
1702
|
-
// @ts-expect-error Accessing private method
|
|
1703
1393
|
if (device.serverNode)
|
|
1704
1394
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1705
1395
|
device.serverNode = undefined;
|
|
1706
1396
|
this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
|
|
1707
1397
|
this.matterbridge.devices.remove(device);
|
|
1708
1398
|
}
|
|
1709
|
-
// @ts-expect-error Accessing private method
|
|
1710
1399
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1711
1400
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1712
1401
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1713
|
-
plugin.restartRequired = false;
|
|
1402
|
+
plugin.restartRequired = false;
|
|
1714
1403
|
let needRestart = 0;
|
|
1715
1404
|
for (const plugin of this.matterbridge.plugins) {
|
|
1716
1405
|
if (plugin.restartRequired)
|
|
1717
1406
|
needRestart++;
|
|
1718
1407
|
}
|
|
1719
1408
|
if (needRestart === 0)
|
|
1720
|
-
this.wssSendRestartNotRequired(true);
|
|
1721
|
-
// Start server nodes
|
|
1722
|
-
// @ts-expect-error Accessing private method
|
|
1409
|
+
this.wssSendRestartNotRequired(true);
|
|
1723
1410
|
if (plugin.serverNode)
|
|
1724
1411
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1725
|
-
// @ts-expect-error Accessing private method
|
|
1726
1412
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1727
1413
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1728
1414
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1926,7 +1612,6 @@ export class Frontend extends EventEmitter {
|
|
|
1926
1612
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
|
|
1927
1613
|
return;
|
|
1928
1614
|
}
|
|
1929
|
-
// istanbul ignore next
|
|
1930
1615
|
const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1931
1616
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
|
|
1932
1617
|
}
|
|
@@ -1940,7 +1625,6 @@ export class Frontend extends EventEmitter {
|
|
|
1940
1625
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
|
|
1941
1626
|
return;
|
|
1942
1627
|
}
|
|
1943
|
-
// istanbul ignore next
|
|
1944
1628
|
const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1945
1629
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
|
|
1946
1630
|
}
|
|
@@ -1992,22 +1676,22 @@ export class Frontend extends EventEmitter {
|
|
|
1992
1676
|
if (isValidString(data.params.value, 4)) {
|
|
1993
1677
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1994
1678
|
if (data.params.value === 'Debug') {
|
|
1995
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1679
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1996
1680
|
}
|
|
1997
1681
|
else if (data.params.value === 'Info') {
|
|
1998
|
-
await this.matterbridge.setLogLevel("info"
|
|
1682
|
+
await this.matterbridge.setLogLevel("info");
|
|
1999
1683
|
}
|
|
2000
1684
|
else if (data.params.value === 'Notice') {
|
|
2001
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1685
|
+
await this.matterbridge.setLogLevel("notice");
|
|
2002
1686
|
}
|
|
2003
1687
|
else if (data.params.value === 'Warn') {
|
|
2004
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1688
|
+
await this.matterbridge.setLogLevel("warn");
|
|
2005
1689
|
}
|
|
2006
1690
|
else if (data.params.value === 'Error') {
|
|
2007
|
-
await this.matterbridge.setLogLevel("error"
|
|
1691
|
+
await this.matterbridge.setLogLevel("error");
|
|
2008
1692
|
}
|
|
2009
1693
|
else if (data.params.value === 'Fatal') {
|
|
2010
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1694
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
2011
1695
|
}
|
|
2012
1696
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2013
1697
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -2018,7 +1702,6 @@ export class Frontend extends EventEmitter {
|
|
|
2018
1702
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
2019
1703
|
this.matterbridge.fileLogger = data.params.value;
|
|
2020
1704
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
2021
|
-
// Create the file logger for matterbridge
|
|
2022
1705
|
if (data.params.value)
|
|
2023
1706
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
2024
1707
|
else
|
|
@@ -2048,12 +1731,11 @@ export class Frontend extends EventEmitter {
|
|
|
2048
1731
|
Logger.level = MatterLogLevel.FATAL;
|
|
2049
1732
|
}
|
|
2050
1733
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1734
|
+
let callbackLogLevel = "notice";
|
|
1735
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1736
|
+
callbackLogLevel = "info";
|
|
1737
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1738
|
+
callbackLogLevel = "debug";
|
|
2057
1739
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
2058
1740
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
2059
1741
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -2105,7 +1787,6 @@ export class Frontend extends EventEmitter {
|
|
|
2105
1787
|
}
|
|
2106
1788
|
break;
|
|
2107
1789
|
case 'setmatterport':
|
|
2108
|
-
// eslint-disable-next-line no-case-declarations
|
|
2109
1790
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2110
1791
|
if (isValidNumber(port, 5540, 5600)) {
|
|
2111
1792
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -2125,7 +1806,6 @@ export class Frontend extends EventEmitter {
|
|
|
2125
1806
|
}
|
|
2126
1807
|
break;
|
|
2127
1808
|
case 'setmatterdiscriminator':
|
|
2128
|
-
// eslint-disable-next-line no-case-declarations
|
|
2129
1809
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2130
1810
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
2131
1811
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -2145,7 +1825,6 @@ export class Frontend extends EventEmitter {
|
|
|
2145
1825
|
}
|
|
2146
1826
|
break;
|
|
2147
1827
|
case 'setmatterpasscode':
|
|
2148
|
-
// eslint-disable-next-line no-case-declarations
|
|
2149
1828
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2150
1829
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
2151
1830
|
this.matterbridge.passcode = passcode;
|
|
@@ -2191,19 +1870,15 @@ export class Frontend extends EventEmitter {
|
|
|
2191
1870
|
return;
|
|
2192
1871
|
}
|
|
2193
1872
|
const config = plugin.configJson;
|
|
2194
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2195
1873
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2196
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2197
1874
|
if (select === 'serial')
|
|
2198
1875
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2199
1876
|
if (select === 'name')
|
|
2200
1877
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2201
1878
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2202
|
-
// Remove postfix from the serial if it exists
|
|
2203
1879
|
if (config.postfix) {
|
|
2204
1880
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2205
1881
|
}
|
|
2206
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2207
1882
|
if (isValidArray(config.whiteList, 1)) {
|
|
2208
1883
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2209
1884
|
config.whiteList.push(data.params.serial);
|
|
@@ -2212,7 +1887,6 @@ export class Frontend extends EventEmitter {
|
|
|
2212
1887
|
config.whiteList.push(data.params.name);
|
|
2213
1888
|
}
|
|
2214
1889
|
}
|
|
2215
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2216
1890
|
if (isValidArray(config.blackList, 1)) {
|
|
2217
1891
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2218
1892
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2240,9 +1914,7 @@ export class Frontend extends EventEmitter {
|
|
|
2240
1914
|
return;
|
|
2241
1915
|
}
|
|
2242
1916
|
const config = plugin.configJson;
|
|
2243
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2244
1917
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2245
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2246
1918
|
if (select === 'serial')
|
|
2247
1919
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2248
1920
|
if (select === 'name')
|
|
@@ -2251,7 +1923,6 @@ export class Frontend extends EventEmitter {
|
|
|
2251
1923
|
if (config.postfix) {
|
|
2252
1924
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2253
1925
|
}
|
|
2254
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2255
1926
|
if (isValidArray(config.whiteList, 1)) {
|
|
2256
1927
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2257
1928
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2260,7 +1931,6 @@ export class Frontend extends EventEmitter {
|
|
|
2260
1931
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2261
1932
|
}
|
|
2262
1933
|
}
|
|
2263
|
-
// Add the serial to the blackList
|
|
2264
1934
|
if (isValidArray(config.blackList)) {
|
|
2265
1935
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2266
1936
|
config.blackList.push(data.params.serial);
|
|
@@ -2283,7 +1953,6 @@ export class Frontend extends EventEmitter {
|
|
|
2283
1953
|
}
|
|
2284
1954
|
}
|
|
2285
1955
|
else {
|
|
2286
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2287
1956
|
const localData = data;
|
|
2288
1957
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2289
1958
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2293,46 +1962,23 @@ export class Frontend extends EventEmitter {
|
|
|
2293
1962
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2294
1963
|
}
|
|
2295
1964
|
}
|
|
2296
|
-
/**
|
|
2297
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2298
|
-
*
|
|
2299
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2300
|
-
* @param {string} time - The time string of the message
|
|
2301
|
-
* @param {string} name - The logger name of the message
|
|
2302
|
-
* @param {string} message - The content of the message.
|
|
2303
|
-
*
|
|
2304
|
-
* @remarks
|
|
2305
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2306
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2307
|
-
* The function sends the message to all connected clients.
|
|
2308
|
-
*/
|
|
2309
1965
|
wssSendLogMessage(level, time, name, message) {
|
|
2310
1966
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2311
1967
|
return;
|
|
2312
1968
|
if (!level || !time || !name || !message)
|
|
2313
1969
|
return;
|
|
2314
|
-
// Remove ANSI escape codes from the message
|
|
2315
|
-
// eslint-disable-next-line no-control-regex
|
|
2316
1970
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2317
|
-
// Remove leading asterisks from the message
|
|
2318
1971
|
message = message.replace(/^\*+/, '');
|
|
2319
|
-
// Replace all occurrences of \t and \n
|
|
2320
1972
|
message = message.replace(/[\t\n]/g, '');
|
|
2321
|
-
// Remove non-printable characters
|
|
2322
|
-
// eslint-disable-next-line no-control-regex
|
|
2323
1973
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2324
|
-
// Replace all occurrences of \" with "
|
|
2325
1974
|
message = message.replace(/\\"/g, '"');
|
|
2326
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2327
1975
|
const maxContinuousLength = 100;
|
|
2328
1976
|
const keepStartLength = 20;
|
|
2329
1977
|
const keepEndLength = 20;
|
|
2330
|
-
// Split the message into words
|
|
2331
1978
|
if (level !== 'spawn') {
|
|
2332
1979
|
message = message
|
|
2333
1980
|
.split(' ')
|
|
2334
1981
|
.map((word) => {
|
|
2335
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2336
1982
|
if (word.length > maxContinuousLength) {
|
|
2337
1983
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2338
1984
|
}
|
|
@@ -2340,34 +1986,14 @@ export class Frontend extends EventEmitter {
|
|
|
2340
1986
|
})
|
|
2341
1987
|
.join(' ');
|
|
2342
1988
|
}
|
|
2343
|
-
// Send the message to all connected clients
|
|
2344
1989
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2345
1990
|
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2348
|
-
*
|
|
2349
|
-
* @param {string} changed - The changed value.
|
|
2350
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2351
|
-
* possible values for changed:
|
|
2352
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2353
|
-
* - 'plugins'
|
|
2354
|
-
* - 'devices'
|
|
2355
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2356
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2357
|
-
*/
|
|
2358
1991
|
wssSendRefreshRequired(changed, params) {
|
|
2359
1992
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2360
1993
|
return;
|
|
2361
1994
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2362
|
-
// Send the message to all connected clients
|
|
2363
1995
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2364
1996
|
}
|
|
2365
|
-
/**
|
|
2366
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2367
|
-
*
|
|
2368
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2369
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2370
|
-
*/
|
|
2371
1997
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2372
1998
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2373
1999
|
return;
|
|
@@ -2376,14 +2002,8 @@ export class Frontend extends EventEmitter {
|
|
|
2376
2002
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2377
2003
|
if (snackbar === true)
|
|
2378
2004
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2379
|
-
// Send the message to all connected clients
|
|
2380
2005
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2381
2006
|
}
|
|
2382
|
-
/**
|
|
2383
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2384
|
-
*
|
|
2385
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2386
|
-
*/
|
|
2387
2007
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2388
2008
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2389
2009
|
return;
|
|
@@ -2391,145 +2011,64 @@ export class Frontend extends EventEmitter {
|
|
|
2391
2011
|
this.matterbridge.restartRequired = false;
|
|
2392
2012
|
if (snackbar === true)
|
|
2393
2013
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2394
|
-
// Send the message to all connected clients
|
|
2395
2014
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2396
2015
|
}
|
|
2397
|
-
/**
|
|
2398
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2399
|
-
*
|
|
2400
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2401
|
-
*/
|
|
2402
2016
|
wssSendUpdateRequired(devVersion = false) {
|
|
2403
2017
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2404
2018
|
return;
|
|
2405
2019
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2406
2020
|
this.matterbridge.updateRequired = true;
|
|
2407
|
-
// Send the message to all connected clients
|
|
2408
2021
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2409
2022
|
}
|
|
2410
|
-
/**
|
|
2411
|
-
* Sends a cpu update message to all connected clients.
|
|
2412
|
-
*
|
|
2413
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2414
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2415
|
-
*/
|
|
2416
2023
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2417
2024
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2418
2025
|
return;
|
|
2419
|
-
// istanbul ignore else
|
|
2420
2026
|
if (hasParameter('debug'))
|
|
2421
2027
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2422
|
-
// Send the message to all connected clients
|
|
2423
2028
|
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 } });
|
|
2424
2029
|
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Sends a memory update message to all connected clients.
|
|
2427
|
-
*
|
|
2428
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2429
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2430
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2431
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2432
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2433
|
-
* @param {string} external - The external memory in bytes.
|
|
2434
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2435
|
-
*/
|
|
2436
2030
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2437
2031
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2438
2032
|
return;
|
|
2439
|
-
// istanbul ignore else
|
|
2440
2033
|
if (hasParameter('debug'))
|
|
2441
2034
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2442
|
-
// Send the message to all connected clients
|
|
2443
2035
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2444
2036
|
}
|
|
2445
|
-
/**
|
|
2446
|
-
* Sends an uptime update message to all connected clients.
|
|
2447
|
-
*
|
|
2448
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2449
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2450
|
-
*/
|
|
2451
2037
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2452
2038
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2453
2039
|
return;
|
|
2454
|
-
// istanbul ignore else
|
|
2455
2040
|
if (hasParameter('debug'))
|
|
2456
2041
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2457
|
-
// Send the message to all connected clients
|
|
2458
2042
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2459
2043
|
}
|
|
2460
|
-
/**
|
|
2461
|
-
* Sends an open snackbar message to all connected clients.
|
|
2462
|
-
*
|
|
2463
|
-
* @param {string} message - The message to send.
|
|
2464
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2465
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2466
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2467
|
-
*
|
|
2468
|
-
* @remarks
|
|
2469
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2470
|
-
*/
|
|
2471
2044
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2472
2045
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2473
2046
|
return;
|
|
2474
2047
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2475
|
-
// Send the message to all connected clients
|
|
2476
2048
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2477
2049
|
}
|
|
2478
|
-
/**
|
|
2479
|
-
* Sends a close snackbar message to all connected clients.
|
|
2480
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2481
|
-
*
|
|
2482
|
-
* @param {string} message - The message to send.
|
|
2483
|
-
*/
|
|
2484
2050
|
wssSendCloseSnackbarMessage(message) {
|
|
2485
2051
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2486
2052
|
return;
|
|
2487
2053
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2488
|
-
// Send the message to all connected clients
|
|
2489
2054
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2490
2055
|
}
|
|
2491
|
-
/**
|
|
2492
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2493
|
-
*
|
|
2494
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2495
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2496
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2497
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2498
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2499
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2500
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2501
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2502
|
-
*
|
|
2503
|
-
* @remarks
|
|
2504
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2505
|
-
* with the updated attribute information.
|
|
2506
|
-
*/
|
|
2507
2056
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2508
2057
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2509
2058
|
return;
|
|
2510
2059
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2511
|
-
// Send the message to all connected clients
|
|
2512
2060
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2513
2061
|
}
|
|
2514
|
-
/**
|
|
2515
|
-
* Sends a message to all connected clients.
|
|
2516
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2517
|
-
*
|
|
2518
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2519
|
-
*/
|
|
2520
2062
|
wssBroadcastMessage(msg) {
|
|
2521
2063
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2522
2064
|
return;
|
|
2523
|
-
// Send the message to all connected clients
|
|
2524
2065
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2525
2066
|
if (msg.method !== 'log')
|
|
2526
2067
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
2527
2068
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2528
|
-
// istanbul ignore else
|
|
2529
2069
|
if (client.readyState === client.OPEN) {
|
|
2530
2070
|
client.send(stringifiedMsg);
|
|
2531
2071
|
}
|
|
2532
2072
|
});
|
|
2533
2073
|
}
|
|
2534
2074
|
}
|
|
2535
|
-
//# sourceMappingURL=frontend.js.map
|