matterbridge 3.1.6-dev-20250720-88d6141 → 3.1.6
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 +6 -4
- package/README-DOCKER.md +35 -0
- 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/dishwasher.d.ts +91 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +78 -3
- package/dist/devices/dishwasher.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 +11 -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/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.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 +93 -7
- 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/dgram/dgram.d.ts +140 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +348 -0
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mdns.d.ts +288 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +840 -0
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +65 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +170 -0
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +91 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +304 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +435 -21
- 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 +460 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +810 -52
- 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 +1351 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- 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 +1328 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1200 -43
- 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();
|
|
@@ -519,6 +696,7 @@ export class Frontend extends EventEmitter {
|
|
|
519
696
|
await withTimeout(new Promise((resolve) => {
|
|
520
697
|
this.webSocketServer?.close((error) => {
|
|
521
698
|
if (error) {
|
|
699
|
+
// istanbul ignore next
|
|
522
700
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
523
701
|
}
|
|
524
702
|
else {
|
|
@@ -531,11 +709,13 @@ export class Frontend extends EventEmitter {
|
|
|
531
709
|
this.webSocketServer.removeAllListeners();
|
|
532
710
|
this.webSocketServer = undefined;
|
|
533
711
|
}
|
|
712
|
+
// Close the http server
|
|
534
713
|
if (this.httpServer) {
|
|
535
714
|
this.log.debug('Closing http server...');
|
|
536
715
|
await withTimeout(new Promise((resolve) => {
|
|
537
716
|
this.httpServer?.close((error) => {
|
|
538
717
|
if (error) {
|
|
718
|
+
// istanbul ignore next
|
|
539
719
|
this.log.error(`Error closing http server: ${error}`);
|
|
540
720
|
}
|
|
541
721
|
else {
|
|
@@ -549,11 +729,13 @@ export class Frontend extends EventEmitter {
|
|
|
549
729
|
this.httpServer = undefined;
|
|
550
730
|
this.log.debug('Frontend http server closed successfully');
|
|
551
731
|
}
|
|
732
|
+
// Close the https server
|
|
552
733
|
if (this.httpsServer) {
|
|
553
734
|
this.log.debug('Closing https server...');
|
|
554
735
|
await withTimeout(new Promise((resolve) => {
|
|
555
736
|
this.httpsServer?.close((error) => {
|
|
556
737
|
if (error) {
|
|
738
|
+
// istanbul ignore next
|
|
557
739
|
this.log.error(`Error closing https server: ${error}`);
|
|
558
740
|
}
|
|
559
741
|
else {
|
|
@@ -569,6 +751,7 @@ export class Frontend extends EventEmitter {
|
|
|
569
751
|
}
|
|
570
752
|
this.log.debug('Frontend stopped successfully');
|
|
571
753
|
}
|
|
754
|
+
// Function to format bytes to KB, MB, or GB
|
|
572
755
|
formatMemoryUsage = (bytes) => {
|
|
573
756
|
if (bytes >= 1024 ** 3) {
|
|
574
757
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -580,6 +763,7 @@ export class Frontend extends EventEmitter {
|
|
|
580
763
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
581
764
|
}
|
|
582
765
|
};
|
|
766
|
+
// Function to format system uptime with only the most significant unit
|
|
583
767
|
formatOsUpTime = (seconds) => {
|
|
584
768
|
if (seconds >= 86400) {
|
|
585
769
|
const days = Math.floor(seconds / 86400);
|
|
@@ -595,7 +779,13 @@ export class Frontend extends EventEmitter {
|
|
|
595
779
|
}
|
|
596
780
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
597
781
|
};
|
|
782
|
+
/**
|
|
783
|
+
* Retrieves the api settings data.
|
|
784
|
+
*
|
|
785
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
786
|
+
*/
|
|
598
787
|
async getApiSettings() {
|
|
788
|
+
// Update the system information
|
|
599
789
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
600
790
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
601
791
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -604,6 +794,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
794
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
605
795
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
606
796
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
797
|
+
// Update the matterbridge information
|
|
607
798
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
608
799
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
609
800
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -615,6 +806,7 @@ export class Frontend extends EventEmitter {
|
|
|
615
806
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
616
807
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
617
808
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
809
|
+
// Update the matterbridge information in bridge mode
|
|
618
810
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
619
811
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
620
812
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -624,6 +816,12 @@ export class Frontend extends EventEmitter {
|
|
|
624
816
|
}
|
|
625
817
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
626
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Retrieves the reachable attribute.
|
|
821
|
+
*
|
|
822
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
823
|
+
* @returns {boolean} The reachable attribute.
|
|
824
|
+
*/
|
|
627
825
|
getReachability(device) {
|
|
628
826
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
629
827
|
return false;
|
|
@@ -635,6 +833,12 @@ export class Frontend extends EventEmitter {
|
|
|
635
833
|
return true;
|
|
636
834
|
return false;
|
|
637
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* Retrieves the power source attribute.
|
|
838
|
+
*
|
|
839
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
840
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
841
|
+
*/
|
|
638
842
|
getPowerSource(endpoint) {
|
|
639
843
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
640
844
|
return undefined;
|
|
@@ -650,13 +854,21 @@ export class Frontend extends EventEmitter {
|
|
|
650
854
|
}
|
|
651
855
|
return;
|
|
652
856
|
};
|
|
857
|
+
// Root endpoint
|
|
653
858
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
654
859
|
return powerSource(endpoint);
|
|
860
|
+
// Child endpoints
|
|
655
861
|
for (const child of endpoint.getChildEndpoints()) {
|
|
656
862
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
657
863
|
return powerSource(child);
|
|
658
864
|
}
|
|
659
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
868
|
+
*
|
|
869
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
870
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
871
|
+
*/
|
|
660
872
|
getMatterDataFromDevice(device) {
|
|
661
873
|
if (device.mode === 'server' && device.serverNode) {
|
|
662
874
|
return {
|
|
@@ -668,6 +880,13 @@ export class Frontend extends EventEmitter {
|
|
|
668
880
|
};
|
|
669
881
|
}
|
|
670
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Retrieves the cluster text description from a given device.
|
|
885
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
886
|
+
*
|
|
887
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
888
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
889
|
+
*/
|
|
671
890
|
getClusterTextFromDevice(device) {
|
|
672
891
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
673
892
|
return '';
|
|
@@ -678,6 +897,7 @@ export class Frontend extends EventEmitter {
|
|
|
678
897
|
if (composed)
|
|
679
898
|
return 'Composed: ' + composed.value;
|
|
680
899
|
}
|
|
900
|
+
// istanbul ignore next cause is not reachable
|
|
681
901
|
return '';
|
|
682
902
|
};
|
|
683
903
|
const getFixedLabel = (device) => {
|
|
@@ -687,11 +907,13 @@ export class Frontend extends EventEmitter {
|
|
|
687
907
|
if (composed)
|
|
688
908
|
return 'Composed: ' + composed.value;
|
|
689
909
|
}
|
|
910
|
+
// istanbul ignore next cause is not reacheable
|
|
690
911
|
return '';
|
|
691
912
|
};
|
|
692
913
|
let attributes = '';
|
|
693
914
|
let supportedModes = [];
|
|
694
915
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
916
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
695
917
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
696
918
|
return;
|
|
697
919
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -781,11 +1003,17 @@ export class Frontend extends EventEmitter {
|
|
|
781
1003
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
782
1004
|
attributes += `${getUserLabel(device)} `;
|
|
783
1005
|
});
|
|
1006
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
784
1007
|
return attributes.trimStart().trimEnd();
|
|
785
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1011
|
+
*
|
|
1012
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1013
|
+
*/
|
|
786
1014
|
getBaseRegisteredPlugins() {
|
|
787
1015
|
if (this.matterbridge.hasCleanupStarted)
|
|
788
|
-
return [];
|
|
1016
|
+
return []; // Skip if cleanup has started
|
|
789
1017
|
const baseRegisteredPlugins = [];
|
|
790
1018
|
for (const plugin of this.matterbridge.plugins) {
|
|
791
1019
|
baseRegisteredPlugins.push({
|
|
@@ -814,6 +1042,7 @@ export class Frontend extends EventEmitter {
|
|
|
814
1042
|
schemaJson: plugin.schemaJson,
|
|
815
1043
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
816
1044
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1045
|
+
// Childbridge mode specific data
|
|
817
1046
|
paired: plugin.serverNode?.state.commissioning.commissioned,
|
|
818
1047
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.qrPairingCode,
|
|
819
1048
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.manualPairingCode,
|
|
@@ -823,13 +1052,21 @@ export class Frontend extends EventEmitter {
|
|
|
823
1052
|
}
|
|
824
1053
|
return baseRegisteredPlugins;
|
|
825
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Retrieves the devices from Matterbridge.
|
|
1057
|
+
*
|
|
1058
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1059
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1060
|
+
*/
|
|
826
1061
|
async getDevices(pluginName) {
|
|
827
1062
|
if (this.matterbridge.hasCleanupStarted)
|
|
828
|
-
return [];
|
|
1063
|
+
return []; // Skip if cleanup has started
|
|
829
1064
|
const devices = [];
|
|
830
1065
|
for (const device of this.matterbridge.devices.array()) {
|
|
1066
|
+
// Filter by pluginName if provided
|
|
831
1067
|
if (pluginName && pluginName !== device.plugin)
|
|
832
1068
|
continue;
|
|
1069
|
+
// Check if the device has the required properties
|
|
833
1070
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
834
1071
|
continue;
|
|
835
1072
|
devices.push({
|
|
@@ -849,22 +1086,37 @@ export class Frontend extends EventEmitter {
|
|
|
849
1086
|
}
|
|
850
1087
|
return devices;
|
|
851
1088
|
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1091
|
+
*
|
|
1092
|
+
* Response for /api/clusters
|
|
1093
|
+
*
|
|
1094
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1095
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1096
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1097
|
+
*/
|
|
852
1098
|
getClusters(pluginName, endpointNumber) {
|
|
853
1099
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
854
1100
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
855
1101
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
856
1102
|
return;
|
|
857
1103
|
}
|
|
1104
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1105
|
+
// Get the device types from the main endpoint
|
|
858
1106
|
const deviceTypes = [];
|
|
859
1107
|
const clusters = [];
|
|
860
1108
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
861
1109
|
deviceTypes.push(d.deviceType);
|
|
862
1110
|
});
|
|
1111
|
+
// Get the clusters from the main endpoint
|
|
863
1112
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
864
1113
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
865
1114
|
return;
|
|
866
1115
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
867
1116
|
return;
|
|
1117
|
+
// console.log(
|
|
1118
|
+
// `${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}`,
|
|
1119
|
+
// );
|
|
868
1120
|
clusters.push({
|
|
869
1121
|
endpoint: endpoint.number.toString(),
|
|
870
1122
|
id: 'main',
|
|
@@ -877,12 +1129,19 @@ export class Frontend extends EventEmitter {
|
|
|
877
1129
|
attributeLocalValue: attributeValue,
|
|
878
1130
|
});
|
|
879
1131
|
});
|
|
1132
|
+
// Get the child endpoints
|
|
880
1133
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1134
|
+
// if (childEndpoints.length === 0) {
|
|
1135
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1136
|
+
// }
|
|
881
1137
|
childEndpoints.forEach((childEndpoint) => {
|
|
1138
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
882
1139
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
883
1140
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
884
1141
|
return;
|
|
885
1142
|
}
|
|
1143
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1144
|
+
// Get the device types of the child endpoint
|
|
886
1145
|
const deviceTypes = [];
|
|
887
1146
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
888
1147
|
deviceTypes.push(d.deviceType);
|
|
@@ -892,9 +1151,12 @@ export class Frontend extends EventEmitter {
|
|
|
892
1151
|
return;
|
|
893
1152
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
894
1153
|
return;
|
|
1154
|
+
// console.log(
|
|
1155
|
+
// `${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}`,
|
|
1156
|
+
// );
|
|
895
1157
|
clusters.push({
|
|
896
1158
|
endpoint: childEndpoint.number.toString(),
|
|
897
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1159
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
898
1160
|
deviceTypes,
|
|
899
1161
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
900
1162
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -907,6 +1169,13 @@ export class Frontend extends EventEmitter {
|
|
|
907
1169
|
});
|
|
908
1170
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
909
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1174
|
+
*
|
|
1175
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1176
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1177
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1178
|
+
*/
|
|
910
1179
|
async wsMessageHandler(client, message) {
|
|
911
1180
|
let data;
|
|
912
1181
|
try {
|
|
@@ -953,32 +1222,42 @@ export class Frontend extends EventEmitter {
|
|
|
953
1222
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
954
1223
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
955
1224
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1225
|
+
// The install comes from InstallPlugins
|
|
956
1226
|
this.matterbridge.plugins
|
|
957
1227
|
.add(packageName)
|
|
958
1228
|
.then((plugin) => {
|
|
959
1229
|
if (plugin) {
|
|
1230
|
+
// The plugin is not registered
|
|
960
1231
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
961
1232
|
this.matterbridge.plugins
|
|
962
1233
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1234
|
+
// eslint-disable-next-line promise/no-nesting
|
|
963
1235
|
.then(() => {
|
|
964
1236
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
965
1237
|
this.wssSendRefreshRequired('plugins');
|
|
966
1238
|
return;
|
|
967
1239
|
})
|
|
1240
|
+
// eslint-disable-next-line promise/no-nesting
|
|
968
1241
|
.catch((_error) => {
|
|
1242
|
+
//
|
|
969
1243
|
});
|
|
970
1244
|
}
|
|
971
1245
|
else {
|
|
1246
|
+
// The plugin is already registered
|
|
972
1247
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
973
1248
|
this.wssSendRefreshRequired('plugins');
|
|
974
1249
|
this.wssSendRestartRequired();
|
|
975
1250
|
}
|
|
976
1251
|
return;
|
|
977
1252
|
})
|
|
1253
|
+
// eslint-disable-next-line promise/no-nesting
|
|
978
1254
|
.catch((_error) => {
|
|
1255
|
+
//
|
|
979
1256
|
});
|
|
980
1257
|
}
|
|
981
1258
|
else {
|
|
1259
|
+
// The package is matterbridge
|
|
1260
|
+
// istanbul ignore next if
|
|
982
1261
|
if (this.matterbridge.restartMode !== '') {
|
|
983
1262
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
984
1263
|
this.matterbridge.shutdownProcess();
|
|
@@ -1001,6 +1280,7 @@ export class Frontend extends EventEmitter {
|
|
|
1001
1280
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1002
1281
|
return;
|
|
1003
1282
|
}
|
|
1283
|
+
// The package is a plugin
|
|
1004
1284
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1005
1285
|
if (plugin) {
|
|
1006
1286
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -1009,6 +1289,7 @@ export class Frontend extends EventEmitter {
|
|
|
1009
1289
|
this.wssSendRefreshRequired('plugins');
|
|
1010
1290
|
this.wssSendRefreshRequired('devices');
|
|
1011
1291
|
}
|
|
1292
|
+
// Uninstall the package
|
|
1012
1293
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1013
1294
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1014
1295
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1049,6 +1330,7 @@ export class Frontend extends EventEmitter {
|
|
|
1049
1330
|
return;
|
|
1050
1331
|
})
|
|
1051
1332
|
.catch((_error) => {
|
|
1333
|
+
//
|
|
1052
1334
|
});
|
|
1053
1335
|
}
|
|
1054
1336
|
else {
|
|
@@ -1095,6 +1377,7 @@ export class Frontend extends EventEmitter {
|
|
|
1095
1377
|
return;
|
|
1096
1378
|
})
|
|
1097
1379
|
.catch((_error) => {
|
|
1380
|
+
//
|
|
1098
1381
|
});
|
|
1099
1382
|
}
|
|
1100
1383
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1205,6 +1488,8 @@ export class Frontend extends EventEmitter {
|
|
|
1205
1488
|
else if (data.method === '/api/advertise') {
|
|
1206
1489
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1207
1490
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1491
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1492
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1208
1493
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1209
1494
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1210
1495
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1327,22 +1612,22 @@ export class Frontend extends EventEmitter {
|
|
|
1327
1612
|
if (isValidString(data.params.value, 4)) {
|
|
1328
1613
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1329
1614
|
if (data.params.value === 'Debug') {
|
|
1330
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1615
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1331
1616
|
}
|
|
1332
1617
|
else if (data.params.value === 'Info') {
|
|
1333
|
-
await this.matterbridge.setLogLevel("info");
|
|
1618
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1334
1619
|
}
|
|
1335
1620
|
else if (data.params.value === 'Notice') {
|
|
1336
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1621
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1337
1622
|
}
|
|
1338
1623
|
else if (data.params.value === 'Warn') {
|
|
1339
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1624
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1340
1625
|
}
|
|
1341
1626
|
else if (data.params.value === 'Error') {
|
|
1342
|
-
await this.matterbridge.setLogLevel("error");
|
|
1627
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1343
1628
|
}
|
|
1344
1629
|
else if (data.params.value === 'Fatal') {
|
|
1345
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1630
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1346
1631
|
}
|
|
1347
1632
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1348
1633
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1353,6 +1638,7 @@ export class Frontend extends EventEmitter {
|
|
|
1353
1638
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1354
1639
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1355
1640
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1641
|
+
// Create the file logger for matterbridge
|
|
1356
1642
|
if (data.params.value)
|
|
1357
1643
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1358
1644
|
else
|
|
@@ -1399,6 +1685,7 @@ export class Frontend extends EventEmitter {
|
|
|
1399
1685
|
});
|
|
1400
1686
|
}
|
|
1401
1687
|
catch (error) {
|
|
1688
|
+
/* istanbul ignore next */
|
|
1402
1689
|
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
1690
|
}
|
|
1404
1691
|
}
|
|
@@ -1407,6 +1694,7 @@ export class Frontend extends EventEmitter {
|
|
|
1407
1694
|
Logger.removeLogger('matterfilelogger');
|
|
1408
1695
|
}
|
|
1409
1696
|
catch (error) {
|
|
1697
|
+
/* istanbul ignore next */
|
|
1410
1698
|
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
1699
|
}
|
|
1412
1700
|
}
|
|
@@ -1519,15 +1807,19 @@ export class Frontend extends EventEmitter {
|
|
|
1519
1807
|
return;
|
|
1520
1808
|
}
|
|
1521
1809
|
const config = plugin.configJson;
|
|
1810
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1522
1811
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1812
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1523
1813
|
if (select === 'serial')
|
|
1524
1814
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1525
1815
|
if (select === 'name')
|
|
1526
1816
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1527
1817
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1818
|
+
// Remove postfix from the serial if it exists
|
|
1528
1819
|
if (config.postfix) {
|
|
1529
1820
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1530
1821
|
}
|
|
1822
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1531
1823
|
if (isValidArray(config.whiteList, 1)) {
|
|
1532
1824
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1533
1825
|
config.whiteList.push(data.params.serial);
|
|
@@ -1536,6 +1828,7 @@ export class Frontend extends EventEmitter {
|
|
|
1536
1828
|
config.whiteList.push(data.params.name);
|
|
1537
1829
|
}
|
|
1538
1830
|
}
|
|
1831
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1539
1832
|
if (isValidArray(config.blackList, 1)) {
|
|
1540
1833
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1541
1834
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1566,7 +1859,9 @@ export class Frontend extends EventEmitter {
|
|
|
1566
1859
|
return;
|
|
1567
1860
|
}
|
|
1568
1861
|
const config = plugin.configJson;
|
|
1862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1569
1863
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1864
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1570
1865
|
if (select === 'serial')
|
|
1571
1866
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1572
1867
|
if (select === 'name')
|
|
@@ -1575,6 +1870,7 @@ export class Frontend extends EventEmitter {
|
|
|
1575
1870
|
if (config.postfix) {
|
|
1576
1871
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1577
1872
|
}
|
|
1873
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1578
1874
|
if (isValidArray(config.whiteList, 1)) {
|
|
1579
1875
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1580
1876
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1583,6 +1879,7 @@ export class Frontend extends EventEmitter {
|
|
|
1583
1879
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1584
1880
|
}
|
|
1585
1881
|
}
|
|
1882
|
+
// Add the serial to the blackList
|
|
1586
1883
|
if (isValidArray(config.blackList)) {
|
|
1587
1884
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1588
1885
|
config.blackList.push(data.params.serial);
|
|
@@ -1616,114 +1913,230 @@ export class Frontend extends EventEmitter {
|
|
|
1616
1913
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1617
1914
|
}
|
|
1618
1915
|
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1918
|
+
*
|
|
1919
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1920
|
+
* @param {string} time - The time string of the message
|
|
1921
|
+
* @param {string} name - The logger name of the message
|
|
1922
|
+
* @param {string} message - The content of the message.
|
|
1923
|
+
*
|
|
1924
|
+
* @remarks
|
|
1925
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1926
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1927
|
+
* The function sends the message to all connected clients.
|
|
1928
|
+
*/
|
|
1619
1929
|
wssSendMessage(level, time, name, message) {
|
|
1620
1930
|
if (!level || !time || !name || !message)
|
|
1621
1931
|
return;
|
|
1932
|
+
// Remove ANSI escape codes from the message
|
|
1933
|
+
// eslint-disable-next-line no-control-regex
|
|
1622
1934
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1935
|
+
// Remove leading asterisks from the message
|
|
1623
1936
|
message = message.replace(/^\*+/, '');
|
|
1937
|
+
// Replace all occurrences of \t and \n
|
|
1624
1938
|
message = message.replace(/[\t\n]/g, '');
|
|
1939
|
+
// Remove non-printable characters
|
|
1940
|
+
// eslint-disable-next-line no-control-regex
|
|
1625
1941
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1942
|
+
// Replace all occurrences of \" with "
|
|
1626
1943
|
message = message.replace(/\\"/g, '"');
|
|
1944
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1627
1945
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1946
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1628
1947
|
const maxContinuousLength = 100;
|
|
1629
1948
|
const keepStartLength = 20;
|
|
1630
1949
|
const keepEndLength = 20;
|
|
1950
|
+
// Split the message into words
|
|
1631
1951
|
message = message
|
|
1632
1952
|
.split(' ')
|
|
1633
1953
|
.map((word) => {
|
|
1954
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1634
1955
|
if (word.length > maxContinuousLength) {
|
|
1635
1956
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1636
1957
|
}
|
|
1637
1958
|
return word;
|
|
1638
1959
|
})
|
|
1639
1960
|
.join(' ');
|
|
1961
|
+
// Send the message to all connected clients
|
|
1640
1962
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1641
1963
|
if (client.readyState === WebSocket.OPEN) {
|
|
1642
1964
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1643
1965
|
}
|
|
1644
1966
|
});
|
|
1645
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1970
|
+
*
|
|
1971
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1972
|
+
* possible values:
|
|
1973
|
+
* - 'matterbridgeLatestVersion'
|
|
1974
|
+
* - 'matterbridgeAdvertise'
|
|
1975
|
+
* - 'online'
|
|
1976
|
+
* - 'offline'
|
|
1977
|
+
* - 'reachability'
|
|
1978
|
+
* - 'settings'
|
|
1979
|
+
* - 'plugins'
|
|
1980
|
+
* - 'pluginsRestart'
|
|
1981
|
+
* - 'devices'
|
|
1982
|
+
* - 'fabrics'
|
|
1983
|
+
* - 'sessions'
|
|
1984
|
+
*/
|
|
1646
1985
|
wssSendRefreshRequired(changed = null) {
|
|
1647
1986
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1987
|
+
// Send the message to all connected clients
|
|
1648
1988
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1649
1989
|
if (client.readyState === WebSocket.OPEN) {
|
|
1650
1990
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1651
1991
|
}
|
|
1652
1992
|
});
|
|
1653
1993
|
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1996
|
+
*
|
|
1997
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1998
|
+
*/
|
|
1654
1999
|
wssSendRestartRequired(snackbar = true) {
|
|
1655
2000
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1656
2001
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1657
2002
|
if (snackbar === true)
|
|
1658
2003
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2004
|
+
// Send the message to all connected clients
|
|
1659
2005
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1660
2006
|
if (client.readyState === WebSocket.OPEN) {
|
|
1661
2007
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1662
2008
|
}
|
|
1663
2009
|
});
|
|
1664
2010
|
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2013
|
+
*
|
|
2014
|
+
*/
|
|
1665
2015
|
wssSendUpdateRequired() {
|
|
1666
2016
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1667
2017
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2018
|
+
// Send the message to all connected clients
|
|
1668
2019
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1669
2020
|
if (client.readyState === WebSocket.OPEN) {
|
|
1670
2021
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1671
2022
|
}
|
|
1672
2023
|
});
|
|
1673
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Sends a cpu update message to all connected clients.
|
|
2027
|
+
*
|
|
2028
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2029
|
+
*/
|
|
1674
2030
|
wssSendCpuUpdate(cpuUsage) {
|
|
1675
2031
|
if (hasParameter('debug'))
|
|
1676
2032
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2033
|
+
// Send the message to all connected clients
|
|
1677
2034
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1678
2035
|
if (client.readyState === WebSocket.OPEN) {
|
|
1679
2036
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1680
2037
|
}
|
|
1681
2038
|
});
|
|
1682
2039
|
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Sends a memory update message to all connected clients.
|
|
2042
|
+
*
|
|
2043
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2044
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2045
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2046
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2047
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2048
|
+
* @param {string} external - The external memory in bytes.
|
|
2049
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2050
|
+
*/
|
|
1683
2051
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1684
2052
|
if (hasParameter('debug'))
|
|
1685
2053
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2054
|
+
// Send the message to all connected clients
|
|
1686
2055
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1687
2056
|
if (client.readyState === WebSocket.OPEN) {
|
|
1688
2057
|
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
2058
|
}
|
|
1690
2059
|
});
|
|
1691
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Sends an uptime update message to all connected clients.
|
|
2063
|
+
*
|
|
2064
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2065
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2066
|
+
*/
|
|
1692
2067
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1693
2068
|
if (hasParameter('debug'))
|
|
1694
2069
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2070
|
+
// Send the message to all connected clients
|
|
1695
2071
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1696
2072
|
if (client.readyState === WebSocket.OPEN) {
|
|
1697
2073
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1698
2074
|
}
|
|
1699
2075
|
});
|
|
1700
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Sends an open snackbar message to all connected clients.
|
|
2079
|
+
*
|
|
2080
|
+
* @param {string} message - The message to send.
|
|
2081
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2082
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2083
|
+
*/
|
|
1701
2084
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1702
2085
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2086
|
+
// Send the message to all connected clients
|
|
1703
2087
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1704
2088
|
if (client.readyState === WebSocket.OPEN) {
|
|
1705
2089
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1706
2090
|
}
|
|
1707
2091
|
});
|
|
1708
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Sends a close snackbar message to all connected clients.
|
|
2095
|
+
*
|
|
2096
|
+
* @param {string} message - The message to send.
|
|
2097
|
+
*/
|
|
1709
2098
|
wssSendCloseSnackbarMessage(message) {
|
|
1710
2099
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2100
|
+
// Send the message to all connected clients
|
|
1711
2101
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1712
2102
|
if (client.readyState === WebSocket.OPEN) {
|
|
1713
2103
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1714
2104
|
}
|
|
1715
2105
|
});
|
|
1716
2106
|
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2109
|
+
*
|
|
2110
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2111
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2112
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2113
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2114
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2115
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2116
|
+
*
|
|
2117
|
+
* @remarks
|
|
2118
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2119
|
+
* with the updated attribute information.
|
|
2120
|
+
*/
|
|
1717
2121
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1718
2122
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2123
|
+
// Send the message to all connected clients
|
|
1719
2124
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1720
2125
|
if (client.readyState === WebSocket.OPEN) {
|
|
1721
2126
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1722
2127
|
}
|
|
1723
2128
|
});
|
|
1724
2129
|
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Sends a message to all connected clients.
|
|
2132
|
+
*
|
|
2133
|
+
* @param {number} id - The message id.
|
|
2134
|
+
* @param {string} method - The message method.
|
|
2135
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2136
|
+
*/
|
|
1725
2137
|
wssBroadcastMessage(id, method, params) {
|
|
1726
2138
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2139
|
+
// Send the message to all connected clients
|
|
1727
2140
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1728
2141
|
if (client.readyState === WebSocket.OPEN) {
|
|
1729
2142
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1731,3 +2144,4 @@ export class Frontend extends EventEmitter {
|
|
|
1731
2144
|
});
|
|
1732
2145
|
}
|
|
1733
2146
|
}
|
|
2147
|
+
//# sourceMappingURL=frontend.js.map
|