matterbridge 1.4.2 → 1.5.0

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 (32) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README-ADVANCED.md +197 -0
  3. package/README-DEV.md +167 -0
  4. package/README.md +26 -318
  5. package/dist/matterbridge.d.ts +17 -1
  6. package/dist/matterbridge.d.ts.map +1 -1
  7. package/dist/matterbridge.js +307 -92
  8. package/dist/matterbridge.js.map +1 -1
  9. package/dist/matterbridgeTypes.d.ts +4 -0
  10. package/dist/matterbridgeTypes.d.ts.map +1 -1
  11. package/dist/pluginManager.d.ts.map +1 -1
  12. package/dist/pluginManager.js +14 -3
  13. package/dist/pluginManager.js.map +1 -1
  14. package/dist/utils/utils.d.ts +21 -1
  15. package/dist/utils/utils.d.ts.map +1 -1
  16. package/dist/utils/utils.js +95 -1
  17. package/dist/utils/utils.js.map +1 -1
  18. package/frontend/build/asset-manifest.json +8 -8
  19. package/frontend/build/index.html +1 -1
  20. package/frontend/build/static/css/main.ee3183e2.css +2 -0
  21. package/frontend/build/static/css/main.ee3183e2.css.map +1 -0
  22. package/frontend/build/static/js/453.abd36b29.chunk.js +2 -0
  23. package/frontend/build/static/js/{453.d855a71b.chunk.js.map → 453.abd36b29.chunk.js.map} +1 -1
  24. package/frontend/build/static/js/main.4c5271fd.js +3 -0
  25. package/frontend/build/static/js/main.4c5271fd.js.map +1 -0
  26. package/package.json +12 -9
  27. package/frontend/build/static/css/main.5174e68c.css +0 -2
  28. package/frontend/build/static/css/main.5174e68c.css.map +0 -1
  29. package/frontend/build/static/js/453.d855a71b.chunk.js +0 -2
  30. package/frontend/build/static/js/main.dec34964.js +0 -3
  31. package/frontend/build/static/js/main.dec34964.js.map +0 -1
  32. /package/frontend/build/static/js/{main.dec34964.js.LICENSE.txt → main.4c5271fd.js.LICENSE.txt} +0 -0
@@ -20,8 +20,7 @@
20
20
  * See the License for the specific language governing permissions and
21
21
  * limitations under the License. *
22
22
  */
23
- import { NodeStorageManager } from 'node-persist-manager';
24
- import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN, nt } from 'node-ansi-logger';
23
+ // Node.js modules
25
24
  import { fileURLToPath } from 'url';
26
25
  import { promises as fs } from 'fs';
27
26
  import { exec, spawn } from 'child_process';
@@ -32,19 +31,22 @@ import express from 'express';
32
31
  import os from 'os';
33
32
  import path from 'path';
34
33
  import WebSocket, { WebSocketServer } from 'ws';
34
+ // NodeStorage and AnsiLogger modules
35
+ import { NodeStorageManager } from 'node-persist-manager';
36
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN, nt } from 'node-ansi-logger';
35
37
  // Matterbridge
36
38
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
37
39
  import { BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster } from './cluster/BridgedDeviceBasicInformationCluster.js';
38
- import { logInterfaces, wait, waiter } from './utils/utils.js';
40
+ import { logInterfaces, wait, waiter, createZip } from './utils/utils.js';
39
41
  // @project-chip/matter-node.js
40
42
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
41
43
  import { BasicInformationCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById } from '@project-chip/matter-node.js/cluster';
42
44
  import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
43
45
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter-node.js/device';
44
- import { createFileLogger, Format, Level, Logger } from '@project-chip/matter-node.js/log';
46
+ import { Format, Level, Logger } from '@project-chip/matter-node.js/log';
45
47
  import { ManualPairingCodeCodec, QrCodeSchema } from '@project-chip/matter-node.js/schema';
46
48
  import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@project-chip/matter-node.js/storage';
