matterbridge 3.2.7-dev-20250913-9d0d095 → 3.2.7
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 +1 -1
- 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 +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -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 +315 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +456 -25
- 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 +465 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +789 -51
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1747 -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 +1515 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1379 -55
- 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 +380 -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 +204 -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/jestHelpers.d.ts +137 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.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,3 +1,27 @@
|
|
|
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';
|
|
@@ -5,28 +29,100 @@ import path from 'node:path';
|
|
|
5
29
|
import { existsSync, promises as fs, unlinkSync } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
7
31
|
import { appendFile } from 'node:fs/promises';
|
|
32
|
+
// Third-party modules
|
|
8
33
|
import express from 'express';
|
|
9
34
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
10
35
|
import multer from 'multer';
|
|
36
|
+
// AnsiLogger module
|
|
11
37
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
38
|
+
// @matter
|
|
12
39
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
|
|
13
40
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
14
41
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
|
|
42
|
+
// Matterbridge
|
|
15
43
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
|
|
16
44
|
import { plg } from './matterbridgeTypes.js';
|
|
17
45
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
18
46
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
47
|
+
/**
|
|
48
|
+
* Websocket message ID for logging.
|
|
49
|
+
*
|
|
50
|
+
* @constant {number}
|
|
51
|
+
*/
|
|
19
52
|
export const WS_ID_LOG = 0;
|
|
53
|
+
/**
|
|
54
|
+
* Websocket message ID indicating a refresh is needed.
|
|
55
|
+
*
|
|
56
|
+
* @constant {number}
|
|
57
|
+
*/
|
|
20
58
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
59
|
+
/**
|
|
60
|
+
* Websocket message ID indicating a restart is needed.
|
|
61
|
+
*
|
|
62
|
+
* @constant {number}
|
|
63
|
+
*/
|
|
21
64
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
65
|
+
/**
|
|
66
|
+
* Websocket message ID indicating a cpu update.
|
|
67
|
+
*
|
|
68
|
+
* @constant {number}
|
|
69
|
+
*/
|
|
22
70
|
export const WS_ID_CPU_UPDATE = 3;
|
|
71
|
+
/**
|
|
72
|
+
* Websocket message ID indicating a memory update.
|
|
73
|
+
*
|
|
74
|
+
* @constant {number}
|
|
75
|
+
*/
|
|
23
76
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
77
|
+
/**
|
|
78
|
+
* Websocket message ID indicating an uptime update.
|
|
79
|
+
*
|
|
80
|
+
* @constant {number}
|
|
81
|
+
*/
|
|
24
82
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
83
|
+
/**
|
|
84
|
+
* Websocket message ID indicating a snackbar message.
|
|
85
|
+
*
|
|
86
|
+
* @constant {number}
|
|
87
|
+
*/
|
|
25
88
|
export const WS_ID_SNACKBAR = 6;
|
|
89
|
+
/**
|
|
90
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
91
|
+
*
|
|
92
|
+
* @constant {number}
|
|
93
|
+
*/
|
|
26
94
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
95
|
+
/**
|
|
96
|
+
* Websocket message ID indicating a state update.
|
|
97
|
+
*
|
|
98
|
+
* @constant {number}
|
|
99
|
+
*/
|
|
27
100
|
export const WS_ID_STATEUPDATE = 8;
|
|
101
|
+
/**
|
|
102
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
103
|
+
*
|
|
104
|
+
* @constant {number}
|
|
105
|
+
*/
|
|
28
106
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
107
|
+
/**
|
|
108
|
+
* Websocket message ID indicating a shelly system update.
|
|
109
|
+
* check:
|
|
110
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
111
|
+
* perform:
|
|
112
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
113
|
+
*
|
|
114
|
+
* @constant {number}
|
|
115
|
+
*/
|
|
29
116
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
117
|
+
/**
|
|
118
|
+
* Websocket message ID indicating a shelly main update.
|
|
119
|
+
* check:
|
|
120
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
121
|
+
* perform:
|
|
122
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
123
|
+
*
|
|
124
|
+
* @constant {number}
|
|
125
|
+
*/
|
|
30
126
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
31
127
|
export class Frontend extends EventEmitter {
|
|
32
128
|
matterbridge;
|
|
@@ -39,7 +135,7 @@ export class Frontend extends EventEmitter {
|
|
|
39
135
|
constructor(matterbridge) {
|
|
40
136
|
super();
|
|
41
137
|
this.matterbridge = matterbridge;
|
|
42
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
138
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
43
139
|
}
|
|
44
140
|
set logLevel(logLevel) {
|
|
45
141
|
this.log.logLevel = logLevel;
|
|
@@ -47,10 +143,39 @@ export class Frontend extends EventEmitter {
|
|
|
47
143
|
async start(port = 8283) {
|
|
48
144
|
this.port = port;
|
|
49
145
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
50
|
-
|
|
146
|
+
// Initialize multer with the upload directory
|
|
147
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
51
148
|
const upload = multer({ dest: uploadDir });
|
|
149
|
+
// Create the express app that serves the frontend
|
|
52
150
|
this.expressApp = express();
|
|
151
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
152
|
+
/*
|
|
153
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
154
|
+
for (const method of methods) {
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
159
|
+
try {
|
|
160
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
161
|
+
return original(path, ...rest);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
*/
|
|
169
|
+
// Log all requests to the server for debugging
|
|
170
|
+
/*
|
|
171
|
+
this.expressApp.use((req, res, next) => {
|
|
172
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
173
|
+
next();
|
|
174
|
+
});
|
|
175
|
+
*/
|
|
176
|
+
// Serve static files from '/static' endpoint
|
|
53
177
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
178
|
+
// Read the package.json file to get the frontend version
|
|
54
179
|
try {
|
|
55
180
|
this.log.debug(`Reading frontend package.json...`);
|
|
56
181
|
const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
|
|
@@ -58,9 +183,11 @@ export class Frontend extends EventEmitter {
|
|
|
58
183
|
this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
|
|
59
184
|
}
|
|
60
185
|
catch (error) {
|
|
186
|
+
// istanbul ignore next
|
|
61
187
|
this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
188
|
}
|
|
63
189
|
if (!hasParameter('ssl')) {
|
|
190
|
+
// Create an HTTP server and attach the express app
|
|
64
191
|
try {
|
|
65
192
|
this.log.debug(`Creating HTTP server...`);
|
|
66
193
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -70,6 +197,7 @@ export class Frontend extends EventEmitter {
|
|
|
70
197
|
this.emit('server_error', error);
|
|
71
198
|
return;
|
|
72
199
|
}
|
|
200
|
+
// Listen on the specified port
|
|
73
201
|
if (hasParameter('ingress')) {
|
|
74
202
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
75
203
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -108,6 +236,7 @@ export class Frontend extends EventEmitter {
|
|
|
108
236
|
let passphrase;
|
|
109
237
|
let httpsServerOptions = {};
|
|
110
238
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
239
|
+
// Load the p12 certificate and the passphrase
|
|
111
240
|
try {
|
|
112
241
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
113
242
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -119,7 +248,7 @@ export class Frontend extends EventEmitter {
|
|
|
119
248
|
}
|
|
120
249
|
try {
|
|
121
250
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
122
|
-
passphrase = passphrase.trim();
|
|
251
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
123
252
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
124
253
|
}
|
|
125
254
|
catch (error) {
|
|
@@ -130,6 +259,7 @@ export class Frontend extends EventEmitter {
|
|
|
130
259
|
httpsServerOptions = { pfx, passphrase };
|
|
131
260
|
}
|
|
132
261
|
else {
|
|
262
|
+
// 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.
|
|
133
263
|
try {
|
|
134
264
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
135
265
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -159,9 +289,10 @@ export class Frontend extends EventEmitter {
|
|
|
159
289
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
160
290
|
}
|
|
161
291
|
if (hasParameter('mtls')) {
|
|
162
|
-
httpsServerOptions.requestCert = true;
|
|
163
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
292
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
293
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
164
294
|
}
|
|
295
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
165
296
|
try {
|
|
166
297
|
this.log.debug(`Creating HTTPS server...`);
|
|
167
298
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -171,6 +302,7 @@ export class Frontend extends EventEmitter {
|
|
|
171
302
|
this.emit('server_error', error);
|
|
172
303
|
return;
|
|
173
304
|
}
|
|
305
|
+
// Listen on the specified port
|
|
174
306
|
if (hasParameter('ingress')) {
|
|
175
307
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
176
308
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -200,17 +332,19 @@ export class Frontend extends EventEmitter {
|
|
|
200
332
|
return;
|
|
201
333
|
});
|
|
202
334
|
}
|
|
335
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
203
336
|
const wssPort = this.port;
|
|
204
337
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
205
338
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
206
339
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
207
340
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
208
341
|
const clientIp = request.socket.remoteAddress;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
342
|
+
// Set the global logger callback for the WebSocketServer
|
|
343
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
344
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
345
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
346
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
347
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
214
348
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
215
349
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
216
350
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -232,6 +366,7 @@ export class Frontend extends EventEmitter {
|
|
|
232
366
|
}
|
|
233
367
|
});
|
|
234
368
|
ws.on('error', (error) => {
|
|
369
|
+
// istanbul ignore next
|
|
235
370
|
this.log.error(`WebSocket client error: ${error}`);
|
|
236
371
|
});
|
|
237
372
|
});
|
|
@@ -245,6 +380,7 @@ export class Frontend extends EventEmitter {
|
|
|
245
380
|
this.webSocketServer.on('error', (ws, error) => {
|
|
246
381
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
247
382
|
});
|
|
383
|
+
// Subscribe to cli events
|
|
248
384
|
cliEmitter.removeAllListeners();
|
|
249
385
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
250
386
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -255,6 +391,8 @@ export class Frontend extends EventEmitter {
|
|
|
255
391
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
256
392
|
this.wssSendCpuUpdate(cpuUsage);
|
|
257
393
|
});
|
|
394
|
+
// Endpoint to validate login code
|
|
395
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
258
396
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
259
397
|
const { password } = req.body;
|
|
260
398
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -273,23 +411,27 @@ export class Frontend extends EventEmitter {
|
|
|
273
411
|
this.log.warn('/api/login error wrong password');
|
|
274
412
|
res.json({ valid: false });
|
|
275
413
|
}
|
|
414
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
276
415
|
}
|
|
277
416
|
catch (error) {
|
|
278
417
|
this.log.error('/api/login error getting password');
|
|
279
418
|
res.json({ valid: false });
|
|
280
419
|
}
|
|
281
420
|
});
|
|
421
|
+
// Endpoint to provide health check for docker
|
|
282
422
|
this.expressApp.get('/health', (req, res) => {
|
|
283
423
|
this.log.debug('Express received /health');
|
|
284
424
|
const healthStatus = {
|
|
285
|
-
status: 'ok',
|
|
286
|
-
uptime: process.uptime(),
|
|
287
|
-
timestamp: new Date().toISOString(),
|
|
425
|
+
status: 'ok', // Indicate service is healthy
|
|
426
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
427
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
288
428
|
};
|
|
289
429
|
res.status(200).json(healthStatus);
|
|
290
430
|
});
|
|
431
|
+
// Endpoint to provide memory usage details
|
|
291
432
|
this.expressApp.get('/memory', async (req, res) => {
|
|
292
433
|
this.log.debug('Express received /memory');
|
|
434
|
+
// Memory usage from process
|
|
293
435
|
const memoryUsageRaw = process.memoryUsage();
|
|
294
436
|
const memoryUsage = {
|
|
295
437
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -298,10 +440,13 @@ export class Frontend extends EventEmitter {
|
|
|
298
440
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
299
441
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
300
442
|
};
|
|
443
|
+
// V8 heap statistics
|
|
301
444
|
const { default: v8 } = await import('node:v8');
|
|
302
445
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
303
446
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
447
|
+
// Format heapStats
|
|
304
448
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
449
|
+
// Format heapSpaces
|
|
305
450
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
306
451
|
...space,
|
|
307
452
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -319,19 +464,23 @@ export class Frontend extends EventEmitter {
|
|
|
319
464
|
};
|
|
320
465
|
res.status(200).json(memoryReport);
|
|
321
466
|
});
|
|
467
|
+
// Endpoint to provide settings
|
|
322
468
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
323
469
|
this.log.debug('The frontend sent /api/settings');
|
|
324
470
|
res.json(await this.getApiSettings());
|
|
325
471
|
});
|
|
472
|
+
// Endpoint to provide plugins
|
|
326
473
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
327
474
|
this.log.debug('The frontend sent /api/plugins');
|
|
328
475
|
res.json(this.getPlugins());
|
|
329
476
|
});
|
|
477
|
+
// Endpoint to provide devices
|
|
330
478
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
331
479
|
this.log.debug('The frontend sent /api/devices');
|
|
332
480
|
const devices = await this.getDevices();
|
|
333
481
|
res.json(devices);
|
|
334
482
|
});
|
|
483
|
+
// Endpoint to view the matterbridge log
|
|
335
484
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
336
485
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
337
486
|
try {
|
|
@@ -344,6 +493,7 @@ export class Frontend extends EventEmitter {
|
|
|
344
493
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
345
494
|
}
|
|
346
495
|
});
|
|
496
|
+
// Endpoint to view the matter.js log
|
|
347
497
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
348
498
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
349
499
|
try {
|
|
@@ -356,9 +506,11 @@ export class Frontend extends EventEmitter {
|
|
|
356
506
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
357
507
|
}
|
|
358
508
|
});
|
|
509
|
+
// Endpoint to view the diagnostic.log
|
|
359
510
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
360
511
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
361
512
|
const serverNodes = [];
|
|
513
|
+
// istanbul ignore else
|
|
362
514
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
363
515
|
if (this.matterbridge.serverNode)
|
|
364
516
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -369,6 +521,7 @@ export class Frontend extends EventEmitter {
|
|
|
369
521
|
serverNodes.push(plugin.serverNode);
|
|
370
522
|
}
|
|
371
523
|
}
|
|
524
|
+
// istanbul ignore next
|
|
372
525
|
for (const device of this.matterbridge.getDevices()) {
|
|
373
526
|
if (device.serverNode)
|
|
374
527
|
serverNodes.push(device.serverNode);
|
|
@@ -391,17 +544,20 @@ export class Frontend extends EventEmitter {
|
|
|
391
544
|
values: [...serverNodes],
|
|
392
545
|
})));
|
|
393
546
|
delete Logger.destinations.diagnostic;
|
|
394
|
-
await wait(500);
|
|
547
|
+
await wait(500); // Wait for the log to be written
|
|
395
548
|
try {
|
|
396
549
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
397
550
|
res.type('text/plain');
|
|
398
551
|
res.send(data.slice(29));
|
|
399
552
|
}
|
|
400
553
|
catch (error) {
|
|
554
|
+
// istanbul ignore next
|
|
401
555
|
this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
556
|
+
// istanbul ignore next
|
|
402
557
|
res.status(500).send('Error reading diagnostic log file.');
|
|
403
558
|
}
|
|
404
559
|
});
|
|
560
|
+
// Endpoint to view the shelly log
|
|
405
561
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
406
562
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
407
563
|
try {
|
|
@@ -414,6 +570,7 @@ export class Frontend extends EventEmitter {
|
|
|
414
570
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
415
571
|
}
|
|
416
572
|
});
|
|
573
|
+
// Endpoint to download the matterbridge log
|
|
417
574
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
418
575
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
419
576
|
try {
|
|
@@ -427,12 +584,14 @@ export class Frontend extends EventEmitter {
|
|
|
427
584
|
}
|
|
428
585
|
res.type('text/plain');
|
|
429
586
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
587
|
+
/* istanbul ignore if */
|
|
430
588
|
if (error) {
|
|
431
589
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
432
590
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
433
591
|
}
|
|
434
592
|
});
|
|
435
593
|
});
|
|
594
|
+
// Endpoint to download the matter log
|
|
436
595
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
437
596
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
438
597
|
try {
|
|
@@ -446,12 +605,14 @@ export class Frontend extends EventEmitter {
|
|
|
446
605
|
}
|
|
447
606
|
res.type('text/plain');
|
|
448
607
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
608
|
+
/* istanbul ignore if */
|
|
449
609
|
if (error) {
|
|
450
610
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
451
611
|
res.status(500).send('Error downloading the matter log file');
|
|
452
612
|
}
|
|
453
613
|
});
|
|
454
614
|
});
|
|
615
|
+
// Endpoint to download the shelly log
|
|
455
616
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
456
617
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
457
618
|
try {
|
|
@@ -465,74 +626,90 @@ export class Frontend extends EventEmitter {
|
|
|
465
626
|
}
|
|
466
627
|
res.type('text/plain');
|
|
467
628
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
629
|
+
/* istanbul ignore if */
|
|
468
630
|
if (error) {
|
|
469
631
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
470
632
|
res.status(500).send('Error downloading Shelly system log file');
|
|
471
633
|
}
|
|
472
634
|
});
|
|
473
635
|
});
|
|
636
|
+
// Endpoint to download the matterbridge storage directory
|
|
474
637
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
475
638
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
476
639
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
477
640
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
641
|
+
/* istanbul ignore if */
|
|
478
642
|
if (error) {
|
|
479
643
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
480
644
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
481
645
|
}
|
|
482
646
|
});
|
|
483
647
|
});
|
|
648
|
+
// Endpoint to download the matter storage file
|
|
484
649
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
485
650
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
486
651
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
487
652
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
653
|
+
/* istanbul ignore if */
|
|
488
654
|
if (error) {
|
|
489
655
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
490
656
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
491
657
|
}
|
|
492
658
|
});
|
|
493
659
|
});
|
|
660
|
+
// Endpoint to download the matterbridge plugin directory
|
|
494
661
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
495
662
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
496
663
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
497
664
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
665
|
+
/* istanbul ignore if */
|
|
498
666
|
if (error) {
|
|
499
667
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
500
668
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
501
669
|
}
|
|
502
670
|
});
|
|
503
671
|
});
|
|
672
|
+
// Endpoint to download the matterbridge plugin config files
|
|
504
673
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
505
674
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
506
675
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
507
676
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
677
|
+
/* istanbul ignore if */
|
|
508
678
|
if (error) {
|
|
509
679
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
510
680
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
511
681
|
}
|
|
512
682
|
});
|
|
513
683
|
});
|
|
684
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
514
685
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
515
686
|
this.log.debug('The frontend sent /api/download-backup');
|
|
516
687
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
688
|
+
/* istanbul ignore if */
|
|
517
689
|
if (error) {
|
|
518
690
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
519
691
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
520
692
|
}
|
|
521
693
|
});
|
|
522
694
|
});
|
|
695
|
+
// Endpoint to upload a package
|
|
523
696
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
524
697
|
const { filename } = req.body;
|
|
525
698
|
const file = req.file;
|
|
699
|
+
/* istanbul ignore if */
|
|
526
700
|
if (!file || !filename) {
|
|
527
701
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
528
702
|
res.status(400).send('Invalid request: file and filename are required');
|
|
529
703
|
return;
|
|
530
704
|
}
|
|
531
705
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
706
|
+
// Define the path where the plugin file will be saved
|
|
532
707
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
533
708
|
try {
|
|
709
|
+
// Move the uploaded file to the specified path
|
|
534
710
|
await fs.rename(file.path, filePath);
|
|
535
711
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
712
|
+
// Install the plugin package
|
|
536
713
|
if (filename.endsWith('.tgz')) {
|
|
537
714
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
538
715
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -552,6 +729,7 @@ export class Frontend extends EventEmitter {
|
|
|
552
729
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
553
730
|
}
|
|
554
731
|
});
|
|
732
|
+
// Fallback for routing (must be the last route)
|
|
555
733
|
this.expressApp.use((req, res) => {
|
|
556
734
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
557
735
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -560,13 +738,16 @@ export class Frontend extends EventEmitter {
|
|
|
560
738
|
}
|
|
561
739
|
async stop() {
|
|
562
740
|
this.log.debug('Stopping the frontend...');
|
|
741
|
+
// Remove listeners from the express app
|
|
563
742
|
if (this.expressApp) {
|
|
564
743
|
this.expressApp.removeAllListeners();
|
|
565
744
|
this.expressApp = undefined;
|
|
566
745
|
this.log.debug('Frontend app closed successfully');
|
|
567
746
|
}
|
|
747
|
+
// Close the WebSocket server
|
|
568
748
|
if (this.webSocketServer) {
|
|
569
749
|
this.log.debug('Closing WebSocket server...');
|
|
750
|
+
// Close all active connections
|
|
570
751
|
this.webSocketServer.clients.forEach((client) => {
|
|
571
752
|
if (client.readyState === WebSocket.OPEN) {
|
|
572
753
|
client.close();
|
|
@@ -575,6 +756,7 @@ export class Frontend extends EventEmitter {
|
|
|
575
756
|
await withTimeout(new Promise((resolve) => {
|
|
576
757
|
this.webSocketServer?.close((error) => {
|
|
577
758
|
if (error) {
|
|
759
|
+
// istanbul ignore next
|
|
578
760
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
579
761
|
}
|
|
580
762
|
else {
|
|
@@ -587,11 +769,13 @@ export class Frontend extends EventEmitter {
|
|
|
587
769
|
this.webSocketServer.removeAllListeners();
|
|
588
770
|
this.webSocketServer = undefined;
|
|
589
771
|
}
|
|
772
|
+
// Close the http server
|
|
590
773
|
if (this.httpServer) {
|
|
591
774
|
this.log.debug('Closing http server...');
|
|
592
775
|
await withTimeout(new Promise((resolve) => {
|
|
593
776
|
this.httpServer?.close((error) => {
|
|
594
777
|
if (error) {
|
|
778
|
+
// istanbul ignore next
|
|
595
779
|
this.log.error(`Error closing http server: ${error}`);
|
|
596
780
|
}
|
|
597
781
|
else {
|
|
@@ -605,11 +789,13 @@ export class Frontend extends EventEmitter {
|
|
|
605
789
|
this.httpServer = undefined;
|
|
606
790
|
this.log.debug('Frontend http server closed successfully');
|
|
607
791
|
}
|
|
792
|
+
// Close the https server
|
|
608
793
|
if (this.httpsServer) {
|
|
609
794
|
this.log.debug('Closing https server...');
|
|
610
795
|
await withTimeout(new Promise((resolve) => {
|
|
611
796
|
this.httpsServer?.close((error) => {
|
|
612
797
|
if (error) {
|
|
798
|
+
// istanbul ignore next
|
|
613
799
|
this.log.error(`Error closing https server: ${error}`);
|
|
614
800
|
}
|
|
615
801
|
else {
|
|
@@ -625,6 +811,7 @@ export class Frontend extends EventEmitter {
|
|
|
625
811
|
}
|
|
626
812
|
this.log.debug('Frontend stopped successfully');
|
|
627
813
|
}
|
|
814
|
+
// Function to format bytes to KB, MB, or GB
|
|
628
815
|
formatMemoryUsage = (bytes) => {
|
|
629
816
|
if (bytes >= 1024 ** 3) {
|
|
630
817
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -636,6 +823,7 @@ export class Frontend extends EventEmitter {
|
|
|
636
823
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
637
824
|
}
|
|
638
825
|
};
|
|
826
|
+
// Function to format system uptime with only the most significant unit
|
|
639
827
|
formatOsUpTime = (seconds) => {
|
|
640
828
|
if (seconds >= 86400) {
|
|
641
829
|
const days = Math.floor(seconds / 86400);
|
|
@@ -651,7 +839,13 @@ export class Frontend extends EventEmitter {
|
|
|
651
839
|
}
|
|
652
840
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
653
841
|
};
|
|
842
|
+
/**
|
|
843
|
+
* Retrieves the api settings data.
|
|
844
|
+
*
|
|
845
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
846
|
+
*/
|
|
654
847
|
async getApiSettings() {
|
|
848
|
+
// Update the system information
|
|
655
849
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
656
850
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
657
851
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -660,6 +854,7 @@ export class Frontend extends EventEmitter {
|
|
|
660
854
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
661
855
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
662
856
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
857
|
+
// Update the matterbridge information
|
|
663
858
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
664
859
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
665
860
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -671,6 +866,7 @@ export class Frontend extends EventEmitter {
|
|
|
671
866
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
672
867
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
673
868
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
869
|
+
// Update the matterbridge information in bridge mode
|
|
674
870
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
675
871
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
676
872
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -680,6 +876,12 @@ export class Frontend extends EventEmitter {
|
|
|
680
876
|
}
|
|
681
877
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
682
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Retrieves the reachable attribute.
|
|
881
|
+
*
|
|
882
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
883
|
+
* @returns {boolean} The reachable attribute.
|
|
884
|
+
*/
|
|
683
885
|
getReachability(device) {
|
|
684
886
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
685
887
|
return false;
|
|
@@ -691,6 +893,12 @@ export class Frontend extends EventEmitter {
|
|
|
691
893
|
return true;
|
|
692
894
|
return false;
|
|
693
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Retrieves the power source attribute.
|
|
898
|
+
*
|
|
899
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
900
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
901
|
+
*/
|
|
694
902
|
getPowerSource(endpoint) {
|
|
695
903
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
696
904
|
return undefined;
|
|
@@ -706,18 +914,33 @@ export class Frontend extends EventEmitter {
|
|
|
706
914
|
}
|
|
707
915
|
return;
|
|
708
916
|
};
|
|
917
|
+
// Root endpoint
|
|
709
918
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
710
919
|
return powerSource(endpoint);
|
|
920
|
+
// Child endpoints
|
|
711
921
|
for (const child of endpoint.getChildEndpoints()) {
|
|
712
922
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
713
923
|
return powerSource(child);
|
|
714
924
|
}
|
|
715
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
928
|
+
*
|
|
929
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
930
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
931
|
+
*/
|
|
716
932
|
getMatterDataFromDevice(device) {
|
|
717
933
|
if (device.mode === 'server' && device.serverNode) {
|
|
718
934
|
return this.matterbridge.getServerNodeData(device.serverNode);
|
|
719
935
|
}
|
|
720
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* Retrieves the cluster text description from a given device.
|
|
939
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
940
|
+
*
|
|
941
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
942
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
943
|
+
*/
|
|
721
944
|
getClusterTextFromDevice(device) {
|
|
722
945
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
723
946
|
return '';
|
|
@@ -728,6 +951,7 @@ export class Frontend extends EventEmitter {
|
|
|
728
951
|
if (composed)
|
|
729
952
|
return 'Composed: ' + composed.value;
|
|
730
953
|
}
|
|
954
|
+
// istanbul ignore next cause is not reachable
|
|
731
955
|
return '';
|
|
732
956
|
};
|
|
733
957
|
const getFixedLabel = (device) => {
|
|
@@ -737,11 +961,13 @@ export class Frontend extends EventEmitter {
|
|
|
737
961
|
if (composed)
|
|
738
962
|
return 'Composed: ' + composed.value;
|
|
739
963
|
}
|
|
964
|
+
// istanbul ignore next cause is not reacheable
|
|
740
965
|
return '';
|
|
741
966
|
};
|
|
742
967
|
let attributes = '';
|
|
743
968
|
let supportedModes = [];
|
|
744
969
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
970
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
745
971
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
746
972
|
return;
|
|
747
973
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -831,11 +1057,17 @@ export class Frontend extends EventEmitter {
|
|
|
831
1057
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
832
1058
|
attributes += `${getUserLabel(device)} `;
|
|
833
1059
|
});
|
|
1060
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
834
1061
|
return attributes.trimStart().trimEnd();
|
|
835
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1065
|
+
*
|
|
1066
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1067
|
+
*/
|
|
836
1068
|
getPlugins() {
|
|
837
1069
|
if (this.matterbridge.hasCleanupStarted)
|
|
838
|
-
return [];
|
|
1070
|
+
return []; // Skip if cleanup has started
|
|
839
1071
|
const baseRegisteredPlugins = [];
|
|
840
1072
|
for (const plugin of this.matterbridge.plugins) {
|
|
841
1073
|
baseRegisteredPlugins.push({
|
|
@@ -865,6 +1097,7 @@ export class Frontend extends EventEmitter {
|
|
|
865
1097
|
schemaJson: plugin.schemaJson,
|
|
866
1098
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
867
1099
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1100
|
+
// Childbridge mode specific data
|
|
868
1101
|
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
869
1102
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
870
1103
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
@@ -874,13 +1107,21 @@ export class Frontend extends EventEmitter {
|
|
|
874
1107
|
}
|
|
875
1108
|
return baseRegisteredPlugins;
|
|
876
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Retrieves the devices from Matterbridge.
|
|
1112
|
+
*
|
|
1113
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1114
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1115
|
+
*/
|
|
877
1116
|
async getDevices(pluginName) {
|
|
878
1117
|
if (this.matterbridge.hasCleanupStarted)
|
|
879
|
-
return [];
|
|
1118
|
+
return []; // Skip if cleanup has started
|
|
880
1119
|
const devices = [];
|
|
881
1120
|
for (const device of this.matterbridge.devices.array()) {
|
|
1121
|
+
// Filter by pluginName if provided
|
|
882
1122
|
if (pluginName && pluginName !== device.plugin)
|
|
883
1123
|
continue;
|
|
1124
|
+
// Check if the device has the required properties
|
|
884
1125
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
885
1126
|
continue;
|
|
886
1127
|
devices.push({
|
|
@@ -900,22 +1141,37 @@ export class Frontend extends EventEmitter {
|
|
|
900
1141
|
}
|
|
901
1142
|
return devices;
|
|
902
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1146
|
+
*
|
|
1147
|
+
* Response for /api/clusters
|
|
1148
|
+
*
|
|
1149
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1150
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1151
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1152
|
+
*/
|
|
903
1153
|
getClusters(pluginName, endpointNumber) {
|
|
904
1154
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
905
1155
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
906
1156
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
907
1157
|
return;
|
|
908
1158
|
}
|
|
1159
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1160
|
+
// Get the device types from the main endpoint
|
|
909
1161
|
const deviceTypes = [];
|
|
910
1162
|
const clusters = [];
|
|
911
1163
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
912
1164
|
deviceTypes.push(d.deviceType);
|
|
913
1165
|
});
|
|
1166
|
+
// Get the clusters from the main endpoint
|
|
914
1167
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
915
1168
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
916
1169
|
return;
|
|
917
1170
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
918
1171
|
return;
|
|
1172
|
+
// console.log(
|
|
1173
|
+
// `${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}`,
|
|
1174
|
+
// );
|
|
919
1175
|
clusters.push({
|
|
920
1176
|
endpoint: endpoint.number.toString(),
|
|
921
1177
|
id: 'main',
|
|
@@ -928,12 +1184,19 @@ export class Frontend extends EventEmitter {
|
|
|
928
1184
|
attributeLocalValue: attributeValue,
|
|
929
1185
|
});
|
|
930
1186
|
});
|
|
1187
|
+
// Get the child endpoints
|
|
931
1188
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1189
|
+
// if (childEndpoints.length === 0) {
|
|
1190
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1191
|
+
// }
|
|
932
1192
|
childEndpoints.forEach((childEndpoint) => {
|
|
1193
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
933
1194
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
934
1195
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
935
1196
|
return;
|
|
936
1197
|
}
|
|
1198
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1199
|
+
// Get the device types of the child endpoint
|
|
937
1200
|
const deviceTypes = [];
|
|
938
1201
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
939
1202
|
deviceTypes.push(d.deviceType);
|
|
@@ -943,9 +1206,12 @@ export class Frontend extends EventEmitter {
|
|
|
943
1206
|
return;
|
|
944
1207
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
945
1208
|
return;
|
|
1209
|
+
// console.log(
|
|
1210
|
+
// `${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}`,
|
|
1211
|
+
// );
|
|
946
1212
|
clusters.push({
|
|
947
1213
|
endpoint: childEndpoint.number.toString(),
|
|
948
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1214
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
949
1215
|
deviceTypes,
|
|
950
1216
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
951
1217
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -958,6 +1224,13 @@ export class Frontend extends EventEmitter {
|
|
|
958
1224
|
});
|
|
959
1225
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
960
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1229
|
+
*
|
|
1230
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1231
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1232
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1233
|
+
*/
|
|
961
1234
|
async wsMessageHandler(client, message) {
|
|
962
1235
|
let data;
|
|
963
1236
|
try {
|
|
@@ -1004,35 +1277,48 @@ export class Frontend extends EventEmitter {
|
|
|
1004
1277
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
1005
1278
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
1006
1279
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1280
|
+
// The install comes from InstallPlugins
|
|
1007
1281
|
this.matterbridge.plugins
|
|
1008
1282
|
.add(packageName)
|
|
1009
1283
|
.then((plugin) => {
|
|
1284
|
+
// istanbul ignore next if
|
|
1010
1285
|
if (plugin) {
|
|
1286
|
+
// The plugin is not registered
|
|
1011
1287
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1288
|
+
// In childbridge mode the plugins server node is not started when added
|
|
1012
1289
|
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
1013
1290
|
this.wssSendRestartRequired(true, true);
|
|
1014
1291
|
this.matterbridge.plugins
|
|
1015
1292
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1293
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1016
1294
|
.then(() => {
|
|
1017
1295
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
1018
1296
|
this.wssSendRefreshRequired('plugins');
|
|
1019
1297
|
return;
|
|
1020
1298
|
})
|
|
1299
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1021
1300
|
.catch((_error) => {
|
|
1301
|
+
//
|
|
1022
1302
|
});
|
|
1023
1303
|
}
|
|
1024
1304
|
else {
|
|
1305
|
+
// The plugin is already registered
|
|
1025
1306
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1026
1307
|
this.wssSendRefreshRequired('plugins');
|
|
1027
1308
|
this.wssSendRestartRequired(true, true);
|
|
1028
1309
|
}
|
|
1029
1310
|
return;
|
|
1030
1311
|
})
|
|
1312
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1031
1313
|
.catch((_error) => {
|
|
1314
|
+
//
|
|
1032
1315
|
});
|
|
1033
1316
|
}
|
|
1034
1317
|
else {
|
|
1318
|
+
// The package is matterbridge
|
|
1319
|
+
// istanbul ignore next
|
|
1035
1320
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1321
|
+
// istanbul ignore next if
|
|
1036
1322
|
if (this.matterbridge.restartMode !== '') {
|
|
1037
1323
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
1038
1324
|
this.matterbridge.shutdownProcess();
|
|
@@ -1054,7 +1340,9 @@ export class Frontend extends EventEmitter {
|
|
|
1054
1340
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1055
1341
|
return;
|
|
1056
1342
|
}
|
|
1343
|
+
// The package is a plugin
|
|
1057
1344
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1345
|
+
// istanbul ignore next if
|
|
1058
1346
|
if (plugin) {
|
|
1059
1347
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1060
1348
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1062,6 +1350,7 @@ export class Frontend extends EventEmitter {
|
|
|
1062
1350
|
this.wssSendRefreshRequired('plugins');
|
|
1063
1351
|
this.wssSendRefreshRequired('devices');
|
|
1064
1352
|
}
|
|
1353
|
+
// Uninstall the package
|
|
1065
1354
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1066
1355
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1067
1356
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1102,6 +1391,7 @@ export class Frontend extends EventEmitter {
|
|
|
1102
1391
|
return;
|
|
1103
1392
|
})
|
|
1104
1393
|
.catch((_error) => {
|
|
1394
|
+
//
|
|
1105
1395
|
});
|
|
1106
1396
|
}
|
|
1107
1397
|
else {
|
|
@@ -1148,6 +1438,7 @@ export class Frontend extends EventEmitter {
|
|
|
1148
1438
|
return;
|
|
1149
1439
|
})
|
|
1150
1440
|
.catch((_error) => {
|
|
1441
|
+
//
|
|
1151
1442
|
});
|
|
1152
1443
|
}
|
|
1153
1444
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1173,6 +1464,7 @@ export class Frontend extends EventEmitter {
|
|
|
1173
1464
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1174
1465
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1175
1466
|
if (plugin.serverNode) {
|
|
1467
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1176
1468
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1177
1469
|
plugin.serverNode = undefined;
|
|
1178
1470
|
}
|
|
@@ -1183,15 +1475,16 @@ export class Frontend extends EventEmitter {
|
|
|
1183
1475
|
}
|
|
1184
1476
|
}
|
|
1185
1477
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1186
|
-
plugin.restartRequired = false;
|
|
1478
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1187
1479
|
let needRestart = 0;
|
|
1188
1480
|
for (const plugin of this.matterbridge.plugins) {
|
|
1189
1481
|
if (plugin.restartRequired)
|
|
1190
1482
|
needRestart++;
|
|
1191
1483
|
}
|
|
1192
1484
|
if (needRestart === 0) {
|
|
1193
|
-
this.wssSendRestartNotRequired(true);
|
|
1485
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1194
1486
|
}
|
|
1487
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1195
1488
|
if (plugin.serverNode)
|
|
1196
1489
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1197
1490
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1463,22 +1756,22 @@ export class Frontend extends EventEmitter {
|
|
|
1463
1756
|
if (isValidString(data.params.value, 4)) {
|
|
1464
1757
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1465
1758
|
if (data.params.value === 'Debug') {
|
|
1466
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1759
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1467
1760
|
}
|
|
1468
1761
|
else if (data.params.value === 'Info') {
|
|
1469
|
-
await this.matterbridge.setLogLevel("info");
|
|
1762
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1470
1763
|
}
|
|
1471
1764
|
else if (data.params.value === 'Notice') {
|
|
1472
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1765
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1473
1766
|
}
|
|
1474
1767
|
else if (data.params.value === 'Warn') {
|
|
1475
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1768
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1476
1769
|
}
|
|
1477
1770
|
else if (data.params.value === 'Error') {
|
|
1478
|
-
await this.matterbridge.setLogLevel("error");
|
|
1771
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1479
1772
|
}
|
|
1480
1773
|
else if (data.params.value === 'Fatal') {
|
|
1481
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1774
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1482
1775
|
}
|
|
1483
1776
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1484
1777
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1489,6 +1782,7 @@ export class Frontend extends EventEmitter {
|
|
|
1489
1782
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1490
1783
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1491
1784
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1785
|
+
// Create the file logger for matterbridge
|
|
1492
1786
|
if (data.params.value)
|
|
1493
1787
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1494
1788
|
else
|
|
@@ -1642,15 +1936,19 @@ export class Frontend extends EventEmitter {
|
|
|
1642
1936
|
return;
|
|
1643
1937
|
}
|
|
1644
1938
|
const config = plugin.configJson;
|
|
1939
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1645
1940
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1941
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1646
1942
|
if (select === 'serial')
|
|
1647
1943
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1648
1944
|
if (select === 'name')
|
|
1649
1945
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1650
1946
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1947
|
+
// Remove postfix from the serial if it exists
|
|
1651
1948
|
if (config.postfix) {
|
|
1652
1949
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1653
1950
|
}
|
|
1951
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1654
1952
|
if (isValidArray(config.whiteList, 1)) {
|
|
1655
1953
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1656
1954
|
config.whiteList.push(data.params.serial);
|
|
@@ -1659,6 +1957,7 @@ export class Frontend extends EventEmitter {
|
|
|
1659
1957
|
config.whiteList.push(data.params.name);
|
|
1660
1958
|
}
|
|
1661
1959
|
}
|
|
1960
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1662
1961
|
if (isValidArray(config.blackList, 1)) {
|
|
1663
1962
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1664
1963
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1689,7 +1988,9 @@ export class Frontend extends EventEmitter {
|
|
|
1689
1988
|
return;
|
|
1690
1989
|
}
|
|
1691
1990
|
const config = plugin.configJson;
|
|
1991
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1692
1992
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1993
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1693
1994
|
if (select === 'serial')
|
|
1694
1995
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1695
1996
|
if (select === 'name')
|
|
@@ -1698,6 +1999,7 @@ export class Frontend extends EventEmitter {
|
|
|
1698
1999
|
if (config.postfix) {
|
|
1699
2000
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1700
2001
|
}
|
|
2002
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1701
2003
|
if (isValidArray(config.whiteList, 1)) {
|
|
1702
2004
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1703
2005
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1706,6 +2008,7 @@ export class Frontend extends EventEmitter {
|
|
|
1706
2008
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1707
2009
|
}
|
|
1708
2010
|
}
|
|
2011
|
+
// Add the serial to the blackList
|
|
1709
2012
|
if (isValidArray(config.blackList)) {
|
|
1710
2013
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1711
2014
|
config.blackList.push(data.params.serial);
|
|
@@ -1739,126 +2042,253 @@ export class Frontend extends EventEmitter {
|
|
|
1739
2042
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1740
2043
|
}
|
|
1741
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2047
|
+
*
|
|
2048
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2049
|
+
* @param {string} time - The time string of the message
|
|
2050
|
+
* @param {string} name - The logger name of the message
|
|
2051
|
+
* @param {string} message - The content of the message.
|
|
2052
|
+
*
|
|
2053
|
+
* @remarks
|
|
2054
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2055
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2056
|
+
* The function sends the message to all connected clients.
|
|
2057
|
+
*/
|
|
1742
2058
|
wssSendMessage(level, time, name, message) {
|
|
1743
2059
|
if (!level || !time || !name || !message)
|
|
1744
2060
|
return;
|
|
2061
|
+
// Remove ANSI escape codes from the message
|
|
2062
|
+
// eslint-disable-next-line no-control-regex
|
|
1745
2063
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2064
|
+
// Remove leading asterisks from the message
|
|
1746
2065
|
message = message.replace(/^\*+/, '');
|
|
2066
|
+
// Replace all occurrences of \t and \n
|
|
1747
2067
|
message = message.replace(/[\t\n]/g, '');
|
|
2068
|
+
// Remove non-printable characters
|
|
2069
|
+
// eslint-disable-next-line no-control-regex
|
|
1748
2070
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2071
|
+
// Replace all occurrences of \" with "
|
|
1749
2072
|
message = message.replace(/\\"/g, '"');
|
|
2073
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1750
2074
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
2075
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1751
2076
|
const maxContinuousLength = 100;
|
|
1752
2077
|
const keepStartLength = 20;
|
|
1753
2078
|
const keepEndLength = 20;
|
|
2079
|
+
// Split the message into words
|
|
1754
2080
|
message = message
|
|
1755
2081
|
.split(' ')
|
|
1756
2082
|
.map((word) => {
|
|
2083
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1757
2084
|
if (word.length > maxContinuousLength) {
|
|
1758
2085
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1759
2086
|
}
|
|
1760
2087
|
return word;
|
|
1761
2088
|
})
|
|
1762
2089
|
.join(' ');
|
|
2090
|
+
// Send the message to all connected clients
|
|
1763
2091
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1764
2092
|
if (client.readyState === WebSocket.OPEN) {
|
|
1765
2093
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1766
2094
|
}
|
|
1767
2095
|
});
|
|
1768
2096
|
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2099
|
+
*
|
|
2100
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
2101
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2102
|
+
* possible values for changed:
|
|
2103
|
+
* - 'matterbridgeLatestVersion'
|
|
2104
|
+
* - 'matterbridgeDevVersion'
|
|
2105
|
+
* - 'matterbridgeAdvertise'
|
|
2106
|
+
* - 'online'
|
|
2107
|
+
* - 'offline'
|
|
2108
|
+
* - 'reachability'
|
|
2109
|
+
* - 'settings'
|
|
2110
|
+
* - 'plugins'
|
|
2111
|
+
* - 'pluginsRestart'
|
|
2112
|
+
* - 'devices'
|
|
2113
|
+
* - 'fabrics'
|
|
2114
|
+
* - 'sessions'
|
|
2115
|
+
* - 'matter' with param 'matter' (QRDivDevice)
|
|
2116
|
+
*/
|
|
1769
2117
|
wssSendRefreshRequired(changed = null, params = {}) {
|
|
1770
2118
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2119
|
+
// Send the message to all connected clients
|
|
1771
2120
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1772
2121
|
if (client.readyState === WebSocket.OPEN) {
|
|
1773
2122
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed, ...params } }));
|
|
1774
2123
|
}
|
|
1775
2124
|
});
|
|
1776
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2128
|
+
*
|
|
2129
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2130
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2131
|
+
*/
|
|
1777
2132
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1778
2133
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1779
2134
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1780
2135
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1781
2136
|
if (snackbar === true)
|
|
1782
2137
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2138
|
+
// Send the message to all connected clients
|
|
1783
2139
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1784
2140
|
if (client.readyState === WebSocket.OPEN) {
|
|
1785
2141
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
|
|
1786
2142
|
}
|
|
1787
2143
|
});
|
|
1788
2144
|
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2147
|
+
*
|
|
2148
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
|
|
2149
|
+
*/
|
|
1789
2150
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1790
2151
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1791
2152
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1792
2153
|
if (snackbar === true)
|
|
1793
2154
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2155
|
+
// Send the message to all connected clients
|
|
1794
2156
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1795
2157
|
if (client.readyState === WebSocket.OPEN) {
|
|
1796
2158
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
|
|
1797
2159
|
}
|
|
1798
2160
|
});
|
|
1799
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2164
|
+
*
|
|
2165
|
+
* @param {boolean} devVersion - If true, the update is for a development version.
|
|
2166
|
+
*/
|
|
1800
2167
|
wssSendUpdateRequired(devVersion = false) {
|
|
1801
2168
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1802
2169
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2170
|
+
// Send the message to all connected clients
|
|
1803
2171
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1804
2172
|
if (client.readyState === WebSocket.OPEN) {
|
|
1805
2173
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
|
|
1806
2174
|
}
|
|
1807
2175
|
});
|
|
1808
2176
|
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Sends a cpu update message to all connected clients.
|
|
2179
|
+
*
|
|
2180
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2181
|
+
*/
|
|
1809
2182
|
wssSendCpuUpdate(cpuUsage) {
|
|
1810
2183
|
if (hasParameter('debug'))
|
|
1811
2184
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2185
|
+
// Send the message to all connected clients
|
|
1812
2186
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1813
2187
|
if (client.readyState === WebSocket.OPEN) {
|
|
1814
2188
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1815
2189
|
}
|
|
1816
2190
|
});
|
|
1817
2191
|
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Sends a memory update message to all connected clients.
|
|
2194
|
+
*
|
|
2195
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2196
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2197
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2198
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2199
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2200
|
+
* @param {string} external - The external memory in bytes.
|
|
2201
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2202
|
+
*/
|
|
1818
2203
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1819
2204
|
if (hasParameter('debug'))
|
|
1820
2205
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2206
|
+
// Send the message to all connected clients
|
|
1821
2207
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1822
2208
|
if (client.readyState === WebSocket.OPEN) {
|
|
1823
2209
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1824
2210
|
}
|
|
1825
2211
|
});
|
|
1826
2212
|
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Sends an uptime update message to all connected clients.
|
|
2215
|
+
*
|
|
2216
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2217
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2218
|
+
*/
|
|
1827
2219
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1828
2220
|
if (hasParameter('debug'))
|
|
1829
2221
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2222
|
+
// Send the message to all connected clients
|
|
1830
2223
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1831
2224
|
if (client.readyState === WebSocket.OPEN) {
|
|
1832
2225
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1833
2226
|
}
|
|
1834
2227
|
});
|
|
1835
2228
|
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Sends an open snackbar message to all connected clients.
|
|
2231
|
+
*
|
|
2232
|
+
* @param {string} message - The message to send.
|
|
2233
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2234
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2235
|
+
*/
|
|
1836
2236
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1837
2237
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2238
|
+
// Send the message to all connected clients
|
|
1838
2239
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1839
2240
|
if (client.readyState === WebSocket.OPEN) {
|
|
1840
2241
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1841
2242
|
}
|
|
1842
2243
|
});
|
|
1843
2244
|
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Sends a close snackbar message to all connected clients.
|
|
2247
|
+
*
|
|
2248
|
+
* @param {string} message - The message to send.
|
|
2249
|
+
*/
|
|
1844
2250
|
wssSendCloseSnackbarMessage(message) {
|
|
1845
2251
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2252
|
+
// Send the message to all connected clients
|
|
1846
2253
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1847
2254
|
if (client.readyState === WebSocket.OPEN) {
|
|
1848
2255
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1849
2256
|
}
|
|
1850
2257
|
});
|
|
1851
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2261
|
+
*
|
|
2262
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2263
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2264
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2265
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2266
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2267
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2268
|
+
*
|
|
2269
|
+
* @remarks
|
|
2270
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2271
|
+
* with the updated attribute information.
|
|
2272
|
+
*/
|
|
1852
2273
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1853
2274
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2275
|
+
// Send the message to all connected clients
|
|
1854
2276
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1855
2277
|
if (client.readyState === WebSocket.OPEN) {
|
|
1856
2278
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1857
2279
|
}
|
|
1858
2280
|
});
|
|
1859
2281
|
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Sends a message to all connected clients.
|
|
2284
|
+
*
|
|
2285
|
+
* @param {number} id - The message id.
|
|
2286
|
+
* @param {string} method - The message method.
|
|
2287
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2288
|
+
*/
|
|
1860
2289
|
wssBroadcastMessage(id, method, params) {
|
|
1861
2290
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2291
|
+
// Send the message to all connected clients
|
|
1862
2292
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1863
2293
|
if (client.readyState === WebSocket.OPEN) {
|
|
1864
2294
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1866,3 +2296,4 @@ export class Frontend extends EventEmitter {
|
|
|
1866
2296
|
});
|
|
1867
2297
|
}
|
|
1868
2298
|
}
|
|
2299
|
+
//# sourceMappingURL=frontend.js.map
|