@matterbridge/core 3.10.0-dev-20260716-572b03b → 3.10.0-dev-20260716-7f8762c

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.
@@ -16,11 +16,13 @@ export function toBaseDevice(device) {
16
16
  vendorName: device.vendorName,
17
17
  productId: device.productId,
18
18
  productName: device.productName,
19
+ productLabel: device.productLabel,
20
+ productUrl: device.productUrl,
21
+ configurationVersion: device.configurationVersion,
19
22
  softwareVersion: device.softwareVersion,
20
23
  softwareVersionString: device.softwareVersionString,
21
24
  hardwareVersion: device.hardwareVersion,
22
25
  hardwareVersionString: device.hardwareVersionString,
23
- productUrl: device.productUrl,
24
26
  tagList: device.tagList,
25
27
  originalId: device.originalId,
26
28
  name: device.name,
@@ -58,7 +58,7 @@ export declare class MatterNode extends EventEmitter<MatterEvents> {
58
58
  startMatterStorage(): Promise<void>;
59
59
  backupMatterStorage(storageName: string, backupName: string): Promise<void>;
60
60
  stopMatterStorage(): Promise<void>;
61
- createServerNodeContext(storeId: string, deviceName: string, deviceType: DeviceTypeId, vendorId: VendorId, vendorName: string, productId: number, productName: string, serialNumber?: string, uniqueId?: string): Promise<StorageContext>;
61
+ createServerNodeContext(storeId: string, deviceName: string, deviceType: DeviceTypeId, vendorId: VendorId, vendorName: string, productId: number, productName: string, serialNumber?: string, uniqueId?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number): Promise<StorageContext>;
62
62
  createServerNode(port?: number, passcode?: number, discriminator?: number): Promise<ServerNode>;
63
63
  getServerNodeData(serverNode: ServerNode): ApiMatter;
64
64
  startServerNode(timeout?: number): Promise<void>;
@@ -15,7 +15,7 @@ import { getIntParameter, getParameter, hasParameter } from '@matterbridge/utils
15
15
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
16
16
  import { getErrorMessage, inspectError } from '@matterbridge/utils/error';
17
17
  import { logModuleLoaded } from '@matterbridge/utils/loader';
18
- import { isValidNumber, isValidString, parseVersionString } from '@matterbridge/utils/validate';
18
+ import { isValidInteger, isValidString, parseVersionString } from '@matterbridge/utils/validate';
19
19
  import { wait, withTimeout } from '@matterbridge/utils/wait';
20
20
  import { AnsiLogger, BLUE, CYAN, db, debugStringify, er, nf, or, zb } from 'node-ansi-logger';
21
21
  import { NodeStorageManager } from 'node-persist-manager';
@@ -328,7 +328,7 @@ export class MatterNode extends EventEmitter {
328
328
  this.log.info('Closed matter node storage');
329
329
  this.emit('closed');
330
330
  }
331
- async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
331
+ async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0', productLabel = 'Matter Endpoint', productUrl = 'https://matterbridge.io', configurationVersion = 1) {
332
332
  if (!this.matterStorageService) {
333
333
  throw new Error('No storage service initialized');
334
334
  }
@@ -344,14 +344,16 @@ export class MatterNode extends EventEmitter {
344
344
  await storageContext.set('vendorName', vendorName.slice(0, 32));
345
345
  await storageContext.set('productId', productId);
346
346
  await storageContext.set('productName', productName.slice(0, 32));
347
- await storageContext.set('nodeLabel', productName.slice(0, 32));
348
- await storageContext.set('productLabel', productName.replace(vendorName, '').trim().slice(0, 32));
347
+ await storageContext.set('productLabel', productLabel.replace(vendorName, '').trim().slice(0, 64));
348
+ await storageContext.set('productUrl', isValidString(productUrl, 8, 256) && productUrl.startsWith('https://') ? productUrl : 'https://matterbridge.io');
349
+ await storageContext.set('nodeLabel', deviceName.slice(0, 32));
349
350
  await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
350
351
  await storageContext.set('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
351
- await storageContext.set('softwareVersion', isValidNumber(parseVersionString(this.matterbridge.matterbridgeVersion), 0, UINT32_MAX) ? parseVersionString(this.matterbridge.matterbridgeVersion) : 1);
352
- await storageContext.set('softwareVersionString', isValidString(this.matterbridge.matterbridgeVersion, 5, 64) ? this.matterbridge.matterbridgeVersion : '1.0.0');
353
- await storageContext.set('hardwareVersion', isValidNumber(parseVersionString(this.matterbridge.systemInformation.osRelease), 0, UINT16_MAX) ? parseVersionString(this.matterbridge.systemInformation.osRelease) : 1);
354
- await storageContext.set('hardwareVersionString', isValidString(this.matterbridge.systemInformation.osRelease, 5, 64) ? this.matterbridge.systemInformation.osRelease : '1.0.0');
352
+ await storageContext.set('softwareVersion', isValidInteger(softwareVersion, 0, UINT32_MAX) ? softwareVersion : 1);
353
+ await storageContext.set('softwareVersionString', isValidString(softwareVersionString, 1, 64) ? softwareVersionString : '1.0.0');
354
+ await storageContext.set('hardwareVersion', isValidInteger(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : 1);
355
+ await storageContext.set('hardwareVersionString', isValidString(hardwareVersionString, 1, 64) ? hardwareVersionString : '1.0.0');
356
+ await storageContext.set('configurationVersion', isValidInteger(configurationVersion, 1, UINT32_MAX) ? configurationVersion : 1);
355
357
  this.log.debug(`Created server node storage context "${storeId}.persist" for ${storeId}:`);
356
358
  this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
357
359
  this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
@@ -360,14 +362,16 @@ export class MatterNode extends EventEmitter {
360
362
  this.log.debug(`- vendorName: ${await storageContext.get('vendorName')}`);
361
363
  this.log.debug(`- productId: ${await storageContext.get('productId')}`);
362
364
  this.log.debug(`- productName: ${await storageContext.get('productName')}`);
363
- this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
364
365
  this.log.debug(`- productLabel: ${await storageContext.get('productLabel')}`);
366
+ this.log.debug(`- productUrl: ${await storageContext.get('productUrl')}`);
367
+ this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
365
368
  this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
366
369
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
367
370
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')}`);
368
371
  this.log.debug(`- softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
369
372
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')}`);
370
373
  this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
374
+ this.log.debug(`- configurationVersion: ${await storageContext.get('configurationVersion')}`);
371
375
  return storageContext;
372
376
  }
373
377
  async createServerNode(port = 5540, passcode = 20252026, discriminator = 3850) {
@@ -376,6 +380,23 @@ export class MatterNode extends EventEmitter {
376
380
  }
377
381
  const storeId = await this.matterStorageContext.get('storeId');
378
382
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
383
+ this.log.debug(`- storeId: ${await this.matterStorageContext.get('storeId')}`);
384
+ this.log.debug(`- deviceName: ${await this.matterStorageContext.get('deviceName')}`);
385
+ this.log.debug(`- deviceType: ${await this.matterStorageContext.get('deviceType')}(0x${(await this.matterStorageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
386
+ this.log.debug(`- vendorId: ${await this.matterStorageContext.get('vendorId')}`);
387
+ this.log.debug(`- vendorName: ${await this.matterStorageContext.get('vendorName')}`);
388
+ this.log.debug(`- productId: ${await this.matterStorageContext.get('productId')}`);
389
+ this.log.debug(`- productName: ${await this.matterStorageContext.get('productName')}`);
390
+ this.log.debug(`- productLabel: ${await this.matterStorageContext.get('productLabel')}`);
391
+ this.log.debug(`- productUrl: ${await this.matterStorageContext.get('productUrl')}`);
392
+ this.log.debug(`- nodeLabel: ${await this.matterStorageContext.get('nodeLabel')}`);
393
+ this.log.debug(`- serialNumber: ${await this.matterStorageContext.get('serialNumber')}`);
394
+ this.log.debug(`- uniqueId: ${await this.matterStorageContext.get('uniqueId')}`);
395
+ this.log.debug(`- softwareVersion: ${await this.matterStorageContext.get('softwareVersion')}`);
396
+ this.log.debug(`- softwareVersionString: ${await this.matterStorageContext.get('softwareVersionString')}`);
397
+ this.log.debug(`- hardwareVersion: ${await this.matterStorageContext.get('hardwareVersion')}`);
398
+ this.log.debug(`- hardwareVersionString: ${await this.matterStorageContext.get('hardwareVersionString')}`);
399
+ this.log.debug(`- configurationVersion: ${await this.matterStorageContext.get('configurationVersion')}`);
379
400
  if (passcode < 0 || passcode > 99999999) {
380
401
  this.log.warn(`Invalid passcode ${passcode} for server node ${storeId}. Passcode must be between 0 and 99999999. Generating a random passcode...`);
381
402
  passcode = PaseClient.generateRandomPasscode(this.environment.get(Crypto));
@@ -413,6 +434,7 @@ export class MatterNode extends EventEmitter {
413
434
  productId: await this.matterStorageContext.get('productId'),
414
435
  productName: await this.matterStorageContext.get('productName'),
415
436
  productLabel: await this.matterStorageContext.get('productLabel'),
437
+ productUrl: await this.matterStorageContext.get('productUrl'),
416
438
  nodeLabel: await this.matterStorageContext.get('nodeLabel'),
417
439
  serialNumber: await this.matterStorageContext.get('serialNumber'),
418
440
  uniqueId: await this.matterStorageContext.get('uniqueId'),
@@ -420,6 +442,7 @@ export class MatterNode extends EventEmitter {
420
442
  softwareVersionString: await this.matterStorageContext.get('softwareVersionString'),
421
443
  hardwareVersion: await this.matterStorageContext.get('hardwareVersion'),
422
444
  hardwareVersionString: await this.matterStorageContext.get('hardwareVersionString'),
445
+ configurationVersion: await this.matterStorageContext.get('configurationVersion'),
423
446
  reachable: true,
424
447
  },
425
448
  });
@@ -544,7 +567,7 @@ export class MatterNode extends EventEmitter {
544
567
  }
545
568
  async createMatterbridgeServerNode() {
546
569
  this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
547
- this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
570
+ this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, 'Matter Bridge', 'https://matterbridge.io', 1);
548
571
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
549
572
  this.aggregatorNode = await this.createAggregatorNode();
550
573
  this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
@@ -565,7 +588,7 @@ export class MatterNode extends EventEmitter {
565
588
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
566
589
  plugin.locked = true;
567
590
  this.log.debug(`Creating accessory plugin ${plg}${plugin.name}${db} server node...`);
568
- this.matterStorageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName);
591
+ this.matterStorageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
569
592
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
570
593
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node...`);
571
594
  await this.serverNode.add(device);
@@ -586,7 +609,7 @@ export class MatterNode extends EventEmitter {
586
609
  if (!plugin.locked) {
587
610
  plugin.locked = true;
588
611
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
589
- this.matterStorageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
612
+ this.matterStorageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io', 1);
590
613
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
591
614
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
592
615
  this.aggregatorNode = await this.createAggregatorNode();
@@ -608,7 +631,7 @@ export class MatterNode extends EventEmitter {
608
631
  }
609
632
  if (device.mode === 'server' && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
610
633
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
611
- this.matterStorageContext = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName);
634
+ this.matterStorageContext = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
612
635
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
613
636
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
614
637
  await this.serverNode.add(device);
@@ -22,7 +22,7 @@ import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/err
22
22
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
23
23
  import { logModuleLoaded } from '@matterbridge/utils/loader';
24
24
  import { excludedInterfaceNamePattern } from '@matterbridge/utils/network';
25
- import { isValidNumber, isValidObject, isValidString, parseVersionString } from '@matterbridge/utils/validate';
25
+ import { isValidInteger, isValidNumber, isValidObject, isValidString, parseVersionString } from '@matterbridge/utils/validate';
26
26
  import { fireAndForget, wait } from '@matterbridge/utils/wait';
27
27
  import { AnsiLogger, BLUE, BRIGHT, CYAN, db, debugStringify, er, GREEN, nf, nt, or, RED, RESET, rs, UNDERLINE, UNDERLINEOFF, wr, zb, } from 'node-ansi-logger';
28
28
  import { NodeStorageManager } from 'node-persist-manager';
@@ -1751,7 +1751,7 @@ export class Matterbridge extends EventEmitter {
1751
1751
  await createDirectory(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR, 'Matterbridge'), `Matter node storage directory for Matterbridge`, this.log);
1752
1752
  this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
1753
1753
  this.log.info('Matter node storage manager "Matterbridge" created');
1754
- this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1754
+ this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId, parseVersionString(this.matterbridgeVersion), this.matterbridgeVersion, parseVersionString(this.systemInformation.osRelease), this.systemInformation.osRelease, 'Matter Bridge', 'https://matterbridge.io', 1);
1755
1755
  this.log.info('Matter node storage started');
1756
1756
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR), path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR + '.backup'));
1757
1757
  }
@@ -1779,7 +1779,7 @@ export class Matterbridge extends EventEmitter {
1779
1779
  this.controllerContext = undefined;
1780
1780
  this.log.info('Matter node storage closed');
1781
1781
  }
1782
- async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1782
+ async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0', productLabel = 'Matter Endpoint', productUrl = 'https://matterbridge.io', configurationVersion = 1) {
1783
1783
  const { randomBytes } = await import('node:crypto');
1784
1784
  if (!this.matterStorageService)
1785
1785
  throw new Error('No storage service initialized');
@@ -1797,14 +1797,16 @@ export class Matterbridge extends EventEmitter {
1797
1797
  await storageContext.set('vendorName', vendorName.slice(0, 32));
1798
1798
  await storageContext.set('productId', productId);
1799
1799
  await storageContext.set('productName', productName.slice(0, 32));
1800
- await storageContext.set('productLabel', productName.replace(vendorName, '').trim().slice(0, 32));
1800
+ await storageContext.set('productLabel', productLabel.replace(vendorName, '').trim().slice(0, 64));
1801
+ await storageContext.set('productUrl', isValidString(productUrl, 8, 256) && productUrl.startsWith('https://') ? productUrl : 'https://matterbridge.io');
1801
1802
  await storageContext.set('nodeLabel', deviceName.slice(0, 32));
1802
1803
  await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
1803
1804
  await storageContext.set('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
1804
- await storageContext.set('softwareVersion', isValidNumber(parseVersionString(this.matterbridgeVersion), 0, UINT32_MAX) ? parseVersionString(this.matterbridgeVersion) : 1);
1805
- await storageContext.set('softwareVersionString', isValidString(this.matterbridgeVersion, 5, 64) ? this.matterbridgeVersion : '1.0.0');
1806
- await storageContext.set('hardwareVersion', isValidNumber(parseVersionString(this.systemInformation.osRelease), 0, UINT16_MAX) ? parseVersionString(this.systemInformation.osRelease) : 1);
1807
- await storageContext.set('hardwareVersionString', isValidString(this.systemInformation.osRelease, 5, 64) ? this.systemInformation.osRelease : '1.0.0');
1805
+ await storageContext.set('softwareVersion', isValidInteger(softwareVersion, 0, UINT32_MAX) ? softwareVersion : 1);
1806
+ await storageContext.set('softwareVersionString', isValidString(softwareVersionString, 1, 64) ? softwareVersionString : '1.0.0');
1807
+ await storageContext.set('hardwareVersion', isValidInteger(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : 1);
1808
+ await storageContext.set('hardwareVersionString', isValidString(hardwareVersionString, 1, 64) ? hardwareVersionString : '1.0.0');
1809
+ await storageContext.set('configurationVersion', isValidInteger(configurationVersion, 1, UINT32_MAX) ? configurationVersion : 1);
1808
1810
  this.log.debug(`Created server node storage context "${storeId}.persist" for ${storeId}:`);
1809
1811
  this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
1810
1812
  this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
@@ -1814,6 +1816,7 @@ export class Matterbridge extends EventEmitter {
1814
1816
  this.log.debug(`- productId: ${await storageContext.get('productId')}`);
1815
1817
  this.log.debug(`- productName: ${await storageContext.get('productName')}`);
1816
1818
  this.log.debug(`- productLabel: ${await storageContext.get('productLabel')}`);
1819
+ this.log.debug(`- productUrl: ${await storageContext.get('productUrl')}`);
1817
1820
  this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
1818
1821
  this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1819
1822
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
@@ -1821,6 +1824,7 @@ export class Matterbridge extends EventEmitter {
1821
1824
  this.log.debug(`- softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1822
1825
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')}`);
1823
1826
  this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1827
+ this.log.debug(`- configurationVersion: ${await storageContext.get('configurationVersion')}`);
1824
1828
  return storageContext;
1825
1829
  }
1826
1830
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
@@ -1834,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
1834
1838
  this.log.debug(`- productId: ${await storageContext.get('productId')}`);
1835
1839
  this.log.debug(`- productName: ${await storageContext.get('productName')}`);
1836
1840
  this.log.debug(`- productLabel: ${await storageContext.get('productLabel')}`);
1841
+ this.log.debug(`- productUrl: ${await storageContext.get('productUrl')}`);
1837
1842
  this.log.debug(`- nodeLabel: ${await storageContext.get('nodeLabel')}`);
1838
1843
  this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1839
1844
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
@@ -1841,6 +1846,7 @@ export class Matterbridge extends EventEmitter {
1841
1846
  this.log.debug(`- softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1842
1847
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')}`);
1843
1848
  this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1849
+ this.log.debug(`- configurationVersion: ${await storageContext.get('configurationVersion')}`);
1844
1850
  if (passcode < 0 || passcode > 99999999) {
1845
1851
  this.log.warn(`Invalid passcode ${passcode} for server node ${storeId}. Passcode must be between 0 and 99999999. Generating a random passcode...`);
1846
1852
  passcode = PaseClient.generateRandomPasscode(this.environment.get(Crypto));
@@ -1877,12 +1883,14 @@ export class Matterbridge extends EventEmitter {
1877
1883
  productId: await storageContext.get('productId'),
1878
1884
  productName: await storageContext.get('productName'),
1879
1885
  productLabel: await storageContext.get('productLabel'),
1886
+ productUrl: await storageContext.get('productUrl'),
1880
1887
  serialNumber: await storageContext.get('serialNumber'),
1881
1888
  uniqueId: await storageContext.get('uniqueId'),
1882
1889
  softwareVersion: await storageContext.get('softwareVersion'),
1883
1890
  softwareVersionString: await storageContext.get('softwareVersionString'),
1884
1891
  hardwareVersion: await storageContext.get('hardwareVersion'),
1885
1892
  hardwareVersionString: await storageContext.get('hardwareVersionString'),
1893
+ configurationVersion: await storageContext.get('configurationVersion'),
1886
1894
  reachable: true,
1887
1895
  },
1888
1896
  });
@@ -2002,7 +2010,7 @@ export class Matterbridge extends EventEmitter {
2002
2010
  plugin.locked = true;
2003
2011
  plugin.device = device;
2004
2012
  this.log.debug(`Creating accessory plugin ${plg}${plugin.name}${db} server node...`);
2005
- plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId);
2013
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
2006
2014
  plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2007
2015
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node...`);
2008
2016
  await plugin.serverNode.add(device);
@@ -2012,7 +2020,7 @@ export class Matterbridge extends EventEmitter {
2012
2020
  if (!plugin.locked) {
2013
2021
  plugin.locked = true;
2014
2022
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
2015
- plugin.storageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
2023
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridgeVersion), this.matterbridgeVersion, parseVersionString(this.systemInformation.osRelease), this.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io', 1);
2016
2024
  plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2017
2025
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
2018
2026
  plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
@@ -2030,7 +2038,7 @@ export class Matterbridge extends EventEmitter {
2030
2038
  device.productId &&
2031
2039
  device.productName) {
2032
2040
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
2033
- const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId);
2041
+ const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
2034
2042
  device.serverNode = await this.createServerNode(context, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2035
2043
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
2036
2044
  await device.serverNode.add(device);
@@ -70,11 +70,13 @@ export declare class MatterbridgeEndpoint extends Endpoint {
70
70
  vendorName: string | undefined;
71
71
  productId: number | undefined;
72
72
  productName: string | undefined;
73
+ productLabel: string | undefined;
74
+ productUrl: string;
73
75
  softwareVersion: number | undefined;
74
76
  softwareVersionString: string | undefined;
75
77
  hardwareVersion: number | undefined;
76
78
  hardwareVersionString: string | undefined;
77
- productUrl: string;
79
+ configurationVersion: number;
78
80
  tagList: Semtag[] | undefined;
79
81
  originalId: string | undefined;
80
82
  name: string | undefined;
@@ -138,8 +140,8 @@ export declare class MatterbridgeEndpoint extends Endpoint {
138
140
  createDefaultPowerSourceBatteryClusterServer(batPercentRemaining?: null | number, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: null | number, batReplaceability?: PowerSource.BatReplaceability): this;
139
141
  createDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplacementDescription?: string, batQuantity?: number, batReplaceability?: PowerSource.BatReplaceability): this;
140
142
  createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplaceability?: PowerSource.BatReplaceability): this;
141
- createDefaultBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productId?: number, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string): this;
142
- createDefaultBridgedDeviceBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string): this;
143
+ createDefaultBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productId?: number, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number): this;
144
+ createDefaultBridgedDeviceBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number): this;
143
145
  createDefaultPowerTopologyClusterServer(): this;
144
146
  createDefaultElectricalEnergyMeasurementClusterServer(energyImported?: number | bigint | null, energyExported?: number | bigint | null): this;
145
147
  createDefaultElectricalPowerMeasurementClusterServer(voltage?: number | bigint | null, current?: number | bigint | null, power?: number | bigint | null, frequency?: number | bigint | null): this;
@@ -32,6 +32,7 @@ import { ThermostatUserInterfaceConfigurationServer } from '@matter/node/behavio
32
32
  import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/node/behaviors/total-volatile-organic-compounds-concentration-measurement';
33
33
  import { getClusterNameById } from '@matter/types/cluster';
34
34
  import { AirQuality } from '@matter/types/clusters/air-quality';
35
+ import { BasicInformation } from '@matter/types/clusters/basic-information';
35
36
  import { BooleanStateConfiguration } from '@matter/types/clusters/boolean-state-configuration';
36
37
  import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
37
38
  import { ColorControl } from '@matter/types/clusters/color-control';
@@ -60,7 +61,7 @@ import { WindowCovering } from '@matter/types/clusters/window-covering';
60
61
  import { VendorId } from '@matter/types/datatype';
61
62
  import { inspectError } from '@matterbridge/utils/error';
62
63
  import { logModuleLoaded } from '@matterbridge/utils/loader';
63
- import { isValidNumber, isValidObject, isValidString } from '@matterbridge/utils/validate';
64
+ import { isValidInteger, isValidNumber, isValidObject, isValidString } from '@matterbridge/utils/validate';
64
65
  import { AnsiLogger, CYAN, db, debugStringify, hk, or, YELLOW, zb } from 'node-ansi-logger';
65
66
  import { MatterbridgeActivatedCarbonFilterMonitoringServer } from './behaviors/activatedCarbonFilterMonitoringServer.js';
66
67
  import { MatterbridgeBooleanStateConfigurationServer } from './behaviors/booleanStateConfigurationServer.js';
@@ -115,11 +116,13 @@ export class MatterbridgeEndpoint extends Endpoint {
115
116
  vendorName = undefined;
116
117
  productId = undefined;
117
118
  productName = undefined;
119
+ productLabel = undefined;
120
+ productUrl = 'https://matterbridge.io';
118
121
  softwareVersion = undefined;
119
122
  softwareVersionString = undefined;
120
123
  hardwareVersion = undefined;
121
124
  hardwareVersionString = undefined;
122
- productUrl = 'https://www.npmjs.com/package/matterbridge';
125
+ configurationVersion = 1;
123
126
  tagList = undefined;
124
127
  originalId = undefined;
125
128
  name = undefined;
@@ -468,6 +471,13 @@ export class MatterbridgeEndpoint extends Endpoint {
468
471
  uniqueId: device.uniqueId,
469
472
  productId: device.productId,
470
473
  productName: device.productName,
474
+ productLabel: device.productLabel,
475
+ productUrl: device.productUrl,
476
+ configurationVersion: device.configurationVersion,
477
+ softwareVersion: device.softwareVersion,
478
+ softwareVersionString: device.softwareVersionString,
479
+ hardwareVersion: device.hardwareVersion,
480
+ hardwareVersionString: device.hardwareVersionString,
471
481
  vendorId: device.vendorId,
472
482
  vendorName: device.vendorName,
473
483
  deviceTypes: Array.from(device.deviceTypes.values()),
@@ -478,6 +488,8 @@ export class MatterbridgeEndpoint extends Endpoint {
478
488
  Object.keys(device.behaviors.supported).forEach((behaviorName) => {
479
489
  if (behaviorName === 'bridgedDeviceBasicInformation')
480
490
  serialized.clusterServersId.push(BridgedDeviceBasicInformation.id);
491
+ if (behaviorName === 'basicInformation')
492
+ serialized.clusterServersId.push(BasicInformation.id);
481
493
  if (behaviorName === 'powerSource')
482
494
  serialized.clusterServersId.push(PowerSource.id);
483
495
  });
@@ -493,9 +505,18 @@ export class MatterbridgeEndpoint extends Endpoint {
493
505
  device.vendorName = serializedDevice.vendorName;
494
506
  device.productId = serializedDevice.productId;
495
507
  device.productName = serializedDevice.productName;
508
+ device.productLabel = serializedDevice.productLabel;
509
+ device.productUrl = serializedDevice.productUrl;
510
+ device.configurationVersion = serializedDevice.configurationVersion;
511
+ device.softwareVersion = serializedDevice.softwareVersion;
512
+ device.softwareVersionString = serializedDevice.softwareVersionString;
513
+ device.hardwareVersion = serializedDevice.hardwareVersion;
514
+ device.hardwareVersionString = serializedDevice.hardwareVersionString;
496
515
  for (const clusterId of serializedDevice.clusterServersId) {
497
516
  if (clusterId === BridgedDeviceBasicInformation.id)
498
- device.createDefaultBridgedDeviceBasicInformationClusterServer(serializedDevice.deviceName, serializedDevice.serialNumber, serializedDevice.vendorId ?? 0xfff1, serializedDevice.vendorName ?? 'Matterbridge', serializedDevice.productName ?? 'Matterbridge device');
517
+ device.createDefaultBridgedDeviceBasicInformationClusterServer(serializedDevice.deviceName, serializedDevice.serialNumber, serializedDevice.vendorId ?? 0xfff1, serializedDevice.vendorName ?? 'Matterbridge', serializedDevice.productName ?? 'Matterbridge device', serializedDevice.softwareVersion, serializedDevice.softwareVersionString, serializedDevice.hardwareVersion, serializedDevice.hardwareVersionString, serializedDevice.productLabel, serializedDevice.productUrl, serializedDevice.configurationVersion);
518
+ else if (clusterId === BasicInformation.id)
519
+ device.createDefaultBasicInformationClusterServer(serializedDevice.deviceName, serializedDevice.serialNumber, serializedDevice.vendorId ?? 0xfff1, serializedDevice.vendorName ?? 'Matterbridge', serializedDevice.productId ?? 0x8000, serializedDevice.productName ?? 'Matterbridge device', serializedDevice.softwareVersion, serializedDevice.softwareVersionString, serializedDevice.hardwareVersion, serializedDevice.hardwareVersionString, serializedDevice.productLabel, serializedDevice.productUrl, serializedDevice.configurationVersion);
499
520
  else if (clusterId === PowerSource.id)
500
521
  device.createDefaultPowerSourceWiredClusterServer();
501
522
  }
@@ -517,49 +538,56 @@ export class MatterbridgeEndpoint extends Endpoint {
517
538
  this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable), getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplaceability));
518
539
  return this;
519
540
  }
520
- createDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productId = 0x8000, productName = 'Matterbridge device', softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
541
+ createDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productId = 0x8000, productName = 'Matterbridge device', softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0', productLabel = 'Matter Endpoint', productUrl = 'https://matterbridge.io', configurationVersion = 1) {
521
542
  this.log.logName = deviceName;
522
543
  this.deviceName = deviceName;
523
544
  this.serialNumber = serialNumber;
524
545
  this.uniqueId = createUniqueId(deviceName, serialNumber, vendorName, productName);
525
546
  this.productId = productId;
526
547
  this.productName = productName;
548
+ this.productLabel = productLabel;
549
+ this.productUrl = productUrl;
527
550
  this.vendorId = vendorId;
528
551
  this.vendorName = vendorName;
529
552
  this.softwareVersion = softwareVersion;
530
553
  this.softwareVersionString = softwareVersionString;
531
554
  this.hardwareVersion = hardwareVersion;
532
555
  this.hardwareVersionString = hardwareVersionString;
556
+ this.configurationVersion = configurationVersion;
533
557
  return this;
534
558
  }
535
- createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productName = 'Matterbridge device', softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
559
+ createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productName = 'Matterbridge device', softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0', productLabel = 'Matter Bridged Endpoint', productUrl = 'https://matterbridge.io', configurationVersion = 1) {
536
560
  this.log.logName = deviceName;
537
561
  this.deviceName = deviceName;
538
562
  this.serialNumber = serialNumber;
539
563
  this.uniqueId = createUniqueId(deviceName, serialNumber, vendorName, productName);
540
564
  this.productId = undefined;
541
565
  this.productName = productName;
566
+ this.productLabel = productLabel;
567
+ this.productUrl = productUrl;
542
568
  this.vendorId = vendorId;
543
569
  this.vendorName = vendorName;
544
570
  this.softwareVersion = softwareVersion;
545
571
  this.softwareVersionString = softwareVersionString;
546
572
  this.hardwareVersion = hardwareVersion;
547
573
  this.hardwareVersionString = hardwareVersionString;
574
+ this.configurationVersion = configurationVersion;
548
575
  this.behaviors.require(BridgedDeviceBasicInformationServer.enable({
549
576
  events: { leave: true, reachableChanged: true },
550
577
  }), {
551
578
  vendorId: VendorId(vendorId),
552
579
  vendorName: vendorName.slice(0, 32),
553
580
  productName: productName.slice(0, 32),
554
- productUrl: this.productUrl.slice(0, 256),
555
- productLabel: productName.replace(vendorName, '').trim().slice(0, 64),
581
+ productLabel: productLabel.replace(vendorName, '').trim().slice(0, 64),
582
+ productUrl: isValidString(productUrl, 8, 256) && productUrl.startsWith('https://') ? productUrl : 'https://matterbridge.io',
556
583
  nodeLabel: deviceName.slice(0, 32),
557
584
  serialNumber: serialNumber.slice(0, 32),
558
585
  uniqueId: this.uniqueId.slice(0, 32),
559
- softwareVersion: isValidNumber(softwareVersion, 0, UINT32_MAX) ? softwareVersion : undefined,
560
- softwareVersionString: isValidString(softwareVersionString) ? softwareVersionString.slice(0, 64) : undefined,
561
- hardwareVersion: isValidNumber(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : undefined,
562
- hardwareVersionString: isValidString(hardwareVersionString) ? hardwareVersionString.slice(0, 64) : undefined,
586
+ softwareVersion: isValidInteger(softwareVersion, 0, UINT32_MAX) ? softwareVersion : 1,
587
+ softwareVersionString: isValidString(softwareVersionString, 1, 64) ? softwareVersionString : '1.0.0',
588
+ hardwareVersion: isValidInteger(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : 1,
589
+ hardwareVersionString: isValidString(hardwareVersionString, 1, 64) ? hardwareVersionString : '1.0.0',
590
+ configurationVersion: isValidInteger(configurationVersion, 1, UINT32_MAX) ? configurationVersion : 1,
563
591
  reachable: true,
564
592
  });
565
593
  return this;
@@ -9,6 +9,13 @@ export interface SerializedMatterbridgeEndpoint {
9
9
  uniqueId: string;
10
10
  productId?: number;
11
11
  productName?: string;
12
+ productUrl: string;
13
+ productLabel?: string;
14
+ configurationVersion: number;
15
+ softwareVersion?: number;
16
+ softwareVersionString?: string;
17
+ hardwareVersion?: number;
18
+ hardwareVersionString?: string;
12
19
  vendorId?: number;
13
20
  vendorName?: string;
14
21
  deviceTypes: DeviceTypeDefinition[];
@@ -247,7 +247,7 @@ export class MatterbridgePlatform {
247
247
  }
248
248
  if (!device.hasClusterServer(BridgedDeviceBasicInformation.id)) {
249
249
  this.log.debug(`Device with name ${CYAN}${device.deviceName}${db} has no BridgedDeviceBasicInformation cluster. Adding it...`);
250
- device.createDefaultBridgedDeviceBasicInformationClusterServer(device.deviceName, device.serialNumber, device.vendorId, device.vendorName, device.productName, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString);
250
+ device.createDefaultBridgedDeviceBasicInformationClusterServer(device.deviceName, device.serialNumber, device.vendorId, device.vendorName, device.productName, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel === 'Matter Endpoint' ? 'Matter Bridged Endpoint' : device.productLabel, device.productUrl, device.configurationVersion);
251
251
  }
252
252
  }
253
253
  await this.#addBridgedEndpoint?.(this.name, device);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.0-dev-20260716-572b03b",
3
+ "version": "3.10.0-dev-20260716-7f8762c",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -132,10 +132,10 @@
132
132
  },
133
133
  "dependencies": {
134
134
  "@matter/main": "0.17.5",
135
- "@matterbridge/dgram": "3.10.0-dev-20260716-572b03b",
136
- "@matterbridge/thread": "3.10.0-dev-20260716-572b03b",
137
- "@matterbridge/types": "3.10.0-dev-20260716-572b03b",
138
- "@matterbridge/utils": "3.10.0-dev-20260716-572b03b",
135
+ "@matterbridge/dgram": "3.10.0-dev-20260716-7f8762c",
136
+ "@matterbridge/thread": "3.10.0-dev-20260716-7f8762c",
137
+ "@matterbridge/types": "3.10.0-dev-20260716-7f8762c",
138
+ "@matterbridge/utils": "3.10.0-dev-20260716-7f8762c",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.5.2",