matterbridge 1.1.8 → 1.1.10

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +20 -4
  3. package/dist/cli.d.ts +22 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +45 -5
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.js +4 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/matterbridge.d.ts +38 -3
  10. package/dist/matterbridge.d.ts.map +1 -1
  11. package/dist/matterbridge.js +458 -72
  12. package/dist/matterbridge.js.map +1 -1
  13. package/dist/matterbridgeAccessoryPlatform.d.ts +9 -0
  14. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  15. package/dist/matterbridgeAccessoryPlatform.js +13 -0
  16. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  17. package/dist/matterbridgeDevice.d.ts +3 -0
  18. package/dist/matterbridgeDevice.d.ts.map +1 -1
  19. package/dist/matterbridgeDevice.js +137 -6
  20. package/dist/matterbridgeDevice.js.map +1 -1
  21. package/dist/matterbridgeDynamicPlatform.d.ts +9 -0
  22. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  23. package/dist/matterbridgeDynamicPlatform.js +13 -0
  24. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  25. package/frontend/build/asset-manifest.json +6 -6
  26. package/frontend/build/index.html +1 -1
  27. package/frontend/build/static/css/main.3804969f.css +2 -0
  28. package/frontend/build/static/css/main.3804969f.css.map +1 -0
  29. package/frontend/build/static/js/main.82822a11.js +3 -0
  30. package/frontend/build/static/js/main.82822a11.js.map +1 -0
  31. package/package.json +2 -1
  32. package/frontend/build/static/css/main.ff2b240c.css +0 -2
  33. package/frontend/build/static/css/main.ff2b240c.css.map +0 -1
  34. package/frontend/build/static/js/main.157c9cd1.js +0 -3
  35. package/frontend/build/static/js/main.157c9cd1.js.map +0 -1
  36. /package/frontend/build/static/js/{main.157c9cd1.js.LICENSE.txt → main.82822a11.js.LICENSE.txt} +0 -0
@@ -25,7 +25,8 @@ import { NodeStorageManager } from 'node-persist-manager';
25
25
  import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
26
26
  import { fileURLToPath, pathToFileURL } from 'url';
27
27
  import { promises as fs } from 'fs';
28
- import { execSync } from 'child_process';
28
+ import { exec, spawn } from 'child_process';
29
+ import EventEmitter from 'events';
29
30
  import express from 'express';
30
31
  import os from 'os';
31
32
  import path from 'path';
@@ -44,7 +45,7 @@ const typ = '\u001B[38;5;207m';
44
45
  /**
45
46
  * Represents the Matterbridge application.
46
47
  */
