@matterbridge/core 3.7.2-dev-20260330-bb55c39 → 3.7.2-dev-20260402-c12a10e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/frontend.js CHANGED
@@ -1,5 +1,3 @@
1
- if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
- console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
3
1
  import EventEmitter from 'node:events';
4
2
  import os from 'node:os';
5
3
  import path from 'node:path';
@@ -10,7 +8,7 @@ import { PowerSource } from '@matter/types/clusters/power-source';
10
8
  import { CommissioningOptions } from '@matter/types/commissioning';
11
9
  import { FabricIndex } from '@matter/types/datatype';
12
10
  import { BroadcastServer } from '@matterbridge/thread/server';
13
- import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
11
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
14
12
  import { getParameter, hasParameter } from '@matterbridge/utils/cli';
15
13
  import { inspectError } from '@matterbridge/utils/error';
16
14
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
@@ -20,6 +18,8 @@ import { AnsiLogger, CYAN, db, debugStringify, er, nf, nt, rs, stringify, UNDERL
20
18
  import { cliEmitter, lastOsCpuUsage, lastProcessCpuUsage } from './cliEmitter.js';
21
19
  import { generateHistoryPage } from './cliHistory.js';
22
20
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
21
+ if (hasParameter('loader'))
22
+ console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
23
23
  export class Frontend extends EventEmitter {
24
24
  matterbridge;
25
25
  log;
@@ -35,6 +35,13 @@ export class Frontend extends EventEmitter {
35
35
  serverFetchTimeout = 2000;
36
36
  debug = hasParameter('debug') || hasParameter('verbose');
37
37
  verbose = hasParameter('verbose');
38
+ readOnly = hasParameter('readonly') || hasParameter('shelly');
39
+ shellyBoard = hasParameter('shelly');
40
+ shellySysUpdate = false;
41
+ shellyMainUpdate = false;
42
+ restartRequired = false;
43
+ fixedRestartRequired = false;
44
+ updateRequired = false;
38
45
  constructor(matterbridge) {
39
46
  super();
40
47
  this.matterbridge = matterbridge;
@@ -45,13 +52,13 @@ export class Frontend extends EventEmitter {
45
52
  logLevel: hasParameter('debug') ? "debug" : "info",
46
53
  });
47
54
  this.server = new BroadcastServer('frontend', this.log);
48
- this.server.on('broadcast_message', this.msgHandler.bind(this));
55
+ this.server.on('broadcast_message', this.broadcastMsgHandler.bind(this));
49
56
  }
50
57
  destroy() {
51
- this.server.off('broadcast_message', this.msgHandler.bind(this));
58
+ this.server.off('broadcast_message', this.broadcastMsgHandler.bind(this));
52
59
  this.server.close();
53
60
  }
54
- async msgHandler(msg) {
61
+ async broadcastMsgHandler(msg) {
55
62
  if (this.server.isWorkerRequest(msg)) {
56
63
  if (this.verbose)
57
64
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
@@ -116,6 +123,8 @@ export class Frontend extends EventEmitter {
116
123
  if (msg.result && msg.result.packageCommand === 'install') {
117
124
  this.wssSendCloseSnackbarMessage(`Installing package ${msg.result.packageName}...`);
118
125
  if (msg.result.success) {
126
+ this.restartRequired = true;
127
+ this.fixedRestartRequired = true;
119
128
  this.wssSendRestartRequired(true, true);
120
129
  this.wssSendSnackbarMessage(`Installed package ${msg.result.packageName}`, 5, 'success');
121
130
  }
@@ -126,6 +135,8 @@ export class Frontend extends EventEmitter {
126
135
  if (msg.result && msg.result.packageCommand === 'uninstall') {
127
136
  this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.result.packageName}...`);
128
137
  if (msg.result.success) {
138
+ this.restartRequired = true;
139
+ this.fixedRestartRequired = true;
129
140
  this.wssSendRestartRequired(true, true);
130
141
  this.wssSendSnackbarMessage(`Uninstalled package ${msg.result.packageName}`, 5, 'success');
131
142
  }
@@ -486,23 +497,23 @@ export class Frontend extends EventEmitter {
486
497
  };
487
498
  res.status(200).json(memoryReport);
488
499
  });
489
- this.expressApp.get('/api/settings', express.json(), async (req, res) => {
500
+ this.expressApp.get('/api/settings', async (req, res) => {
490
501
  this.log.debug('The frontend sent /api/settings');
491
502
  if (!this.validateReq(req, res))
492
503
  return;
493
- res.json(await this.getApiSettings());
504
+ res.json(this.getApiSettings());
494
505
  });
495
506
  this.expressApp.get('/api/plugins', async (req, res) => {
496
507
  this.log.debug('The frontend sent /api/plugins');
497
508
  if (!this.validateReq(req, res))
498
509
  return;
499
- res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
510
+ res.json(this.getApiPlugins());
500
511
  });
501
512
  this.expressApp.get('/api/devices', async (req, res) => {
502
513
  this.log.debug('The frontend sent /api/devices');
503
514
  if (!this.validateReq(req, res))
504
515
  return;
505
- res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
516
+ res.json(this.getApiDevices());
506
517
  });
507
518
  this.expressApp.get('/api/view-mblog', async (req, res) => {
508
519
  this.log.debug('The frontend sent /api/view-mblog');
@@ -699,14 +710,14 @@ export class Frontend extends EventEmitter {
699
710
  this.log.debug('The frontend sent /api/download-mjstorage');
700
711
  if (!this.validateReq(req, res))
701
712
  return;
702
- res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
713
+ res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_DIR}.zip`), `matterbridge.${MATTER_STORAGE_DIR}.zip`, (error) => {
703
714
  this.wssSendCloseSnackbarMessage('Creating matter storage backup...');
704
715
  if (error) {
705
- this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
716
+ this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_DIR}.zip: ${error instanceof Error ? error.message : error}`);
706
717
  res.status(500).send('Error downloading the matter storage zip file');
707
718
  }
708
719
  else {
709
- this.log.debug(`Matter storage matterbridge.${MATTER_STORAGE_NAME}.zip downloaded successfully`);
720
+ this.log.debug(`Matter storage matterbridge.${MATTER_STORAGE_DIR}.zip downloaded successfully`);
710
721
  }
711
722
  });
712
723
  });
@@ -842,7 +853,7 @@ export class Frontend extends EventEmitter {
842
853
  }
843
854
  this.log.debug('Frontend stopped successfully');
844
855
  }
845
- async getApiSettings() {
856
+ getApiSettings() {
846
857
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
847
858
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
848
859
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -863,31 +874,31 @@ export class Frontend extends EventEmitter {
863
874
  matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
864
875
  matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
865
876
  frontendVersion: this.matterbridge.frontendVersion,
866
- dockerDev: undefined,
867
- dockerVersion: undefined,
868
- dockerLatestVersion: undefined,
869
- dockerDevVersion: undefined,
877
+ dockerDev: this.matterbridge.dockerDev,
878
+ dockerVersion: this.matterbridge.dockerVersion,
879
+ dockerLatestVersion: this.matterbridge.dockerLatestVersion,
880
+ dockerDevVersion: this.matterbridge.dockerDevVersion,
870
881
  bridgeMode: this.matterbridge.bridgeMode,
871
882
  restartMode: this.matterbridge.restartMode,
872
883
  virtualMode: this.matterbridge.virtualMode,
873
884
  profile: this.matterbridge.profile,
874
- readOnly: this.matterbridge.readOnly,
875
- shellyBoard: this.matterbridge.shellyBoard,
876
- shellySysUpdate: this.matterbridge.shellySysUpdate,
877
- shellyMainUpdate: this.matterbridge.shellyMainUpdate,
878
- loggerLevel: await this.matterbridge.getLogLevel(),
885
+ loggerLevel: this.matterbridge.logLevel,
879
886
  fileLogger: this.matterbridge.fileLogger,
880
- matterLoggerLevel: Logger.level,
887
+ matterLoggerLevel: this.matterbridge.matterLogLevel,
881
888
  matterFileLogger: this.matterbridge.matterFileLogger,
882
889
  matterMdnsInterface: this.matterbridge.mdnsInterface,
883
890
  matterIpv4Address: this.matterbridge.ipv4Address,
884
891
  matterIpv6Address: this.matterbridge.ipv6Address,
885
- matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
886
- matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
887
- matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
888
- restartRequired: this.matterbridge.restartRequired,
889
- fixedRestartRequired: this.matterbridge.fixedRestartRequired,
890
- updateRequired: this.matterbridge.updateRequired,
892
+ matterPort: this.matterbridge.port || 5540,
893
+ matterDiscriminator: this.matterbridge.discriminator,
894
+ matterPasscode: this.matterbridge.passcode,
895
+ readOnly: this.readOnly,
896
+ shellyBoard: this.shellyBoard,
897
+ shellySysUpdate: this.shellySysUpdate,
898
+ shellyMainUpdate: this.shellyMainUpdate,
899
+ restartRequired: this.restartRequired,
900
+ fixedRestartRequired: this.fixedRestartRequired,
901
+ updateRequired: this.updateRequired,
891
902
  };
892
903
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
893
904
  }
@@ -1065,7 +1076,7 @@ export class Frontend extends EventEmitter {
1065
1076
  });
1066
1077
  return attributes.trimStart().trimEnd();
1067
1078
  }
1068
- getPlugins() {
1079
+ getApiPlugins() {
1069
1080
  if (this.matterbridge.hasCleanupStarted)
1070
1081
  return [];
1071
1082
  const plugins = [];
@@ -1100,7 +1111,7 @@ export class Frontend extends EventEmitter {
1100
1111
  }
1101
1112
  return plugins;
1102
1113
  }
1103
- getDevices(pluginName) {
1114
+ getApiDevices(pluginName) {
1104
1115
  if (this.matterbridge.hasCleanupStarted)
1105
1116
  return [];
1106
1117
  const devices = [];
@@ -1488,7 +1499,7 @@ export class Frontend extends EventEmitter {
1488
1499
  else if (data.method === '/api/create-matter-storage-backup') {
1489
1500
  this.wssSendSnackbarMessage('Creating matter storage backup...', 0);
1490
1501
  this.log.notice(`Creating matter storage backup...`);
1491
- this.zip('zip', path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), [path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME)], '');
1502
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_DIR}.zip`), [path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR)], '');
1492
1503
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1493
1504
  }
1494
1505
  else if (data.method === '/api/create-plugin-backup') {
@@ -1580,14 +1591,13 @@ export class Frontend extends EventEmitter {
1580
1591
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: matter });
1581
1592
  }
1582
1593
  else if (data.method === '/api/settings') {
1583
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
1594
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: this.getApiSettings() });
1584
1595
  }
1585
1596
  else if (data.method === '/api/plugins') {
1586
- const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
1587
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1597
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: this.getApiPlugins() });
1588
1598
  }
