matterbridge 1.2.6 → 1.2.8
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 +29 -0
- package/README.md +2 -2
- package/TODO.md +0 -4
- package/dist/AirQualityCluster.d.ts +1 -1
- package/dist/AirQualityCluster.d.ts.map +1 -1
- package/dist/TvocCluster.d.ts +1 -1
- package/dist/TvocCluster.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/matterbridge.d.ts +21 -7
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +259 -107
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +50 -17
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +100 -18
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/utils.d.ts +52 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +121 -1
- package/dist/utils.js.map +1 -1
- package/frontend/build/asset-manifest.json +6 -6
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/css/{main.61f6cf42.css → main.4c325919.css} +2 -2
- package/frontend/build/static/css/main.4c325919.css.map +1 -0
- package/frontend/build/static/js/{main.6b861489.js → main.90cbbf6e.js} +3 -3
- package/frontend/build/static/js/main.90cbbf6e.js.map +1 -0
- package/matterbridge.service +1 -1
- package/package.json +13 -11
- package/dist/EveHistoryCluster.d.ts +0 -426
- package/dist/EveHistoryCluster.d.ts.map +0 -1
- package/dist/EveHistoryCluster.js +0 -162
- package/dist/EveHistoryCluster.js.map +0 -1
- package/frontend/build/static/css/main.61f6cf42.css.map +0 -1
- package/frontend/build/static/js/main.6b861489.js.map +0 -1
- /package/frontend/build/static/js/{main.6b861489.js.LICENSE.txt → main.90cbbf6e.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
24
24
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
25
|
-
import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
|
|
25
|
+
import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr, RED, GREEN } from 'node-ansi-logger';
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
27
27
|
import { promises as fs } from 'fs';
|
|
28
28
|
import { exec, spawn } from 'child_process';
|
|
@@ -30,6 +30,7 @@ import EventEmitter from 'events';
|
|
|
30
30
|
import express from 'express';
|
|
31
31
|
import os from 'os';
|
|
32
32
|
import path from 'path';
|
|
33
|
+
import WebSocket, { WebSocketServer } from 'ws';
|
|
33
34
|
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
|
|
34
35
|
import { BasicInformationCluster, BooleanStateCluster,
|
|
35
36
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -90,6 +91,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
90
91
|
nodeContext;
|
|
91
92
|
expressApp;
|
|
92
93
|
expressServer;
|
|
94
|
+
webSocketServer;
|
|
93
95
|
storageManager;
|
|
94
96
|
matterbridgeContext;
|
|
95
97
|
mattercontrollerContext;
|
|
@@ -112,13 +114,74 @@ export class Matterbridge extends EventEmitter {
|
|
|
112
114
|
static async loadInstance(initialize = false) {
|
|
113
115
|
if (!Matterbridge.instance) {
|
|
114
116
|
// eslint-disable-next-line no-console
|
|
115
|
-
|
|
117
|
+
if (hasParameter('debug'))
|
|
118
|
+
console.log(wr + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
116
119
|
Matterbridge.instance = new Matterbridge();
|
|
117
120
|
if (initialize)
|
|
118
121
|
await Matterbridge.instance.initialize();
|
|
119
122
|
}
|
|
120
123
|
return Matterbridge.instance;
|
|
121
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
127
|
+
*
|
|
128
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
129
|
+
*/
|
|
130
|
+
async initializeAsExtension(dataPath, debugEnabled) {
|
|
131
|
+
// Set the first port to use
|
|
132
|
+
this.port = 5560;
|
|
133
|
+
// Set Matterbridge logger
|
|
134
|
+
this.debugEnabled = debugEnabled;
|
|
135
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
|
|
136
|
+
this.log.debug('Matterbridge extension is starting...');
|
|
137
|
+
// Initialize NodeStorage
|
|
138
|
+
this.matterbridgeDirectory = dataPath;
|
|
139
|
+
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
140
|
+
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
141
|
+
this.log.debug('Creating node storage context for matterbridge: matterbridge');
|
|
142
|
+
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
143
|
+
// Log system info and create .matterbridge directory
|
|
144
|
+
await this.logNodeAndSystemInfo();
|
|
145
|
+
this.matterbridgeDirectory = dataPath;
|
|
146
|
+
// Set matter.js logger level and format
|
|
147
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
148
|
+
Logger.format = Format.ANSI;
|
|
149
|
+
// Start the storage and create matterbridgeContext
|
|
150
|
+
await this.startStorage('json', path.join(this.matterbridgeDirectory, 'matterbridge.json'));
|
|
151
|
+
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
152
|
+
if (!this.storageManager || !this.matterbridgeContext)
|
|
153
|
+
return;
|
|
154
|
+
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
155
|
+
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
156
|
+
await this.matterbridgeContext.set('hardwareVersion', 0);
|
|
157
|
+
await this.matterbridgeContext.set('hardwareVersionString', '0.0.1');
|
|
158
|
+
this.createMatterServer(this.storageManager);
|
|
159
|
+
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
160
|
+
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
161
|
+
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
162
|
+
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
|
|
163
|
+
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
164
|
+
this.commissioningServer.addDevice(this.matterAggregator);
|
|
165
|
+
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
166
|
+
await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
167
|
+
this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
168
|
+
this.commissioningServer.setReachability(true);
|
|
169
|
+
this.log.debug('Starting matter server...');
|
|
170
|
+
await this.startMatterServer();
|
|
171
|
+
this.log.info('Matter server started');
|
|
172
|
+
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
176
|
+
*
|
|
177
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
178
|
+
*/
|
|
179
|
+
async closeAsExtension() {
|
|
180
|
+
// Closing matter
|
|
181
|
+
await this.stopMatter();
|
|
182
|
+
// Closing storage
|
|
183
|
+
await this.stopStorage();
|
|
184
|
+
}
|
|
122
185
|
/**
|
|
123
186
|
* Initializes the Matterbridge application.
|
|
124
187
|
*
|
|
@@ -199,72 +262,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
199
262
|
// Set matter.js logger level and format
|
|
200
263
|
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
201
264
|
Logger.format = Format.ANSI;
|
|
202
|
-
// Initialize NodeStorage
|
|
203
|
-
/*
|
|
204
|
-
this.log.debug('Creating node storage manager');
|
|
205
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
|
|
206
|
-
this.log.debug('Creating node storage context for matterbridge');
|
|
207
|
-
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
208
|
-
// Get the plugins from node storage
|
|
209
|
-
this.registeredPlugins = await this.nodeContext.get<RegisteredPlugin[]>('plugins', []);
|
|
210
|
-
for (const plugin of this.registeredPlugins) {
|
|
211
|
-
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
212
|
-
plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
|
|
213
|
-
await plugin.nodeContext.set<string>('name', plugin.name);
|
|
214
|
-
await plugin.nodeContext.set<string>('type', plugin.type);
|
|
215
|
-
await plugin.nodeContext.set<string>('path', plugin.path);
|
|
216
|
-
await plugin.nodeContext.set<string>('version', plugin.version);
|
|
217
|
-
await plugin.nodeContext.set<string>('description', plugin.description);
|
|
218
|
-
await plugin.nodeContext.set<string>('author', plugin.author);
|
|
219
|
-
}
|
|
220
|
-
*/
|
|
221
265
|
// Parse command line
|
|
222
266
|
this.parseCommandLine();
|
|
223
267
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Spawns a child process with the given command and arguments.
|
|
226
|
-
* @param command - The command to execute.
|
|
227
|
-
* @param args - The arguments to pass to the command (default: []).
|
|
228
|
-
* @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
229
|
-
*/
|
|
230
|
-
async spawnCommand(command, args = []) {
|
|
231
|
-
/*
|
|
232
|
-
npm > npm.cmd on windows
|
|
233
|
-
*/
|
|
234
|
-
if (process.platform === 'win32' && command === 'npm') {
|
|
235
|
-
command = command + '.cmd';
|
|
236
|
-
}
|
|
237
|
-
if (process.platform === 'linux' && command === 'npm') {
|
|
238
|
-
args.unshift(command);
|
|
239
|
-
command = 'sudo';
|
|
240
|
-
}
|
|
241
|
-
return new Promise((resolve, reject) => {
|
|
242
|
-
const childProcess = spawn(command, args, {
|
|
243
|
-
stdio: 'inherit',
|
|
244
|
-
});
|
|
245
|
-
childProcess.on('error', (err) => {
|
|
246
|
-
this.log.error(`Failed to start child process: ${err.message}`);
|
|
247
|
-
reject(err); // Reject the promise on error
|
|
248
|
-
});
|
|
249
|
-
childProcess.on('close', (code) => {
|
|
250
|
-
if (code === 0) {
|
|
251
|
-
this.log.info(`Child process stdio streams have closed with code ${code}`);
|
|
252
|
-
resolve();
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
this.log.error(`Child process stdio streams have closed with code ${code}`);
|
|
256
|
-
reject(new Error(`Process exited with code ${code}`));
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
// The 'exit' event might be redundant here since 'close' is also being handled
|
|
260
|
-
childProcess.on('exit', (code, signal) => {
|
|
261
|
-
this.log.info(`Child process exited with code ${code} and signal ${signal}`);
|
|
262
|
-
});
|
|
263
|
-
childProcess.on('disconnect', () => {
|
|
264
|
-
this.log.info('Child process has been disconnected from the parent');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
268
|
/**
|
|
269
269
|
* Parses the command line arguments and performs the corresponding actions.
|
|
270
270
|
* @private
|
|
@@ -272,15 +272,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
272
272
|
*/
|
|
273
273
|
async parseCommandLine() {
|
|
274
274
|
if (hasParameter('list')) {
|
|
275
|
-
this.log.info('Registered plugins
|
|
275
|
+
this.log.info('│ Registered plugins');
|
|
276
276
|
this.registeredPlugins.forEach((plugin, index) => {
|
|
277
|
-
if (index
|
|
278
|
-
this.log.info(
|
|
279
|
-
this.log.info(
|
|
277
|
+
if (index !== this.registeredPlugins.length - 1) {
|
|
278
|
+
this.log.info(`├─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${plugin.enabled ? GREEN : RED}enabled ${plugin.paired ? GREEN : RED}paired${nf}`);
|
|
279
|
+
this.log.info(`│ └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
|
|
280
280
|
}
|
|
281
281
|
else {
|
|
282
|
-
this.log.info(
|
|
283
|
-
this.log.info(
|
|
282
|
+
this.log.info(`└─┬─ plugin ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${plugin.enabled ? GREEN : RED}enabled ${plugin.paired ? GREEN : RED}paired${nf}`);
|
|
283
|
+
this.log.info(` └─ entry ${UNDERLINE}${db}${plugin.path}${UNDERLINEOFF}${db}`);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const serializedRegisteredDevices = await this.nodeContext?.get('devices', []);
|
|
287
|
+
this.log.info('│ Registered devices');
|
|
288
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
289
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
290
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
291
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
295
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
284
296
|
}
|
|
285
297
|
});
|
|
286
298
|
this.emit('shutdown');
|
|
@@ -631,6 +643,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
631
643
|
this.expressApp.removeAllListeners();
|
|
632
644
|
this.expressApp = undefined;
|
|
633
645
|
}
|
|
646
|
+
// Close the WebSocket server
|
|
647
|
+
if (this.webSocketServer) {
|
|
648
|
+
// Close all active connections
|
|
649
|
+
this.webSocketServer.clients.forEach((client) => {
|
|
650
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
651
|
+
client.close();
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
this.webSocketServer.close((error) => {
|
|
655
|
+
if (error) {
|
|
656
|
+
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
this.log.debug('WebSocket server closed successfully');
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
this.webSocketServer = undefined;
|
|
663
|
+
}
|
|
634
664
|
/*const cleanupTimeout1 =*/ setTimeout(async () => {
|
|
635
665
|
// Closing matter
|
|
636
666
|
await this.stopMatter();
|
|
@@ -641,7 +671,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
641
671
|
this.log.info('Saving registered devices...');
|
|
642
672
|
const serializedRegisteredDevices = [];
|
|
643
673
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
644
|
-
|
|
674
|
+
const serializedMatterbridgeDevice = registeredDevice.device.serialize(registeredDevice.plugin);
|
|
675
|
+
//this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
676
|
+
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
645
677
|
});
|
|
646
678
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
647
679
|
this.log.info('Saved registered devices');
|
|
@@ -677,7 +709,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
677
709
|
Matterbridge.instance = undefined;
|
|
678
710
|
this.emit('shutdown');
|
|
679
711
|
}
|
|
680
|
-
},
|
|
712
|
+
}, 2 * 1000);
|
|
681
713
|
//cleanupTimeout2.unref();
|
|
682
714
|
}, 3 * 1000);
|
|
683
715
|
//cleanupTimeout1.unref();
|
|
@@ -812,10 +844,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
812
844
|
if (plugin.addedDevices !== undefined)
|
|
813
845
|
plugin.addedDevices--;
|
|
814
846
|
}
|
|
815
|
-
//
|
|
847
|
+
// Remove the device in childbridge mode
|
|
816
848
|
if (this.bridgeMode === 'childbridge') {
|
|
817
849
|
if (plugin.type === 'AccessoryPlatform') {
|
|
818
|
-
this.log.
|
|
850
|
+
this.log.info(`Removing bridged device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}: AccessoryPlatform not supported in childbridge mode`);
|
|
819
851
|
}
|
|
820
852
|
else if (plugin.type === 'DynamicPlatform') {
|
|
821
853
|
this.registeredDevices.forEach((registeredDevice, index) => {
|
|
@@ -844,7 +876,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
844
876
|
async removeAllBridgedDevices(pluginName) {
|
|
845
877
|
const plugin = this.findPlugin(pluginName);
|
|
846
878
|
if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
|
|
847
|
-
this.log.
|
|
879
|
+
this.log.info(`Removing devices for plugin ${plg}${pluginName}${nf}: AccessoryPlatform not supported in childbridge mode`);
|
|
848
880
|
return;
|
|
849
881
|
}
|
|
850
882
|
const devicesToRemove = [];
|
|
@@ -1209,7 +1241,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1209
1241
|
// Call the default export function of the plugin, passing this MatterBridge instance
|
|
1210
1242
|
if (pluginInstance.default) {
|
|
1211
1243
|
const config = await this.loadPluginConfig(plugin);
|
|
1212
|
-
const
|
|
1244
|
+
const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
|
|
1245
|
+
//log.setCallback(this.wssSendMessage.bind(this));
|
|
1246
|
+
const platform = pluginInstance.default(this, log, config);
|
|
1213
1247
|
platform.name = packageJson.name;
|
|
1214
1248
|
platform.config = config;
|
|
1215
1249
|
plugin.name = packageJson.name;
|
|
@@ -2017,11 +2051,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2017
2051
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
2018
2052
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
2019
2053
|
// Global node_modules directory
|
|
2020
|
-
/*
|
|
2021
|
-
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
2022
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
2023
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
2024
|
-
*/
|
|
2025
2054
|
if (this.nodeContext)
|
|
2026
2055
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
2027
2056
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
@@ -2089,11 +2118,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2089
2118
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
2090
2119
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
2091
2120
|
// Matterbridge latest version
|
|
2092
|
-
/*
|
|
2093
|
-
this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
|
|
2094
|
-
this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
|
|
2095
|
-
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
2096
|
-
*/
|
|
2097
2121
|
if (this.nodeContext)
|
|
2098
2122
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
2099
2123
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
@@ -2163,15 +2187,121 @@ export class Matterbridge extends EventEmitter {
|
|
|
2163
2187
|
}));
|
|
2164
2188
|
return baseRegisteredPlugins;
|
|
2165
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Spawns a child process with the given command and arguments.
|
|
2192
|
+
* @param command - The command to execute.
|
|
2193
|
+
* @param args - The arguments to pass to the command (default: []).
|
|
2194
|
+
* @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2195
|
+
*/
|
|
2196
|
+
async spawnCommand(command, args = []) {
|
|
2197
|
+
/*
|
|
2198
|
+
npm > npm.cmd on windows
|
|
2199
|
+
cmd.exe ['dir'] on windows
|
|
2200
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2201
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2202
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2203
|
+
});
|
|
2204
|
+
*/
|
|
2205
|
+
if (process.platform === 'win32' && command === 'npm') {
|
|
2206
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2207
|
+
const argstring = 'npm ' + args.join(' ');
|
|
2208
|
+
args.splice(0, args.length, '/c', argstring);
|
|
2209
|
+
command = 'cmd.exe';
|
|
2210
|
+
}
|
|
2211
|
+
if (process.platform === 'linux' && command === 'npm' && !hasParameter('docker')) {
|
|
2212
|
+
args.unshift(command);
|
|
2213
|
+
command = 'sudo';
|
|
2214
|
+
}
|
|
2215
|
+
this.log.debug(`Spawning command ${command} with ${debugStringify(args)}`);
|
|
2216
|
+
return new Promise((resolve, reject) => {
|
|
2217
|
+
const childProcess = spawn(command, args, {
|
|
2218
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
2219
|
+
});
|
|
2220
|
+
childProcess.on('error', (err) => {
|
|
2221
|
+
this.log.error(`Failed to start child process: ${err.message}`);
|
|
2222
|
+
reject(err); // Reject the promise on error
|
|
2223
|
+
});
|
|
2224
|
+
childProcess.on('close', (code) => {
|
|
2225
|
+
if (code === 0) {
|
|
2226
|
+
this.log.debug(`Child process stdio streams have closed with code ${code}`);
|
|
2227
|
+
resolve();
|
|
2228
|
+
}
|
|
2229
|
+
else {
|
|
2230
|
+
this.log.error(`Child process stdio streams have closed with code ${code}`);
|
|
2231
|
+
reject(new Error(`Child process stdio streams have closed with code ${code}`));
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
childProcess.on('exit', (code, signal) => {
|
|
2235
|
+
if (code === 0) {
|
|
2236
|
+
this.log.debug(`Child process exited with code ${code} and signal ${signal}`);
|
|
2237
|
+
resolve();
|
|
2238
|
+
}
|
|
2239
|
+
else {
|
|
2240
|
+
this.log.error(`Child process exited with code ${code} and signal ${signal}`);
|
|
2241
|
+
reject(new Error(`Child process exited with code ${code} and signal ${signal}`));
|
|
2242
|
+
}
|
|
2243
|
+
});
|
|
2244
|
+
childProcess.on('disconnect', () => {
|
|
2245
|
+
this.log.debug('Child process has been disconnected from the parent');
|
|
2246
|
+
resolve();
|
|
2247
|
+
});
|
|
2248
|
+
if (childProcess.stdout) {
|
|
2249
|
+
childProcess.stdout.on('data', (data) => {
|
|
2250
|
+
const message = data.toString().trim();
|
|
2251
|
+
//this.log.info('\n' + message);
|
|
2252
|
+
this.wssSendMessage('spawn', 'stdout', message);
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
if (childProcess.stderr) {
|
|
2256
|
+
childProcess.stderr.on('data', (data) => {
|
|
2257
|
+
const message = data.toString().trim();
|
|
2258
|
+
//this.log.debug('\n' + message);
|
|
2259
|
+
this.wssSendMessage('spawn', 'stderr', message);
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
wssSendMessage(type, subType, message) {
|
|
2265
|
+
// Remove ANSI escape codes from the message
|
|
2266
|
+
// eslint-disable-next-line no-control-regex
|
|
2267
|
+
const cleanMessage = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2268
|
+
// Remove leading asterisks from the message
|
|
2269
|
+
const finalMessage = cleanMessage.replace(/^\*+/, '');
|
|
2270
|
+
this.webSocketServer?.clients.forEach((client) => {
|
|
2271
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2272
|
+
client.send(JSON.stringify({ type, subType, message: finalMessage }));
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2166
2276
|
/**
|
|
2167
2277
|
* Initializes the frontend of Matterbridge.
|
|
2168
2278
|
*
|
|
2169
2279
|
* @param port The port number to run the frontend server on. Default is 3000.
|
|
2170
2280
|
*/
|
|
2171
|
-
async initializeFrontend(port =
|
|
2281
|
+
async initializeFrontend(port = 8283) {
|
|
2172
2282
|
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2173
|
-
|
|
2283
|
+
// Create a WebSocket server
|
|
2284
|
+
this.webSocketServer = new WebSocketServer({ port: 8284 });
|
|
2285
|
+
this.webSocketServer.on('connection', (ws) => {
|
|
2286
|
+
this.log.debug('WebSocketServer client connected');
|
|
2287
|
+
this.log.setGlobalCallback(this.wssSendMessage.bind(this));
|
|
2288
|
+
this.log.debug('WebSocketServer activated logger callback');
|
|
2289
|
+
ws.on('message', (message) => {
|
|
2290
|
+
this.log.debug(`WebSocketServer received message => ${message}`);
|
|
2291
|
+
});
|
|
2292
|
+
ws.on('close', () => {
|
|
2293
|
+
this.log.debug('WebSocketServer client disconnected');
|
|
2294
|
+
if (this.webSocketServer?.clients.size === 0) {
|
|
2295
|
+
this.log.setGlobalCallback(undefined);
|
|
2296
|
+
this.log.debug('WebSocketServer deactivated logger callback');
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
ws.on('error', (error) => {
|
|
2300
|
+
this.log.error(`WebSocketServer error: ${error}`);
|
|
2301
|
+
});
|
|
2302
|
+
});
|
|
2174
2303
|
// Serve React build directory
|
|
2304
|
+
this.expressApp = express();
|
|
2175
2305
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2176
2306
|
// Endpoint to provide login code
|
|
2177
2307
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
@@ -2193,6 +2323,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2193
2323
|
res.json({ valid: false });
|
|
2194
2324
|
}
|
|
2195
2325
|
});
|
|
2326
|
+
// Endpoint to provide host
|
|
2327
|
+
this.expressApp.get('/api/wsshost', express.json(), async (req, res) => {
|
|
2328
|
+
this.log.debug('The frontend sent /api/wsshost');
|
|
2329
|
+
res.json({ host: os.hostname() });
|
|
2330
|
+
});
|
|
2331
|
+
// Endpoint to provide port
|
|
2332
|
+
this.expressApp.get('/api/wssport', express.json(), async (req, res) => {
|
|
2333
|
+
this.log.debug('The frontend sent /api/wssport');
|
|
2334
|
+
res.json({ port: 8284 });
|
|
2335
|
+
});
|
|
2336
|
+
// Endpoint to provide manual pairing code
|
|
2337
|
+
this.expressApp.get('/api/pairing-code', (req, res) => {
|
|
2338
|
+
this.log.debug('The frontend sent /api/pairing-code');
|
|
2339
|
+
if (!this.matterbridgeContext) {
|
|
2340
|
+
this.log.error('/api/pairing-code matterbridgeContext not found');
|
|
2341
|
+
res.json([]);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
try {
|
|
2345
|
+
const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
|
|
2346
|
+
res.json(qrData);
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
if (this.bridgeMode === 'bridge')
|
|
2350
|
+
this.log.error('qrPairingCode for /api/qr-code not found');
|
|
2351
|
+
res.json({});
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2196
2354
|
// Endpoint to provide QR pairing code
|
|
2197
2355
|
this.expressApp.get('/api/qr-code', (req, res) => {
|
|
2198
2356
|
this.log.debug('The frontend sent /api/qr-code');
|
|
@@ -2307,7 +2465,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2307
2465
|
res.status(400).json({ error: 'No command provided' });
|
|
2308
2466
|
return;
|
|
2309
2467
|
}
|
|
2310
|
-
this.log.debug(
|
|
2468
|
+
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
2311
2469
|
// Handle the command setpassword from Settings
|
|
2312
2470
|
if (command === 'setpassword') {
|
|
2313
2471
|
const password = param.slice(1, -1); // Remove the first and last characters
|
|
@@ -2345,32 +2503,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2345
2503
|
}
|
|
2346
2504
|
// Handle the command update from Header
|
|
2347
2505
|
if (command === 'update') {
|
|
2348
|
-
this.log.
|
|
2506
|
+
this.log.info('Updating matterbridge...');
|
|
2507
|
+
//this.wssSendMessage('cmd', 'update', 'Updating matterbridge');
|
|
2349
2508
|
try {
|
|
2350
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2509
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--loglevel=verbose']);
|
|
2351
2510
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
2511
|
+
//this.wssSendMessage('cmd', 'update', 'Matterbridge has been updated. Full restart required.');
|
|
2352
2512
|
}
|
|
2353
2513
|
catch (error) {
|
|
2354
2514
|
this.log.error('Error updating matterbridge');
|
|
2515
|
+
//this.wssSendMessage('cmd', 'update', 'Error updating matterbridge');
|
|
2355
2516
|
res.json({ message: 'Command received' });
|
|
2356
2517
|
return;
|
|
2357
2518
|
}
|
|
2358
2519
|
this.updateProcess();
|
|
2359
2520
|
}
|
|
2360
|
-
// Handle the command update from Header
|
|
2361
|
-
if (command === 'update') {
|
|
2362
|
-
this.log.warn(`The /api/command/${command} is not yet implemented`);
|
|
2363
|
-
}
|
|
2364
2521
|
// Handle the command installplugin from Home
|
|
2365
2522
|
if (command === 'installplugin') {
|
|
2366
2523
|
param = param.replace(/\*/g, '\\');
|
|
2367
|
-
this.log.
|
|
2524
|
+
this.log.info(`Installing plugin ${plg}${param}${db}...`);
|
|
2525
|
+
//this.wssSendMessage('cmd', 'installplugin', `Installing plugin ${param}`);
|
|
2368
2526
|
try {
|
|
2369
|
-
await this.spawnCommand('npm', ['install', '-g', param]);
|
|
2370
|
-
this.log.info(`Plugin ${plg}${param}${nf} installed.
|
|
2527
|
+
await this.spawnCommand('npm', ['install', '-g', param, '--loglevel=verbose']);
|
|
2528
|
+
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
2529
|
+
//this.wssSendMessage('cmd', 'installplugin', `Plugin ${param} installed. Full restart required.`);
|
|
2371
2530
|
}
|
|
2372
2531
|
catch (error) {
|
|
2373
2532
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2533
|
+
//this.wssSendMessage('cmd', 'installplugin', `Error installing plugin${param}`);
|
|
2374
2534
|
res.json({ message: 'Command received' });
|
|
2375
2535
|
return;
|
|
2376
2536
|
}
|
|
@@ -2563,7 +2723,6 @@ function restartProcess() {
|
|
|
2563
2723
|
}
|
|
2564
2724
|
|
|
2565
2725
|
import * as WebSocket from 'ws';
|
|
2566
|
-
const globalModulesDir = require('global-modules');
|
|
2567
2726
|
|
|
2568
2727
|
const wss = new WebSocket.Server({ port: 8080 });
|
|
2569
2728
|
|
|
@@ -2582,13 +2741,6 @@ ws.onmessage = (event) => {
|
|
|
2582
2741
|
console.log(`Received message => ${event.data}`);
|
|
2583
2742
|
};
|
|
2584
2743
|
|
|
2585
|
-
*/
|
|
2586
|
-
/*
|
|
2587
|
-
// In Matterbridge
|
|
2588
|
-
global.matterbridgeInstance = Matterbridge.loadInstance();
|
|
2589
|
-
|
|
2590
|
-
// In plugins
|
|
2591
|
-
const matterbridge = global.matterbridgeInstance;
|
|
2592
2744
|
*/
|
|
2593
2745
|
/*
|
|
2594
2746
|
npx create-react-app matterbridge-frontend
|