47
- import { requireMinNodeVersion, getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
49
+ import { getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
48
50
  import { CryptoNode } from '@project-chip/matter-node.js/crypto';
49
51
  import { PluginManager } from './pluginManager.js';
50
52
  import { DeviceManager } from './deviceManager.js';
@@ -80,6 +82,8 @@ export class Matterbridge extends EventEmitter {
80
82
  globalModulesDirectory: '',
81
83
  matterbridgeVersion: '',
82
84
  matterbridgeLatestVersion: '',
85
+ matterbridgeQrPairingCode: undefined,
86
+ matterbridgeManualPairingCode: undefined,
83
87
  matterbridgeFabricInformations: [],
84
88
  matterbridgeSessionInformations: [],
85
89
  matterbridgePaired: false,
@@ -87,7 +91,9 @@ export class Matterbridge extends EventEmitter {
87
91
  bridgeMode: '',
88
92
  restartMode: '',
89
93
  loggerLevel: "info" /* LogLevel.INFO */,
94
+ fileLogger: false,
90
95
  matterLoggerLevel: Level.INFO,
96
+ matterFileLogger: false,
91
97
  };
92
98
  homeDirectory = '';
93
99
  rootDirectory = '';
@@ -96,14 +102,14 @@ export class Matterbridge extends EventEmitter {
96
102
  globalModulesDirectory = '';
97
103
  matterbridgeVersion = '';
98
104
  matterbridgeLatestVersion = '';
105
+ matterbridgeQrPairingCode = undefined;
106
+ matterbridgeManualPairingCode = undefined;
99
107
  matterbridgeFabricInformations = [];
100
108
  matterbridgeSessionInformations = [];
101
109
  matterbridgePaired = false;
102
110
  matterbridgeConnected = false;
103
111
  bridgeMode = '';
104
112
  restartMode = '';
105
- // public debugEnabled = false;
106
- // public loggerLevel: LogLevel = LogLevel.INFO;
107
113
  profile = getParameter('profile');
108
114
  log;
109
115
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
@@ -119,6 +125,7 @@ export class Matterbridge extends EventEmitter {
119
125
  hasCleanupStarted = false;
120
126
  initialized = false;
121
127
  execRunningCount = 0;
128
+ startMatterInterval;
122
129
  cleanupTimeout1;
123
130
  cleanupTimeout2;
124
131
  checkUpdateInterval;
@@ -208,12 +215,19 @@ export class Matterbridge extends EventEmitter {
208
215
  // Set the matterbridge directory
209
216
  this.homeDirectory = os.homedir();
210
217
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
211
- // Create the file logger for matterbridge
212
- if (hasParameter('filelogger'))
213
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
218
+ // Initialize nodeStorage and nodeContext
219
+ // this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
220
+ this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
221
+ // this.log.debug('Creating node storage context for matterbridge');
222
+ this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
214
223
  // Create matterbridge logger
215
224
  this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
216
- // Set matterbridge logger level
225
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
226
+ if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
227
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
228
+ this.matterbridgeInformation.fileLogger = true;
229
+ }
230
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
217
231
  if (hasParameter('logger')) {
218
232
  const level = getParameter('logger');
219
233
  if (level === 'debug') {
@@ -240,11 +254,12 @@ export class Matterbridge extends EventEmitter {
240
254
  }
241
255
  }
242
256
  else {
243
- this.log.logLevel = "info" /* LogLevel.INFO */;
257
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
244
258
  }
245
259
  MatterbridgeDevice.logLevel = this.log.logLevel;
246
260
  this.log.debug('Matterbridge is starting...');
247
- // Set matter.js logger level, format and logger
261
+ this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
262
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
248
263
  if (hasParameter('matterlogger')) {
249
264
  const level = getParameter('matterlogger');
250
265
  if (level === 'debug') {
@@ -271,28 +286,19 @@ export class Matterbridge extends EventEmitter {
271
286
  }
272
287
  }
273
288
  else {
274
- Logger.defaultLogLevel = Level.INFO;
289
+ Logger.defaultLogLevel = await this.nodeContext.get('matterLogLevel', Level.INFO);
275
290
  }
276
291
  Logger.format = Format.ANSI;
277
292
  Logger.setLogger('default', this.createMatterLogger());
278
- // Create the file logger for matter.js
279
- if (hasParameter('matterfilelogger')) {
280
- try {
281
- await fs.unlink(path.join(this.matterbridgeDirectory, this.matterLoggerFile));
282
- }
283
- catch (error) {
284
- this.log.debug(`Error unlinking the log file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
285
- }
286
- Logger.addLogger('filelogger', await createFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile)), {
293
+ // Create the file logger for matter.js (context: matterFileLog)
294
+ if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
295
+ this.matterbridgeInformation.matterFileLogger = true;
296
+ Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
287
297
  defaultLogLevel: Level.DEBUG,
288
298
  logFormat: Format.PLAIN,
289
299
  });
290
300
  }
291
- // Initialize nodeStorage and nodeContext
292
- this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
293
- this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
294
- this.log.debug('Creating node storage context for matterbridge');
295
- this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
301
+ this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
296
302
  // Initialize PluginManager
297
303
  this.plugins = new PluginManager(this);
298
304
  await this.plugins.loadFromStorage();
@@ -301,8 +307,9 @@ export class Matterbridge extends EventEmitter {
301
307
  // Get the plugins from node storage and create the plugins node storage contexts
302
308
  for (const plugin of this.plugins) {
303
309
  const packageJson = await this.plugins.parse(plugin);
304
- if (packageJson === null) {
310
+ if (packageJson === null && !hasParameter('add')) {
305
311
  // Try to reinstall the plugin from npm (for Docker pull and external plugins)
312
+ // We don't do this when the add parameter is set because we shut down the process after adding the plugin
306
313
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
307
314
  try {
308
315
  await this.spawnCommand('npm', ['install', '-g', plugin.name]);
@@ -327,10 +334,20 @@ export class Matterbridge extends EventEmitter {
327
334
  }
328
335
  // Log system info and create .matterbridge directory
329
336
  await this.logNodeAndSystemInfo();
330
- this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ${hasParameter('bridge') ? 'mode bridge' : ''}${hasParameter('childbridge') ? 'mode childbridge' : ''}${hasParameter('controller') ? 'mode controller' : ''} ` +
331
- `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch} `);
337
+ this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
338
+ `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
339
+ `${hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge') ? 'mode childbridge ' : ''}` +
340
+ `${hasParameter('controller') ? 'mode controller ' : ''}` +
341
+ `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
342
+ `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
332
343
  // Check node version and throw error
333
- requireMinNodeVersion(18);
344
+ const minNodeVersion = 18;
345
+ const nodeVersion = process.versions.node;
346
+ const versionMajor = parseInt(nodeVersion.split('.')[0]);
347
+ if (versionMajor < minNodeVersion) {
348
+ this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
349
+ throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
350
+ }
334
351
  // Register SIGINT SIGTERM signal handlers
335
352
  this.registerSignalHandlers();
336
353
  // Parse command line
@@ -351,6 +368,8 @@ export class Matterbridge extends EventEmitter {
351
368
  - childbridge: start Matterbridge in childbridge mode
352
369
  - port [port]: start the commissioning server on the given port (default 5540)
353
370
  - mdnsinterface [name]: set the interface to use for the matter server mdnsInterface (default all interfaces)
371
+ - ipv4address [address]: set the ipv4 interface address to use for the matter listener (default all interfaces)
372
+ - ipv6address [address]: set the ipv6 interface address to use for the matter listener (default all interfaces)
354
373
  - frontend [port]: start the frontend on the given port (default 8283)
355
374
  - logger: set the matterbridge logger level: debug | info | notice | warn | error | fatal (default info)
356
375
  - matterlogger: set the matter.js logger level: debug | info | notice | warn | error | fatal (default info)
@@ -511,8 +530,8 @@ export class Matterbridge extends EventEmitter {
511
530
  }
512
531
  }, 60 * 60 * 1000);
513
532
  if (hasParameter('test')) {
514
- this.bridgeMode = 'childbridge';
515
- MatterbridgeDevice.bridgeMode = 'childbridge';
533
+ this.bridgeMode = 'bridge';
534
+ MatterbridgeDevice.bridgeMode = 'bridge';
516
535
  await this.startTest();
517
536
  return;
518
537
  }
@@ -521,7 +540,12 @@ export class Matterbridge extends EventEmitter {
521
540
  await this.startController();
522
541
  return;
523
542
  }
524
- if (hasParameter('bridge')) {
543
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
544
+ if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
545
+ this.log.info('Setting default matterbridge start mode to bridge');
546
+ await this.nodeContext?.set('bridgeMode', 'bridge');
547
+ }
548
+ if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
525
549
  this.bridgeMode = 'bridge';
526
550
  MatterbridgeDevice.bridgeMode = 'bridge';
527
551
  if (!this.storageManager)
@@ -562,12 +586,12 @@ export class Matterbridge extends EventEmitter {
562
586
  plugin.addedDevices = undefined;
563
587
  plugin.qrPairingCode = undefined;
564
588
  plugin.manualPairingCode = undefined;
565
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
589
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
566
590
  }
567
591
  await this.startBridge();
568
592
  return;
569
593
  }
570
- if (hasParameter('childbridge')) {
594
+ if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
571
595
  this.bridgeMode = 'childbridge';
572
596
  MatterbridgeDevice.bridgeMode = 'childbridge';
573
597
  if (!this.storageManager)
@@ -598,9 +622,9 @@ export class Matterbridge extends EventEmitter {
598
622
  plugin.connected = false;
599
623
  plugin.registeredDevices = undefined;
600
624
  plugin.addedDevices = undefined;
601
- plugin.qrPairingCode = (await plugin.nodeContext?.get('qrPairingCode', undefined)) ?? undefined;
602
- plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
603
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
625
+ plugin.qrPairingCode = undefined;
626
+ plugin.manualPairingCode = undefined;
627
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
604
628
  }
605
629
  await this.startChildbridge();
606
630
  return;
@@ -847,7 +871,10 @@ export class Matterbridge extends EventEmitter {
847
871
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
848
872
  await this.nodeContext?.set('matterbridgeLatestVersion', this.matterbridgeLatestVersion);
849
873
  if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
850
- this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
874
+ this.log.notice(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}. Latest version: ${this.matterbridgeLatestVersion}.`);
875
+ }
876
+ else {
877
+ this.log.debug(`Matterbridge is up to date. Current version: ${this.matterbridgeVersion}. Latest version: ${this.matterbridgeLatestVersion}.`);
851
878
  }
852
879
  })
853
880
  .catch((error) => {
@@ -870,15 +897,20 @@ export class Matterbridge extends EventEmitter {
870
897
  .then(async (latestVersion) => {
871
898
  plugin.latestVersion = latestVersion;
872
899
  if (plugin.version !== latestVersion)
873
- this.log.warn(`The plugin ${plg}${plugin.name}${wr} is out of date. Current version: ${plugin.version}, Latest version: ${latestVersion}`);
900
+ this.log.notice(`The plugin ${plg}${plugin.name}${nt} is out of date. Current version: ${plugin.version}. Latest version: ${latestVersion}.`);
874
901
  else
875
- this.log.info(`The plugin ${plg}${plugin.name}${nf} is up to date. Current version: ${plugin.version}, Latest version: ${latestVersion}`);
902
+ this.log.debug(`The plugin ${plg}${plugin.name}${db} is up to date. Current version: ${plugin.version}. Latest version: ${latestVersion}.`);
876
903
  })
877
904
  .catch((error) => {
878
- this.log.error(`Error getting ${plugin.name} latest version: ${error.message}`);
905
+ this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
879
906
  // error.stack && this.log.debug(error.stack);
880
907
  });
881
908
  }
909
+ /**
910
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
911
+ *
912
+ * @returns {Function} The MatterLogger function.
913
+ */
882
914
  createMatterLogger() {
883
915
  const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
884
916
  return (_level, formattedLog) => {
@@ -910,6 +942,63 @@ export class Matterbridge extends EventEmitter {
910
942
  }
911
943
  };
912
944
  }
945
+ /**
946
+ * Creates a Matter File Logger.
947
+ *
948
+ * @param {string} filePath - The path to the log file.
949
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
950
+ * @returns {Function} - A function that logs formatted messages to the log file.
951
+ */
952
+ async createMatterFileLogger(filePath, unlink = false) {
953
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
954
+ let fileSize = 0;
955
+ if (unlink) {
956
+ try {
957
+ await fs.unlink(filePath);
958
+ }
959
+ catch (error) {
960
+ this.log.debug(`Error unlinking the log file ${CYAN}${filePath}${db}: ${error instanceof Error ? error.message : error}`);
961
+ }
962
+ }
963
+ return async (_level, formattedLog) => {
964
+ if (fileSize > 100000000)
965
+ return;
966
+ fileSize += formattedLog.length;
967
+ if (fileSize > 100000000) {
968
+ await fs.appendFile(filePath, `Logging on file has been stoppped because the file size is greater then 100MB.` + os.EOL);
969
+ return;
970
+ }
971
+ const now = new Date();
972
+ const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`;
973
+ const message = formattedLog.slice(24);
974
+ const parts = message.split(' ');
975
+ const logger = parts[1];
976
+ const finalMessage = parts.slice(2).join(' ') + os.EOL;
977
+ switch (_level) {
978
+ case Level.DEBUG:
979
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [debug] ${finalMessage}`);
980
+ break;
981
+ case Level.INFO:
982
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [info] ${finalMessage}`);
983
+ break;
984
+ case Level.NOTICE:
985
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [notice] ${finalMessage}`);
986
+ break;
987
+ case Level.WARN:
988
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [warn] ${finalMessage}`);
989
+ break;
990
+ case Level.ERROR:
991
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [error] ${finalMessage}`);
992
+ break;
993
+ case Level.FATAL:
994
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] [fatal] ${finalMessage}`);
995
+ break;
996
+ default:
997
+ await fs.appendFile(filePath, `[${timestamp}] [${logger}] ${finalMessage}`);
998
+ break;
999
+ }
1000
+ };
1001
+ }
913
1002
  /**
914
1003
  * Update matterbridge and cleanup.
915
1004
  */
@@ -962,11 +1051,18 @@ export class Matterbridge extends EventEmitter {
962
1051
  this.log.info(message);
963
1052
  // Deregisters the SIGINT and SIGTERM signal handlers
964
1053
  this.deregisterSignalHandlers();
1054
+ // Clear the start matter interval
1055
+ if (this.startMatterInterval) {
1056
+ clearInterval(this.startMatterInterval);
1057
+ this.startMatterInterval = undefined;
1058
+ this.log.debug('Start matter interval cleared');
1059
+ }
965
1060
  // Clear the check update interval
966
- if (this.checkUpdateInterval)
1061
+ if (this.checkUpdateInterval) {
967
1062
  clearInterval(this.checkUpdateInterval);
968
- this.checkUpdateInterval = undefined;
969
- this.log.debug('Check update interval cleared');
1063
+ this.checkUpdateInterval = undefined;
1064
+ this.log.debug('Check update interval cleared');
1065
+ }
970
1066
  // Clear the configure timeout
971
1067
  if (this.configureTimeout) {
972
1068
  clearTimeout(this.configureTimeout);
@@ -1033,6 +1129,14 @@ export class Matterbridge extends EventEmitter {
1033
1129
  await this.stopMatterServer();
1034
1130
  // Closing matter storage
1035
1131
  await this.stopMatterStorage();
1132
+ // Remove the matterfilelogger
1133
+ try {
1134
+ Logger.removeLogger('matterfilelogger');
1135
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1136
+ }
1137
+ catch (error) {
1138
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1139
+ }
1036
1140
  // Serialize registeredDevices
1037
1141
  if (this.nodeStorage && this.nodeContext) {
1038
1142
  this.log.info('Saving registered devices...');
@@ -1192,8 +1296,10 @@ export class Matterbridge extends EventEmitter {
1192
1296
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1193
1297
  return;
1194
1298
  }
1195
- device.setBridgedDeviceReachability(false);
1196
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1299
+ if (device.number !== undefined) {
1300
+ device.setBridgedDeviceReachability(false);
1301
+ device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1302
+ }
1197
1303
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1198
1304
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1199
1305
  this.matterAggregator?.removeBridgedDevice(device);
@@ -1234,8 +1340,10 @@ export class Matterbridge extends EventEmitter {
1234
1340
  return;
1235
1341
  }
1236
1342
  });
1237
- device.setBridgedDeviceReachability(false);
1238
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1343
+ if (device.number !== undefined) {
1344
+ device.setBridgedDeviceReachability(false);
1345
+ device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1346
+ }
1239
1347
  plugin.aggregator.removeBridgedDevice(device);
1240
1348
  }
1241
1349
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
@@ -1284,13 +1392,14 @@ export class Matterbridge extends EventEmitter {
1284
1392
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1285
1393
  this.log.debug('***Starting startMatterInterval in bridge mode');
1286
1394
  let failCount = 0;
1287
- const startMatterInterval = setInterval(async () => {
1395
+ this.startMatterInterval = setInterval(async () => {
1288
1396
  for (const plugin of this.plugins) {
1289
1397
  // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1290
1398
  if (!plugin.enabled)
1291
1399
  continue;
1292
1400
  if (plugin.error) {
1293
- clearInterval(startMatterInterval);
1401
+ clearInterval(this.startMatterInterval);
1402
+ this.startMatterInterval = undefined;
1294
1403
  this.log.debug('***Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1295
1404
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1296
1405
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
@@ -1307,7 +1416,8 @@ export class Matterbridge extends EventEmitter {
1307
1416
  return;
1308
1417
  }
1309
1418
  }
1310
- clearInterval(startMatterInterval);
1419
+ clearInterval(this.startMatterInterval);
1420
+ this.startMatterInterval = undefined;
1311
1421
  this.log.debug('***Cleared startMatterInterval interval for Matterbridge');
1312
1422
  await this.startMatterServer();
1313
1423
  this.log.notice('Matter server started');
@@ -1348,14 +1458,15 @@ export class Matterbridge extends EventEmitter {
1348
1458
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1349
1459
  this.log.debug('***Starting start matter interval in childbridge mode...');
1350
1460
  let failCount = 0;
1351
- const startMatterInterval = setInterval(async () => {
1461
+ this.startMatterInterval = setInterval(async () => {
1352
1462
  let allStarted = true;
1353
1463
  for (const plugin of this.plugins) {
1354
1464
  // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1355
1465
  if (!plugin.enabled)
1356
1466
  continue;
1357
1467
  if (plugin.error) {
1358
- clearInterval(startMatterInterval);
1468
+ clearInterval(this.startMatterInterval);
1469
+ this.startMatterInterval = undefined;
1359
1470
  this.log.debug('***Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1360
1471
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1361
1472
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
@@ -1375,7 +1486,8 @@ export class Matterbridge extends EventEmitter {
1375
1486
  }
1376
1487
  if (!allStarted)
1377
1488
  return;
1378
- clearInterval(startMatterInterval);
1489
+ clearInterval(this.startMatterInterval);
1490
+ this.startMatterInterval = undefined;
1379
1491
  this.log.debug('***Cleared startMatterInterval interval in childbridge mode');
1380
1492
  await this.startMatterServer();
1381
1493
  this.log.notice('Matter server started');
@@ -1800,8 +1912,8 @@ export class Matterbridge extends EventEmitter {
1800
1912
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${this.port} passcode ${this.passcode} discriminator ${this.discriminator}`);
1801
1913
  const commissioningServer = new CommissioningServer({
1802
1914
  port: this.port++,
1803
- // listeningAddressIpv4
1804
- // listeningAddressIpv6
1915
+ listeningAddressIpv4: getParameter('ipv4address'),
1916
+ listeningAddressIpv6: getParameter('ipv6address'),
1805
1917
  passcode: this.passcode,
1806
1918
  discriminator: this.discriminator,
1807
1919
  deviceName,
@@ -2028,16 +2140,15 @@ export class Matterbridge extends EventEmitter {
2028
2140
  }
2029
2141
  if (!commissioningServer.isCommissioned()) {
2030
2142
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2031
- await storageContext.set('qrPairingCode', qrPairingCode);
2032
- await storageContext.set('manualPairingCode', manualPairingCode);
2033
- await nodeContext.set('qrPairingCode', qrPairingCode);
2034
- await nodeContext.set('manualPairingCode', manualPairingCode);
2035
2143
  const QrCode = new QrCodeSchema();
2036
2144
  this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
2037
2145
  // eslint-disable-next-line no-console
2038
- console.log(`${QrCode.encode(qrPairingCode)}\n`);
2039
- this.log.info(`${plg}${pluginName}${nf}\n\nqrPairingCode: ${qrPairingCode}\n\nManual pairing code: ${manualPairingCode}\n`);
2146
+ if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
2147
+ console.log(`${QrCode.encode(qrPairingCode)}\n`);
2148
+ this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2040
2149
  if (pluginName === 'Matterbridge') {
2150
+ this.matterbridgeQrPairingCode = qrPairingCode;
2151
+ this.matterbridgeManualPairingCode = manualPairingCode;
2041
2152
  this.matterbridgeFabricInformations = [];
2042
2153
  this.matterbridgeSessionInformations = [];
2043
2154
  this.matterbridgePaired = false;
@@ -2522,40 +2633,21 @@ export class Matterbridge extends EventEmitter {
2522
2633
  // Endpoint to provide settings
2523
2634
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2524
2635
  this.log.debug('The frontend sent /api/settings');
2525
- if (!this.matterbridgeContext) {
2526
- this.log.error('/api/settings matterbridgeContext not found');
2527
- res.json({});
2528
- return;
2529
- }
2530
- let qrPairingCode = '';
2531
- let manualPairingCode = '';
2532
- try {
2533
- qrPairingCode = await this.matterbridgeContext.get('qrPairingCode');
2534
- manualPairingCode = await this.matterbridgeContext.get('manualPairingCode');
2535
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2536
- }
2537
- catch (error) {
2538
- if (this.bridgeMode === 'bridge')
2539
- this.log.warn('pairingCodes for /api/settings not found');
2540
- }
2541
2636
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
2542
2637
  this.matterbridgeInformation.restartMode = this.restartMode;
2543
2638
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
2544
2639
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
2545
2640
  this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
2546
2641
  this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
2642
+ this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
2643
+ this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
2547
2644
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2548
2645
  this.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridgeSessionInformations;
2549
2646
  if (this.profile)
2550
2647
  this.matterbridgeInformation.profile = this.profile;
2551
- const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2648
+ // const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2649
+ const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2552
2650
  // this.log.debug('Response:', debugStringify(response));
2553
- /*
2554
- if (this.webSocketServer && this.webSocketServer.clients.size > 0 && !AnsiLogger.getGlobalCallback()) {
2555
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), LogLevel.DEBUG);
2556
- this.log.debug('WebSocketServer logger global callback added');
2557
- }
2558
- */
2559
2651
  res.json(response);
2560
2652
  });
2561
2653
  // Endpoint to provide plugins
@@ -2681,9 +2773,16 @@ export class Matterbridge extends EventEmitter {
2681
2773
  res.status(500).send('Error reading log file');
2682
2774
  }
2683
2775
  });
2684
- // Endpoint to download the log
2685
- this.expressApp.get('/api/download-mblog', (req, res) => {
2776
+ // Endpoint to download the matterbridge log
2777
+ this.expressApp.get('/api/download-mblog', async (req, res) => {
2686
2778
  this.log.debug('The frontend sent /api/download-mblog');
2779
+ try {
2780
+ await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
2781
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2782
+ }
2783
+ catch (error) {
2784
+ fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
2785
+ }
2687
2786
  res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
2688
2787
  if (error) {
2689
2788
  this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -2691,9 +2790,16 @@ export class Matterbridge extends EventEmitter {
2691
2790
  }
2692
2791
  });
2693
2792
  });
2694
- // Endpoint to download the log
2695
- this.expressApp.get('/api/download-mjlog', (req, res) => {
2793
+ // Endpoint to download the matter log
2794
+ this.expressApp.get('/api/download-mjlog', async (req, res) => {
2696
2795
  this.log.debug('The frontend sent /api/download-mjlog');
2796
+ try {
2797
+ await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
2798
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2799
+ }
2800
+ catch (error) {
2801
+ fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
2802
+ }
2697
2803
  res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
2698
2804
  if (error) {
2699
2805
  this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -2701,6 +2807,60 @@ export class Matterbridge extends EventEmitter {
2701
2807
  }
2702
2808
  });
2703
2809
  });
2810
+ // Endpoint to download the matter storage file
2811
+ this.expressApp.get('/api/download-mjstorage', (req, res) => {
2812
+ this.log.debug('The frontend sent /api/download-mjstorage');
2813
+ res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
2814
+ if (error) {
2815
+ this.log.error(`Error downloading log file ${this.matterStorageName}: ${error instanceof Error ? error.message : error}`);
2816
+ res.status(500).send('Error downloading the matter storage file');
2817
+ }
2818
+ });
2819
+ });
2820
+ // Endpoint to download the matterbridge storage directory
2821
+ this.expressApp.get('/api/download-mbstorage', async (req, res) => {
2822
+ this.log.debug('The frontend sent /api/download-mbstorage');
2823
+ await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
2824
+ res.download(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), `matterbridge.${this.nodeStorageName}.zip`, (error) => {
2825
+ if (error) {
2826
+ this.log.error(`Error downloading file ${`matterbridge.${this.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
2827
+ res.status(500).send('Error downloading the matterbridge storage file');
2828
+ }
2829
+ });
2830
+ });
2831
+ // Endpoint to download the matterbridge plugin directory
2832
+ this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
2833
+ this.log.debug('The frontend sent /api/download-pluginstorage');
2834
+ await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
2835
+ res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
2836
+ if (error) {
2837
+ this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
2838
+ res.status(500).send('Error downloading the matterbridge plugin storage file');
2839
+ }
2840
+ });
2841
+ });
2842
+ // Endpoint to download the matterbridge plugin config files
2843
+ this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
2844
+ this.log.debug('The frontend sent /api/download-pluginconfig');
2845
+ await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
2846
+ // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
2847
+ res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
2848
+ if (error) {
2849
+ this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
2850
+ res.status(500).send('Error downloading the matterbridge plugin storage file');
2851
+ }
2852
+ });
2853
+ });
2854
+ // Endpoint to download the matterbridge plugin config files
2855
+ this.expressApp.get('/api/download-backup', async (req, res) => {
2856
+ this.log.debug('The frontend sent /api/download-backup');
2857
+ res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
2858
+ if (error) {
2859
+ this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
2860
+ res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
2861
+ }
2862
+ });
2863
+ });
2704
2864
  // Endpoint to receive commands
2705
2865
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2706
2866
  const command = req.params.command;
@@ -2719,6 +2879,21 @@ export class Matterbridge extends EventEmitter {
2719
2879
  res.json({ message: 'Command received' });
2720
2880
  return;
2721
2881
  }
2882
+ // Handle the command setbridgemode from Settings
2883
+ if (command === 'setbridgemode') {
2884
+ this.log.debug(`setbridgemode: ${param}`);
2885
+ await this.nodeContext?.set('bridgeMode', param);
2886
+ res.json({ message: 'Command received' });
2887
+ return;
2888
+ }
2889
+ // Handle the command backup from Settings
2890
+ if (command === 'backup') {
2891
+ this.log.notice(`Prepairing the backup...`);
2892
+ await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
2893
+ this.log.notice(`Backup ready to be downloaded.`);
2894
+ res.json({ message: 'Command received' });
2895
+ return;
2896
+ }
2722
2897
  // Handle the command setmbloglevel from Settings
2723
2898
  if (command === 'setmbloglevel') {
2724
2899
  this.log.debug('Matterbridge log level:', param);
@@ -2777,6 +2952,46 @@ export class Matterbridge extends EventEmitter {
2777
2952
  res.json({ message: 'Command received' });
2778
2953
  return;
2779
2954
  }
2955
+ // Handle the command setmbloglevel from Settings
2956
+ if (command === 'setmblogfile') {
2957
+ this.log.debug('Matterbridge file log:', param);
2958
+ this.matterbridgeInformation.fileLogger = param === 'true';
2959
+ await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
2960
+ // Create the file logger for matterbridge
2961
+ if (param === 'true')
2962
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
2963
+ else
2964
+ AnsiLogger.setGlobalLogfile(undefined);
2965
+ res.json({ message: 'Command received' });
2966
+ return;
2967
+ }
2968
+ // Handle the command setmbloglevel from Settings
2969
+ if (command === 'setmjlogfile') {
2970
+ this.log.debug('Matter file log:', param);
2971
+ this.matterbridgeInformation.matterFileLogger = param === 'true';
2972
+ await this.nodeContext?.set('matterFileLog', param === 'true');
2973
+ if (param === 'true') {
2974
+ try {
2975
+ Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
2976
+ defaultLogLevel: Level.DEBUG,
2977
+ logFormat: Format.PLAIN,
2978
+ });
2979
+ }
2980
+ catch (error) {
2981
+ this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
2982
+ }
2983
+ }
2984
+ else {
2985
+ try {
2986
+ Logger.removeLogger('matterfilelogger');
2987
+ }
2988
+ catch (error) {
2989
+ this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
2990
+ }
2991
+ }
2992
+ res.json({ message: 'Command received' });
2993
+ return;
2994
+ }
2780
2995
  // Handle the command unregister from Settings
2781
2996
  if (command === 'unregister') {
2782
2997
  await this.unregisterAndShutdownProcess();