1589
1599
  else if (data.method === '/api/devices') {
1590
- const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1600
+ const devices = this.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1591
1601
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1592
1602
  }
1593
1603
  else if (data.method === '/api/clusters') {
@@ -2033,8 +2043,8 @@ export class Frontend extends EventEmitter {
2033
2043
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2034
2044
  return;
2035
2045
  this.log.debug('Sending a restart required message to all connected clients');
2036
- this.matterbridge.restartRequired = true;
2037
- this.matterbridge.fixedRestartRequired = fixed;
2046
+ this.restartRequired = true;
2047
+ this.fixedRestartRequired = fixed;
2038
2048
  if (snackbar === true)
2039
2049
  this.wssSendSnackbarMessage(`Restart required`, 0);
2040
2050
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
@@ -2043,7 +2053,7 @@ export class Frontend extends EventEmitter {
2043
2053
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2044
2054
  return;
2045
2055
  this.log.debug('Sending a restart not required message to all connected clients');
2046
- this.matterbridge.restartRequired = false;
2056
+ this.restartRequired = false;
2047
2057
  if (snackbar === true)
2048
2058
  this.wssSendCloseSnackbarMessage(`Restart required`);
2049
2059
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
@@ -2052,7 +2062,7 @@ export class Frontend extends EventEmitter {
2052
2062
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2053
2063
  return;
2054
2064
  this.log.debug('Sending an update required message to all connected clients');
2055
- this.matterbridge.updateRequired = true;
2065
+ this.updateRequired = true;
2056
2066
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2057
2067
  }
2058
2068
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
@@ -2119,7 +2129,7 @@ export class Frontend extends EventEmitter {
2119
2129
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2120
2130
  return;
2121
2131
  const stringifiedMsg = JSON.stringify(msg);
2122
- if (msg.method !== 'log')
2132
+ if (!['log', 'cpu_update', 'memory_update', 'uptime_update'].includes(msg.method))
2123
2133
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2124
2134
  this.webSocketServer?.clients.forEach((client) => {
2125
2135
  if (client.readyState === client.OPEN) {
package/dist/helpers.js CHANGED
@@ -74,11 +74,6 @@ export async function addVirtualDevice(aggregatorEndpoint, name, type, callback)
74
74
  }
75
75
  export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
76
76
  if (hasParameter('experimental') && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
77
- const lockPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_pin' });
78
- lockPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
79
- lockPin.createPinDoorLockClusterServer();
80
- lockPin.addRequiredClusterServers();
81
- await aggregatorEndpoint.add(lockPin);
82
77
  const lockUserPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_user_pin' });
83
78
  lockUserPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge User Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
84
79
  lockUserPin.createUserPinDoorLockClusterServer();
@@ -83,7 +83,7 @@ export declare function createTestEnvironment(name: string, createOnly?: boolean
83
83
  export declare function destroyTestEnvironment(createOnly?: boolean): Promise<void>;
84
84
  export declare function flushAsync(ticks?: number, microTurns?: number, pause?: number): Promise<void>;
85
85
  export declare function logKeepAlives(log?: AnsiLogger): number;
86
- export declare function flushAllEndpointNumberPersistence(targetServer: ServerNode, rounds?: number): Promise<void>;
86
+ export declare function flushAllEndpointNumberPersistence(targetServer: ServerNode, rounds?: number, pause?: number): Promise<void>;
87
87
  export declare function assertAllEndpointNumbersPersisted(targetServer: ServerNode): Promise<number>;
88
88
  export declare function closeServerNodeStores(targetServer?: ServerNode): Promise<void>;
89
89
  export declare function startServerNode(name: string, port: number, deviceType?: DeviceTypeId, createOnly?: boolean): Promise<[ServerNode<ServerNode.RootEndpoint>, Endpoint<AggregatorEndpoint>]>;
@@ -8,7 +8,7 @@ import { MdnsService } from '@matter/protocol';
8
8
  import { ColorControl } from '@matter/types/clusters/color-control';
9
9
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
10
10
  import { BroadcastServer } from '@matterbridge/thread/server';
11
- import { MATTER_STORAGE_NAME, NODE_STORAGE_DIR } from '@matterbridge/types';
11
+ import { MATTER_STORAGE_DIR, NODE_STORAGE_DIR } from '@matterbridge/types';
12
12
  import { AnsiLogger, er, rs, UNDERLINE, UNDERLINEOFF } from 'node-ansi-logger';
13
13
  import { NodeStorageManager } from 'node-persist-manager';
14
14
  import { Frontend } from '../frontend.js';
@@ -162,7 +162,7 @@ export async function startMatterbridge(bridgeMode = 'bridge', frontendPort = 82
162
162
  process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS'] = '100';
163
163
  process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS'] = '100';
164
164
  process.argv.length = 0;
165
- process.argv.push(...originalProcessArgv, '-novirtual', '-debug', '-verbose', '-logger', 'debug', '-matterlogger', 'debug', bridgeMode === '' ? '-test' : '-' + bridgeMode, '-homedir', HOMEDIR, '-frontend', frontendPort.toString(), '-port', matterPort.toString(), '-passcode', passcode.toString(), '-discriminator', discriminator.toString());
165
+ process.argv.push(...originalProcessArgv, '--novirtual', '--debug', '--verbose', '--logger', 'debug', '--matterlogger', 'debug', bridgeMode === '' ? '--test' : '--' + bridgeMode, '--homedir', HOMEDIR, '--frontend', frontendPort.toString(), '--port', matterPort.toString(), '--passcode', passcode.toString(), '--discriminator', discriminator.toString());
166
166
  expect(Matterbridge.instance).toBeUndefined();
167
167
  matterbridge = await Matterbridge.loadInstance(true);
168
168
  expect(matterbridge.environment).toBeDefined();
@@ -198,7 +198,7 @@ export async function startMatterbridge(bridgeMode = 'bridge', frontendPort = 82
198
198
  expect(frontend.webSocketServer).toBeDefined();
199
199
  expect(matterbridge.nodeStorage).toBeDefined();
200
200
  expect(matterbridge.nodeContext).toBeDefined();
201
- expect(Environment.default.vars.get('path.root')).toBe(path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
201
+ expect(Environment.default.vars.get('path.root')).toBe(path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
202
202
  expect(matterbridge.matterStorageService).toBeDefined();
203
203
  expect(matterbridge.matterStorageManager).toBeDefined();
204
204
  expect(matterbridge.matterbridgeContext).toBeDefined();
@@ -391,7 +391,7 @@ export function createTestEnvironment(name, createOnly = false) {
391
391
  environment = Environment.default;
392
392
  environment.vars.set('log.level', MatterLogLevel.DEBUG);
393
393
  environment.vars.set('log.format', MatterLogFormat.ANSI);
394
- environment.vars.set('path.root', path.join(HOMEDIR, '.matterbridge', MATTER_STORAGE_NAME));
394
+ environment.vars.set('path.root', path.join(HOMEDIR, '.matterbridge', MATTER_STORAGE_DIR));
395
395
  environment.vars.set('runtime.signals', false);
396
396
  environment.vars.set('runtime.exitcode', false);
397
397
  if (createOnly)
@@ -442,13 +442,20 @@ export function logKeepAlives(log) {
442
442
  }
443
443
  return summary.handles.length + summary.requests.length;
444
444
  }
445
- export async function flushAllEndpointNumberPersistence(targetServer, rounds = 2) {
445
+ export async function flushAllEndpointNumberPersistence(targetServer, rounds = 3, pause = 10) {
446
446
  const nodeStore = targetServer.env.get(ServerNodeStore);
447
447
  for (let i = 0; i < rounds; i++) {
448
- await new Promise((resolve) => setImmediate(resolve));
448
+ await flushAsync(undefined, undefined, pause);
449
449
  await nodeStore.endpointStores.close();
450
450
  }
451
451
  }
452
+ function getOwningServerNode(owner) {
453
+ let current = owner;
454
+ while (current.owner) {
455
+ current = current.owner;
456
+ }
457
+ return current;
458
+ }
452
459
  function collectAllEndpoints(root) {
453
460
  const list = [];
454
461
  const walk = (ep) => {
@@ -569,7 +576,8 @@ export async function addDevice(owner, device, pause = 10) {
569
576
  expect(owner.lifecycle.isReady).toBeTruthy();
570
577
  expect(owner.construction.status).toBe(Lifecycle.Status.Active);
571
578
  expect(owner.lifecycle.isPartsReady).toBeTruthy();
572
- await flushAsync(undefined, undefined, pause);
579
+ const targetServer = getOwningServerNode(owner);
580
+ await flushAllEndpointNumberPersistence(targetServer, 3, pause);
573
581
  try {
574
582
  await owner.add(device);
575
583
  }
@@ -587,7 +595,7 @@ export async function addDevice(owner, device, pause = 10) {
587
595
  expect(device.lifecycle.hasId).toBeTruthy();
588
596
  expect(device.lifecycle.hasNumber).toBeTruthy();
589
597
  expect(device.construction.status).toBe(Lifecycle.Status.Active);
590
- await flushAsync(undefined, undefined, pause);
598
+ await flushAllEndpointNumberPersistence(targetServer, 3, pause);
591
599
  return true;
592
600
  }
593
601
  export async function deleteDevice(owner, device, pause = 10) {
@@ -596,6 +604,8 @@ export async function deleteDevice(owner, device, pause = 10) {
596
604
  expect(owner.lifecycle.isReady).toBeTruthy();
597
605
  expect(owner.construction.status).toBe(Lifecycle.Status.Active);
598
606
  expect(owner.lifecycle.isPartsReady).toBeTruthy();
607
+ const targetServer = getOwningServerNode(owner);
608
+ await flushAllEndpointNumberPersistence(targetServer, 3, pause);
599
609
  try {
600
610
  await device.delete();
601
611
  }
@@ -612,7 +622,7 @@ export async function deleteDevice(owner, device, pause = 10) {
612
622
  expect(device.lifecycle.hasId).toBeTruthy();
613
623
  expect(device.lifecycle.hasNumber).toBeTruthy();
614
624
  expect(device.construction.status).toBe(Lifecycle.Status.Destroyed);
615
- await flushAsync(undefined, undefined, pause);
625
+ await flushAllEndpointNumberPersistence(targetServer, 3, pause);
616
626
  return true;
617
627
  }
618
628
  export function getMoveToLevelRequest(level, transitionTime, executeIfOff) {
@@ -10,7 +10,7 @@ import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
10
10
  import { MdnsService } from '@matter/protocol';
11
11
  import { DeviceTypeId, VendorId } from '@matter/types';
12
12
  import { BroadcastServer } from '@matterbridge/thread/server';
13
- import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from '@matterbridge/types';
13
+ import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from '@matterbridge/types';
14
14
  import { getIntParameter, getParameter, hasParameter } from '@matterbridge/utils/cli';
15
15
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
16
16
  import { inspectError } from '@matterbridge/utils/error';
@@ -78,8 +78,8 @@ export class MatterNode extends EventEmitter {
78
78
  if (this.verbose)
79
79
  this.log.debug(`PluginManager is ready`);
80
80
  this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
81
- this.environment.vars.set('log.format', MatterLogFormat.ANSI);
82
- this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
81
+ this.environment.vars.set('log.format', hasParameter('no-ansi') || process.env.NO_COLOR === '1' ? MatterLogFormat.PLAIN : MatterLogFormat.ANSI);
82
+ this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
83
83
  this.environment.vars.set('runtime.signals', false);
84
84
  this.environment.vars.set('runtime.exitcode', false);
85
85
  if (this.verbose)
@@ -280,17 +280,27 @@ export class MatterNode extends EventEmitter {
280
280
  this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
281
281
  }
282
282
  return (text, message) => {
283
- const logger = text.slice(44, 44 + 20).trim();
284
- const msg = text.slice(65);
285
- this.matterLog.logName = logger;
286
- this.matterLog.log(MatterLogLevel.names[message.level], msg);
283
+ try {
284
+ let msg;
285
+ if (Logger.format === MatterLogFormat.ANSI) {
286
+ msg = text.slice(65);
287
+ }
288
+ else {
289
+ msg = text.split(message.facility)[1]?.trim();
290
+ }
291
+ this.matterLog.logName = message.facility;
292
+ this.matterLog.log(MatterLogLevel.names[message.level], msg);
293
+ }
294
+ catch (_error) {
295
+ this.log.debug(`Error parsing matter log message facility ${message.facility}`);
296
+ }
287
297
  };
288
298
  }
289
299
  async startMatterStorage() {
290
300
  this.log.info(`Starting matter node storage...`);
291
301
  this.matterStorageService = this.environment.get(StorageService);
292
302
  this.log.info(`Started matter node storage in ${CYAN}${this.matterStorageService.location}${nf}`);
293
- await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
303
+ await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR + '.backup'));
294
304
  }
295
305
  async backupMatterStorage(storageName, backupName) {
296
306
  this.log.info(`Creating matter node storage backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}...`);
@@ -59,13 +59,6 @@ export declare class Matterbridge extends EventEmitter<MatterbridgeEvents> {
59
59
  readonly matterLog: AnsiLogger;
60
60
  matterLogLevel: LogLevel;
61
61
  matterFileLogger: boolean;
62
- readonly readOnly: boolean;
63
- readonly shellyBoard: boolean;
64
- shellySysUpdate: boolean;
65
- shellyMainUpdate: boolean;
66
- restartRequired: boolean;
67
- fixedRestartRequired: boolean;
68
- updateRequired: boolean;
69
62
  readonly plugins: PluginManager;
70
63
  readonly devices: DeviceManager;
71
64
  readonly frontend: Frontend;
@@ -14,7 +14,7 @@ import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
14
14
  import { PaseClient, Read, Subscribe } from '@matter/protocol';
15
15
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
16
16
  import { BroadcastServer } from '@matterbridge/thread/server';
17
- import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
17
+ import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
18
18
  import { wait } from '@matterbridge/utils';
19
19
  import { getIntParameter, getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
20
20
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
@@ -90,13 +90,6 @@ export class Matterbridge extends EventEmitter {
90
90
  });
91
91
  matterLogLevel = this.matterLog.logLevel;
92
92
  matterFileLogger = false;
93
- readOnly = hasParameter('readonly') || hasParameter('shelly');
94
- shellyBoard = hasParameter('shelly');
95
- shellySysUpdate = false;
96
- shellyMainUpdate = false;
97
- restartRequired = false;
98
- fixedRestartRequired = false;
99
- updateRequired = false;
100
93
  plugins = new PluginManager(this);
101
94
  devices = new DeviceManager();
102
95
  frontend = new Frontend(this);
@@ -207,8 +200,6 @@ export class Matterbridge extends EventEmitter {
207
200
  port: this.port,
208
201
  discriminator: this.discriminator,
209
202
  passcode: this.passcode,
210
- shellySysUpdate: this.shellySysUpdate,
211
- shellyMainUpdate: this.shellyMainUpdate,
212
203
  };
213
204
  }
214
205
  async msgHandler(msg) {
@@ -245,14 +236,6 @@ export class Matterbridge extends EventEmitter {
245
236
  await this.nodeContext?.set('globalModulesDirectory', msg.params.prefix);
246
237
  this.server.respond({ ...msg, result: { success: true } });
247
238
  break;
248
- case 'matterbridge_shelly_sys_update':
249
- this.shellySysUpdate = true;
250
- this.server.respond({ ...msg, result: { success: true } });
251
- break;
252
- case 'matterbridge_shelly_main_update':
253
- this.shellyMainUpdate = true;
254
- this.server.respond({ ...msg, result: { success: true } });
255
- break;
256
239
  case 'matterbridge_platform':
257
240
  this.server.respond({ ...msg, result: { data: this.getPlatformMatterbridge(), success: true } });
258
241
  break;
@@ -306,8 +289,6 @@ export class Matterbridge extends EventEmitter {
306
289
  switch (msg.type) {
307
290
  case 'manager_spawn_response':
308
291
  if (msg.result && msg.result.success && msg.result.packageCommand === 'install') {
309
- this.restartRequired = true;
310
- this.fixedRestartRequired = true;
311
292
  const packageName = msg.result.packageName.replace(/@.*$/, '');
312
293
  if (packageName === 'matterbridge') {
313
294
  this.log.info('Matterbridge has been updated. Full restart required.');
@@ -349,7 +330,7 @@ export class Matterbridge extends EventEmitter {
349
330
  this.rootDirectory = currentFileDirectory.includes(path.join('packages', 'core')) ? path.resolve(currentFileDirectory, '../', '../', '../') : path.resolve(currentFileDirectory, '../', '../', '..', '../');
350
331
  this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
351
332
  this.environment.vars.set('log.format', hasParameter('no-ansi') || process.env.NO_COLOR === '1' ? MatterLogFormat.PLAIN : MatterLogFormat.ANSI);
352
- this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
333
+ this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR));
353
334
  this.environment.vars.set('runtime.signals', false);
354
335
  this.environment.vars.set('runtime.exitcode', false);
355
336
  this.registerProcessHandlers();
@@ -1053,28 +1034,19 @@ export class Matterbridge extends EventEmitter {
1053
1034
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
1054
1035
  }
1055
1036
  return (text, message) => {
1056
- const logger = text.slice(44, 44 + 20).trim();
1057
- const msg = text.slice(65);
1058
- this.matterLog.logName = logger;
1059
- switch (message.level) {
1060
- case MatterLogLevel.DEBUG:
1061
- this.matterLog.log("debug", msg);
1062
- break;
1063
- case MatterLogLevel.INFO:
1064
- this.matterLog.log("info", msg);
1065
- break;
1066
- case MatterLogLevel.NOTICE:
1067
- this.matterLog.log("notice", msg);
1068
- break;
1069
- case MatterLogLevel.WARN:
1070
- this.matterLog.log("warn", msg);
1071
- break;
1072
- case MatterLogLevel.ERROR:
1073
- this.matterLog.log("error", msg);
1074
- break;
1075
- case MatterLogLevel.FATAL:
1076
- this.matterLog.log("fatal", msg);
1077
- break;
1037
+ try {
1038
+ let msg;
1039
+ if (Logger.format === MatterLogFormat.ANSI) {
1040
+ msg = text.slice(65);
1041
+ }
1042
+ else {
1043
+ msg = text.split(message.facility)[1]?.trim();
1044
+ }
1045
+ this.matterLog.logName = message.facility;
1046
+ this.matterLog.log(MatterLogLevel.names[message.level], msg);
1047
+ }
1048
+ catch (_error) {
1049
+ this.log.debug(`Error parsing matter log message facility ${message.facility}`);
1078
1050
  }
1079
1051
  };
1080
1052
  }
@@ -1266,19 +1238,19 @@ export class Matterbridge extends EventEmitter {
1266
1238
  }
1267
1239
  if (hasParameter('reset-sessions')) {
1268
1240
  this.log.debug(`Cleaning matter storage context for ${GREEN}Matterbridge${db}...`);
1269
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'sessions.resumptionRecords'), this.log);
1270
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'root.subscriptions.subscriptions'), this.log);
1241
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, 'Matterbridge', 'sessions.resumptionRecords'), this.log);
1242
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, 'Matterbridge', 'root.subscriptions.subscriptions'), this.log);
1271
1243
  for (const plugin of this.plugins.array()) {
1272
1244
  this.log.debug(`Cleaning matter storage context for plugin ${plg}${plugin.name}${db}...`);
1273
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'sessions.resumptionRecords'), this.log);
1274
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'root.subscriptions.subscriptions'), this.log);
1245
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, plugin.name, 'sessions.resumptionRecords'), this.log);
1246
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, plugin.name, 'root.subscriptions.subscriptions'), this.log);
1275
1247
  }
1276
1248
  for (const device of this.devices.array().filter((d) => d.mode === 'server')) {
1277
1249
  if (!device.deviceName)
1278
1250
  continue;
1279
1251
  this.log.debug(`Cleaning matter storage context for server node device ${dev}${device.deviceName}${db}...`);
1280
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'sessions.resumptionRecords'), this.log);
1281
- unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'root.subscriptions.subscriptions'), this.log);
1252
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, device.deviceName.replace(/[ .]/g, ''), 'sessions.resumptionRecords'), this.log);
1253
+ unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, device.deviceName.replace(/[ .]/g, ''), 'root.subscriptions.subscriptions'), this.log);
1282
1254
  }
