matterbridge 3.1.5-dev-20250718-054cd80 → 3.1.5
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 +5 -6
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +9 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +103 -14
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/frontend.d.ts +304 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +430 -22
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +447 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +791 -51
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1340 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +61 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +709 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +579 -15
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1250 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1106 -42
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +322 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +310 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +233 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +195 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +59 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +54 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +117 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +263 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +12 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +49 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +58 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +74 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +81 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +40 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +56 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +62 -9
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,30 +1,126 @@
|
|
|
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.2.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node modules
|
|
1
25
|
import { createServer } from 'node:http';
|
|
2
26
|
import https from 'node:https';
|
|
3
27
|
import os from 'node:os';
|
|
4
28
|
import path from 'node:path';
|
|
5
29
|
import { existsSync, promises as fs } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// Third-party modules
|
|
7
32
|
import express from 'express';
|
|
8
33
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
34
|
import multer from 'multer';
|
|
35
|
+
// AnsiLogger module
|
|
10
36
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
37
|
+
// @matter
|
|
11
38
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
12
39
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
|
|
14
42
|
import { plg } from './matterbridgeTypes.js';
|
|
15
43
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
16
44
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
45
|
+
/**
|
|
46
|
+
* Websocket message ID for logging.
|
|
47
|
+
*
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
17
50
|
export const WS_ID_LOG = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a refresh is needed.
|
|
53
|
+
*
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
18
56
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a restart is needed.
|
|
59
|
+
*
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
19
62
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a cpu update.
|
|
65
|
+
*
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
20
68
|
export const WS_ID_CPU_UPDATE = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
*
|
|
72
|
+
* @constant {number}
|
|
73
|
+
*/
|
|
21
74
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
75
|
+
/**
|
|
76
|
+
* Websocket message ID indicating an uptime update.
|
|
77
|
+
*
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
22
80
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a snackbar message.
|
|
83
|
+
*
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
23
86
|
export const WS_ID_SNACKBAR = 6;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
89
|
+
*
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
24
92
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a state update.
|
|
95
|
+
*
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
25
98
|
export const WS_ID_STATEUPDATE = 8;
|
|
99
|
+
/**
|
|
100
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
101
|
+
*
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
26
104
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
105
|
+
/**
|
|
106
|
+
* Websocket message ID indicating a shelly system update.
|
|
107
|
+
* check:
|
|
108
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
109
|
+
* perform:
|
|
110
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
111
|
+
*
|
|
112
|
+
* @constant {number}
|
|
113
|
+
*/
|
|
27
114
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
115
|
+
/**
|
|
116
|
+
* Websocket message ID indicating a shelly main update.
|
|
117
|
+
* check:
|
|
118
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
119
|
+
* perform:
|
|
120
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
121
|
+
*
|
|
122
|
+
* @constant {number}
|
|
123
|
+
*/
|
|
28
124
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
29
125
|
export class Frontend extends EventEmitter {
|
|
30
126
|
matterbridge;
|
|
@@ -37,7 +133,7 @@ export class Frontend extends EventEmitter {
|
|
|
37
133
|
constructor(matterbridge) {
|
|
38
134
|
super();
|
|
39
135
|
this.matterbridge = matterbridge;
|
|
40
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
136
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
41
137
|
}
|
|
42
138
|
set logLevel(logLevel) {
|
|
43
139
|
this.log.logLevel = logLevel;
|
|
@@ -45,12 +141,41 @@ export class Frontend extends EventEmitter {
|
|
|
45
141
|
async start(port = 8283) {
|
|
46
142
|
this.port = port;
|
|
47
143
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
144
|
+
// Initialize multer with the upload directory
|
|
48
145
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
49
146
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
50
147
|
const upload = multer({ dest: uploadDir });
|
|
148
|
+
// Create the express app that serves the frontend
|
|
51
149
|
this.expressApp = express();
|
|
150
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
151
|
+
/*
|
|
152
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
153
|
+
for (const method of methods) {
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
158
|
+
try {
|
|
159
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
160
|
+
return original(path, ...rest);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
*/
|
|
168
|
+
// Log all requests to the server for debugging
|
|
169
|
+
/*
|
|
170
|
+
this.expressApp.use((req, res, next) => {
|
|
171
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
172
|
+
next();
|
|
173
|
+
});
|
|
174
|
+
*/
|
|
175
|
+
// Serve static files from '/static' endpoint
|
|
52
176
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
53
177
|
if (!hasParameter('ssl')) {
|
|
178
|
+
// Create an HTTP server and attach the express app
|
|
54
179
|
try {
|
|
55
180
|
this.log.debug(`Creating HTTP server...`);
|
|
56
181
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
185
|
this.emit('server_error', error);
|
|
61
186
|
return;
|
|
62
187
|
}
|
|
188
|
+
// Listen on the specified port
|
|
63
189
|
if (hasParameter('ingress')) {
|
|
64
190
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
65
191
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -98,6 +224,7 @@ export class Frontend extends EventEmitter {
|
|
|
98
224
|
let passphrase;
|
|
99
225
|
let httpsServerOptions = {};
|
|
100
226
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
227
|
+
// Load the p12 certificate and the passphrase
|
|
101
228
|
try {
|
|
102
229
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
103
230
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -109,7 +236,7 @@ export class Frontend extends EventEmitter {
|
|
|
109
236
|
}
|
|
110
237
|
try {
|
|
111
238
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
112
|
-
passphrase = passphrase.trim();
|
|
239
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
113
240
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
114
241
|
}
|
|
115
242
|
catch (error) {
|
|
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
|
|
|
120
247
|
httpsServerOptions = { pfx, passphrase };
|
|
121
248
|
}
|
|
122
249
|
else {
|
|
250
|
+
// 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.
|
|
123
251
|
try {
|
|
124
252
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
125
253
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -149,9 +277,10 @@ export class Frontend extends EventEmitter {
|
|
|
149
277
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
150
278
|
}
|
|
151
279
|
if (hasParameter('mtls')) {
|
|
152
|
-
httpsServerOptions.requestCert = true;
|
|
153
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
280
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
281
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
154
282
|
}
|
|
283
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
155
284
|
try {
|
|
156
285
|
this.log.debug(`Creating HTTPS server...`);
|
|
157
286
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -161,6 +290,7 @@ export class Frontend extends EventEmitter {
|
|
|
161
290
|
this.emit('server_error', error);
|
|
162
291
|
return;
|
|
163
292
|
}
|
|
293
|
+
// Listen on the specified port
|
|
164
294
|
if (hasParameter('ingress')) {
|
|
165
295
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
166
296
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -190,17 +320,19 @@ export class Frontend extends EventEmitter {
|
|
|
190
320
|
return;
|
|
191
321
|
});
|
|
192
322
|
}
|
|
323
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
193
324
|
const wssPort = this.port;
|
|
194
325
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
195
326
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
196
327
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
197
328
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
198
329
|
const clientIp = request.socket.remoteAddress;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
330
|
+
// Set the global logger callback for the WebSocketServer
|
|
331
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
332
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
333
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
334
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
335
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
204
336
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
205
337
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
206
338
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -222,6 +354,7 @@ export class Frontend extends EventEmitter {
|
|
|
222
354
|
}
|
|
223
355
|
});
|
|
224
356
|
ws.on('error', (error) => {
|
|
357
|
+
// istanbul ignore next
|
|
225
358
|
this.log.error(`WebSocket client error: ${error}`);
|
|
226
359
|
});
|
|
227
360
|
});
|
|
@@ -235,6 +368,7 @@ export class Frontend extends EventEmitter {
|
|
|
235
368
|
this.webSocketServer.on('error', (ws, error) => {
|
|
236
369
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
237
370
|
});
|
|
371
|
+
// Subscribe to cli events
|
|
238
372
|
cliEmitter.removeAllListeners();
|
|
239
373
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
240
374
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -245,6 +379,8 @@ export class Frontend extends EventEmitter {
|
|
|
245
379
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
246
380
|
this.wssSendCpuUpdate(cpuUsage);
|
|
247
381
|
});
|
|
382
|
+
// Endpoint to validate login code
|
|
383
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
248
384
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
249
385
|
const { password } = req.body;
|
|
250
386
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -263,23 +399,27 @@ export class Frontend extends EventEmitter {
|
|
|
263
399
|
this.log.warn('/api/login error wrong password');
|
|
264
400
|
res.json({ valid: false });
|
|
265
401
|
}
|
|
402
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
266
403
|
}
|
|
267
404
|
catch (error) {
|
|
268
405
|
this.log.error('/api/login error getting password');
|
|
269
406
|
res.json({ valid: false });
|
|
270
407
|
}
|
|
271
408
|
});
|
|
409
|
+
// Endpoint to provide health check for docker
|
|
272
410
|
this.expressApp.get('/health', (req, res) => {
|
|
273
411
|
this.log.debug('Express received /health');
|
|
274
412
|
const healthStatus = {
|
|
275
|
-
status: 'ok',
|
|
276
|
-
uptime: process.uptime(),
|
|
277
|
-
timestamp: new Date().toISOString(),
|
|
413
|
+
status: 'ok', // Indicate service is healthy
|
|
414
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
415
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
278
416
|
};
|
|
279
417
|
res.status(200).json(healthStatus);
|
|
280
418
|
});
|
|
419
|
+
// Endpoint to provide memory usage details
|
|
281
420
|
this.expressApp.get('/memory', async (req, res) => {
|
|
282
421
|
this.log.debug('Express received /memory');
|
|
422
|
+
// Memory usage from process
|
|
283
423
|
const memoryUsageRaw = process.memoryUsage();
|
|
284
424
|
const memoryUsage = {
|
|
285
425
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -288,10 +428,13 @@ export class Frontend extends EventEmitter {
|
|
|
288
428
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
289
429
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
290
430
|
};
|
|
431
|
+
// V8 heap statistics
|
|
291
432
|
const { default: v8 } = await import('node:v8');
|
|
292
433
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
293
434
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
435
|
+
// Format heapStats
|
|
294
436
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
437
|
+
// Format heapSpaces
|
|
295
438
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
296
439
|
...space,
|
|
297
440
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -309,19 +452,23 @@ export class Frontend extends EventEmitter {
|
|
|
309
452
|
};
|
|
310
453
|
res.status(200).json(memoryReport);
|
|
311
454
|
});
|
|
455
|
+
// Endpoint to provide settings
|
|
312
456
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
313
457
|
this.log.debug('The frontend sent /api/settings');
|
|
314
458
|
res.json(await this.getApiSettings());
|
|
315
459
|
});
|
|
460
|
+
// Endpoint to provide plugins
|
|
316
461
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
317
462
|
this.log.debug('The frontend sent /api/plugins');
|
|
318
463
|
res.json(this.getBaseRegisteredPlugins());
|
|
319
464
|
});
|
|
465
|
+
// Endpoint to provide devices
|
|
320
466
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
321
467
|
this.log.debug('The frontend sent /api/devices');
|
|
322
468
|
const devices = await this.getDevices();
|
|
323
469
|
res.json(devices);
|
|
324
470
|
});
|
|
471
|
+
// Endpoint to view the matterbridge log
|
|
325
472
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
326
473
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
327
474
|
try {
|
|
@@ -334,6 +481,7 @@ export class Frontend extends EventEmitter {
|
|
|
334
481
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
335
482
|
}
|
|
336
483
|
});
|
|
484
|
+
// Endpoint to view the matter.js log
|
|
337
485
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
338
486
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
339
487
|
try {
|
|
@@ -346,6 +494,7 @@ export class Frontend extends EventEmitter {
|
|
|
346
494
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
347
495
|
}
|
|
348
496
|
});
|
|
497
|
+
// Endpoint to view the shelly log
|
|
349
498
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
350
499
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
351
500
|
try {
|
|
@@ -358,9 +507,11 @@ export class Frontend extends EventEmitter {
|
|
|
358
507
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
359
508
|
}
|
|
360
509
|
});
|
|
510
|
+
// Endpoint to download the matterbridge log
|
|
361
511
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
362
512
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
363
513
|
try {
|
|
514
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
364
515
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
|
|
365
516
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
|
|
366
517
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
|
|
@@ -371,15 +522,18 @@ export class Frontend extends EventEmitter {
|
|
|
371
522
|
}
|
|
372
523
|
res.type('text/plain');
|
|
373
524
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
525
|
+
/* istanbul ignore if */
|
|
374
526
|
if (error) {
|
|
375
527
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
376
528
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
377
529
|
}
|
|
378
530
|
});
|
|
379
531
|
});
|
|
532
|
+
// Endpoint to download the matter log
|
|
380
533
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
381
534
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
382
535
|
try {
|
|
536
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
383
537
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
384
538
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
385
539
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -390,15 +544,18 @@ export class Frontend extends EventEmitter {
|
|
|
390
544
|
}
|
|
391
545
|
res.type('text/plain');
|
|
392
546
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
547
|
+
/* istanbul ignore if */
|
|
393
548
|
if (error) {
|
|
394
549
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
395
550
|
res.status(500).send('Error downloading the matter log file');
|
|
396
551
|
}
|
|
397
552
|
});
|
|
398
553
|
});
|
|
554
|
+
// Endpoint to download the shelly log
|
|
399
555
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
400
556
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
401
557
|
try {
|
|
558
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
402
559
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
403
560
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
404
561
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -409,74 +566,90 @@ export class Frontend extends EventEmitter {
|
|
|
409
566
|
}
|
|
410
567
|
res.type('text/plain');
|
|
411
568
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
569
|
+
/* istanbul ignore if */
|
|
412
570
|
if (error) {
|
|
413
571
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
414
572
|
res.status(500).send('Error downloading Shelly system log file');
|
|
415
573
|
}
|
|
416
574
|
});
|
|
417
575
|
});
|
|
576
|
+
// Endpoint to download the matterbridge storage directory
|
|
418
577
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
419
578
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
420
579
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
421
580
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
581
|
+
/* istanbul ignore if */
|
|
422
582
|
if (error) {
|
|
423
583
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
424
584
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
425
585
|
}
|
|
426
586
|
});
|
|
427
587
|
});
|
|
588
|
+
// Endpoint to download the matter storage file
|
|
428
589
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
429
590
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
430
591
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
431
592
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
593
|
+
/* istanbul ignore if */
|
|
432
594
|
if (error) {
|
|
433
595
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
434
596
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
435
597
|
}
|
|
436
598
|
});
|
|
437
599
|
});
|
|
600
|
+
// Endpoint to download the matterbridge plugin directory
|
|
438
601
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
439
602
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
440
603
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
441
604
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
605
|
+
/* istanbul ignore if */
|
|
442
606
|
if (error) {
|
|
443
607
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
444
608
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
445
609
|
}
|
|
446
610
|
});
|
|
447
611
|
});
|
|
612
|
+
// Endpoint to download the matterbridge plugin config files
|
|
448
613
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
449
614
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
450
615
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
451
616
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
617
|
+
/* istanbul ignore if */
|
|
452
618
|
if (error) {
|
|
453
619
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
454
620
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
455
621
|
}
|
|
456
622
|
});
|
|
457
623
|
});
|
|
624
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
458
625
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
459
626
|
this.log.debug('The frontend sent /api/download-backup');
|
|
460
627
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
628
|
+
/* istanbul ignore if */
|
|
461
629
|
if (error) {
|
|
462
630
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
463
631
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
464
632
|
}
|
|
465
633
|
});
|
|
466
634
|
});
|
|
635
|
+
// Endpoint to upload a package
|
|
467
636
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
468
637
|
const { filename } = req.body;
|
|
469
638
|
const file = req.file;
|
|
639
|
+
/* istanbul ignore if */
|
|
470
640
|
if (!file || !filename) {
|
|
471
641
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
472
642
|
res.status(400).send('Invalid request: file and filename are required');
|
|
473
643
|
return;
|
|
474
644
|
}
|
|
475
645
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
646
|
+
// Define the path where the plugin file will be saved
|
|
476
647
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
477
648
|
try {
|
|
649
|
+
// Move the uploaded file to the specified path
|
|
478
650
|
await fs.rename(file.path, filePath);
|
|
479
651
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
652
|
+
// Install the plugin package
|
|
480
653
|
if (filename.endsWith('.tgz')) {
|
|
481
654
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
482
655
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -496,6 +669,7 @@ export class Frontend extends EventEmitter {
|
|
|
496
669
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
497
670
|
}
|
|
498
671
|
});
|
|
672
|
+
// Fallback for routing (must be the last route)
|
|
499
673
|
this.expressApp.use((req, res) => {
|
|
500
674
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
501
675
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -504,13 +678,16 @@ export class Frontend extends EventEmitter {
|
|
|
504
678
|
}
|
|
505
679
|
async stop() {
|
|
506
680
|
this.log.debug('Stopping the frontend...');
|
|
681
|
+
// Remove listeners from the express app
|
|
507
682
|
if (this.expressApp) {
|
|
508
683
|
this.expressApp.removeAllListeners();
|
|
509
684
|
this.expressApp = undefined;
|
|
510
685
|
this.log.debug('Frontend app closed successfully');
|
|
511
686
|
}
|
|
687
|
+
// Close the WebSocket server
|
|
512
688
|
if (this.webSocketServer) {
|
|
513
689
|
this.log.debug('Closing WebSocket server...');
|
|
690
|
+
// Close all active connections
|
|
514
691
|
this.webSocketServer.clients.forEach((client) => {
|
|
515
692
|
if (client.readyState === WebSocket.OPEN) {
|
|
516
693
|
client.close();
|
|
@@ -531,6 +708,7 @@ export class Frontend extends EventEmitter {
|
|
|
531
708
|
this.webSocketServer.removeAllListeners();
|
|
532
709
|
this.webSocketServer = undefined;
|
|
533
710
|
}
|
|
711
|
+
// Close the http server
|
|
534
712
|
if (this.httpServer) {
|
|
535
713
|
this.log.debug('Closing http server...');
|
|
536
714
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -549,6 +727,7 @@ export class Frontend extends EventEmitter {
|
|
|
549
727
|
this.httpServer = undefined;
|
|
550
728
|
this.log.debug('Frontend http server closed successfully');
|
|
551
729
|
}
|
|
730
|
+
// Close the https server
|
|
552
731
|
if (this.httpsServer) {
|
|
553
732
|
this.log.debug('Closing https server...');
|
|
554
733
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -569,6 +748,7 @@ export class Frontend extends EventEmitter {
|
|
|
569
748
|
}
|
|
570
749
|
this.log.debug('Frontend stopped successfully');
|
|
571
750
|
}
|
|
751
|
+
// Function to format bytes to KB, MB, or GB
|
|
572
752
|
formatMemoryUsage = (bytes) => {
|
|
573
753
|
if (bytes >= 1024 ** 3) {
|
|
574
754
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -580,6 +760,7 @@ export class Frontend extends EventEmitter {
|
|
|
580
760
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
581
761
|
}
|
|
582
762
|
};
|
|
763
|
+
// Function to format system uptime with only the most significant unit
|
|
583
764
|
formatOsUpTime = (seconds) => {
|
|
584
765
|
if (seconds >= 86400) {
|
|
585
766
|
const days = Math.floor(seconds / 86400);
|
|
@@ -595,7 +776,13 @@ export class Frontend extends EventEmitter {
|
|
|
595
776
|
}
|
|
596
777
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
597
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* Retrieves the api settings data.
|
|
781
|
+
*
|
|
782
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
783
|
+
*/
|
|
598
784
|
async getApiSettings() {
|
|
785
|
+
// Update the system information
|
|
599
786
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
600
787
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
601
788
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -604,6 +791,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
791
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
605
792
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
606
793
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
794
|
+
// Update the matterbridge information
|
|
607
795
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
608
796
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
609
797
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -615,7 +803,8 @@ export class Frontend extends EventEmitter {
|
|
|
615
803
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
616
804
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
617
805
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
618
|
-
|
|
806
|
+
// Update the matterbridge information in bridge mode
|
|
807
|
+
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
619
808
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
620
809
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
621
810
|
this.matterbridge.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.manualPairingCode;
|
|
@@ -624,6 +813,12 @@ export class Frontend extends EventEmitter {
|
|
|
624
813
|
}
|
|
625
814
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
626
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Retrieves the reachable attribute.
|
|
818
|
+
*
|
|
819
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
820
|
+
* @returns {boolean} The reachable attribute.
|
|
821
|
+
*/
|
|
627
822
|
getReachability(device) {
|
|
628
823
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
629
824
|
return false;
|
|
@@ -635,6 +830,12 @@ export class Frontend extends EventEmitter {
|
|
|
635
830
|
return true;
|
|
636
831
|
return false;
|
|
637
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Retrieves the power source attribute.
|
|
835
|
+
*
|
|
836
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
837
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
838
|
+
*/
|
|
638
839
|
getPowerSource(endpoint) {
|
|
639
840
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
640
841
|
return undefined;
|
|
@@ -650,13 +851,21 @@ export class Frontend extends EventEmitter {
|
|
|
650
851
|
}
|
|
651
852
|
return;
|
|
652
853
|
};
|
|
854
|
+
// Root endpoint
|
|
653
855
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
654
856
|
return powerSource(endpoint);
|
|
857
|
+
// Child endpoints
|
|
655
858
|
for (const child of endpoint.getChildEndpoints()) {
|
|
656
859
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
657
860
|
return powerSource(child);
|
|
658
861
|
}
|
|
659
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
865
|
+
*
|
|
866
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
867
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
868
|
+
*/
|
|
660
869
|
getMatterDataFromDevice(device) {
|
|
661
870
|
if (device.mode === 'server' && device.serverNode) {
|
|
662
871
|
return {
|
|
@@ -668,6 +877,13 @@ export class Frontend extends EventEmitter {
|
|
|
668
877
|
};
|
|
669
878
|
}
|
|
670
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Retrieves the cluster text description from a given device.
|
|
882
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
883
|
+
*
|
|
884
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
885
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
886
|
+
*/
|
|
671
887
|
getClusterTextFromDevice(device) {
|
|
672
888
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
673
889
|
return '';
|
|
@@ -692,6 +908,7 @@ export class Frontend extends EventEmitter {
|
|
|
692
908
|
let attributes = '';
|
|
693
909
|
let supportedModes = [];
|
|
694
910
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
911
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
695
912
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
696
913
|
return;
|
|
697
914
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -781,11 +998,17 @@ export class Frontend extends EventEmitter {
|
|
|
781
998
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
782
999
|
attributes += `${getUserLabel(device)} `;
|
|
783
1000
|
});
|
|
1001
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
784
1002
|
return attributes.trimStart().trimEnd();
|
|
785
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1006
|
+
*
|
|
1007
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1008
|
+
*/
|
|
786
1009
|
getBaseRegisteredPlugins() {
|
|
787
1010
|
if (this.matterbridge.hasCleanupStarted)
|
|
788
|
-
return [];
|
|
1011
|
+
return []; // Skip if cleanup has started
|
|
789
1012
|
const baseRegisteredPlugins = [];
|
|
790
1013
|
for (const plugin of this.matterbridge.plugins) {
|
|
791
1014
|
baseRegisteredPlugins.push({
|
|
@@ -814,6 +1037,7 @@ export class Frontend extends EventEmitter {
|
|
|
814
1037
|
schemaJson: plugin.schemaJson,
|
|
815
1038
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
816
1039
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1040
|
+
// Childbridge mode specific data
|
|
817
1041
|
paired: plugin.serverNode?.state.commissioning.commissioned,
|
|
818
1042
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.qrPairingCode,
|
|
819
1043
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.manualPairingCode,
|
|
@@ -823,13 +1047,21 @@ export class Frontend extends EventEmitter {
|
|
|
823
1047
|
}
|
|
824
1048
|
return baseRegisteredPlugins;
|
|
825
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Retrieves the devices from Matterbridge.
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1054
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1055
|
+
*/
|
|
826
1056
|
async getDevices(pluginName) {
|
|
827
1057
|
if (this.matterbridge.hasCleanupStarted)
|
|
828
|
-
return [];
|
|
1058
|
+
return []; // Skip if cleanup has started
|
|
829
1059
|
const devices = [];
|
|
830
1060
|
for (const device of this.matterbridge.devices.array()) {
|
|
1061
|
+
// Filter by pluginName if provided
|
|
831
1062
|
if (pluginName && pluginName !== device.plugin)
|
|
832
1063
|
continue;
|
|
1064
|
+
// Check if the device has the required properties
|
|
833
1065
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
834
1066
|
continue;
|
|
835
1067
|
devices.push({
|
|
@@ -849,22 +1081,37 @@ export class Frontend extends EventEmitter {
|
|
|
849
1081
|
}
|
|
850
1082
|
return devices;
|
|
851
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1086
|
+
*
|
|
1087
|
+
* Response for /api/clusters
|
|
1088
|
+
*
|
|
1089
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1090
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1091
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1092
|
+
*/
|
|
852
1093
|
getClusters(pluginName, endpointNumber) {
|
|
853
1094
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
854
1095
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
855
1096
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
856
1097
|
return;
|
|
857
1098
|
}
|
|
1099
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1100
|
+
// Get the device types from the main endpoint
|
|
858
1101
|
const deviceTypes = [];
|
|
859
1102
|
const clusters = [];
|
|
860
1103
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
861
1104
|
deviceTypes.push(d.deviceType);
|
|
862
1105
|
});
|
|
1106
|
+
// Get the clusters from the main endpoint
|
|
863
1107
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
864
1108
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
865
1109
|
return;
|
|
866
1110
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
867
1111
|
return;
|
|
1112
|
+
// console.log(
|
|
1113
|
+
// `${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}`,
|
|
1114
|
+
// );
|
|
868
1115
|
clusters.push({
|
|
869
1116
|
endpoint: endpoint.number.toString(),
|
|
870
1117
|
id: 'main',
|
|
@@ -877,12 +1124,18 @@ export class Frontend extends EventEmitter {
|
|
|
877
1124
|
attributeLocalValue: attributeValue,
|
|
878
1125
|
});
|
|
879
1126
|
});
|
|
1127
|
+
// Get the child endpoints
|
|
880
1128
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1129
|
+
// if (childEndpoints.length === 0) {
|
|
1130
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1131
|
+
// }
|
|
881
1132
|
childEndpoints.forEach((childEndpoint) => {
|
|
882
1133
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
883
1134
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
884
1135
|
return;
|
|
885
1136
|
}
|
|
1137
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1138
|
+
// Get the device types of the child endpoint
|
|
886
1139
|
const deviceTypes = [];
|
|
887
1140
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
888
1141
|
deviceTypes.push(d.deviceType);
|
|
@@ -892,9 +1145,12 @@ export class Frontend extends EventEmitter {
|
|
|
892
1145
|
return;
|
|
893
1146
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
894
1147
|
return;
|
|
1148
|
+
// console.log(
|
|
1149
|
+
// `${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}`,
|
|
1150
|
+
// );
|
|
895
1151
|
clusters.push({
|
|
896
1152
|
endpoint: childEndpoint.number.toString(),
|
|
897
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1153
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
898
1154
|
deviceTypes,
|
|
899
1155
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
900
1156
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -907,6 +1163,13 @@ export class Frontend extends EventEmitter {
|
|
|
907
1163
|
});
|
|
908
1164
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
909
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1168
|
+
*
|
|
1169
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1170
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1171
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1172
|
+
*/
|
|
910
1173
|
async wsMessageHandler(client, message) {
|
|
911
1174
|
let data;
|
|
912
1175
|
try {
|
|
@@ -953,32 +1216,42 @@ export class Frontend extends EventEmitter {
|
|
|
953
1216
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
954
1217
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
955
1218
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1219
|
+
// The install comes from InstallPlugins
|
|
956
1220
|
this.matterbridge.plugins
|
|
957
1221
|
.add(packageName)
|
|
958
1222
|
.then((plugin) => {
|
|
959
1223
|
if (plugin) {
|
|
1224
|
+
// The plugin is not registered
|
|
960
1225
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
961
1226
|
this.matterbridge.plugins
|
|
962
1227
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1228
|
+
// eslint-disable-next-line promise/no-nesting
|
|
963
1229
|
.then(() => {
|
|
964
1230
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
965
1231
|
this.wssSendRefreshRequired('plugins');
|
|
966
1232
|
return;
|
|
967
1233
|
})
|
|
1234
|
+
// eslint-disable-next-line promise/no-nesting
|
|
968
1235
|
.catch((_error) => {
|
|
1236
|
+
//
|
|
969
1237
|
});
|
|
970
1238
|
}
|
|
971
1239
|
else {
|
|
1240
|
+
// The plugin is already registered
|
|
972
1241
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
973
1242
|
this.wssSendRefreshRequired('plugins');
|
|
974
1243
|
this.wssSendRestartRequired();
|
|
975
1244
|
}
|
|
976
1245
|
return;
|
|
977
1246
|
})
|
|
1247
|
+
// eslint-disable-next-line promise/no-nesting
|
|
978
1248
|
.catch((_error) => {
|
|
1249
|
+
//
|
|
979
1250
|
});
|
|
980
1251
|
}
|
|
981
1252
|
else {
|
|
1253
|
+
// The package is matterbridge
|
|
1254
|
+
// istanbul ignore next if
|
|
982
1255
|
if (this.matterbridge.restartMode !== '') {
|
|
983
1256
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
984
1257
|
this.matterbridge.shutdownProcess();
|
|
@@ -1001,6 +1274,7 @@ export class Frontend extends EventEmitter {
|
|
|
1001
1274
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1002
1275
|
return;
|
|
1003
1276
|
}
|
|
1277
|
+
// The package is a plugin
|
|
1004
1278
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1005
1279
|
if (plugin) {
|
|
1006
1280
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -1009,6 +1283,7 @@ export class Frontend extends EventEmitter {
|
|
|
1009
1283
|
this.wssSendRefreshRequired('plugins');
|
|
1010
1284
|
this.wssSendRefreshRequired('devices');
|
|
1011
1285
|
}
|
|
1286
|
+
// Uninstall the package
|
|
1012
1287
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1013
1288
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1014
1289
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1049,6 +1324,7 @@ export class Frontend extends EventEmitter {
|
|
|
1049
1324
|
return;
|
|
1050
1325
|
})
|
|
1051
1326
|
.catch((_error) => {
|
|
1327
|
+
//
|
|
1052
1328
|
});
|
|
1053
1329
|
}
|
|
1054
1330
|
else {
|
|
@@ -1095,6 +1371,7 @@ export class Frontend extends EventEmitter {
|
|
|
1095
1371
|
return;
|
|
1096
1372
|
})
|
|
1097
1373
|
.catch((_error) => {
|
|
1374
|
+
//
|
|
1098
1375
|
});
|
|
1099
1376
|
}
|
|
1100
1377
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1205,6 +1482,8 @@ export class Frontend extends EventEmitter {
|
|
|
1205
1482
|
else if (data.method === '/api/advertise') {
|
|
1206
1483
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1207
1484
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1485
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1486
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1208
1487
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1209
1488
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1210
1489
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1327,22 +1606,22 @@ export class Frontend extends EventEmitter {
|
|
|
1327
1606
|
if (isValidString(data.params.value, 4)) {
|
|
1328
1607
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1329
1608
|
if (data.params.value === 'Debug') {
|
|
1330
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1609
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1331
1610
|
}
|
|
1332
1611
|
else if (data.params.value === 'Info') {
|
|
1333
|
-
await this.matterbridge.setLogLevel("info");
|
|
1612
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1334
1613
|
}
|
|
1335
1614
|
else if (data.params.value === 'Notice') {
|
|
1336
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1615
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1337
1616
|
}
|
|
1338
1617
|
else if (data.params.value === 'Warn') {
|
|
1339
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1618
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1340
1619
|
}
|
|
1341
1620
|
else if (data.params.value === 'Error') {
|
|
1342
|
-
await this.matterbridge.setLogLevel("error");
|
|
1621
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1343
1622
|
}
|
|
1344
1623
|
else if (data.params.value === 'Fatal') {
|
|
1345
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1624
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1346
1625
|
}
|
|
1347
1626
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1348
1627
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1353,6 +1632,7 @@ export class Frontend extends EventEmitter {
|
|
|
1353
1632
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1354
1633
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1355
1634
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1635
|
+
// Create the file logger for matterbridge
|
|
1356
1636
|
if (data.params.value)
|
|
1357
1637
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1358
1638
|
else
|
|
@@ -1399,6 +1679,7 @@ export class Frontend extends EventEmitter {
|
|
|
1399
1679
|
});
|
|
1400
1680
|
}
|
|
1401
1681
|
catch (error) {
|
|
1682
|
+
/* istanbul ignore next */
|
|
1402
1683
|
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1403
1684
|
}
|
|
1404
1685
|
}
|
|
@@ -1407,6 +1688,7 @@ export class Frontend extends EventEmitter {
|
|
|
1407
1688
|
Logger.removeLogger('matterfilelogger');
|
|
1408
1689
|
}
|
|
1409
1690
|
catch (error) {
|
|
1691
|
+
/* istanbul ignore next */
|
|
1410
1692
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1411
1693
|
}
|
|
1412
1694
|
}
|
|
@@ -1519,15 +1801,19 @@ export class Frontend extends EventEmitter {
|
|
|
1519
1801
|
return;
|
|
1520
1802
|
}
|
|
1521
1803
|
const config = plugin.configJson;
|
|
1804
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1522
1805
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1806
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1523
1807
|
if (select === 'serial')
|
|
1524
1808
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1525
1809
|
if (select === 'name')
|
|
1526
1810
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1527
1811
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1812
|
+
// Remove postfix from the serial if it exists
|
|
1528
1813
|
if (config.postfix) {
|
|
1529
1814
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1530
1815
|
}
|
|
1816
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1531
1817
|
if (isValidArray(config.whiteList, 1)) {
|
|
1532
1818
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1533
1819
|
config.whiteList.push(data.params.serial);
|
|
@@ -1536,6 +1822,7 @@ export class Frontend extends EventEmitter {
|
|
|
1536
1822
|
config.whiteList.push(data.params.name);
|
|
1537
1823
|
}
|
|
1538
1824
|
}
|
|
1825
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1539
1826
|
if (isValidArray(config.blackList, 1)) {
|
|
1540
1827
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1541
1828
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1566,7 +1853,9 @@ export class Frontend extends EventEmitter {
|
|
|
1566
1853
|
return;
|
|
1567
1854
|
}
|
|
1568
1855
|
const config = plugin.configJson;
|
|
1856
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1569
1857
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1858
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1570
1859
|
if (select === 'serial')
|
|
1571
1860
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1572
1861
|
if (select === 'name')
|
|
@@ -1575,6 +1864,7 @@ export class Frontend extends EventEmitter {
|
|
|
1575
1864
|
if (config.postfix) {
|
|
1576
1865
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1577
1866
|
}
|
|
1867
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1578
1868
|
if (isValidArray(config.whiteList, 1)) {
|
|
1579
1869
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1580
1870
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1583,6 +1873,7 @@ export class Frontend extends EventEmitter {
|
|
|
1583
1873
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1584
1874
|
}
|
|
1585
1875
|
}
|
|
1876
|
+
// Add the serial to the blackList
|
|
1586
1877
|
if (isValidArray(config.blackList)) {
|
|
1587
1878
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1588
1879
|
config.blackList.push(data.params.serial);
|
|
@@ -1616,114 +1907,230 @@ export class Frontend extends EventEmitter {
|
|
|
1616
1907
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1617
1908
|
}
|
|
1618
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1912
|
+
*
|
|
1913
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1914
|
+
* @param {string} time - The time string of the message
|
|
1915
|
+
* @param {string} name - The logger name of the message
|
|
1916
|
+
* @param {string} message - The content of the message.
|
|
1917
|
+
*
|
|
1918
|
+
* @remarks
|
|
1919
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1920
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1921
|
+
* The function sends the message to all connected clients.
|
|
1922
|
+
*/
|
|
1619
1923
|
wssSendMessage(level, time, name, message) {
|
|
1620
1924
|
if (!level || !time || !name || !message)
|
|
1621
1925
|
return;
|
|
1926
|
+
// Remove ANSI escape codes from the message
|
|
1927
|
+
// eslint-disable-next-line no-control-regex
|
|
1622
1928
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1929
|
+
// Remove leading asterisks from the message
|
|
1623
1930
|
message = message.replace(/^\*+/, '');
|
|
1931
|
+
// Replace all occurrences of \t and \n
|
|
1624
1932
|
message = message.replace(/[\t\n]/g, '');
|
|
1933
|
+
// Remove non-printable characters
|
|
1934
|
+
// eslint-disable-next-line no-control-regex
|
|
1625
1935
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1936
|
+
// Replace all occurrences of \" with "
|
|
1626
1937
|
message = message.replace(/\\"/g, '"');
|
|
1938
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1627
1939
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1940
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1628
1941
|
const maxContinuousLength = 100;
|
|
1629
1942
|
const keepStartLength = 20;
|
|
1630
1943
|
const keepEndLength = 20;
|
|
1944
|
+
// Split the message into words
|
|
1631
1945
|
message = message
|
|
1632
1946
|
.split(' ')
|
|
1633
1947
|
.map((word) => {
|
|
1948
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1634
1949
|
if (word.length > maxContinuousLength) {
|
|
1635
1950
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1636
1951
|
}
|
|
1637
1952
|
return word;
|
|
1638
1953
|
})
|
|
1639
1954
|
.join(' ');
|
|
1955
|
+
// Send the message to all connected clients
|
|
1640
1956
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1641
1957
|
if (client.readyState === WebSocket.OPEN) {
|
|
1642
1958
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1643
1959
|
}
|
|
1644
1960
|
});
|
|
1645
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1964
|
+
*
|
|
1965
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1966
|
+
* possible values:
|
|
1967
|
+
* - 'matterbridgeLatestVersion'
|
|
1968
|
+
* - 'matterbridgeAdvertise'
|
|
1969
|
+
* - 'online'
|
|
1970
|
+
* - 'offline'
|
|
1971
|
+
* - 'reachability'
|
|
1972
|
+
* - 'settings'
|
|
1973
|
+
* - 'plugins'
|
|
1974
|
+
* - 'pluginsRestart'
|
|
1975
|
+
* - 'devices'
|
|
1976
|
+
* - 'fabrics'
|
|
1977
|
+
* - 'sessions'
|
|
1978
|
+
*/
|
|
1646
1979
|
wssSendRefreshRequired(changed = null) {
|
|
1647
1980
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1981
|
+
// Send the message to all connected clients
|
|
1648
1982
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1649
1983
|
if (client.readyState === WebSocket.OPEN) {
|
|
1650
1984
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1651
1985
|
}
|
|
1652
1986
|
});
|
|
1653
1987
|
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1990
|
+
*
|
|
1991
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1992
|
+
*/
|
|
1654
1993
|
wssSendRestartRequired(snackbar = true) {
|
|
1655
1994
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1656
1995
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1657
1996
|
if (snackbar === true)
|
|
1658
1997
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1998
|
+
// Send the message to all connected clients
|
|
1659
1999
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1660
2000
|
if (client.readyState === WebSocket.OPEN) {
|
|
1661
2001
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1662
2002
|
}
|
|
1663
2003
|
});
|
|
1664
2004
|
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2007
|
+
*
|
|
2008
|
+
*/
|
|
1665
2009
|
wssSendUpdateRequired() {
|
|
1666
2010
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1667
2011
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2012
|
+
// Send the message to all connected clients
|
|
1668
2013
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1669
2014
|
if (client.readyState === WebSocket.OPEN) {
|
|
1670
2015
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1671
2016
|
}
|
|
1672
2017
|
});
|
|
1673
2018
|
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Sends a cpu update message to all connected clients.
|
|
2021
|
+
*
|
|
2022
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2023
|
+
*/
|
|
1674
2024
|
wssSendCpuUpdate(cpuUsage) {
|
|
1675
2025
|
if (hasParameter('debug'))
|
|
1676
2026
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2027
|
+
// Send the message to all connected clients
|
|
1677
2028
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1678
2029
|
if (client.readyState === WebSocket.OPEN) {
|
|
1679
2030
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1680
2031
|
}
|
|
1681
2032
|
});
|
|
1682
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Sends a memory update message to all connected clients.
|
|
2036
|
+
*
|
|
2037
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2038
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2039
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2040
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2041
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2042
|
+
* @param {string} external - The external memory in bytes.
|
|
2043
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2044
|
+
*/
|
|
1683
2045
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1684
2046
|
if (hasParameter('debug'))
|
|
1685
2047
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2048
|
+
// Send the message to all connected clients
|
|
1686
2049
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1687
2050
|
if (client.readyState === WebSocket.OPEN) {
|
|
1688
2051
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1689
2052
|
}
|
|
1690
2053
|
});
|
|
1691
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Sends an uptime update message to all connected clients.
|
|
2057
|
+
*
|
|
2058
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2059
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2060
|
+
*/
|
|
1692
2061
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1693
2062
|
if (hasParameter('debug'))
|
|
1694
2063
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2064
|
+
// Send the message to all connected clients
|
|
1695
2065
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1696
2066
|
if (client.readyState === WebSocket.OPEN) {
|
|
1697
2067
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1698
2068
|
}
|
|
1699
2069
|
});
|
|
1700
2070
|
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Sends an open snackbar message to all connected clients.
|
|
2073
|
+
*
|
|
2074
|
+
* @param {string} message - The message to send.
|
|
2075
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2076
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2077
|
+
*/
|
|
1701
2078
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1702
2079
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2080
|
+
// Send the message to all connected clients
|
|
1703
2081
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1704
2082
|
if (client.readyState === WebSocket.OPEN) {
|
|
1705
2083
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1706
2084
|
}
|
|
1707
2085
|
});
|
|
1708
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Sends a close snackbar message to all connected clients.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {string} message - The message to send.
|
|
2091
|
+
*/
|
|
1709
2092
|
wssSendCloseSnackbarMessage(message) {
|
|
1710
2093
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2094
|
+
// Send the message to all connected clients
|
|
1711
2095
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1712
2096
|
if (client.readyState === WebSocket.OPEN) {
|
|
1713
2097
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1714
2098
|
}
|
|
1715
2099
|
});
|
|
1716
2100
|
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2103
|
+
*
|
|
2104
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2105
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2106
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2107
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2108
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2109
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2110
|
+
*
|
|
2111
|
+
* @remarks
|
|
2112
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2113
|
+
* with the updated attribute information.
|
|
2114
|
+
*/
|
|
1717
2115
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1718
2116
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2117
|
+
// Send the message to all connected clients
|
|
1719
2118
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1720
2119
|
if (client.readyState === WebSocket.OPEN) {
|
|
1721
2120
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1722
2121
|
}
|
|
1723
2122
|
});
|
|
1724
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Sends a message to all connected clients.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {number} id - The message id.
|
|
2128
|
+
* @param {string} method - The message method.
|
|
2129
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2130
|
+
*/
|
|
1725
2131
|
wssBroadcastMessage(id, method, params) {
|
|
1726
2132
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2133
|
+
// Send the message to all connected clients
|
|
1727
2134
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1728
2135
|
if (client.readyState === WebSocket.OPEN) {
|
|
1729
2136
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1731,3 +2138,4 @@ export class Frontend extends EventEmitter {
|
|
|
1731
2138
|
});
|
|
1732
2139
|
}
|
|
1733
2140
|
}
|
|
2141
|
+
//# sourceMappingURL=frontend.js.map
|