matterbridge 3.2.4-dev-20250830-5c48452 → 3.2.4
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/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/cooktop.d.ts +60 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +55 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- 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 +15 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +4 -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 +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +93 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +80 -0
- package/dist/devices/refrigerator.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/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +25 -3
- package/dist/devices/temperatureControl.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/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.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 +113 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +51 -13
- package/dist/dgram/mb_mdns.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 +298 -137
- 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 +60 -1
- 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 +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +313 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +451 -24
- 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 +462 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +789 -50
- 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 +65 -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 +1356 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1220 -54
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +345 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +331 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +256 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +198 -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 +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +99 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -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 +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -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 +84 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +91 -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 +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- 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,10 +141,39 @@ 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}`);
|
|
48
|
-
|
|
144
|
+
// Initialize multer with the upload directory
|
|
145
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
49
146
|
const upload = multer({ dest: uploadDir });
|
|
147
|
+
// Create the express app that serves the frontend
|
|
50
148
|
this.expressApp = express();
|
|
149
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
150
|
+
/*
|
|
151
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
152
|
+
for (const method of methods) {
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
157
|
+
try {
|
|
158
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
159
|
+
return original(path, ...rest);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
*/
|
|
167
|
+
// Log all requests to the server for debugging
|
|
168
|
+
/*
|
|
169
|
+
this.expressApp.use((req, res, next) => {
|
|
170
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
171
|
+
next();
|
|
172
|
+
});
|
|
173
|
+
*/
|
|
174
|
+
// Serve static files from '/static' endpoint
|
|
51
175
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
176
|
+
// Read the package.json file to get the frontend version
|
|
52
177
|
try {
|
|
53
178
|
this.log.debug(`Reading frontend package.json...`);
|
|
54
179
|
const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
|
|
@@ -56,9 +181,11 @@ export class Frontend extends EventEmitter {
|
|
|
56
181
|
this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
|
|
57
182
|
}
|
|
58
183
|
catch (error) {
|
|
184
|
+
// istanbul ignore next
|
|
59
185
|
this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
186
|
}
|
|
61
187
|
if (!hasParameter('ssl')) {
|
|
188
|
+
// Create an HTTP server and attach the express app
|
|
62
189
|
try {
|
|
63
190
|
this.log.debug(`Creating HTTP server...`);
|
|
64
191
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -68,6 +195,7 @@ export class Frontend extends EventEmitter {
|
|
|
68
195
|
this.emit('server_error', error);
|
|
69
196
|
return;
|
|
70
197
|
}
|
|
198
|
+
// Listen on the specified port
|
|
71
199
|
if (hasParameter('ingress')) {
|
|
72
200
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
73
201
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -106,6 +234,7 @@ export class Frontend extends EventEmitter {
|
|
|
106
234
|
let passphrase;
|
|
107
235
|
let httpsServerOptions = {};
|
|
108
236
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
237
|
+
// Load the p12 certificate and the passphrase
|
|
109
238
|
try {
|
|
110
239
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
111
240
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -117,7 +246,7 @@ export class Frontend extends EventEmitter {
|
|
|
117
246
|
}
|
|
118
247
|
try {
|
|
119
248
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
120
|
-
passphrase = passphrase.trim();
|
|
249
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
121
250
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
122
251
|
}
|
|
123
252
|
catch (error) {
|
|
@@ -128,6 +257,7 @@ export class Frontend extends EventEmitter {
|
|
|
128
257
|
httpsServerOptions = { pfx, passphrase };
|
|
129
258
|
}
|
|
130
259
|
else {
|
|
260
|
+
// 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.
|
|
131
261
|
try {
|
|
132
262
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
133
263
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -157,9 +287,10 @@ export class Frontend extends EventEmitter {
|
|
|
157
287
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
158
288
|
}
|
|
159
289
|
if (hasParameter('mtls')) {
|
|
160
|
-
httpsServerOptions.requestCert = true;
|
|
161
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
290
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
291
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
162
292
|
}
|
|
293
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
163
294
|
try {
|
|
164
295
|
this.log.debug(`Creating HTTPS server...`);
|
|
165
296
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -169,6 +300,7 @@ export class Frontend extends EventEmitter {
|
|
|
169
300
|
this.emit('server_error', error);
|
|
170
301
|
return;
|
|
171
302
|
}
|
|
303
|
+
// Listen on the specified port
|
|
172
304
|
if (hasParameter('ingress')) {
|
|
173
305
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
174
306
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -198,17 +330,19 @@ export class Frontend extends EventEmitter {
|
|
|
198
330
|
return;
|
|
199
331
|
});
|
|
200
332
|
}
|
|
333
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
201
334
|
const wssPort = this.port;
|
|
202
335
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
203
336
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
204
337
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
205
338
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
206
339
|
const clientIp = request.socket.remoteAddress;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
340
|
+
// Set the global logger callback for the WebSocketServer
|
|
341
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
342
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
343
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
344
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
345
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
212
346
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
213
347
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
214
348
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -230,6 +364,7 @@ export class Frontend extends EventEmitter {
|
|
|
230
364
|
}
|
|
231
365
|
});
|
|
232
366
|
ws.on('error', (error) => {
|
|
367
|
+
// istanbul ignore next
|
|
233
368
|
this.log.error(`WebSocket client error: ${error}`);
|
|
234
369
|
});
|
|
235
370
|
});
|
|
@@ -243,6 +378,7 @@ export class Frontend extends EventEmitter {
|
|
|
243
378
|
this.webSocketServer.on('error', (ws, error) => {
|
|
244
379
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
245
380
|
});
|
|
381
|
+
// Subscribe to cli events
|
|
246
382
|
cliEmitter.removeAllListeners();
|
|
247
383
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
248
384
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -253,6 +389,8 @@ export class Frontend extends EventEmitter {
|
|
|
253
389
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
254
390
|
this.wssSendCpuUpdate(cpuUsage);
|
|
255
391
|
});
|
|
392
|
+
// Endpoint to validate login code
|
|
393
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
256
394
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
257
395
|
const { password } = req.body;
|
|
258
396
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -271,23 +409,27 @@ export class Frontend extends EventEmitter {
|
|
|
271
409
|
this.log.warn('/api/login error wrong password');
|
|
272
410
|
res.json({ valid: false });
|
|
273
411
|
}
|
|
412
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
274
413
|
}
|
|
275
414
|
catch (error) {
|
|
276
415
|
this.log.error('/api/login error getting password');
|
|
277
416
|
res.json({ valid: false });
|
|
278
417
|
}
|
|
279
418
|
});
|
|
419
|
+
// Endpoint to provide health check for docker
|
|
280
420
|
this.expressApp.get('/health', (req, res) => {
|
|
281
421
|
this.log.debug('Express received /health');
|
|
282
422
|
const healthStatus = {
|
|
283
|
-
status: 'ok',
|
|
284
|
-
uptime: process.uptime(),
|
|
285
|
-
timestamp: new Date().toISOString(),
|
|
423
|
+
status: 'ok', // Indicate service is healthy
|
|
424
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
425
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
286
426
|
};
|
|
287
427
|
res.status(200).json(healthStatus);
|
|
288
428
|
});
|
|
429
|
+
// Endpoint to provide memory usage details
|
|
289
430
|
this.expressApp.get('/memory', async (req, res) => {
|
|
290
431
|
this.log.debug('Express received /memory');
|
|
432
|
+
// Memory usage from process
|
|
291
433
|
const memoryUsageRaw = process.memoryUsage();
|
|
292
434
|
const memoryUsage = {
|
|
293
435
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -296,10 +438,13 @@ export class Frontend extends EventEmitter {
|
|
|
296
438
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
297
439
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
298
440
|
};
|
|
441
|
+
// V8 heap statistics
|
|
299
442
|
const { default: v8 } = await import('node:v8');
|
|
300
443
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
301
444
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
445
|
+
// Format heapStats
|
|
302
446
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
447
|
+
// Format heapSpaces
|
|
303
448
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
304
449
|
...space,
|
|
305
450
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -317,19 +462,23 @@ export class Frontend extends EventEmitter {
|
|
|
317
462
|
};
|
|
318
463
|
res.status(200).json(memoryReport);
|
|
319
464
|
});
|
|
465
|
+
// Endpoint to provide settings
|
|
320
466
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
321
467
|
this.log.debug('The frontend sent /api/settings');
|
|
322
468
|
res.json(await this.getApiSettings());
|
|
323
469
|
});
|
|
470
|
+
// Endpoint to provide plugins
|
|
324
471
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
325
472
|
this.log.debug('The frontend sent /api/plugins');
|
|
326
473
|
res.json(this.getPlugins());
|
|
327
474
|
});
|
|
475
|
+
// Endpoint to provide devices
|
|
328
476
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
329
477
|
this.log.debug('The frontend sent /api/devices');
|
|
330
478
|
const devices = await this.getDevices();
|
|
331
479
|
res.json(devices);
|
|
332
480
|
});
|
|
481
|
+
// Endpoint to view the matterbridge log
|
|
333
482
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
334
483
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
335
484
|
try {
|
|
@@ -342,6 +491,7 @@ export class Frontend extends EventEmitter {
|
|
|
342
491
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
343
492
|
}
|
|
344
493
|
});
|
|
494
|
+
// Endpoint to view the matter.js log
|
|
345
495
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
346
496
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
347
497
|
try {
|
|
@@ -354,6 +504,7 @@ export class Frontend extends EventEmitter {
|
|
|
354
504
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
355
505
|
}
|
|
356
506
|
});
|
|
507
|
+
// Endpoint to view the shelly log
|
|
357
508
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
358
509
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
359
510
|
try {
|
|
@@ -366,6 +517,7 @@ export class Frontend extends EventEmitter {
|
|
|
366
517
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
367
518
|
}
|
|
368
519
|
});
|
|
520
|
+
// Endpoint to download the matterbridge log
|
|
369
521
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
370
522
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
371
523
|
try {
|
|
@@ -379,12 +531,14 @@ export class Frontend extends EventEmitter {
|
|
|
379
531
|
}
|
|
380
532
|
res.type('text/plain');
|
|
381
533
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
534
|
+
/* istanbul ignore if */
|
|
382
535
|
if (error) {
|
|
383
536
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
384
537
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
385
538
|
}
|
|
386
539
|
});
|
|
387
540
|
});
|
|
541
|
+
// Endpoint to download the matter log
|
|
388
542
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
389
543
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
390
544
|
try {
|
|
@@ -398,12 +552,14 @@ export class Frontend extends EventEmitter {
|
|
|
398
552
|
}
|
|
399
553
|
res.type('text/plain');
|
|
400
554
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
555
|
+
/* istanbul ignore if */
|
|
401
556
|
if (error) {
|
|
402
557
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
403
558
|
res.status(500).send('Error downloading the matter log file');
|
|
404
559
|
}
|
|
405
560
|
});
|
|
406
561
|
});
|
|
562
|
+
// Endpoint to download the shelly log
|
|
407
563
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
408
564
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
409
565
|
try {
|
|
@@ -417,74 +573,90 @@ export class Frontend extends EventEmitter {
|
|
|
417
573
|
}
|
|
418
574
|
res.type('text/plain');
|
|
419
575
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
576
|
+
/* istanbul ignore if */
|
|
420
577
|
if (error) {
|
|
421
578
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
422
579
|
res.status(500).send('Error downloading Shelly system log file');
|
|
423
580
|
}
|
|
424
581
|
});
|
|
425
582
|
});
|
|
583
|
+
// Endpoint to download the matterbridge storage directory
|
|
426
584
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
427
585
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
428
586
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
429
587
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
588
|
+
/* istanbul ignore if */
|
|
430
589
|
if (error) {
|
|
431
590
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
432
591
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
433
592
|
}
|
|
434
593
|
});
|
|
435
594
|
});
|
|
595
|
+
// Endpoint to download the matter storage file
|
|
436
596
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
437
597
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
438
598
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
439
599
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
600
|
+
/* istanbul ignore if */
|
|
440
601
|
if (error) {
|
|
441
602
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
442
603
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
443
604
|
}
|
|
444
605
|
});
|
|
445
606
|
});
|
|
607
|
+
// Endpoint to download the matterbridge plugin directory
|
|
446
608
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
447
609
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
448
610
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
449
611
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
612
|
+
/* istanbul ignore if */
|
|
450
613
|
if (error) {
|
|
451
614
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
452
615
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
453
616
|
}
|
|
454
617
|
});
|
|
455
618
|
});
|
|
619
|
+
// Endpoint to download the matterbridge plugin config files
|
|
456
620
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
457
621
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
458
622
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
459
623
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
624
|
+
/* istanbul ignore if */
|
|
460
625
|
if (error) {
|
|
461
626
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
462
627
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
463
628
|
}
|
|
464
629
|
});
|
|
465
630
|
});
|
|
631
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
466
632
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
467
633
|
this.log.debug('The frontend sent /api/download-backup');
|
|
468
634
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
635
|
+
/* istanbul ignore if */
|
|
469
636
|
if (error) {
|
|
470
637
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
471
638
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
472
639
|
}
|
|
473
640
|
});
|
|
474
641
|
});
|
|
642
|
+
// Endpoint to upload a package
|
|
475
643
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
476
644
|
const { filename } = req.body;
|
|
477
645
|
const file = req.file;
|
|
646
|
+
/* istanbul ignore if */
|
|
478
647
|
if (!file || !filename) {
|
|
479
648
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
480
649
|
res.status(400).send('Invalid request: file and filename are required');
|
|
481
650
|
return;
|
|
482
651
|
}
|
|
483
652
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
653
|
+
// Define the path where the plugin file will be saved
|
|
484
654
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
485
655
|
try {
|
|
656
|
+
// Move the uploaded file to the specified path
|
|
486
657
|
await fs.rename(file.path, filePath);
|
|
487
658
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
659
|
+
// Install the plugin package
|
|
488
660
|
if (filename.endsWith('.tgz')) {
|
|
489
661
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
490
662
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -504,6 +676,7 @@ export class Frontend extends EventEmitter {
|
|
|
504
676
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
505
677
|
}
|
|
506
678
|
});
|
|
679
|
+
// Fallback for routing (must be the last route)
|
|
507
680
|
this.expressApp.use((req, res) => {
|
|
508
681
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
509
682
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -512,13 +685,16 @@ export class Frontend extends EventEmitter {
|
|
|
512
685
|
}
|
|
513
686
|
async stop() {
|
|
514
687
|
this.log.debug('Stopping the frontend...');
|
|
688
|
+
// Remove listeners from the express app
|
|
515
689
|
if (this.expressApp) {
|
|
516
690
|
this.expressApp.removeAllListeners();
|
|
517
691
|
this.expressApp = undefined;
|
|
518
692
|
this.log.debug('Frontend app closed successfully');
|
|
519
693
|
}
|
|
694
|
+
// Close the WebSocket server
|
|
520
695
|
if (this.webSocketServer) {
|
|
521
696
|
this.log.debug('Closing WebSocket server...');
|
|
697
|
+
// Close all active connections
|
|
522
698
|
this.webSocketServer.clients.forEach((client) => {
|
|
523
699
|
if (client.readyState === WebSocket.OPEN) {
|
|
524
700
|
client.close();
|
|
@@ -527,6 +703,7 @@ export class Frontend extends EventEmitter {
|
|
|
527
703
|
await withTimeout(new Promise((resolve) => {
|
|
528
704
|
this.webSocketServer?.close((error) => {
|
|
529
705
|
if (error) {
|
|
706
|
+
// istanbul ignore next
|
|
530
707
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
531
708
|
}
|
|
532
709
|
else {
|
|
@@ -539,11 +716,13 @@ export class Frontend extends EventEmitter {
|
|
|
539
716
|
this.webSocketServer.removeAllListeners();
|
|
540
717
|
this.webSocketServer = undefined;
|
|
541
718
|
}
|
|
719
|
+
// Close the http server
|
|
542
720
|
if (this.httpServer) {
|
|
543
721
|
this.log.debug('Closing http server...');
|
|
544
722
|
await withTimeout(new Promise((resolve) => {
|
|
545
723
|
this.httpServer?.close((error) => {
|
|
546
724
|
if (error) {
|
|
725
|
+
// istanbul ignore next
|
|
547
726
|
this.log.error(`Error closing http server: ${error}`);
|
|
548
727
|
}
|
|
549
728
|
else {
|
|
@@ -557,11 +736,13 @@ export class Frontend extends EventEmitter {
|
|
|
557
736
|
this.httpServer = undefined;
|
|
558
737
|
this.log.debug('Frontend http server closed successfully');
|
|
559
738
|
}
|
|
739
|
+
// Close the https server
|
|
560
740
|
if (this.httpsServer) {
|
|
561
741
|
this.log.debug('Closing https server...');
|
|
562
742
|
await withTimeout(new Promise((resolve) => {
|
|
563
743
|
this.httpsServer?.close((error) => {
|
|
564
744
|
if (error) {
|
|
745
|
+
// istanbul ignore next
|
|
565
746
|
this.log.error(`Error closing https server: ${error}`);
|
|
566
747
|
}
|
|
567
748
|
else {
|
|
@@ -577,6 +758,7 @@ export class Frontend extends EventEmitter {
|
|
|
577
758
|
}
|
|
578
759
|
this.log.debug('Frontend stopped successfully');
|
|
579
760
|
}
|
|
761
|
+
// Function to format bytes to KB, MB, or GB
|
|
580
762
|
formatMemoryUsage = (bytes) => {
|
|
581
763
|
if (bytes >= 1024 ** 3) {
|
|
582
764
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -588,6 +770,7 @@ export class Frontend extends EventEmitter {
|
|
|
588
770
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
589
771
|
}
|
|
590
772
|
};
|
|
773
|
+
// Function to format system uptime with only the most significant unit
|
|
591
774
|
formatOsUpTime = (seconds) => {
|
|
592
775
|
if (seconds >= 86400) {
|
|
593
776
|
const days = Math.floor(seconds / 86400);
|
|
@@ -603,7 +786,13 @@ export class Frontend extends EventEmitter {
|
|
|
603
786
|
}
|
|
604
787
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
605
788
|
};
|
|
789
|
+
/**
|
|
790
|
+
* Retrieves the api settings data.
|
|
791
|
+
*
|
|
792
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
793
|
+
*/
|
|
606
794
|
async getApiSettings() {
|
|
795
|
+
// Update the system information
|
|
607
796
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
608
797
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
609
798
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -612,6 +801,7 @@ export class Frontend extends EventEmitter {
|
|
|
612
801
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
613
802
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
614
803
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
804
|
+
// Update the matterbridge information
|
|
615
805
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
616
806
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
617
807
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -623,6 +813,7 @@ export class Frontend extends EventEmitter {
|
|
|
623
813
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
624
814
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
625
815
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
816
|
+
// Update the matterbridge information in bridge mode
|
|
626
817
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
627
818
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
628
819
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -632,6 +823,12 @@ export class Frontend extends EventEmitter {
|
|
|
632
823
|
}
|
|
633
824
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
634
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Retrieves the reachable attribute.
|
|
828
|
+
*
|
|
829
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
830
|
+
* @returns {boolean} The reachable attribute.
|
|
831
|
+
*/
|
|
635
832
|
getReachability(device) {
|
|
636
833
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
637
834
|
return false;
|
|
@@ -643,6 +840,12 @@ export class Frontend extends EventEmitter {
|
|
|
643
840
|
return true;
|
|
644
841
|
return false;
|
|
645
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Retrieves the power source attribute.
|
|
845
|
+
*
|
|
846
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
847
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
848
|
+
*/
|
|
646
849
|
getPowerSource(endpoint) {
|
|
647
850
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
648
851
|
return undefined;
|
|
@@ -658,13 +861,21 @@ export class Frontend extends EventEmitter {
|
|
|
658
861
|
}
|
|
659
862
|
return;
|
|
660
863
|
};
|
|
864
|
+
// Root endpoint
|
|
661
865
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
662
866
|
return powerSource(endpoint);
|
|
867
|
+
// Child endpoints
|
|
663
868
|
for (const child of endpoint.getChildEndpoints()) {
|
|
664
869
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
665
870
|
return powerSource(child);
|
|
666
871
|
}
|
|
667
872
|
}
|
|
873
|
+
/**
|
|
874
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
875
|
+
*
|
|
876
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
877
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
878
|
+
*/
|
|
668
879
|
getMatterDataFromDevice(device) {
|
|
669
880
|
if (device.mode === 'server' && device.serverNode) {
|
|
670
881
|
return {
|
|
@@ -676,6 +887,13 @@ export class Frontend extends EventEmitter {
|
|
|
676
887
|
};
|
|
677
888
|
}
|
|
678
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Retrieves the cluster text description from a given device.
|
|
892
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
893
|
+
*
|
|
894
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
895
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
896
|
+
*/
|
|
679
897
|
getClusterTextFromDevice(device) {
|
|
680
898
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
681
899
|
return '';
|
|
@@ -686,6 +904,7 @@ export class Frontend extends EventEmitter {
|
|
|
686
904
|
if (composed)
|
|
687
905
|
return 'Composed: ' + composed.value;
|
|
688
906
|
}
|
|
907
|
+
// istanbul ignore next cause is not reachable
|
|
689
908
|
return '';
|
|
690
909
|
};
|
|
691
910
|
const getFixedLabel = (device) => {
|
|
@@ -695,11 +914,13 @@ export class Frontend extends EventEmitter {
|
|
|
695
914
|
if (composed)
|
|
696
915
|
return 'Composed: ' + composed.value;
|
|
697
916
|
}
|
|
917
|
+
// istanbul ignore next cause is not reacheable
|
|
698
918
|
return '';
|
|
699
919
|
};
|
|
700
920
|
let attributes = '';
|
|
701
921
|
let supportedModes = [];
|
|
702
922
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
923
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
703
924
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
704
925
|
return;
|
|
705
926
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -789,11 +1010,17 @@ export class Frontend extends EventEmitter {
|
|
|
789
1010
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
790
1011
|
attributes += `${getUserLabel(device)} `;
|
|
791
1012
|
});
|
|
1013
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
792
1014
|
return attributes.trimStart().trimEnd();
|
|
793
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1018
|
+
*
|
|
1019
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1020
|
+
*/
|
|
794
1021
|
getPlugins() {
|
|
795
1022
|
if (this.matterbridge.hasCleanupStarted)
|
|
796
|
-
return [];
|
|
1023
|
+
return []; // Skip if cleanup has started
|
|
797
1024
|
const baseRegisteredPlugins = [];
|
|
798
1025
|
for (const plugin of this.matterbridge.plugins) {
|
|
799
1026
|
baseRegisteredPlugins.push({
|
|
@@ -823,6 +1050,7 @@ export class Frontend extends EventEmitter {
|
|
|
823
1050
|
schemaJson: plugin.schemaJson,
|
|
824
1051
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
825
1052
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1053
|
+
// Childbridge mode specific data
|
|
826
1054
|
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
827
1055
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
828
1056
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
@@ -832,13 +1060,21 @@ export class Frontend extends EventEmitter {
|
|
|
832
1060
|
}
|
|
833
1061
|
return baseRegisteredPlugins;
|
|
834
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Retrieves the devices from Matterbridge.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1067
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1068
|
+
*/
|
|
835
1069
|
async getDevices(pluginName) {
|
|
836
1070
|
if (this.matterbridge.hasCleanupStarted)
|
|
837
|
-
return [];
|
|
1071
|
+
return []; // Skip if cleanup has started
|
|
838
1072
|
const devices = [];
|
|
839
1073
|
for (const device of this.matterbridge.devices.array()) {
|
|
1074
|
+
// Filter by pluginName if provided
|
|
840
1075
|
if (pluginName && pluginName !== device.plugin)
|
|
841
1076
|
continue;
|
|
1077
|
+
// Check if the device has the required properties
|
|
842
1078
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
843
1079
|
continue;
|
|
844
1080
|
devices.push({
|
|
@@ -858,22 +1094,37 @@ export class Frontend extends EventEmitter {
|
|
|
858
1094
|
}
|
|
859
1095
|
return devices;
|
|
860
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1099
|
+
*
|
|
1100
|
+
* Response for /api/clusters
|
|
1101
|
+
*
|
|
1102
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1103
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1104
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1105
|
+
*/
|
|
861
1106
|
getClusters(pluginName, endpointNumber) {
|
|
862
1107
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
863
1108
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
864
1109
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
865
1110
|
return;
|
|
866
1111
|
}
|
|
1112
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1113
|
+
// Get the device types from the main endpoint
|
|
867
1114
|
const deviceTypes = [];
|
|
868
1115
|
const clusters = [];
|
|
869
1116
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
870
1117
|
deviceTypes.push(d.deviceType);
|
|
871
1118
|
});
|
|
1119
|
+
// Get the clusters from the main endpoint
|
|
872
1120
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
873
1121
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
874
1122
|
return;
|
|
875
1123
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
876
1124
|
return;
|
|
1125
|
+
// console.log(
|
|
1126
|
+
// `${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}`,
|
|
1127
|
+
// );
|
|
877
1128
|
clusters.push({
|
|
878
1129
|
endpoint: endpoint.number.toString(),
|
|
879
1130
|
id: 'main',
|
|
@@ -886,12 +1137,19 @@ export class Frontend extends EventEmitter {
|
|
|
886
1137
|
attributeLocalValue: attributeValue,
|
|
887
1138
|
});
|
|
888
1139
|
});
|
|
1140
|
+
// Get the child endpoints
|
|
889
1141
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1142
|
+
// if (childEndpoints.length === 0) {
|
|
1143
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1144
|
+
// }
|
|
890
1145
|
childEndpoints.forEach((childEndpoint) => {
|
|
1146
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
891
1147
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
892
1148
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
893
1149
|
return;
|
|
894
1150
|
}
|
|
1151
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1152
|
+
// Get the device types of the child endpoint
|
|
895
1153
|
const deviceTypes = [];
|
|
896
1154
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
897
1155
|
deviceTypes.push(d.deviceType);
|
|
@@ -901,9 +1159,12 @@ export class Frontend extends EventEmitter {
|
|
|
901
1159
|
return;
|
|
902
1160
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
903
1161
|
return;
|
|
1162
|
+
// console.log(
|
|
1163
|
+
// `${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}`,
|
|
1164
|
+
// );
|
|
904
1165
|
clusters.push({
|
|
905
1166
|
endpoint: childEndpoint.number.toString(),
|
|
906
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1167
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
907
1168
|
deviceTypes,
|
|
908
1169
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
909
1170
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -916,6 +1177,13 @@ export class Frontend extends EventEmitter {
|
|
|
916
1177
|
});
|
|
917
1178
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
918
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1182
|
+
*
|
|
1183
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1184
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1185
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1186
|
+
*/
|
|
919
1187
|
async wsMessageHandler(client, message) {
|
|
920
1188
|
let data;
|
|
921
1189
|
try {
|
|
@@ -962,33 +1230,45 @@ export class Frontend extends EventEmitter {
|
|
|
962
1230
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
963
1231
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
964
1232
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1233
|
+
// The install comes from InstallPlugins
|
|
965
1234
|
this.matterbridge.plugins
|
|
966
1235
|
.add(packageName)
|
|
967
1236
|
.then((plugin) => {
|
|
1237
|
+
// istanbul ignore next if
|
|
968
1238
|
if (plugin) {
|
|
1239
|
+
// The plugin is not registered
|
|
969
1240
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
970
1241
|
this.matterbridge.plugins
|
|
971
1242
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1243
|
+
// eslint-disable-next-line promise/no-nesting
|
|
972
1244
|
.then(() => {
|
|
973
1245
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
974
1246
|
this.wssSendRefreshRequired('plugins');
|
|
975
1247
|
return;
|
|
976
1248
|
})
|
|
1249
|
+
// eslint-disable-next-line promise/no-nesting
|
|
977
1250
|
.catch((_error) => {
|
|
1251
|
+
//
|
|
978
1252
|
});
|
|
979
1253
|
}
|
|
980
1254
|
else {
|
|
1255
|
+
// The plugin is already registered
|
|
981
1256
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
982
1257
|
this.wssSendRefreshRequired('plugins');
|
|
983
1258
|
this.wssSendRestartRequired(true, true);
|
|
984
1259
|
}
|
|
985
1260
|
return;
|
|
986
1261
|
})
|
|
1262
|
+
// eslint-disable-next-line promise/no-nesting
|
|
987
1263
|
.catch((_error) => {
|
|
1264
|
+
//
|
|
988
1265
|
});
|
|
989
1266
|
}
|
|
990
1267
|
else {
|
|
1268
|
+
// The package is matterbridge
|
|
1269
|
+
// istanbul ignore next
|
|
991
1270
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1271
|
+
// istanbul ignore next if
|
|
992
1272
|
if (this.matterbridge.restartMode !== '') {
|
|
993
1273
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
994
1274
|
this.matterbridge.shutdownProcess();
|
|
@@ -1010,7 +1290,9 @@ export class Frontend extends EventEmitter {
|
|
|
1010
1290
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1011
1291
|
return;
|
|
1012
1292
|
}
|
|
1293
|
+
// The package is a plugin
|
|
1013
1294
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1295
|
+
// istanbul ignore next if
|
|
1014
1296
|
if (plugin) {
|
|
1015
1297
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1016
1298
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1018,6 +1300,7 @@ export class Frontend extends EventEmitter {
|
|
|
1018
1300
|
this.wssSendRefreshRequired('plugins');
|
|
1019
1301
|
this.wssSendRefreshRequired('devices');
|
|
1020
1302
|
}
|
|
1303
|
+
// Uninstall the package
|
|
1021
1304
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1022
1305
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1023
1306
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1058,6 +1341,7 @@ export class Frontend extends EventEmitter {
|
|
|
1058
1341
|
return;
|
|
1059
1342
|
})
|
|
1060
1343
|
.catch((_error) => {
|
|
1344
|
+
//
|
|
1061
1345
|
});
|
|
1062
1346
|
}
|
|
1063
1347
|
else {
|
|
@@ -1104,6 +1388,7 @@ export class Frontend extends EventEmitter {
|
|
|
1104
1388
|
return;
|
|
1105
1389
|
})
|
|
1106
1390
|
.catch((_error) => {
|
|
1391
|
+
//
|
|
1107
1392
|
});
|
|
1108
1393
|
}
|
|
1109
1394
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1129,6 +1414,7 @@ export class Frontend extends EventEmitter {
|
|
|
1129
1414
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1130
1415
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1131
1416
|
if (plugin.serverNode) {
|
|
1417
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1132
1418
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1133
1419
|
plugin.serverNode = undefined;
|
|
1134
1420
|
}
|
|
@@ -1139,15 +1425,16 @@ export class Frontend extends EventEmitter {
|
|
|
1139
1425
|
}
|
|
1140
1426
|
}
|
|
1141
1427
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1142
|
-
plugin.restartRequired = false;
|
|
1428
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1143
1429
|
let needRestart = 0;
|
|
1144
1430
|
for (const plugin of this.matterbridge.plugins) {
|
|
1145
1431
|
if (plugin.restartRequired)
|
|
1146
1432
|
needRestart++;
|
|
1147
1433
|
}
|
|
1148
1434
|
if (needRestart === 0) {
|
|
1149
|
-
this.wssSendRestartNotRequired(true);
|
|
1435
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1150
1436
|
}
|
|
1437
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1151
1438
|
if (plugin.serverNode)
|
|
1152
1439
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1153
1440
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1253,6 +1540,8 @@ export class Frontend extends EventEmitter {
|
|
|
1253
1540
|
else if (data.method === '/api/advertise') {
|
|
1254
1541
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1255
1542
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1543
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1544
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1256
1545
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1257
1546
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1258
1547
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1375,22 +1664,22 @@ export class Frontend extends EventEmitter {
|
|
|
1375
1664
|
if (isValidString(data.params.value, 4)) {
|
|
1376
1665
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1377
1666
|
if (data.params.value === 'Debug') {
|
|
1378
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1667
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1379
1668
|
}
|
|
1380
1669
|
else if (data.params.value === 'Info') {
|
|
1381
|
-
await this.matterbridge.setLogLevel("info");
|
|
1670
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1382
1671
|
}
|
|
1383
1672
|
else if (data.params.value === 'Notice') {
|
|
1384
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1673
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1385
1674
|
}
|
|
1386
1675
|
else if (data.params.value === 'Warn') {
|
|
1387
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1676
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1388
1677
|
}
|
|
1389
1678
|
else if (data.params.value === 'Error') {
|
|
1390
|
-
await this.matterbridge.setLogLevel("error");
|
|
1679
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1391
1680
|
}
|
|
1392
1681
|
else if (data.params.value === 'Fatal') {
|
|
1393
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1682
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1394
1683
|
}
|
|
1395
1684
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1396
1685
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1401,6 +1690,7 @@ export class Frontend extends EventEmitter {
|
|
|
1401
1690
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1402
1691
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1403
1692
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1693
|
+
// Create the file logger for matterbridge
|
|
1404
1694
|
if (data.params.value)
|
|
1405
1695
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1406
1696
|
else
|
|
@@ -1447,6 +1737,7 @@ export class Frontend extends EventEmitter {
|
|
|
1447
1737
|
});
|
|
1448
1738
|
}
|
|
1449
1739
|
catch (error) {
|
|
1740
|
+
/* istanbul ignore next */
|
|
1450
1741
|
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}`);
|
|
1451
1742
|
}
|
|
1452
1743
|
}
|
|
@@ -1455,6 +1746,7 @@ export class Frontend extends EventEmitter {
|
|
|
1455
1746
|
Logger.removeLogger('matterfilelogger');
|
|
1456
1747
|
}
|
|
1457
1748
|
catch (error) {
|
|
1749
|
+
/* istanbul ignore next */
|
|
1458
1750
|
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}`);
|
|
1459
1751
|
}
|
|
1460
1752
|
}
|
|
@@ -1567,15 +1859,19 @@ export class Frontend extends EventEmitter {
|
|
|
1567
1859
|
return;
|
|
1568
1860
|
}
|
|
1569
1861
|
const config = plugin.configJson;
|
|
1862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1570
1863
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1864
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1571
1865
|
if (select === 'serial')
|
|
1572
1866
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1573
1867
|
if (select === 'name')
|
|
1574
1868
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1575
1869
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1870
|
+
// Remove postfix from the serial if it exists
|
|
1576
1871
|
if (config.postfix) {
|
|
1577
1872
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1578
1873
|
}
|
|
1874
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1579
1875
|
if (isValidArray(config.whiteList, 1)) {
|
|
1580
1876
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1581
1877
|
config.whiteList.push(data.params.serial);
|
|
@@ -1584,6 +1880,7 @@ export class Frontend extends EventEmitter {
|
|
|
1584
1880
|
config.whiteList.push(data.params.name);
|
|
1585
1881
|
}
|
|
1586
1882
|
}
|
|
1883
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1587
1884
|
if (isValidArray(config.blackList, 1)) {
|
|
1588
1885
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1589
1886
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1614,7 +1911,9 @@ export class Frontend extends EventEmitter {
|
|
|
1614
1911
|
return;
|
|
1615
1912
|
}
|
|
1616
1913
|
const config = plugin.configJson;
|
|
1914
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1617
1915
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1916
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1618
1917
|
if (select === 'serial')
|
|
1619
1918
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1620
1919
|
if (select === 'name')
|
|
@@ -1623,6 +1922,7 @@ export class Frontend extends EventEmitter {
|
|
|
1623
1922
|
if (config.postfix) {
|
|
1624
1923
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1625
1924
|
}
|
|
1925
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1626
1926
|
if (isValidArray(config.whiteList, 1)) {
|
|
1627
1927
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1628
1928
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1631,6 +1931,7 @@ export class Frontend extends EventEmitter {
|
|
|
1631
1931
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1632
1932
|
}
|
|
1633
1933
|
}
|
|
1934
|
+
// Add the serial to the blackList
|
|
1634
1935
|
if (isValidArray(config.blackList)) {
|
|
1635
1936
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1636
1937
|
config.blackList.push(data.params.serial);
|
|
@@ -1664,126 +1965,251 @@ export class Frontend extends EventEmitter {
|
|
|
1664
1965
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1665
1966
|
}
|
|
1666
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1970
|
+
*
|
|
1971
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1972
|
+
* @param {string} time - The time string of the message
|
|
1973
|
+
* @param {string} name - The logger name of the message
|
|
1974
|
+
* @param {string} message - The content of the message.
|
|
1975
|
+
*
|
|
1976
|
+
* @remarks
|
|
1977
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1978
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1979
|
+
* The function sends the message to all connected clients.
|
|
1980
|
+
*/
|
|
1667
1981
|
wssSendMessage(level, time, name, message) {
|
|
1668
1982
|
if (!level || !time || !name || !message)
|
|
1669
1983
|
return;
|
|
1984
|
+
// Remove ANSI escape codes from the message
|
|
1985
|
+
// eslint-disable-next-line no-control-regex
|
|
1670
1986
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1987
|
+
// Remove leading asterisks from the message
|
|
1671
1988
|
message = message.replace(/^\*+/, '');
|
|
1989
|
+
// Replace all occurrences of \t and \n
|
|
1672
1990
|
message = message.replace(/[\t\n]/g, '');
|
|
1991
|
+
// Remove non-printable characters
|
|
1992
|
+
// eslint-disable-next-line no-control-regex
|
|
1673
1993
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1994
|
+
// Replace all occurrences of \" with "
|
|
1674
1995
|
message = message.replace(/\\"/g, '"');
|
|
1996
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1675
1997
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1998
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1676
1999
|
const maxContinuousLength = 100;
|
|
1677
2000
|
const keepStartLength = 20;
|
|
1678
2001
|
const keepEndLength = 20;
|
|
2002
|
+
// Split the message into words
|
|
1679
2003
|
message = message
|
|
1680
2004
|
.split(' ')
|
|
1681
2005
|
.map((word) => {
|
|
2006
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1682
2007
|
if (word.length > maxContinuousLength) {
|
|
1683
2008
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1684
2009
|
}
|
|
1685
2010
|
return word;
|
|
1686
2011
|
})
|
|
1687
2012
|
.join(' ');
|
|
2013
|
+
// Send the message to all connected clients
|
|
1688
2014
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1689
2015
|
if (client.readyState === WebSocket.OPEN) {
|
|
1690
2016
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1691
2017
|
}
|
|
1692
2018
|
});
|
|
1693
2019
|
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2022
|
+
*
|
|
2023
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
2024
|
+
* possible values:
|
|
2025
|
+
* - 'matterbridgeLatestVersion'
|
|
2026
|
+
* - 'matterbridgeDevVersion'
|
|
2027
|
+
* - 'matterbridgeAdvertise'
|
|
2028
|
+
* - 'online'
|
|
2029
|
+
* - 'offline'
|
|
2030
|
+
* - 'reachability'
|
|
2031
|
+
* - 'settings'
|
|
2032
|
+
* - 'plugins'
|
|
2033
|
+
* - 'pluginsRestart'
|
|
2034
|
+
* - 'devices'
|
|
2035
|
+
* - 'fabrics'
|
|
2036
|
+
* - 'sessions'
|
|
2037
|
+
*/
|
|
1694
2038
|
wssSendRefreshRequired(changed = null) {
|
|
1695
2039
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2040
|
+
// Send the message to all connected clients
|
|
1696
2041
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1697
2042
|
if (client.readyState === WebSocket.OPEN) {
|
|
1698
2043
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1699
2044
|
}
|
|
1700
2045
|
});
|
|
1701
2046
|
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2049
|
+
*
|
|
2050
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2051
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2052
|
+
*/
|
|
1702
2053
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1703
2054
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1704
2055
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1705
2056
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1706
2057
|
if (snackbar === true)
|
|
1707
2058
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2059
|
+
// Send the message to all connected clients
|
|
1708
2060
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1709
2061
|
if (client.readyState === WebSocket.OPEN) {
|
|
1710
2062
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
|
|
1711
2063
|
}
|
|
1712
2064
|
});
|
|
1713
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2068
|
+
*
|
|
2069
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
|
|
2070
|
+
*/
|
|
1714
2071
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1715
2072
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1716
2073
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1717
2074
|
if (snackbar === true)
|
|
1718
2075
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2076
|
+
// Send the message to all connected clients
|
|
1719
2077
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1720
2078
|
if (client.readyState === WebSocket.OPEN) {
|
|
1721
2079
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
|
|
1722
2080
|
}
|
|
1723
2081
|
});
|
|
1724
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2085
|
+
*
|
|
2086
|
+
* @param {boolean} devVersion - If true, the update is for a development version.
|
|
2087
|
+
*/
|
|
1725
2088
|
wssSendUpdateRequired(devVersion = false) {
|
|
1726
2089
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1727
2090
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2091
|
+
// Send the message to all connected clients
|
|
1728
2092
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1729
2093
|
if (client.readyState === WebSocket.OPEN) {
|
|
1730
2094
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
|
|
1731
2095
|
}
|
|
1732
2096
|
});
|
|
1733
2097
|
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Sends a cpu update message to all connected clients.
|
|
2100
|
+
*
|
|
2101
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2102
|
+
*/
|
|
1734
2103
|
wssSendCpuUpdate(cpuUsage) {
|
|
1735
2104
|
if (hasParameter('debug'))
|
|
1736
2105
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2106
|
+
// Send the message to all connected clients
|
|
1737
2107
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1738
2108
|
if (client.readyState === WebSocket.OPEN) {
|
|
1739
2109
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1740
2110
|
}
|
|
1741
2111
|
});
|
|
1742
2112
|
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Sends a memory update message to all connected clients.
|
|
2115
|
+
*
|
|
2116
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2117
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2118
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2119
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2120
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2121
|
+
* @param {string} external - The external memory in bytes.
|
|
2122
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2123
|
+
*/
|
|
1743
2124
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1744
2125
|
if (hasParameter('debug'))
|
|
1745
2126
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2127
|
+
// Send the message to all connected clients
|
|
1746
2128
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1747
2129
|
if (client.readyState === WebSocket.OPEN) {
|
|
1748
2130
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1749
2131
|
}
|
|
1750
2132
|
});
|
|
1751
2133
|
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Sends an uptime update message to all connected clients.
|
|
2136
|
+
*
|
|
2137
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2138
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2139
|
+
*/
|
|
1752
2140
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1753
2141
|
if (hasParameter('debug'))
|
|
1754
2142
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2143
|
+
// Send the message to all connected clients
|
|
1755
2144
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1756
2145
|
if (client.readyState === WebSocket.OPEN) {
|
|
1757
2146
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1758
2147
|
}
|
|
1759
2148
|
});
|
|
1760
2149
|
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Sends an open snackbar message to all connected clients.
|
|
2152
|
+
*
|
|
2153
|
+
* @param {string} message - The message to send.
|
|
2154
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2155
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2156
|
+
*/
|
|
1761
2157
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1762
2158
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2159
|
+
// Send the message to all connected clients
|
|
1763
2160
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1764
2161
|
if (client.readyState === WebSocket.OPEN) {
|
|
1765
2162
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1766
2163
|
}
|
|
1767
2164
|
});
|
|
1768
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Sends a close snackbar message to all connected clients.
|
|
2168
|
+
*
|
|
2169
|
+
* @param {string} message - The message to send.
|
|
2170
|
+
*/
|
|
1769
2171
|
wssSendCloseSnackbarMessage(message) {
|
|
1770
2172
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2173
|
+
// Send the message to all connected clients
|
|
1771
2174
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1772
2175
|
if (client.readyState === WebSocket.OPEN) {
|
|
1773
2176
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1774
2177
|
}
|
|
1775
2178
|
});
|
|
1776
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2182
|
+
*
|
|
2183
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2184
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2185
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2186
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2187
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2188
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2189
|
+
*
|
|
2190
|
+
* @remarks
|
|
2191
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2192
|
+
* with the updated attribute information.
|
|
2193
|
+
*/
|
|
1777
2194
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1778
2195
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2196
|
+
// Send the message to all connected clients
|
|
1779
2197
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1780
2198
|
if (client.readyState === WebSocket.OPEN) {
|
|
1781
2199
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1782
2200
|
}
|
|
1783
2201
|
});
|
|
1784
2202
|
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Sends a message to all connected clients.
|
|
2205
|
+
*
|
|
2206
|
+
* @param {number} id - The message id.
|
|
2207
|
+
* @param {string} method - The message method.
|
|
2208
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2209
|
+
*/
|
|
1785
2210
|
wssBroadcastMessage(id, method, params) {
|
|
1786
2211
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2212
|
+
// Send the message to all connected clients
|
|
1787
2213
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1788
2214
|
if (client.readyState === WebSocket.OPEN) {
|
|
1789
2215
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1791,3 +2217,4 @@ export class Frontend extends EventEmitter {
|
|
|
1791
2217
|
});
|
|
1792
2218
|
}
|
|
1793
2219
|
}
|
|
2220
|
+
//# sourceMappingURL=frontend.js.map
|