1283
1255
  }
1284
1256
  await this.frontend.stop();
@@ -1308,10 +1280,10 @@ export class Matterbridge extends EventEmitter {
1308
1280
  this.devices.clear();
1309
1281
  if (message === 'shutting down with factory reset...') {
1310
1282
  try {
1311
- const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1283
+ const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR);
1312
1284
  this.log.info(`Removing matter storage directory: ${dir}`);
1313
1285
  await fs.promises.rm(dir, { recursive: true });
1314
- const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup');
1286
+ const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR + '.backup');
1315
1287
  this.log.info(`Removing matter storage backup directory: ${backup}`);
1316
1288
  await fs.promises.rm(backup, { recursive: true });
1317
1289
  }
@@ -1831,7 +1803,7 @@ export class Matterbridge extends EventEmitter {
1831
1803
  this.log.info('Matter node storage manager "Matterbridge" created');
1832
1804
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1833
1805
  this.log.info('Matter node storage started');
1834
- await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1806
+ await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR), path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR + '.backup'));
1835
1807
  }
1836
1808
  async backupMatterStorage(storageName, backupName) {
1837
1809
  this.log.info('Creating matter node storage backup...');
@@ -24,7 +24,7 @@ import { ClusterId, EndpointNumber } from '@matter/types/datatype';
24
24
  import { Semtag } from '@matter/types/globals';
25
25
  import { AnsiLogger, LogLevel } from 'node-ansi-logger';
26
26
  import { DeviceTypeDefinition } from './matterbridgeDeviceTypes.js';
27
- import { CommandHandler, CommandHandlerData, CommandHandlerFunction, CommandHandlers } from './matterbridgeEndpointCommandHandler.js';
27
+ import { CommandHandler, CommandHandlerData, CommandHandlerExecutionResult, CommandHandlerFunction, CommandHandlers } from './matterbridgeEndpointCommandHandler.js';
28
28
  import { MatterbridgeEndpointOptions, SerializedMatterbridgeEndpoint } from './matterbridgeEndpointTypes.js';
29
29
  type BehaviorCommandName<T extends Behavior.Type> = {
30
30
  [K in keyof CommandsOfBehavior<T>]: K;
@@ -101,7 +101,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
101
101
  addUserLabel(label: string, value: string): Promise<this>;
102
102
  addCommandHandler<C extends CommandHandlers>(command: C, handler: CommandHandlerFunction<C>): this;
103
103
  removeCommandHandler<C extends CommandHandlers>(command: C, handler: CommandHandlerFunction<C>): this;
104
- executeCommandHandler<C extends CommandHandlers>(command: C, request: CommandHandlerData<C>['request'], cluster: CommandHandlerData<C>['cluster'], attributes: CommandHandlerData<C>['attributes'], endpoint: CommandHandlerData<C>['endpoint']): Promise<void>;
104
+ executeCommandHandler<C extends CommandHandlers>(command: C, request: CommandHandlerData<C>['request'], cluster: CommandHandlerData<C>['cluster'], attributes: CommandHandlerData<C>['attributes'], endpoint: CommandHandlerData<C>['endpoint']): Promise<CommandHandlerExecutionResult<C>>;
105
105
  invokeBehaviorCommand<T extends Behavior.Type, C extends BehaviorCommandName<T>>(cluster: T, command: C, params?: BehaviorCommandParams<T, C>): Promise<void>;
106
106
  invokeBehaviorCommand<T extends ClusterType, C extends keyof ClusterType.CommandsOf<T>>(cluster: T, command: C, params?: ClusterType.CommandsOf<T>[C] extends {
107
107
  requestSchema: infer S extends import('@matter/types/tlv').TlvSchema<unknown>;
@@ -182,9 +182,8 @@ export declare class MatterbridgeEndpoint extends Endpoint {
182
182
  }, airflowDirection?: FanControl.AirflowDirection): this;
183
183
  createDefaultHepaFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
184
184
  createDefaultActivatedCarbonFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
185
- createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
186
- createPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
187
- createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
185
+ createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number): this;
186
+ createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number, minPinCodeLength?: number, maxPinCodeLength?: number): this;
188
187
  createDefaultModeSelectClusterServer(description: string, supportedModes: ModeSelect.ModeOption[], currentMode?: number, startUpMode?: number): this;
189
188
  createDefaultValveConfigurationAndControlClusterServer(valveState?: ValveConfigurationAndControl.ValveState, valveLevel?: number): this;
190
189
  createDefaultPumpConfigurationAndControlClusterServer(pumpMode?: PumpConfigurationAndControl.OperationMode): this;