@matterbridge/core 3.7.2-dev-20260331-ac050d8 → 3.7.2-dev-20260402-c12a10e

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.
@@ -0,0 +1,18 @@
1
+ import { type SharedMatterbridge } from '@matterbridge/types';
2
+ import express from 'express';
3
+ import type { Frontend } from './frontend.js';
4
+ export declare class BackendExpress {
5
+ private debug;
6
+ private verbose;
7
+ private expressApp;
8
+ private log;
9
+ private backend;
10
+ private matterbridge;
11
+ private readonly server;
12
+ constructor(matterbridge: SharedMatterbridge, backend: Frontend);
13
+ destroy(): void;
14
+ private broadcastMsgHandler;
15
+ private validateReq;
16
+ start(): Promise<express.Application | undefined>;
17
+ stop(): Promise<express.Application | undefined>;
18
+ }
@@ -0,0 +1,445 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { BroadcastServer } from '@matterbridge/thread';
4
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_BACKUP_FILE, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, MATTERBRIDGE_PLUGIN_CONFIG_FILE, MATTERBRIDGE_PLUGIN_STORAGE_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
5
+ import { hasParameter } from '@matterbridge/utils/cli';
6
+ import { getErrorMessage } from '@matterbridge/utils/error';
7
+ import { formatBytes } from '@matterbridge/utils/format';
8
+ import express from 'express';
9
+ import multer from 'multer';
10
+ import { AnsiLogger, er, nf } from 'node-ansi-logger';
11
+ if (hasParameter('loader'))
12
+ console.log('\u001B[32mBackendExpress loaded.\u001B[40;0m');
13
+ export class BackendExpress {
14
+ debug;
15
+ verbose;
16
+ expressApp;
17
+ log;
18
+ backend;
19
+ matterbridge;
20
+ server;
21
+ constructor(matterbridge, backend) {
22
+ this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-frontend') || hasParameter('verbose-frontend');
23
+ this.verbose = hasParameter('verbose') || hasParameter('verbose-frontend');
24
+ this.backend = backend;
25
+ this.matterbridge = matterbridge;
26
+ this.log = new AnsiLogger({
27
+ logName: 'BackendExpress',
28
+ logNameColor: '\x1b[38;5;97m',
29
+ logTimestampFormat: 4,
30
+ logLevel: this.debug ? "debug" : "info",
31
+ });
32
+ this.server = new BroadcastServer('frontend', this.log);
33
+ this.server.on('broadcast_message', this.broadcastMsgHandler.bind(this));
34
+ }
35
+ destroy() {
36
+ this.server.off('broadcast_message', this.broadcastMsgHandler.bind(this));
37
+ this.server.close();
38
+ }
39
+ async broadcastMsgHandler(msg) {
40
+ if (this.server.isWorkerRequest(msg)) {
41
+ switch (msg.type) {
42
+ case 'get_log_level':
43
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
44
+ break;
45
+ case 'set_log_level':
46
+ this.log.logLevel = msg.params.logLevel;
47
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ validateReq(req, res) {
53
+ if (req.ip && !this.backend.authClients.has(req.ip)) {
54
+ this.log.warn(`Warning blocked unauthorized access request ${req.originalUrl ?? req.url} from ${req.ip}`);
55
+ res.status(401).json({ error: 'Unauthorized' });
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ async start() {
61
+ this.log.debug('Starting express application...');
62
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
63
+ const upload = multer({ dest: uploadDir });
64
+ this.expressApp = express();
65
+ if (this.verbose) {
66
+ this.expressApp.use((req, res, next) => {
67
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
68
+ next();
69
+ });
70
+ }
71
+ this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build')));
72
+ this.expressApp.post('/api/login', express.json(), async (req, res) => {
73
+ const { password } = req.body;
74
+ this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
75
+ if (this.backend.storedPassword === '' || password === this.backend.storedPassword) {
76
+ this.log.debug('/api/login password valid');
77
+ res.json({ valid: true });
78
+ if (req.ip)
79
+ this.backend.authClients.add(req.ip);
80
+ }
81
+ else {
82
+ this.log.warn('/api/login error wrong password');
83
+ res.json({ valid: false });
84
+ }
85
+ });
86
+ this.expressApp.get('/health', (req, res) => {
87
+ this.log.debug('Express received /health');
88
+ const healthStatus = {
89
+ status: 'ok',
90
+ uptime: process.uptime(),
91
+ timestamp: new Date().toISOString(),
92
+ };
93
+ res.status(200).json(healthStatus);
94
+ });
95
+ this.expressApp.get('/memory', async (req, res) => {
96
+ this.log.debug('Express received /memory');
97
+ const memoryUsageRaw = process.memoryUsage();
98
+ const memoryUsage = {
99
+ rss: formatBytes(memoryUsageRaw.rss),
100
+ heapTotal: formatBytes(memoryUsageRaw.heapTotal),
101
+ heapUsed: formatBytes(memoryUsageRaw.heapUsed),
102
+ external: formatBytes(memoryUsageRaw.external),
103
+ arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
104
+ };
105
+ const { default: v8 } = await import('node:v8');
106
+ const heapStatsRaw = v8.getHeapStatistics();
107
+ const heapSpacesRaw = v8.getHeapSpaceStatistics();
108
+ const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
109
+ const heapSpaces = heapSpacesRaw.map((space) => ({
110
+ ...space,
111
+ space_size: formatBytes(space.space_size),
112
+ space_used_size: formatBytes(space.space_used_size),
113
+ space_available_size: formatBytes(space.space_available_size),
114
+ physical_space_size: formatBytes(space.physical_space_size),
115
+ }));
116
+ const { createRequire } = await import('node:module');
117
+ const require = createRequire(import.meta.url);
118
+ const cjsModules = Object.keys(require.cache).sort();
119
+ const memoryReport = {
120
+ memoryUsage,
121
+ heapStats,
122
+ heapSpaces,
123
+ cjsModules,
124
+ };
125
+ res.status(200).json(memoryReport);
126
+ });
127
+ this.expressApp.get('/api/settings', async (req, res) => {
128
+ this.log.debug('The frontend sent /api/settings');
129
+ if (!this.validateReq(req, res))
130
+ return;
131
+ res.json(this.backend.getApiSettings());
132
+ });
133
+ this.expressApp.get('/api/plugins', async (req, res) => {
134
+ this.log.debug('The frontend sent /api/plugins');
135
+ if (!this.validateReq(req, res))
136
+ return;
137
+ res.json(this.backend.getApiPlugins());
138
+ });
139
+ this.expressApp.get('/api/devices', async (req, res) => {
140
+ this.log.debug('The frontend sent /api/devices');
141
+ if (!this.validateReq(req, res))
142
+ return;
143
+ res.json(this.backend.getApiDevices());
144
+ });
145
+ this.expressApp.get('/api/view-mblog', async (req, res) => {
146
+ this.log.debug('The frontend sent /api/view-mblog');
147
+ if (!this.validateReq(req, res))
148
+ return;
149
+ try {
150
+ const fs = await import('node:fs');
151
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
152
+ res.type('text/plain; charset=utf-8');
153
+ res.send(data);
154
+ }
155
+ catch (error) {
156
+ this.log.error(`Error reading matterbridge log file ${MATTERBRIDGE_LOGGER_FILE}: ${getErrorMessage(error)}`);
157
+ res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
158
+ }
159
+ });
160
+ this.expressApp.get('/api/download-mblog', async (req, res) => {
161
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
162
+ if (!this.validateReq(req, res))
163
+ return;
164
+ const fs = await import('node:fs');
165
+ try {
166
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
167
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
168
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
169
+ }
170
+ catch (error) {
171
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
172
+ this.log.debug(`Error in /api/download-mblog: ${getErrorMessage(error)}`);
173
+ }
174
+ res.type('text/plain; charset=utf-8');
175
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
176
+ if (error) {
177
+ this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${getErrorMessage(error)}`);
178
+ res.status(500).send('Error downloading the matterbridge log file');
179
+ }
180
+ else {
181
+ this.log.debug(`Matterbridge log file ${MATTERBRIDGE_LOGGER_FILE} downloaded successfully`);
182
+ }
183
+ });
184
+ });
185
+ this.expressApp.get('/api/view-mjlog', async (req, res) => {
186
+ this.log.debug('The frontend sent /api/view-mjlog');
187
+ if (!this.validateReq(req, res))
188
+ return;
189
+ try {
190
+ const fs = await import('node:fs');
191
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
192
+ res.type('text/plain; charset=utf-8');
193
+ res.send(data);
194
+ }
195
+ catch (error) {
196
+ this.log.error(`Error reading matter log file ${MATTER_LOGGER_FILE}: ${getErrorMessage(error)}`);
197
+ res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
198
+ }
199
+ });
200
+ this.expressApp.get('/api/download-mjlog', async (req, res) => {
201
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE)}`);
202
+ if (!this.validateReq(req, res))
203
+ return;
204
+ const fs = await import('node:fs');
205
+ try {
206
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
207
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
208
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
209
+ }
210
+ catch (error) {
211
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
212
+ this.log.debug(`Error in /api/download-mjlog: ${getErrorMessage(error)}`);
213
+ }
214
+ res.type('text/plain; charset=utf-8');
215
+ res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
216
+ if (error) {
217
+ this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${getErrorMessage(error)}`);
218
+ res.status(500).send('Error downloading the matter log file');
219
+ }
220
+ else {
221
+ this.log.debug(`Matter log file ${MATTER_LOGGER_FILE} downloaded successfully`);
222
+ }
223
+ });
224
+ });
225
+ this.expressApp.get('/api/view-diagnostic', async (req, res) => {
226
+ this.log.debug('The frontend sent /api/view-diagnostic');
227
+ if (!this.validateReq(req, res))
228
+ return;
229
+ await this.backend.generateDiagnostic();
230
+ try {
231
+ const fs = await import('node:fs');
232
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
233
+ res.type('text/plain; charset=utf-8');
234
+ res.send(data.slice(29));
235
+ }
236
+ catch (error) {
237
+ this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${getErrorMessage(error)}`);
238
+ res.status(500).send('Error reading diagnostic log file.');
239
+ }
240
+ });
241
+ this.expressApp.get('/api/download-diagnostic', async (req, res) => {
242
+ this.log.debug(`The frontend sent /api/download-diagnostic`);
243
+ if (!this.validateReq(req, res))
244
+ return;
245
+ await this.backend.generateDiagnostic();
246
+ try {
247
+ const fs = await import('node:fs');
248
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), fs.constants.F_OK);
249
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
250
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
251
+ }
252
+ catch (error) {
253
+ this.log.debug(`Error in /api/download-diagnostic: ${getErrorMessage(error)}`);
254
+ }
255
+ res.type('text/plain; charset=utf-8');
256
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
257
+ if (error) {
258
+ this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${getErrorMessage(error)}`);
259
+ res.status(500).send('Error downloading the diagnostic log file');
260
+ }
261
+ else {
262
+ this.log.debug(`Diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE} downloaded successfully`);
263
+ }
264
+ });
265
+ });
266
+ this.expressApp.get('/api/viewhistory', async (req, res) => {
267
+ this.log.debug('The frontend sent /api/viewhistory');
268
+ if (!this.validateReq(req, res))
269
+ return;
270
+ try {
271
+ const fs = await import('node:fs');
272
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
273
+ res.type('text/html; charset=utf-8');
274
+ res.send(data);
275
+ }
276
+ catch (error) {
277
+ this.log.error(`Error in /api/viewhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${getErrorMessage(error)}`);
278
+ res.status(500).send('Error reading history file.');
279
+ }
280
+ });
281
+ this.expressApp.get('/api/downloadhistory', async (req, res) => {
282
+ this.log.debug(`The frontend sent /api/downloadhistory`);
283
+ if (!this.validateReq(req, res))
284
+ return;
285
+ try {
286
+ const fs = await import('node:fs');
287
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), fs.constants.F_OK);
288
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
289
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
290
+ res.type('text/html; charset=utf-8');
291
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
292
+ if (error) {
293
+ this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${getErrorMessage(error)}`);
294
+ res.status(500).send('Error downloading history file');
295
+ }
296
+ else {
297
+ this.log.debug(`History file ${MATTERBRIDGE_HISTORY_FILE} downloaded successfully`);
298
+ }
299
+ });
300
+ }
301
+ catch (error) {
302
+ this.log.error(`Error in /api/downloadhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${getErrorMessage(error)}`);
303
+ res.status(500).send('Error reading history file.');
304
+ }
305
+ });
306
+ this.expressApp.get('/api/download-backup', async (req, res) => {
307
+ this.log.debug('The frontend sent /api/download-backup');
308
+ if (!this.validateReq(req, res))
309
+ return;
310
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_BACKUP_FILE), MATTERBRIDGE_BACKUP_FILE, (error) => {
311
+ this.backend.wssSendCloseSnackbarMessage('Creating matterbridge backup...');
312
+ if (error) {
313
+ this.log.error(`Error downloading file ${MATTERBRIDGE_BACKUP_FILE}: ${getErrorMessage(error)}`);
314
+ res.status(500).send(`Error downloading the matterbridge backup file.`);
315
+ }
316
+ else {
317
+ this.log.debug(`Backup ${MATTERBRIDGE_BACKUP_FILE} downloaded successfully`);
318
+ }
319
+ });
320
+ });
321
+ this.expressApp.get('/api/download-mbstorage', async (req, res) => {
322
+ this.log.debug('The frontend sent /api/download-mbstorage');
323
+ if (!this.validateReq(req, res))
324
+ return;
325
+ res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
326
+ this.backend.wssSendCloseSnackbarMessage('Creating matterbridge storage backup...');
327
+ if (error) {
328
+ this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${getErrorMessage(error)}`);
329
+ res.status(500).send('Error downloading the matterbridge storage file');
330
+ }
331
+ else {
332
+ this.log.debug(`Matterbridge storage matterbridge.${NODE_STORAGE_DIR}.zip downloaded successfully`);
333
+ }
334
+ });
335
+ });
336
+ this.expressApp.get('/api/download-mjstorage', async (req, res) => {
337
+ this.log.debug('The frontend sent /api/download-mjstorage');
338
+ if (!this.validateReq(req, res))
339
+ return;
340
+ res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_DIR}.zip`), `matterbridge.${MATTER_STORAGE_DIR}.zip`, (error) => {
341
+ this.backend.wssSendCloseSnackbarMessage('Creating matter storage backup...');
342
+ if (error) {
343
+ this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_DIR}.zip: ${getErrorMessage(error)}`);
344
+ res.status(500).send('Error downloading the matter storage file');
345
+ }
346
+ else {
347
+ this.log.debug(`Matter storage matterbridge.${MATTER_STORAGE_DIR}.zip downloaded successfully`);
348
+ }
349
+ });
350
+ });
351
+ this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
352
+ this.log.debug('The frontend sent /api/download-pluginstorage');
353
+ if (!this.validateReq(req, res))
354
+ return;
355
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_PLUGIN_STORAGE_FILE), MATTERBRIDGE_PLUGIN_STORAGE_FILE, (error) => {
356
+ this.backend.wssSendCloseSnackbarMessage('Creating plugin backup...');
357
+ if (error) {
358
+ this.log.error(`Error downloading file ${MATTERBRIDGE_PLUGIN_STORAGE_FILE}: ${getErrorMessage(error)}`);
359
+ res.status(500).send('Error downloading the matterbridge plugin storage file');
360
+ }
361
+ else {
362
+ this.log.debug(`Plugin storage ${MATTERBRIDGE_PLUGIN_STORAGE_FILE} downloaded successfully`);
363
+ }
364
+ });
365
+ });
366
+ this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
367
+ this.log.debug('The frontend sent /api/download-pluginconfig');
368
+ if (!this.validateReq(req, res))
369
+ return;
370
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_PLUGIN_CONFIG_FILE), MATTERBRIDGE_PLUGIN_CONFIG_FILE, (error) => {
371
+ this.backend.wssSendCloseSnackbarMessage('Creating config backup...');
372
+ if (error) {
373
+ this.log.error(`Error downloading file ${MATTERBRIDGE_PLUGIN_CONFIG_FILE}: ${getErrorMessage(error)}`);
374
+ res.status(500).send('Error downloading the matterbridge plugin config file');
375
+ }
376
+ else {
377
+ this.log.debug(`Plugin config ${MATTERBRIDGE_PLUGIN_CONFIG_FILE} downloaded successfully`);
378
+ }
379
+ });
380
+ });
381
+ this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
382
+ this.log.debug('The frontend sent /api/uploadpackage');
383
+ if (!this.validateReq(req, res))
384
+ return;
385
+ const { filename } = req.body;
386
+ const file = req.file;
387
+ if (!file || !filename) {
388
+ this.log.error(`uploadpackage: invalid request: file and filename are required`);
389
+ res.status(400).send('Invalid request: file and filename are required');
390
+ return;
391
+ }
392
+ this.backend.wssSendSnackbarMessage(`Installing package ${filename}...`, 0);
393
+ const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
394
+ try {
395
+ const fs = await import('node:fs');
396
+ await fs.promises.rename(file.path, filePath);
397
+ this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
398
+ if (filename.endsWith('.tgz')) {
399
+ this.server.request({
400
+ type: 'manager_run',
401
+ src: 'frontend',
402
+ dst: 'manager',
403
+ params: {
404
+ name: 'SpawnCommand',
405
+ workerData: {
406
+ threadName: 'SpawnCommand',
407
+ command: 'npm',
408
+ args: ['install', '-g', filePath, '--omit=dev', '--verbose'],
409
+ packageCommand: 'install',
410
+ packageName: filename,
411
+ },
412
+ },
413
+ });
414
+ }
415
+ res.send(`File ${filename} uploaded successfully`);
416
+ }
417
+ catch (err) {
418
+ this.log.error(`Error uploading or installing plugin package file ${plg}${filename}${er}:`, err);
419
+ this.backend.wssSendCloseSnackbarMessage(`Installing package ${filename}...`);
420
+ this.backend.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
421
+ res.status(500).send(`Error uploading or installing plugin package ${filename}`);
422
+ }
423
+ });
424
+ this.expressApp.use((req, res) => {
425
+ const filePath = path.resolve(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build');
426
+ this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
427
+ res.sendFile('index.html', { root: filePath });
428
+ });
429
+ this.log.debug('Express application started');
430
+ return this.expressApp;
431
+ }
432
+ async stop() {
433
+ if (this.expressApp) {
434
+ this.log.debug('Stopping express application...');
435
+ this.expressApp.removeAllListeners();
436
+ this.expressApp = undefined;
437
+ this.log.debug('Frontend app closed successfully');
438
+ this.log.debug('Express application stopped');
439
+ }
440
+ else {
441
+ this.log.debug('Express application is not running');
442
+ }
443
+ return this.expressApp;
444
+ }
445
+ }
@@ -0,0 +1,35 @@
1
+ import type { EndpointNumber } from '@matter/types/datatype';
2
+ import type { ApiMatter, RefreshRequiredChanged, SharedMatterbridge, WsMessageBroadcast } from '@matterbridge/types';
3
+ import { WebSocketServer } from 'ws';
4
+ import type { Frontend } from './frontend.js';
5
+ export declare class BackendsWsServer {
6
+ private debug;
7
+ private verbose;
8
+ private webSocketServer;
9
+ private log;
10
+ private backend;
11
+ private matterbridge;
12
+ private readonly server;
13
+ constructor(matterbridge: SharedMatterbridge, backend: Frontend);
14
+ destroy(): void;
15
+ private broadcastMsgHandler;
16
+ start(): Promise<WebSocketServer | undefined>;
17
+ stop(): Promise<WebSocketServer | undefined>;
18
+ hasActiveClients(): boolean;
19
+ private wsMessageHandler;
20
+ wssBroadcastMessage(msg: WsMessageBroadcast): void;
21
+ wssSendLogMessage(level: string, time: string, name: string, message: string): void;
22
+ wssSendRefreshRequired(changed: RefreshRequiredChanged, params?: {
23
+ matter?: ApiMatter;
24
+ lock?: string;
25
+ }): void;
26
+ wssSendRestartRequired(snackbar?: boolean, fixed?: boolean): void;
27
+ wssSendRestartNotRequired(snackbar?: boolean): void;
28
+ wssSendUpdateRequired(devVersion?: boolean): void;
29
+ wssSendCpuUpdate(cpuUsage: number, processCpuUsage: number): void;
30
+ wssSendMemoryUpdate(totalMemory: string, freeMemory: string, rss: string, heapTotal: string, heapUsed: string, external: string, arrayBuffers: string): void;
31
+ wssSendUptimeUpdate(systemUptime: string, processUptime: string): void;
32
+ wssSendSnackbarMessage(message: string, timeout?: number, severity?: 'info' | 'warning' | 'error' | 'success'): void;
33
+ wssSendCloseSnackbarMessage(message: string): void;
34
+ wssSendAttributeChangedMessage(plugin: string, serialNumber: string, uniqueId: string, number: EndpointNumber, id: string, cluster: string, attribute: string, value: number | string | boolean | null): void;
35
+ }