matterbridge 3.2.6-dev-20250906-4b022a0 → 3.2.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 +3 -2
- 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/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.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 +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -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 +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -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 +100 -9
- 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/speaker.d.ts +83 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +80 -0
- package/dist/devices/speaker.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 +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -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 +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -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 +450 -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/jest-utils/jestHelpers.d.ts +103 -0
- package/dist/jest-utils/jestHelpers.d.ts.map +1 -0
- package/dist/jest-utils/jestHelpers.js +124 -2
- package/dist/jest-utils/jestHelpers.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 +457 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +821 -90
- 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 +761 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +630 -17
- 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 +1438 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1301 -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 +379 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +304 -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 +13 -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, 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 {
|
|
@@ -677,6 +888,13 @@ export class Frontend extends EventEmitter {
|
|
|
677
888
|
};
|
|
678
889
|
}
|
|
679
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Retrieves the cluster text description from a given device.
|
|
893
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
894
|
+
*
|
|
895
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
896
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
897
|
+
*/
|
|
680
898
|
getClusterTextFromDevice(device) {
|
|
681
899
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
682
900
|
return '';
|
|
@@ -687,6 +905,7 @@ export class Frontend extends EventEmitter {
|
|
|
687
905
|
if (composed)
|
|
688
906
|
return 'Composed: ' + composed.value;
|
|
689
907
|
}
|
|
908
|
+
// istanbul ignore next cause is not reachable
|
|
690
909
|
return '';
|
|
691
910
|
};
|
|
692
911
|
const getFixedLabel = (device) => {
|
|
@@ -696,11 +915,13 @@ export class Frontend extends EventEmitter {
|
|
|
696
915
|
if (composed)
|
|
697
916
|
return 'Composed: ' + composed.value;
|
|
698
917
|
}
|
|
918
|
+
// istanbul ignore next cause is not reacheable
|
|
699
919
|
return '';
|
|
700
920
|
};
|
|
701
921
|
let attributes = '';
|
|
702
922
|
let supportedModes = [];
|
|
703
923
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
924
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
704
925
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
705
926
|
return;
|
|
706
927
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -790,11 +1011,17 @@ export class Frontend extends EventEmitter {
|
|
|
790
1011
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
791
1012
|
attributes += `${getUserLabel(device)} `;
|
|
792
1013
|
});
|
|
1014
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
793
1015
|
return attributes.trimStart().trimEnd();
|
|
794
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1019
|
+
*
|
|
1020
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1021
|
+
*/
|
|
795
1022
|
getPlugins() {
|
|
796
1023
|
if (this.matterbridge.hasCleanupStarted)
|
|
797
|
-
return [];
|
|
1024
|
+
return []; // Skip if cleanup has started
|
|
798
1025
|
const baseRegisteredPlugins = [];
|
|
799
1026
|
for (const plugin of this.matterbridge.plugins) {
|
|
800
1027
|
baseRegisteredPlugins.push({
|
|
@@ -824,6 +1051,7 @@ export class Frontend extends EventEmitter {
|
|
|
824
1051
|
schemaJson: plugin.schemaJson,
|
|
825
1052
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
826
1053
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1054
|
+
// Childbridge mode specific data
|
|
827
1055
|
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
828
1056
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
829
1057
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
@@ -833,13 +1061,21 @@ export class Frontend extends EventEmitter {
|
|
|
833
1061
|
}
|
|
834
1062
|
return baseRegisteredPlugins;
|
|
835
1063
|
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Retrieves the devices from Matterbridge.
|
|
1066
|
+
*
|
|
1067
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1068
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1069
|
+
*/
|
|
836
1070
|
async getDevices(pluginName) {
|
|
837
1071
|
if (this.matterbridge.hasCleanupStarted)
|
|
838
|
-
return [];
|
|
1072
|
+
return []; // Skip if cleanup has started
|
|
839
1073
|
const devices = [];
|
|
840
1074
|
for (const device of this.matterbridge.devices.array()) {
|
|
1075
|
+
// Filter by pluginName if provided
|
|
841
1076
|
if (pluginName && pluginName !== device.plugin)
|
|
842
1077
|
continue;
|
|
1078
|
+
// Check if the device has the required properties
|
|
843
1079
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
844
1080
|
continue;
|
|
845
1081
|
devices.push({
|
|
@@ -859,22 +1095,37 @@ export class Frontend extends EventEmitter {
|
|
|
859
1095
|
}
|
|
860
1096
|
return devices;
|
|
861
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1100
|
+
*
|
|
1101
|
+
* Response for /api/clusters
|
|
1102
|
+
*
|
|
1103
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1104
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1105
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1106
|
+
*/
|
|
862
1107
|
getClusters(pluginName, endpointNumber) {
|
|
863
1108
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
864
1109
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
865
1110
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
866
1111
|
return;
|
|
867
1112
|
}
|
|
1113
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1114
|
+
// Get the device types from the main endpoint
|
|
868
1115
|
const deviceTypes = [];
|
|
869
1116
|
const clusters = [];
|
|
870
1117
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
871
1118
|
deviceTypes.push(d.deviceType);
|
|
872
1119
|
});
|
|
1120
|
+
// Get the clusters from the main endpoint
|
|
873
1121
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
874
1122
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
875
1123
|
return;
|
|
876
1124
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
877
1125
|
return;
|
|
1126
|
+
// console.log(
|
|
1127
|
+
// `${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}`,
|
|
1128
|
+
// );
|
|
878
1129
|
clusters.push({
|
|
879
1130
|
endpoint: endpoint.number.toString(),
|
|
880
1131
|
id: 'main',
|
|
@@ -887,12 +1138,19 @@ export class Frontend extends EventEmitter {
|
|
|
887
1138
|
attributeLocalValue: attributeValue,
|
|
888
1139
|
});
|
|
889
1140
|
});
|
|
1141
|
+
// Get the child endpoints
|
|
890
1142
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1143
|
+
// if (childEndpoints.length === 0) {
|
|
1144
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1145
|
+
// }
|
|
891
1146
|
childEndpoints.forEach((childEndpoint) => {
|
|
1147
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
892
1148
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
893
1149
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
894
1150
|
return;
|
|
895
1151
|
}
|
|
1152
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1153
|
+
// Get the device types of the child endpoint
|
|
896
1154
|
const deviceTypes = [];
|
|
897
1155
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
898
1156
|
deviceTypes.push(d.deviceType);
|
|
@@ -902,9 +1160,12 @@ export class Frontend extends EventEmitter {
|
|
|
902
1160
|
return;
|
|
903
1161
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
904
1162
|
return;
|
|
1163
|
+
// console.log(
|
|
1164
|
+
// `${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}`,
|
|
1165
|
+
// );
|
|
905
1166
|
clusters.push({
|
|
906
1167
|
endpoint: childEndpoint.number.toString(),
|
|
907
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1168
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
908
1169
|
deviceTypes,
|
|
909
1170
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
910
1171
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -917,6 +1178,13 @@ export class Frontend extends EventEmitter {
|
|
|
917
1178
|
});
|
|
918
1179
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
919
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1183
|
+
*
|
|
1184
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1185
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1186
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1187
|
+
*/
|
|
920
1188
|
async wsMessageHandler(client, message) {
|
|
921
1189
|
let data;
|
|
922
1190
|
try {
|
|
@@ -963,35 +1231,48 @@ export class Frontend extends EventEmitter {
|
|
|
963
1231
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
964
1232
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
965
1233
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1234
|
+
// The install comes from InstallPlugins
|
|
966
1235
|
this.matterbridge.plugins
|
|
967
1236
|
.add(packageName)
|
|
968
1237
|
.then((plugin) => {
|
|
1238
|
+
// istanbul ignore next if
|
|
969
1239
|
if (plugin) {
|
|
1240
|
+
// The plugin is not registered
|
|
970
1241
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1242
|
+
// In childbridge mode the plugins server node is not started when added
|
|
971
1243
|
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
972
1244
|
this.wssSendRestartRequired(true, true);
|
|
973
1245
|
this.matterbridge.plugins
|
|
974
1246
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1247
|
+
// eslint-disable-next-line promise/no-nesting
|
|
975
1248
|
.then(() => {
|
|
976
1249
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
977
1250
|
this.wssSendRefreshRequired('plugins');
|
|
978
1251
|
return;
|
|
979
1252
|
})
|
|
1253
|
+
// eslint-disable-next-line promise/no-nesting
|
|
980
1254
|
.catch((_error) => {
|
|
1255
|
+
//
|
|
981
1256
|
});
|
|
982
1257
|
}
|
|
983
1258
|
else {
|
|
1259
|
+
// The plugin is already registered
|
|
984
1260
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
985
1261
|
this.wssSendRefreshRequired('plugins');
|
|
986
1262
|
this.wssSendRestartRequired(true, true);
|
|
987
1263
|
}
|
|
988
1264
|
return;
|
|
989
1265
|
})
|
|
1266
|
+
// eslint-disable-next-line promise/no-nesting
|
|
990
1267
|
.catch((_error) => {
|
|
1268
|
+
//
|
|
991
1269
|
});
|
|
992
1270
|
}
|
|
993
1271
|
else {
|
|
1272
|
+
// The package is matterbridge
|
|
1273
|
+
// istanbul ignore next
|
|
994
1274
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1275
|
+
// istanbul ignore next if
|
|
995
1276
|
if (this.matterbridge.restartMode !== '') {
|
|
996
1277
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
997
1278
|
this.matterbridge.shutdownProcess();
|
|
@@ -1013,7 +1294,9 @@ export class Frontend extends EventEmitter {
|
|
|
1013
1294
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1014
1295
|
return;
|
|
1015
1296
|
}
|
|
1297
|
+
// The package is a plugin
|
|
1016
1298
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1299
|
+
// istanbul ignore next if
|
|
1017
1300
|
if (plugin) {
|
|
1018
1301
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1019
1302
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1021,6 +1304,7 @@ export class Frontend extends EventEmitter {
|
|
|
1021
1304
|
this.wssSendRefreshRequired('plugins');
|
|
1022
1305
|
this.wssSendRefreshRequired('devices');
|
|
1023
1306
|
}
|
|
1307
|
+
// Uninstall the package
|
|
1024
1308
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1025
1309
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1026
1310
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1061,6 +1345,7 @@ export class Frontend extends EventEmitter {
|
|
|
1061
1345
|
return;
|
|
1062
1346
|
})
|
|
1063
1347
|
.catch((_error) => {
|
|
1348
|
+
//
|
|
1064
1349
|
});
|
|
1065
1350
|
}
|
|
1066
1351
|
else {
|
|
@@ -1107,6 +1392,7 @@ export class Frontend extends EventEmitter {
|
|
|
1107
1392
|
return;
|
|
1108
1393
|
})
|
|
1109
1394
|
.catch((_error) => {
|
|
1395
|
+
//
|
|
1110
1396
|
});
|
|
1111
1397
|
}
|
|
1112
1398
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1132,6 +1418,7 @@ export class Frontend extends EventEmitter {
|
|
|
1132
1418
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1133
1419
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1134
1420
|
if (plugin.serverNode) {
|
|
1421
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1135
1422
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1136
1423
|
plugin.serverNode = undefined;
|
|
1137
1424
|
}
|
|
@@ -1142,15 +1429,16 @@ export class Frontend extends EventEmitter {
|
|
|
1142
1429
|
}
|
|
1143
1430
|
}
|
|
1144
1431
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1145
|
-
plugin.restartRequired = false;
|
|
1432
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1146
1433
|
let needRestart = 0;
|
|
1147
1434
|
for (const plugin of this.matterbridge.plugins) {
|
|
1148
1435
|
if (plugin.restartRequired)
|
|
1149
1436
|
needRestart++;
|
|
1150
1437
|
}
|
|
1151
1438
|
if (needRestart === 0) {
|
|
1152
|
-
this.wssSendRestartNotRequired(true);
|
|
1439
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1153
1440
|
}
|
|
1441
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1154
1442
|
if (plugin.serverNode)
|
|
1155
1443
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1156
1444
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1256,6 +1544,8 @@ export class Frontend extends EventEmitter {
|
|
|
1256
1544
|
else if (data.method === '/api/advertise') {
|
|
1257
1545
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1258
1546
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1547
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1548
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1259
1549
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1260
1550
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1261
1551
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1378,22 +1668,22 @@ export class Frontend extends EventEmitter {
|
|
|
1378
1668
|
if (isValidString(data.params.value, 4)) {
|
|
1379
1669
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1380
1670
|
if (data.params.value === 'Debug') {
|
|
1381
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1671
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1382
1672
|
}
|
|
1383
1673
|
else if (data.params.value === 'Info') {
|
|
1384
|
-
await this.matterbridge.setLogLevel("info");
|
|
1674
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1385
1675
|
}
|
|
1386
1676
|
else if (data.params.value === 'Notice') {
|
|
1387
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1677
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1388
1678
|
}
|
|
1389
1679
|
else if (data.params.value === 'Warn') {
|
|
1390
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1680
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1391
1681
|
}
|
|
1392
1682
|
else if (data.params.value === 'Error') {
|
|
1393
|
-
await this.matterbridge.setLogLevel("error");
|
|
1683
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1394
1684
|
}
|
|
1395
1685
|
else if (data.params.value === 'Fatal') {
|
|
1396
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1686
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1397
1687
|
}
|
|
1398
1688
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1399
1689
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1404,6 +1694,7 @@ export class Frontend extends EventEmitter {
|
|
|
1404
1694
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1405
1695
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1406
1696
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1697
|
+
// Create the file logger for matterbridge
|
|
1407
1698
|
if (data.params.value)
|
|
1408
1699
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1409
1700
|
else
|
|
@@ -1557,15 +1848,19 @@ export class Frontend extends EventEmitter {
|
|
|
1557
1848
|
return;
|
|
1558
1849
|
}
|
|
1559
1850
|
const config = plugin.configJson;
|
|
1851
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1560
1852
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1853
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1561
1854
|
if (select === 'serial')
|
|
1562
1855
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1563
1856
|
if (select === 'name')
|
|
1564
1857
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1565
1858
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1859
|
+
// Remove postfix from the serial if it exists
|
|
1566
1860
|
if (config.postfix) {
|
|
1567
1861
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1568
1862
|
}
|
|
1863
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1569
1864
|
if (isValidArray(config.whiteList, 1)) {
|
|
1570
1865
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1571
1866
|
config.whiteList.push(data.params.serial);
|
|
@@ -1574,6 +1869,7 @@ export class Frontend extends EventEmitter {
|
|
|
1574
1869
|
config.whiteList.push(data.params.name);
|
|
1575
1870
|
}
|
|
1576
1871
|
}
|
|
1872
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1577
1873
|
if (isValidArray(config.blackList, 1)) {
|
|
1578
1874
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1579
1875
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1604,7 +1900,9 @@ export class Frontend extends EventEmitter {
|
|
|
1604
1900
|
return;
|
|
1605
1901
|
}
|
|
1606
1902
|
const config = plugin.configJson;
|
|
1903
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1607
1904
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1905
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1608
1906
|
if (select === 'serial')
|
|
1609
1907
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1610
1908
|
if (select === 'name')
|
|
@@ -1613,6 +1911,7 @@ export class Frontend extends EventEmitter {
|
|
|
1613
1911
|
if (config.postfix) {
|
|
1614
1912
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1615
1913
|
}
|
|
1914
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1616
1915
|
if (isValidArray(config.whiteList, 1)) {
|
|
1617
1916
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1618
1917
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1621,6 +1920,7 @@ export class Frontend extends EventEmitter {
|
|
|
1621
1920
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1622
1921
|
}
|
|
1623
1922
|
}
|
|
1923
|
+
// Add the serial to the blackList
|
|
1624
1924
|
if (isValidArray(config.blackList)) {
|
|
1625
1925
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1626
1926
|
config.blackList.push(data.params.serial);
|
|
@@ -1654,126 +1954,251 @@ export class Frontend extends EventEmitter {
|
|
|
1654
1954
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1655
1955
|
}
|
|
1656
1956
|
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1959
|
+
*
|
|
1960
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1961
|
+
* @param {string} time - The time string of the message
|
|
1962
|
+
* @param {string} name - The logger name of the message
|
|
1963
|
+
* @param {string} message - The content of the message.
|
|
1964
|
+
*
|
|
1965
|
+
* @remarks
|
|
1966
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1967
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1968
|
+
* The function sends the message to all connected clients.
|
|
1969
|
+
*/
|
|
1657
1970
|
wssSendMessage(level, time, name, message) {
|
|
1658
1971
|
if (!level || !time || !name || !message)
|
|
1659
1972
|
return;
|
|
1973
|
+
// Remove ANSI escape codes from the message
|
|
1974
|
+
// eslint-disable-next-line no-control-regex
|
|
1660
1975
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1976
|
+
// Remove leading asterisks from the message
|
|
1661
1977
|
message = message.replace(/^\*+/, '');
|
|
1978
|
+
// Replace all occurrences of \t and \n
|
|
1662
1979
|
message = message.replace(/[\t\n]/g, '');
|
|
1980
|
+
// Remove non-printable characters
|
|
1981
|
+
// eslint-disable-next-line no-control-regex
|
|
1663
1982
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1983
|
+
// Replace all occurrences of \" with "
|
|
1664
1984
|
message = message.replace(/\\"/g, '"');
|
|
1985
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1665
1986
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1987
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1666
1988
|
const maxContinuousLength = 100;
|
|
1667
1989
|
const keepStartLength = 20;
|
|
1668
1990
|
const keepEndLength = 20;
|
|
1991
|
+
// Split the message into words
|
|
1669
1992
|
message = message
|
|
1670
1993
|
.split(' ')
|
|
1671
1994
|
.map((word) => {
|
|
1995
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1672
1996
|
if (word.length > maxContinuousLength) {
|
|
1673
1997
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1674
1998
|
}
|
|
1675
1999
|
return word;
|
|
1676
2000
|
})
|
|
1677
2001
|
.join(' ');
|
|
2002
|
+
// Send the message to all connected clients
|
|
1678
2003
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1679
2004
|
if (client.readyState === WebSocket.OPEN) {
|
|
1680
2005
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1681
2006
|
}
|
|
1682
2007
|
});
|
|
1683
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2011
|
+
*
|
|
2012
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
2013
|
+
* possible values:
|
|
2014
|
+
* - 'matterbridgeLatestVersion'
|
|
2015
|
+
* - 'matterbridgeDevVersion'
|
|
2016
|
+
* - 'matterbridgeAdvertise'
|
|
2017
|
+
* - 'online'
|
|
2018
|
+
* - 'offline'
|
|
2019
|
+
* - 'reachability'
|
|
2020
|
+
* - 'settings'
|
|
2021
|
+
* - 'plugins'
|
|
2022
|
+
* - 'pluginsRestart'
|
|
2023
|
+
* - 'devices'
|
|
2024
|
+
* - 'fabrics'
|
|
2025
|
+
* - 'sessions'
|
|
2026
|
+
*/
|
|
1684
2027
|
wssSendRefreshRequired(changed = null) {
|
|
1685
2028
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2029
|
+
// Send the message to all connected clients
|
|
1686
2030
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1687
2031
|
if (client.readyState === WebSocket.OPEN) {
|
|
1688
2032
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1689
2033
|
}
|
|
1690
2034
|
});
|
|
1691
2035
|
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2038
|
+
*
|
|
2039
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2040
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2041
|
+
*/
|
|
1692
2042
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1693
2043
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1694
2044
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1695
2045
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1696
2046
|
if (snackbar === true)
|
|
1697
2047
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2048
|
+
// Send the message to all connected clients
|
|
1698
2049
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1699
2050
|
if (client.readyState === WebSocket.OPEN) {
|
|
1700
2051
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
|
|
1701
2052
|
}
|
|
1702
2053
|
});
|
|
1703
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2057
|
+
*
|
|
2058
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
|
|
2059
|
+
*/
|
|
1704
2060
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1705
2061
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1706
2062
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1707
2063
|
if (snackbar === true)
|
|
1708
2064
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2065
|
+
// Send the message to all connected clients
|
|
1709
2066
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1710
2067
|
if (client.readyState === WebSocket.OPEN) {
|
|
1711
2068
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
|
|
1712
2069
|
}
|
|
1713
2070
|
});
|
|
1714
2071
|
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2074
|
+
*
|
|
2075
|
+
* @param {boolean} devVersion - If true, the update is for a development version.
|
|
2076
|
+
*/
|
|
1715
2077
|
wssSendUpdateRequired(devVersion = false) {
|
|
1716
2078
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1717
2079
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2080
|
+
// Send the message to all connected clients
|
|
1718
2081
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1719
2082
|
if (client.readyState === WebSocket.OPEN) {
|
|
1720
2083
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
|
|
1721
2084
|
}
|
|
1722
2085
|
});
|
|
1723
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Sends a cpu update message to all connected clients.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2091
|
+
*/
|
|
1724
2092
|
wssSendCpuUpdate(cpuUsage) {
|
|
1725
2093
|
if (hasParameter('debug'))
|
|
1726
2094
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2095
|
+
// Send the message to all connected clients
|
|
1727
2096
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1728
2097
|
if (client.readyState === WebSocket.OPEN) {
|
|
1729
2098
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1730
2099
|
}
|
|
1731
2100
|
});
|
|
1732
2101
|
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Sends a memory update message to all connected clients.
|
|
2104
|
+
*
|
|
2105
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2106
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2107
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2108
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2109
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2110
|
+
* @param {string} external - The external memory in bytes.
|
|
2111
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2112
|
+
*/
|
|
1733
2113
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1734
2114
|
if (hasParameter('debug'))
|
|
1735
2115
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2116
|
+
// Send the message to all connected clients
|
|
1736
2117
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1737
2118
|
if (client.readyState === WebSocket.OPEN) {
|
|
1738
2119
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1739
2120
|
}
|
|
1740
2121
|
});
|
|
1741
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Sends an uptime update message to all connected clients.
|
|
2125
|
+
*
|
|
2126
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2127
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2128
|
+
*/
|
|
1742
2129
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1743
2130
|
if (hasParameter('debug'))
|
|
1744
2131
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2132
|
+
// Send the message to all connected clients
|
|
1745
2133
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1746
2134
|
if (client.readyState === WebSocket.OPEN) {
|
|
1747
2135
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1748
2136
|
}
|
|
1749
2137
|
});
|
|
1750
2138
|
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Sends an open snackbar message to all connected clients.
|
|
2141
|
+
*
|
|
2142
|
+
* @param {string} message - The message to send.
|
|
2143
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2144
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2145
|
+
*/
|
|
1751
2146
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1752
2147
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2148
|
+
// Send the message to all connected clients
|
|
1753
2149
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1754
2150
|
if (client.readyState === WebSocket.OPEN) {
|
|
1755
2151
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1756
2152
|
}
|
|
1757
2153
|
});
|
|
1758
2154
|
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Sends a close snackbar message to all connected clients.
|
|
2157
|
+
*
|
|
2158
|
+
* @param {string} message - The message to send.
|
|
2159
|
+
*/
|
|
1759
2160
|
wssSendCloseSnackbarMessage(message) {
|
|
1760
2161
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2162
|
+
// Send the message to all connected clients
|
|
1761
2163
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1762
2164
|
if (client.readyState === WebSocket.OPEN) {
|
|
1763
2165
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1764
2166
|
}
|
|
1765
2167
|
});
|
|
1766
2168
|
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2171
|
+
*
|
|
2172
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2173
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2174
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2175
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2176
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2177
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2178
|
+
*
|
|
2179
|
+
* @remarks
|
|
2180
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2181
|
+
* with the updated attribute information.
|
|
2182
|
+
*/
|
|
1767
2183
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1768
2184
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2185
|
+
// Send the message to all connected clients
|
|
1769
2186
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1770
2187
|
if (client.readyState === WebSocket.OPEN) {
|
|
1771
2188
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1772
2189
|
}
|
|
1773
2190
|
});
|
|
1774
2191
|
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Sends a message to all connected clients.
|
|
2194
|
+
*
|
|
2195
|
+
* @param {number} id - The message id.
|
|
2196
|
+
* @param {string} method - The message method.
|
|
2197
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2198
|
+
*/
|
|
1775
2199
|
wssBroadcastMessage(id, method, params) {
|
|
1776
2200
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2201
|
+
// Send the message to all connected clients
|
|
1777
2202
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1778
2203
|
if (client.readyState === WebSocket.OPEN) {
|
|
1779
2204
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1781,3 +2206,4 @@ export class Frontend extends EventEmitter {
|
|
|
1781
2206
|
});
|
|
1782
2207
|
}
|
|
1783
2208
|
}
|
|
2209
|
+
//# sourceMappingURL=frontend.js.map
|