47
- export class Matterbridge {
48
+ export class Matterbridge extends EventEmitter {
48
49
  systemInformation = {
49
50
  ipv4Address: '',
50
51
  ipv6Address: '',
@@ -61,7 +62,9 @@ export class Matterbridge {
61
62
  homeDirectory = '';
62
63
  rootDirectory = '';
63
64
  matterbridgeDirectory = '';
65
+ matterbridgePluginDirectory = '';
64
66
  matterbridgeVersion = '';
67
+ matterbridgeLatestVersion = '';
65
68
  globalModulesDir = '';
66
69
  bridgeMode = '';
67
70
  debugEnabled = false;
@@ -71,7 +74,8 @@ export class Matterbridge {
71
74
  registeredDevices = [];
72
75
  nodeStorage;
73
76
  nodeContext;
74
- app;
77
+ expressApp;
78
+ expressServer;
75
79
  storageManager;
76
80
  matterbridgeContext;
77
81
  mattercontrollerContext;
@@ -81,26 +85,25 @@ export class Matterbridge {
81
85
  commissioningController;
82
86
  static instance;
83
87
  constructor() {
84
- // we load asynchroneously the instance
88
+ super();
89
+ // We load asyncronously
85
90
  }
86
91
  /**
87
92
  * Loads an instance of the Matterbridge class.
88
93
  * If an instance already exists, return that instance.
89
94
  * @returns The loaded instance of the Matterbridge class.
90
95
  */
91
- static async loadInstance(cli = false) {
92
- // eslint-disable-next-line no-console
93
- console.error('loadInstance cli:', cli);
96
+ static async loadInstance(initialize = false) {
94
97
  if (!Matterbridge.instance) {
95
98
  // eslint-disable-next-line no-console
96
- console.error('Matterbridge instance does not exists');
99
+ console.log('Matterbridge instance does not exists');
97
100
  Matterbridge.instance = new Matterbridge();
98
- if (cli)
101
+ if (initialize)
99
102
  await Matterbridge.instance.initialize();
100
103
  }
101
104
  else {
102
105
  // eslint-disable-next-line no-console
103
- console.error('Matterbridge instance already exists');
106
+ console.log('Matterbridge instance already exists');
104
107
  }
105
108
  return Matterbridge.instance;
106
109
  }
@@ -115,6 +118,10 @@ export class Matterbridge {
115
118
  * @returns A Promise that resolves when the initialization is complete.
116
119
  */
117
120
  async initialize() {
121
+ /*
122
+ const wtf = await import('wtfnode');
123
+ wtf.dump();
124
+ */
118
125
  // Display the help
119
126
  if (hasParameter('help')) {
120
127
  // eslint-disable-next-line no-console
@@ -161,17 +168,54 @@ export class Matterbridge {
161
168
  this.registeredPlugins = await this.nodeContext.get('plugins', []);
162
169
  for (const plugin of this.registeredPlugins) {
163
170
  this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
164
- plugin.nodeContext = await this.nodeStorage?.createStorage(plugin.name);
165
- await plugin.nodeContext?.set('name', plugin.name);
166
- await plugin.nodeContext?.set('type', plugin.type);
167
- await plugin.nodeContext?.set('path', plugin.path);
168
- await plugin.nodeContext?.set('version', plugin.version);
169
- await plugin.nodeContext?.set('description', plugin.description);
170
- await plugin.nodeContext?.set('author', plugin.author);
171
+ plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
172
+ await plugin.nodeContext.set('name', plugin.name);
173
+ await plugin.nodeContext.set('type', plugin.type);
174
+ await plugin.nodeContext.set('path', plugin.path);
175
+ await plugin.nodeContext.set('version', plugin.version);
176
+ await plugin.nodeContext.set('description', plugin.description);
177
+ await plugin.nodeContext.set('author', plugin.author);
171
178
  }
172
179
  // Parse command line
173
180
  this.parseCommandLine();
174
181
  }
182
+ /**
183
+ * Spawns a child process with the given command and arguments.
184
+ * @param command - The command to execute.
185
+ * @param args - The arguments to pass to the command (default: []).
186
+ * @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
187
+ */
188
+ async spawnCommand(command, args = []) {
189
+ /*
190
+ npm > npm.cmd on windows
191
+ */
192
+ return new Promise((resolve, reject) => {
193
+ const childProcess = spawn(command, args, {
194
+ stdio: 'inherit',
195
+ });
196
+ childProcess.on('error', (err) => {
197
+ this.log.error(`Failed to start child process: ${err.message}`);
198
+ reject(err); // Reject the promise on error
199
+ });
200
+ childProcess.on('close', (code) => {
201
+ if (code === 0) {
202
+ this.log.info(`Child process stdio streams have closed with code ${code}`);
203
+ resolve();
204
+ }
205
+ else {
206
+ this.log.error(`Child process stdio streams have closed with code ${code}`);
207
+ reject(new Error(`Process exited with code ${code}`));
208
+ }
209
+ });
210
+ // The 'exit' event might be redundant here since 'close' is also being handled
211
+ childProcess.on('exit', (code, signal) => {
212
+ this.log.info(`Child process exited with code ${code} and signal ${signal}`);
213
+ });
214
+ childProcess.on('disconnect', () => {
215
+ this.log.info('Child process has been disconnected from the parent');
216
+ });
217
+ });
218
+ }
175
219
  /**
176
220
  * Parses the command line arguments and performs the corresponding actions.
177
221
  * @private
@@ -190,26 +234,31 @@ export class Matterbridge {
190
234
  this.log.info(`│ └─ ${db}${plugin.path}${db}`);
191
235
  }
192
236
  });
237
+ this.emit('shutdown');
193
238
  process.exit(0);
194
239
  }
195
240
  if (getParameter('add')) {
196
241
  this.log.debug(`Registering plugin ${getParameter('add')}`);
197
242
  await this.executeCommandLine(getParameter('add'), 'add');
243
+ this.emit('shutdown');
198
244
  process.exit(0);
199
245
  }
200
246
  if (getParameter('remove')) {
201
247
  this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
202
248
  await this.executeCommandLine(getParameter('remove'), 'remove');
249
+ this.emit('shutdown');
203
250
  process.exit(0);
204
251
  }
205
252
  if (getParameter('enable')) {
206
253
  this.log.debug(`Enable plugin ${getParameter('enable')}`);
207
254
  await this.executeCommandLine(getParameter('enable'), 'enable');
255
+ this.emit('shutdown');
208
256
  process.exit(0);
209
257
  }
210
258
  if (getParameter('disable')) {
211
259
  this.log.debug(`Disable plugin ${getParameter('disable')}`);
212
260
  await this.executeCommandLine(getParameter('disable'), 'disable');
261
+ this.emit('shutdown');
213
262
  process.exit(0);
214
263
  }
215
264
  // Start the storage (we need it now for frontend and later for matterbridge)
@@ -227,8 +276,10 @@ export class Matterbridge {
227
276
  this.bridgeMode = 'bridge';
228
277
  MatterbridgeDevice.bridgeMode = 'bridge';
229
278
  for (const plugin of this.registeredPlugins) {
230
- if (!plugin.enabled)
279
+ if (!plugin.enabled) {
280
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
231
281
  continue;
282
+ }
232
283
  plugin.loaded = false;
233
284
  plugin.started = false;
234
285
  plugin.configured = false;
@@ -243,8 +294,10 @@ export class Matterbridge {
243
294
  this.bridgeMode = 'childbridge';
244
295
  MatterbridgeDevice.bridgeMode = 'childbridge';
245
296
  for (const plugin of this.registeredPlugins) {
246
- if (!plugin.enabled)
297
+ if (!plugin.enabled) {
298
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
247
299
  continue;
300
+ }
248
301
  plugin.loaded = false;
249
302
  plugin.started = false;
250
303
  plugin.configured = false;
@@ -275,7 +328,7 @@ export class Matterbridge {
275
328
  this.log.debug(`Package.json not found at ${packageJsonPath}`);
276
329
  this.log.debug(`Trying at ${this.globalModulesDir}`);
277
330
  packageJsonPath = path.join(this.globalModulesDir, pluginPath);
278
- this.log.debug(`Got ${packageJsonPath}`);
331
+ //this.log.debug(`Got ${packageJsonPath}`);
279
332
  }
280
333
  try {
281
334
  // Load the package.json of the plugin
@@ -360,7 +413,6 @@ export class Matterbridge {
360
413
  this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
361
414
  }
362
415
  }
363
- //}
364
416
  }
365
417
  catch (err) {
366
418
  this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
@@ -371,21 +423,32 @@ export class Matterbridge {
371
423
  * When either of these signals are received, the cleanup method is called with an appropriate message.
372
424
  */
373
425
  async registerSignalHandlers() {
374
- process.on('SIGINT', async () => {
426
+ process.once('SIGINT', async () => {
375
427
  await this.cleanup('SIGINT received, cleaning up...');
376
428
  });
377
- process.on('SIGTERM', async () => {
429
+ process.once('SIGTERM', async () => {
378
430
  await this.cleanup('SIGTERM received, cleaning up...');
379
431
  });
380
432
  }
433
+ /**
434
+ * Restarts the process by spawning a new process and exiting the current process.
435
+ */
436
+ async restartProcess() {
437
+ //this.log.info('Restarting still not implemented');
438
+ //return;
439
+ await this.cleanup('restarting...', true);
440
+ this.hasCleanupStarted = false;
441
+ }
381
442
  /**
382
443
  * Performs cleanup operations before shutting down Matterbridge.
383
444
  * @param message - The reason for the cleanup.
384
445
  */
385
- async cleanup(message) {
446
+ async cleanup(message, restart = false) {
386
447
  if (!this.hasCleanupStarted) {
387
448
  this.hasCleanupStarted = true;
388
449
  this.log.info(message);
450
+ process.removeAllListeners('SIGINT');
451
+ process.removeAllListeners('SIGTERM');
389
452
  // Callint the shutdown functions with a reason
390
453
  for (const plugin of this.registeredPlugins) {
391
454
  if (plugin.platform)
@@ -408,23 +471,55 @@ export class Matterbridge {
408
471
  if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') registeredDevice.device.setBridgedDeviceReachability(false);
409
472
  });
410
473
  */
411
- setTimeout(async () => {
474
+ // Close the express server
475
+ if (this.expressServer) {
476
+ this.expressServer.close();
477
+ this.expressServer = undefined;
478
+ }
479
+ // Remove listeners
480
+ if (this.expressApp) {
481
+ this.expressApp.removeAllListeners();
482
+ this.expressApp = undefined;
483
+ }
484
+ const cleanupTimeout1 = setTimeout(async () => {
412
485
  // Closing matter
413
486
  await this.stopMatter();
414
487
  // Closing storage
415
488
  await this.stopStorage();
416
489
  // Serialize registeredDevices
417
- const serializedRegisteredDevices = [];
418
- this.registeredDevices.forEach((registeredDevice) => {
419
- serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
420
- });
421
- //console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
422
- await this.nodeContext?.set('devices', serializedRegisteredDevices);
423
- setTimeout(() => {
424
- this.log.info('Cleanup completed.');
425
- process.exit(0);
490
+ if (this.nodeContext) {
491
+ this.log.info('Saving registered devices...');
492
+ const serializedRegisteredDevices = [];
493
+ this.registeredDevices.forEach((registeredDevice) => {
494
+ serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
495
+ });
496
+ //console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
497
+ await this.nodeContext.set('devices', serializedRegisteredDevices);
498
+ this.log.info('Saved registered devices');
499
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
500
+ this.nodeContext = undefined;
501
+ this.nodeStorage = undefined;
502
+ }
503
+ else {
504
+ this.log.error('Error saving registered devices: nodeContext not found!');
505
+ }
506
+ this.registeredPlugins = [];
507
+ this.registeredDevices = [];
508
+ const cleanupTimeout2 = setTimeout(async () => {
509
+ if (restart) {
510
+ this.log.info('Cleanup completed. Restarting...');
511
+ Matterbridge.instance = undefined;
512
+ this.emit('restart');
513
+ }
514
+ else {
515
+ this.log.info('Cleanup completed. Shutting down...');
516
+ Matterbridge.instance = undefined;
517
+ this.emit('shutdown');
518
+ }
426
519
  }, 2 * 1000);
520
+ cleanupTimeout2.unref();
427
521
  }, 3 * 1000);
522
+ cleanupTimeout1.unref();
428
523
  }
429
524
  }
430
525
  /**
@@ -448,29 +543,33 @@ export class Matterbridge {
448
543
  * @returns A Promise that resolves when the device is added successfully.
449
544
  */
450
545
  async addDevice(pluginName, device) {
451
- this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
546
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
547
+ this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
548
+ return;
549
+ }
550
+ this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
452
551
  // Check if the plugin is registered
453
552
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
454
553
  if (!plugin) {
455
- this.log.error(`addDevice error: device ${dev}${device.name}${nf} plugin ${plg}${pluginName}${er} not found`);
554
+ this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
456
555
  return;
457
556
  }
458
557
  // Register and add the device to matterbridge aggregator in bridge mode
459
558
  if (this.bridgeMode === 'bridge') {
460
- this.matterAggregator.addBridgedDevice(device);
559
+ this.matterAggregator?.addBridgedDevice(device);
461
560
  this.registeredDevices.push({ plugin: pluginName, device, added: true });
462
561
  if (plugin.registeredDevices !== undefined)
463
562
  plugin.registeredDevices++;
464
563
  if (plugin.addedDevices !== undefined)
465
564
  plugin.addedDevices++;
466
- this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
565
+ this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
467
566
  }
468
567
  // Only register the device in childbridge mode
469
568
  if (this.bridgeMode === 'childbridge') {
470
569
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
471
570
  if (plugin.registeredDevices !== undefined)
472
571
  plugin.registeredDevices++;
473
- this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
572
+ this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
474
573
  }
475
574
  }
476
575
  /**
@@ -480,29 +579,111 @@ export class Matterbridge {
480
579
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
481
580
  */
482
581
  async addBridgedDevice(pluginName, device) {
483
- this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
582
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
583
+ this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
584
+ return;
585
+ }
586
+ this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
484
587
  // Check if the plugin is registered
485
588
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
486
589
  if (!plugin) {
487
- this.log.error(`addBridgedDevice error: device ${dev}${device.name}${nf} plugin ${plg}${pluginName}${er} not found`);
590
+ this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
488
591
  return;
489
592
  }
490
593
  // Register and add the device to matterbridge aggregator in bridge mode
491
594
  if (this.bridgeMode === 'bridge') {
492
- this.matterAggregator.addBridgedDevice(device);
595
+ this.matterAggregator?.addBridgedDevice(device);
493
596
  this.registeredDevices.push({ plugin: pluginName, device, added: true });
494
597
  if (plugin.registeredDevices !== undefined)
495
598
  plugin.registeredDevices++;
496
599
  if (plugin.addedDevices !== undefined)
497
600
  plugin.addedDevices++;
498
- this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
601
+ this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
499
602
  }
500
603
  // Only register the device in childbridge mode
501
604
  if (this.bridgeMode === 'childbridge') {
502
605
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
503
606
  if (plugin.registeredDevices !== undefined)
504
607
  plugin.registeredDevices++;
505
- this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
608
+ this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
609
+ }
610
+ }
611
+ async removeBridgedDevice(pluginName, device) {
612
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
613
+ this.log.error(`Removing bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
614
+ return;
615
+ }
616
+ this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
617
+ // Check if the plugin is registered
618
+ const plugin = this.findPlugin(pluginName);
619
+ if (!plugin) {
620
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
621
+ return;
622
+ }
623
+ if (this.bridgeMode === 'childbridge' && !plugin.aggregator) {
624
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} aggregator not found`);
625
+ return;
626
+ }
627
+ if (this.bridgeMode === 'childbridge' && !plugin.connected) {
628
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not connected`);
629
+ return;
630
+ }
631
+ // Register and add the device to matterbridge aggregator in bridge mode
632
+ if (this.bridgeMode === 'bridge') {
633
+ this.matterAggregator.removeBridgedDevice(device);
634
+ this.registeredDevices.forEach((registeredDevice, index) => {
635
+ if (registeredDevice.device === device) {
636
+ this.registeredDevices.splice(index, 1);
637
+ return;
638
+ }
639
+ });
640
+ if (plugin.registeredDevices !== undefined)
641
+ plugin.registeredDevices--;
642
+ if (plugin.addedDevices !== undefined)
643
+ plugin.addedDevices--;
644
+ this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
645
+ }
646
+ // Only register the device in childbridge mode
647
+ if (this.bridgeMode === 'childbridge') {
648
+ if (plugin.type === 'AccessoryPlatform') {
649
+ this.log.warn(`Removing bridged device ${dev}${device.name}-${device.deviceName}${wr} for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
650
+ }
651
+ else if (plugin.type === 'DynamicPlatform') {
652
+ this.registeredDevices.forEach((registeredDevice, index) => {
653
+ if (registeredDevice.device === device) {
654
+ this.registeredDevices.splice(index, 1);
655
+ return;
656
+ }
657
+ });
658
+ plugin.aggregator.removeBridgedDevice(device);
659
+ }
660
+ if (plugin.registeredDevices !== undefined)
661
+ plugin.registeredDevices--;
662
+ if (plugin.addedDevices !== undefined)
663
+ plugin.addedDevices--;
664
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
665
+ }
666
+ }
667
+ /**
668
+ * Removes all bridged devices associated with a specific plugin.
669
+ *
670
+ * @param pluginName - The name of the plugin.
671
+ * @returns A promise that resolves when all devices have been removed.
672
+ */
673
+ async removeAllBridgedDevices(pluginName) {
674
+ const plugin = this.findPlugin(pluginName);
675
+ if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
676
+ this.log.warn(`Removing devices for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
677
+ return;
678
+ }
679
+ const devicesToRemove = [];
680
+ for (const registeredDevice of this.registeredDevices) {
681
+ if (registeredDevice.plugin === pluginName) {
682
+ devicesToRemove.push(registeredDevice);
683
+ }
684
+ }
685
+ for (const registeredDevice of devicesToRemove) {
686
+ this.removeBridgedDevice(pluginName, registeredDevice.device);
506
687
  }
507
688
  }
508
689
  /**
@@ -512,18 +693,22 @@ export class Matterbridge {
512
693
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
513
694
  */
514
695
  async startStorage(storageType, storageName) {
515
- if (!storageName.endsWith('.json')) {
516
- storageName += '.json';
517
- }
518
696
  this.log.debug(`Starting storage ${storageType} ${storageName}`);
519
697
  if (storageType === 'disk') {
520
698
  const storageDisk = new StorageBackendDisk(storageName);
521
699
  this.storageManager = new StorageManager(storageDisk);
522
700
  }
523
- if (storageType === 'json') {
701
+ else if (storageType === 'json') {
702
+ if (!storageName.endsWith('.json'))
703
+ storageName += '.json';
524
704
  const storageJson = new StorageBackendJsonFile(storageName);
525
705
  this.storageManager = new StorageManager(storageJson);
526
706
  }
707
+ else {
708
+ this.log.error(`Unsupported storage type ${storageType}`);
709
+ await this.cleanup('Unsupported storage type');
710
+ return;
711
+ }
527
712
  try {
528
713
  await this.storageManager.initialize();
529
714
  this.log.debug('Storage initialized');
@@ -533,7 +718,7 @@ export class Matterbridge {
533
718
  }
534
719
  catch (error) {
535
720
  this.log.error('Storage initialize() error!');
536
- process.exit(1);
721
+ await this.cleanup('Storage initialize() error!');
537
722
  }
538
723
  }
539
724
  /**
@@ -568,8 +753,11 @@ export class Matterbridge {
568
753
  */
569
754
  async stopStorage() {
570
755
  this.log.debug('Stopping storage');
571
- await this.storageManager.close();
756
+ await this.storageManager?.close();
572
757
  this.log.debug('Storage closed');
758
+ this.storageManager = undefined;
759
+ this.matterbridgeContext = undefined;
760
+ this.mattercontrollerContext = undefined;
573
761
  }
574
762
  async testStartMatterBridge() {
575
763
  /*
@@ -734,13 +922,27 @@ export class Matterbridge {
734
922
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
735
923
  */
736
924
  async startMatterBridge() {
925
+ if (!this.storageManager) {
926
+ this.log.error('No storage manager initialized');
927
+ await this.cleanup('No storage manager initialized');
928
+ return;
929
+ }
737
930
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
738
931
  this.createMatterServer(this.storageManager);
932
+ if (!this.matterServer) {
933
+ this.log.error('No matter server initialized');
934
+ await this.cleanup('No matter server initialized');
935
+ return;
936
+ }
739
937
  if (this.bridgeMode === 'bridge') {
740
938
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
741
- // Plugins are started and configured by callback when Matterbridge is commissioned and plugin.started is set to true
939
+ // Plugins are started and configured by callback when Matterbridge is commissioned
742
940
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
743
941
  this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
942
+ if (!this.matterbridgeContext) {
943
+ this.log.error(`Error creating storage context${er}`);
944
+ return;
945
+ }
744
946
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
745
947
  this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
746
948
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -754,17 +956,22 @@ export class Matterbridge {
754
956
  this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
755
957
  }
756
958
  if (this.bridgeMode === 'childbridge') {
757
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded is set to true and plugin.started is set to true
959
+ // Plugins are loaded and started by loadPlugin on startup
758
960
  // addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
759
961
  // or to the plugin aggregator for Dynamic Platform after the commissioning is done
760
962
  // Plugins are configured by callback when the plugin is commissioned
761
- this.registeredPlugins.forEach(async (plugin) => {
963
+ this.registeredPlugins.forEach((plugin) => {
762
964
  if (!plugin.enabled)
763
965
  return;
764
966
  // Start the interval to check if the plugins is started
765
967
  // TODO set a counter or a timeout
766
968
  this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
767
969
  const startInterval = setInterval(async () => {
970
+ if (!this.matterServer) {
971
+ this.log.error('No matter server initialized');
972
+ await this.cleanup('No matter server initialized');
973
+ return;
974
+ }
768
975
  if (!plugin.loaded || !plugin.started) {
769
976
  this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
770
977
  return;
@@ -774,7 +981,7 @@ export class Matterbridge {
774
981
  .filter((registeredDevice) => registeredDevice.plugin === plugin.name)
775
982
  .forEach((registeredDevice) => {
776
983
  if (!plugin.storageContext)
777
- plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device); // Generate serialNumber and uniqueId
984
+ plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
778
985
  if (!plugin.commissioningServer)
779
986
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
780
987
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
@@ -784,9 +991,7 @@ export class Matterbridge {
784
991
  await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
785
992
  }
786
993
  if (plugin.type === 'DynamicPlatform') {
787
- plugin.storageContext = this.createCommissioningServerContext(
788
- // Generate serialNumber and uniqueId
789
- plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
994
+ plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
790
995
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
791
996
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
792
997
  plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
@@ -840,6 +1045,11 @@ export class Matterbridge {
840
1045
  }
841
1046
  }
842
1047
  async startMatterServer() {
1048
+ if (!this.matterServer) {
1049
+ this.log.error('No matter server initialized');
1050
+ await this.cleanup('No matter server initialized');
1051
+ return;
1052
+ }
843
1053
  this.log.debug('Starting matter server');
844
1054
  await this.matterServer.start();
845
1055
  this.log.debug('Started matter server');
@@ -879,6 +1089,10 @@ export class Matterbridge {
879
1089
  * @returns The storage context for the commissioning server.
880
1090
  */
881
1091
  createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1092
+ if (!this.storageManager) {
1093
+ this.log.error('No storage manager initialized');
1094
+ process.exit(1);
1095
+ }
882
1096
  this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
883
1097
  const random = 'CS' + CryptoNode.getRandomData(8).toHex();
884
1098
  const storageContext = this.storageManager.createContext(pluginName);
@@ -1044,7 +1258,7 @@ export class Matterbridge {
1044
1258
  if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
1045
1259
  for (const registeredDevice of this.registeredDevices) {
1046
1260
  if (registeredDevice.plugin === name) {
1047
- this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1261
+ this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1048
1262
  if (!plugin.aggregator) {
1049
1263
  this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
1050
1264
  continue;
@@ -1052,7 +1266,9 @@ export class Matterbridge {
1052
1266
  plugin.aggregator.addBridgedDevice(registeredDevice.device);
1053
1267
  if (plugin.addedDevices !== undefined)
1054
1268
  plugin.addedDevices++;
1055
- this.log.info(`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}${nf} for plugin ${plg}${plugin.name}${nf}`);
1269
+ this.log.info(
1270
+ // eslint-disable-next-line max-len
1271
+ `Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} for plugin ${plg}${plugin.name}${nf}`);
1056
1272
  registeredDevice.added = true;
1057
1273
  }
1058
1274
  }
@@ -1088,6 +1304,7 @@ export class Matterbridge {
1088
1304
  createMatterServer(storageManager) {
1089
1305
  this.log.debug('Creating matter server');
1090
1306
  this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
1307
+ this.log.debug('Created matter server');
1091
1308
  }
1092
1309
  /**
1093
1310
  * Creates a Matter Aggregator.
@@ -1135,6 +1352,43 @@ export class Matterbridge {
1135
1352
  this.log.debug('Stopping matter server');
1136
1353
  await this.matterServer?.close();
1137
1354
  this.log.debug('Matter server closed');
1355
+ this.commissioningController = undefined;
1356
+ this.commissioningServer = undefined;
1357
+ this.matterAggregator = undefined;
1358
+ this.matterServer = undefined;
1359
+ }
1360
+ /**
1361
+ * Retrieves the latest version of a package from the npm registry.
1362
+ * @param packageName - The name of the package.
1363
+ * @returns A Promise that resolves to the latest version of the package.
1364
+ */
1365
+ async getLatestVersion(packageName) {
1366
+ return new Promise((resolve, reject) => {
1367
+ exec(`npm view ${packageName} version`, (error, stdout) => {
1368
+ if (error) {
1369
+ reject(error);
1370
+ }
1371
+ else {
1372
+ resolve(stdout.trim());
1373
+ }
1374
+ });
1375
+ });
1376
+ }
1377
+ /**
1378
+ * Retrieves the path to the global Node.js modules directory.
1379
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
1380
+ */
1381
+ async getGlobalNodeModules() {
1382
+ return new Promise((resolve, reject) => {
1383
+ exec('npm root -g', (error, stdout) => {
1384
+ if (error) {
1385
+ reject(error);
1386
+ }
1387
+ else {
1388
+ resolve(stdout.trim());
1389
+ }
1390
+ });
1391
+ });
1138
1392
  }
1139
1393
  /**
1140
1394
  * Logs the node and system information.
@@ -1196,7 +1450,7 @@ export class Matterbridge {
1196
1450
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
1197
1451
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
1198
1452
  // Global node_modules directory
1199
- this.globalModulesDir = execSync('npm root -g').toString().trim();
1453
+ this.globalModulesDir = await this.getGlobalNodeModules();
1200
1454
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDir}`);
1201
1455
  // Create the data directory .matterbridge in the home directory
1202
1456
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
@@ -1204,13 +1458,55 @@ export class Matterbridge {
1204
1458
  await fs.access(this.matterbridgeDirectory);
1205
1459
  }
1206
1460
  catch (err) {
1207
- await fs.mkdir(this.matterbridgeDirectory);
1461
+ if (err instanceof Error) {
1462
+ const nodeErr = err;
1463
+ if (nodeErr.code === 'ENOENT') {
1464
+ try {
1465
+ await fs.mkdir(this.matterbridgeDirectory, { recursive: true });
1466
+ this.log.info(`Created Matterbridge Directory: ${this.matterbridgeDirectory}`);
1467
+ }
1468
+ catch (err) {
1469
+ this.log.error(`Error creating directory: ${err}`);
1470
+ }
1471
+ }
1472
+ else {
1473
+ this.log.error(`Error accessing directory: ${err}`);
1474
+ }
1475
+ }
1208
1476
  }
1209
1477
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
1478
+ // Create the data directory .matterbridge in the home directory
1479
+ this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
1480
+ try {
1481
+ await fs.access(this.matterbridgePluginDirectory);
1482
+ }
1483
+ catch (err) {
1484
+ if (err instanceof Error) {
1485
+ const nodeErr = err;
1486
+ if (nodeErr.code === 'ENOENT') {
1487
+ try {
1488
+ await fs.mkdir(this.matterbridgePluginDirectory, { recursive: true });
1489
+ this.log.info(`Created Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
1490
+ }
1491
+ catch (err) {
1492
+ this.log.error(`Error creating directory: ${err}`);
1493
+ }
1494
+ }
1495
+ else {
1496
+ this.log.error(`Error accessing directory: ${err}`);
1497
+ }
1498
+ }
1499
+ }
1500
+ this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
1210
1501
  // Matterbridge version
1211
1502
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
1212
1503
  this.matterbridgeVersion = packageJson.version;
1213
1504
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1505
+ this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
1506
+ this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1507
+ if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
1508
+ this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
1509
+ }
1214
1510
  // Current working directory
1215
1511
  const currentDir = process.cwd();
1216
1512
  this.log.debug(`Current Working Directory: ${currentDir}`);
@@ -1262,14 +1558,16 @@ export class Matterbridge {
1262
1558
  */
1263
1559
  async initializeFrontend(port = 3000) {
1264
1560
  this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
1265
- this.app = express();
1561
+ this.expressApp = express();
1266
1562
  // Serve React build directory
1267
- this.app.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
1563
+ this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
1268
1564
  // Endpoint to provide QR pairing code
1269
- this.app.get('/api/qr-code', (req, res) => {
1565
+ this.expressApp.get('/api/qr-code', (req, res) => {
1270
1566
  this.log.debug('The frontend sent /api/qr-code');
1271
- if (!this.matterbridgeContext)
1272
- this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1567
+ if (!this.matterbridgeContext) {
1568
+ res.json([]);
1569
+ return;
1570
+ }
1273
1571
  try {
1274
1572
  const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
1275
1573
  res.json(qrData);
@@ -1281,17 +1579,17 @@ export class Matterbridge {
1281
1579
  }
1282
1580
  });
1283
1581
  // Endpoint to provide system information
1284
- this.app.get('/api/system-info', (req, res) => {
1582
+ this.expressApp.get('/api/system-info', (req, res) => {
1285
1583
  this.log.debug('The frontend sent /api/system-info');
1286
1584
  res.json(this.systemInformation);
1287
1585
  });
1288
1586
  // Endpoint to provide plugins
1289
- this.app.get('/api/plugins', (req, res) => {
1587
+ this.expressApp.get('/api/plugins', (req, res) => {
1290
1588
  this.log.debug('The frontend sent /api/plugins');
1291
1589
  res.json(this.getBaseRegisteredPlugins());
1292
1590
  });
1293
1591
  // Endpoint to provide devices
1294
- this.app.get('/api/devices', (req, res) => {
1592
+ this.expressApp.get('/api/devices', (req, res) => {
1295
1593
  this.log.debug('The frontend sent /api/devices');
1296
1594
  const data = [];
1297
1595
  this.registeredDevices.forEach((registeredDevice) => {
@@ -1318,7 +1616,7 @@ export class Matterbridge {
1318
1616
  res.json(data);
1319
1617
  });
1320
1618
  // Endpoint to provide the cluster servers of the devices
1321
- this.app.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
1619
+ this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
1322
1620
  const selectedPluginName = req.params.selectedPluginName;
1323
1621
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
1324
1622
  this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
@@ -1360,12 +1658,83 @@ export class Matterbridge {
1360
1658
  });
1361
1659
  res.json(data);
1362
1660
  });
1661
+ // Endpoint to receive commands
1662
+ this.expressApp.post('/api/command/:command/:param', async (req, res) => {
1663
+ const command = req.params.command;
1664
+ const param = req.params.param;
1665
+ this.log.debug(`The frontend sent /api/command/${command}/${param}`);
1666
+ if (!command) {
1667
+ res.status(400).json({ error: 'No command provided' });
1668
+ return;
1669
+ }
1670
+ this.log.info(`***Received command: ${command}:${param}`);
1671
+ // Handle the command debugLevel from Settings
1672
+ if (command === 'setloglevel') {
1673
+ if (param === 'Debug') {
1674
+ this.log.setLogDebug(true);
1675
+ this.debugEnabled = true;
1676
+ Logger.defaultLogLevel = Level.DEBUG;
1677
+ }
1678
+ else if (param === 'Info') {
1679
+ this.log.setLogDebug(false);
1680
+ this.debugEnabled = false;
1681
+ Logger.defaultLogLevel = Level.INFO;
1682
+ }
1683
+ else if (param === 'Warn') {
1684
+ this.log.setLogDebug(false);
1685
+ this.debugEnabled = false;
1686
+ Logger.defaultLogLevel = Level.WARN;
1687
+ }
1688
+ this.registeredPlugins.forEach((plugin) => {
1689
+ plugin.platform?.log.setLogDebug(this.debugEnabled);
1690
+ });
1691
+ }
1692
+ // Handle the command debugLevel from Header
1693
+ if (command === 'restart') {
1694
+ this.restartProcess();
1695
+ }
1696
+ // Handle the command update from Header
1697
+ if (command === 'update') {
1698
+ this.log.warn(`The /api/command/${command} is not yet implemented`);
1699
+ }
1700
+ // Handle the command addplugin from Header
1701
+ if (command === 'addplugin') {
1702
+ this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
1703
+ }
1704
+ // Handle the command enableplugin from Home
1705
+ if (command === 'enableplugin') {
1706
+ const plugin = this.findPlugin(param);
1707
+ if (plugin) {
1708
+ plugin.enabled = true;
1709
+ plugin.loaded = undefined;
1710
+ plugin.started = undefined;
1711
+ plugin.configured = undefined;
1712
+ plugin.connected = undefined;
1713
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1714
+ this.log.info(`Enabled plugin ${plg}${param}${nf}`);
1715
+ }
1716
+ }
1717
+ // Handle the command disableplugin from Home
1718
+ if (command === 'disableplugin') {
1719
+ const plugin = this.findPlugin(param);
1720
+ if (plugin) {
1721
+ plugin.enabled = false;
1722
+ plugin.loaded = undefined;
1723
+ plugin.started = undefined;
1724
+ plugin.configured = undefined;
1725
+ plugin.connected = undefined;
1726
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1727
+ this.log.info(`Disabled plugin ${plg}${param}${nf}`);
1728
+ }
1729
+ }
1730
+ res.json({ message: 'Command received' });
1731
+ });
1363
1732
  // Fallback for routing
1364
- this.app.get('*', (req, res) => {
1733
+ this.expressApp.get('*', (req, res) => {
1365
1734
  this.log.warn('The frontend sent *', req.url);
1366
1735
  res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
1367
1736
  });
1368
- this.app.listen(port, () => {
1737
+ this.expressServer = this.expressApp.listen(port, () => {
1369
1738
  this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
1370
1739
  });
1371
1740
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
@@ -1413,6 +1782,23 @@ export class Matterbridge {
1413
1782
  }
1414
1783
  /*
1415
1784
  TO IMPLEMENT
1785
+
1786
+ import { spawn } from 'child_process';
1787
+
1788
+ function restartProcess() {
1789
+ // Spawn a new process
1790
+ const newProcess = spawn(process.argv[0], process.argv.slice(1), {
1791
+ detached: true,
1792
+ stdio: 'inherit',
1793
+ });
1794
+
1795
+ // Unreference the new process so that the current process can exit
1796
+ newProcess.unref();
1797
+
1798
+ // Exit the current process
1799
+ process.exit();
1800
+ }
1801
+
1416
1802
  import * as WebSocket from 'ws';
1417
1803
  const globalModulesDir = require('global-modules');
1418
1804