@riddix/hamh 2.1.0-alpha.754 → 2.1.0-alpha.756

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.
@@ -124071,6 +124071,14 @@ function computeControllerWarnings(controllers, exposed) {
124071
124071
  }
124072
124072
  return warnings;
124073
124073
  }
124074
+ function controllerWarningsForFabrics(fabrics, exposed) {
124075
+ const controllers = [
124076
+ ...new Set(fabrics.map((f) => classifyController(f.rootVendorId)).filter((c) => c !== void 0))
124077
+ ];
124078
+ if (controllers.length === 0)
124079
+ return [];
124080
+ return computeControllerWarnings(controllers, exposed);
124081
+ }
124074
124082
  var controllerByVendorId, deviceTypeIdSupport, controllerLabels;
124075
124083
  var init_controller_compat = __esm({
124076
124084
  "../common/dist/controller-compat.js"() {
@@ -127121,12 +127129,7 @@ function healthApi(bridgeService, haClient, version2, startTime) {
127121
127129
  const fabrics = data.commissioning?.fabrics ?? [];
127122
127130
  const sessionInfo = b.getSessionInfo();
127123
127131
  const exposed = b.getExposedDeviceTypes();
127124
- const controllers = [
127125
- ...new Set(
127126
- fabrics.map((f) => classifyController(f.rootVendorId)).filter((c) => c !== void 0)
127127
- )
127128
- ];
127129
- const controllerWarnings = controllers.length > 0 ? computeControllerWarnings(controllers, exposed) : [];
127132
+ const controllerWarnings = data.controllerWarnings ?? [];
127130
127133
  const entityDiagnostics = buildEntityDiagnostics(
127131
127134
  exposed,
127132
127135
  data.failedEntities ?? [],
@@ -128353,15 +128356,13 @@ function metricsApi(bridgeService, haClient, haRegistry, startTime) {
128353
128356
  router.get("/", (_, res) => {
128354
128357
  const memoryUsage = process.memoryUsage();
128355
128358
  const bridges = bridgeService.bridges;
128356
- const running = bridges.filter((b) => b.data.status === "running").length;
128357
- const stopped = bridges.filter((b) => b.data.status === "stopped").length;
128358
- const failed = bridges.filter((b) => b.data.status === "failed").length;
128359
- const totalDevices = bridges.reduce(
128360
- (sum, b) => sum + b.data.deviceCount,
128361
- 0
128362
- );
128363
- const totalFabrics = bridges.reduce(
128364
- (sum, b) => sum + (b.data.commissioning?.fabrics?.length ?? 0),
128359
+ const datas = bridges.map((b) => b.data);
128360
+ const running = datas.filter((d) => d.status === "running").length;
128361
+ const stopped = datas.filter((d) => d.status === "stopped").length;
128362
+ const failed = datas.filter((d) => d.status === "failed").length;
128363
+ const totalDevices = datas.reduce((sum, d) => sum + d.deviceCount, 0);
128364
+ const totalFabrics = datas.reduce(
128365
+ (sum, d) => sum + (d.commissioning?.fabrics?.length ?? 0),
128365
128366
  0
128366
128367
  );
128367
128368
  const metrics = {
@@ -128393,15 +128394,13 @@ function metricsApi(bridgeService, haClient, haRegistry, startTime) {
128393
128394
  const memoryUsage = process.memoryUsage();
128394
128395
  const bridges = bridgeService.bridges;
128395
128396
  const uptime2 = Math.floor((Date.now() - startTime) / 1e3);
128396
- const running = bridges.filter((b) => b.data.status === "running").length;
128397
- const stopped = bridges.filter((b) => b.data.status === "stopped").length;
128398
- const failed = bridges.filter((b) => b.data.status === "failed").length;
128399
- const totalDevices = bridges.reduce(
128400
- (sum, b) => sum + b.data.deviceCount,
128401
- 0
128402
- );
128403
- const totalFabrics = bridges.reduce(
128404
- (sum, b) => sum + (b.data.commissioning?.fabrics?.length ?? 0),
128397
+ const datas = bridges.map((b) => b.data);
128398
+ const running = datas.filter((d) => d.status === "running").length;
128399
+ const stopped = datas.filter((d) => d.status === "stopped").length;
128400
+ const failed = datas.filter((d) => d.status === "failed").length;
128401
+ const totalDevices = datas.reduce((sum, d) => sum + d.deviceCount, 0);
128402
+ const totalFabrics = datas.reduce(
128403
+ (sum, d) => sum + (d.commissioning?.fabrics?.length ?? 0),
128405
128404
  0
128406
128405
  );
128407
128406
  const lines = [
@@ -128459,8 +128458,9 @@ function metricsApi(bridgeService, haClient, haRegistry, startTime) {
128459
128458
  ""
128460
128459
  ];
128461
128460
  for (const bridge of bridges) {
128462
- const status3 = bridge.data.status === "running" ? 1 : 0;
128463
- const safeName = bridge.data.name.replace(/[^a-zA-Z0-9_]/g, "_");
128461
+ const data = bridge.data;
128462
+ const status3 = data.status === "running" ? 1 : 0;
128463
+ const safeName = data.name.replace(/[^a-zA-Z0-9_]/g, "_");
128464
128464
  lines.push(
128465
128465
  `# HELP hamh_bridge_status Bridge status (1=running, 0=not running)`,
128466
128466
  `# TYPE hamh_bridge_status gauge`,
@@ -128468,7 +128468,7 @@ function metricsApi(bridgeService, haClient, haRegistry, startTime) {
128468
128468
  "",
128469
128469
  `# HELP hamh_bridge_devices Number of devices on bridge`,
128470
128470
  `# TYPE hamh_bridge_devices gauge`,
128471
- `hamh_bridge_devices{bridge_id="${bridge.id}",bridge_name="${safeName}"} ${bridge.data.deviceCount}`,
128471
+ `hamh_bridge_devices{bridge_id="${bridge.id}",bridge_name="${safeName}"} ${data.deviceCount}`,
128472
128472
  ""
128473
128473
  );
128474
128474
  }
@@ -147233,6 +147233,7 @@ init_esm7();
147233
147233
  import crypto4 from "node:crypto";
147234
147234
 
147235
147235
  // src/services/bridges/bridge-data-provider.ts
147236
+ init_dist();
147236
147237
  init_service();
147237
147238
  import { values as values2 } from "lodash-es";
147238
147239
  var BridgeDataProvider = class extends Service {
@@ -147289,8 +147290,13 @@ var BridgeDataProvider = class extends Service {
147289
147290
  /**
147290
147291
  * @deprecated
147291
147292
  */
147292
- withMetadata(status3, serverNode, deviceCount, failedEntities = []) {
147293
+ withMetadata(status3, serverNode, deviceCount, failedEntities = [], exposedDeviceTypes = []) {
147293
147294
  const commissioning = serverNode.state.commissioning;
147295
+ const fabrics = commissioning ? values2(commissioning.fabrics) : [];
147296
+ const controllerWarnings = controllerWarningsForFabrics(
147297
+ fabrics,
147298
+ exposedDeviceTypes
147299
+ );
147294
147300
  return {
147295
147301
  id: this.id,
147296
147302
  name: this.name,
@@ -147311,7 +147317,7 @@ var BridgeDataProvider = class extends Service {
147311
147317
  discriminator: commissioning.discriminator,
147312
147318
  manualPairingCode: commissioning.pairingCodes.manualPairingCode,
147313
147319
  qrPairingCode: commissioning.pairingCodes.qrPairingCode,
147314
- fabrics: values2(commissioning.fabrics).map((fabric) => ({
147320
+ fabrics: fabrics.map((fabric) => ({
147315
147321
  fabricIndex: fabric.fabricIndex,
147316
147322
  fabricId: Number(fabric.fabricId),
147317
147323
  nodeId: Number(fabric.nodeId),
@@ -147321,7 +147327,8 @@ var BridgeDataProvider = class extends Service {
147321
147327
  }))
147322
147328
  } : void 0,
147323
147329
  deviceCount,
147324
- failedEntities: failedEntities.length > 0 ? failedEntities : void 0
147330
+ failedEntities: failedEntities.length > 0 ? failedEntities : void 0,
147331
+ controllerWarnings: controllerWarnings.length > 0 ? controllerWarnings : void 0
147325
147332
  };
147326
147333
  }
147327
147334
  };
@@ -148436,6 +148443,7 @@ function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
148436
148443
  // src/services/bridges/bridge.ts
148437
148444
  var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
148438
148445
  var DEAD_SESSION_TIMEOUT_MS = 6e4;
148446
+ var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
148439
148447
  var Bridge = class {
148440
148448
  constructor(env, logger234, dataProvider, endpointManager) {
148441
148449
  this.dataProvider = dataProvider;
@@ -148499,7 +148507,8 @@ var Bridge = class {
148499
148507
  this.status,
148500
148508
  this.server,
148501
148509
  this.aggregator.parts.size,
148502
- this.endpointManager.failedEntities
148510
+ this.endpointManager.failedEntities,
148511
+ this.getExposedDeviceTypes()
148503
148512
  );
148504
148513
  }
148505
148514
  getSessionInfo() {
@@ -148685,6 +148694,7 @@ ${e?.toString()}`);
148685
148694
  async runStop(code, reason) {
148686
148695
  this.unwireSessionDiagnostics();
148687
148696
  this.stopAutoForceSync();
148697
+ await this.closeActiveSessions();
148688
148698
  await this.endpointManager.stopPlugins();
148689
148699
  this.endpointManager.stopObserving();
148690
148700
  try {
@@ -148715,6 +148725,7 @@ ${e?.toString()}`);
148715
148725
  this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS / 1e3}s`);
148716
148726
  }
148717
148727
  wireSessionDiagnostics() {
148728
+ this.unwireSessionDiagnostics();
148718
148729
  try {
148719
148730
  const sessionManager = this.server.env.get(SessionManager);
148720
148731
  seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
@@ -148870,6 +148881,42 @@ ${e?.toString()}`);
148870
148881
  } catch {
148871
148882
  }
148872
148883
  }
148884
+ // Close every active session on shutdown so each controller is told to drop
148885
+ // its CASE session instead of being left with a stale one. Mirrors the
148886
+ // dead-session close, but covers all sessions and waits (capped) so the
148887
+ // close actually reaches the peer before the server is canceled.
148888
+ async closeActiveSessions() {
148889
+ try {
148890
+ const sessionManager = this.server.env.get(SessionManager);
148891
+ const closes = [];
148892
+ for (const s of [...sessionManager.sessions]) {
148893
+ if (s.isClosing) {
148894
+ continue;
148895
+ }
148896
+ closes.push(
148897
+ s.initiateClose().catch(() => {
148898
+ return s.initiateForceClose({
148899
+ cause: new Error("graceful close failed, forcing")
148900
+ });
148901
+ })
148902
+ );
148903
+ }
148904
+ if (closes.length === 0) {
148905
+ return;
148906
+ }
148907
+ this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
148908
+ let timer;
148909
+ const timeout = new Promise((resolve11) => {
148910
+ timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS);
148911
+ });
148912
+ try {
148913
+ await Promise.race([Promise.allSettled(closes), timeout]);
148914
+ } finally {
148915
+ clearTimeout(timer);
148916
+ }
148917
+ } catch {
148918
+ }
148919
+ }
148873
148920
  /**
148874
148921
  * Force a fresh mDNS operational advertisement after session cleanup.
148875
148922
  * matter.js DeviceAdvertiser only re-announces when a subscription is
@@ -164341,6 +164388,7 @@ init_dist();
164341
164388
  init_diagnostic_event_bus();
164342
164389
  var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
164343
164390
  var DEAD_SESSION_TIMEOUT_MS2 = 6e4;
164391
+ var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
164344
164392
  function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOString()) {
164345
164393
  return { ...state, last_updated: now };
164346
164394
  }
@@ -164387,7 +164435,8 @@ var ServerModeBridge = class {
164387
164435
  this.status,
164388
164436
  this.server,
164389
164437
  this.endpointManager.devices.length,
164390
- this.endpointManager.failedEntities
164438
+ this.endpointManager.failedEntities,
164439
+ this.getExposedDeviceTypes()
164391
164440
  );
164392
164441
  }
164393
164442
  /**
@@ -164504,6 +164553,7 @@ ${e?.toString()}`);
164504
164553
  this.unwireSessionDiagnostics();
164505
164554
  this.cancelWarmStart();
164506
164555
  this.stopAutoForceSync();
164556
+ await this.closeActiveSessions();
164507
164557
  this.endpointManager.stopObserving();
164508
164558
  try {
164509
164559
  await this.server.cancel();
@@ -164573,6 +164623,7 @@ ${e?.toString()}`);
164573
164623
  this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS2 / 1e3}s`);
164574
164624
  }
164575
164625
  wireSessionDiagnostics() {
164626
+ this.unwireSessionDiagnostics();
164576
164627
  try {
164577
164628
  const sessionManager = this.server.env.get(SessionManager);
164578
164629
  this.sessionDiagHandler = (session) => {
@@ -164693,6 +164744,42 @@ ${e?.toString()}`);
164693
164744
  } catch {
164694
164745
  }
164695
164746
  }
164747
+ // Close every active session on shutdown so each controller is told to drop
164748
+ // its CASE session instead of being left with a stale one. Mirrors the
164749
+ // dead-session close, but covers all sessions and waits (capped) so the
164750
+ // close actually reaches the peer before the server is canceled.
164751
+ async closeActiveSessions() {
164752
+ try {
164753
+ const sessionManager = this.server.env.get(SessionManager);
164754
+ const closes = [];
164755
+ for (const s of [...sessionManager.sessions]) {
164756
+ if (s.isClosing) {
164757
+ continue;
164758
+ }
164759
+ closes.push(
164760
+ s.initiateClose().catch(() => {
164761
+ return s.initiateForceClose({
164762
+ cause: new Error("graceful close failed, forcing")
164763
+ });
164764
+ })
164765
+ );
164766
+ }
164767
+ if (closes.length === 0) {
164768
+ return;
164769
+ }
164770
+ this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
164771
+ let timer;
164772
+ const timeout = new Promise((resolve11) => {
164773
+ timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2);
164774
+ });
164775
+ try {
164776
+ await Promise.race([Promise.allSettled(closes), timeout]);
164777
+ } finally {
164778
+ clearTimeout(timer);
164779
+ }
164780
+ } catch {
164781
+ }
164782
+ }
164696
164783
  /**
164697
164784
  * Force a fresh mDNS operational advertisement after session cleanup.
164698
164785
  * matter.js DeviceAdvertiser only re-announces when a subscription is
@@ -165918,6 +166005,14 @@ async function startHandler(startOptions, webUiDist) {
165918
166005
  if (shuttingDown) return;
165919
166006
  shuttingDown = true;
165920
166007
  console.log(`Received ${signal}, shutting down gracefully...`);
166008
+ try {
166009
+ await Promise.race([
166010
+ bridgeService.stopAll(),
166011
+ new Promise((resolve11) => setTimeout(resolve11, 1e4))
166012
+ ]);
166013
+ } catch (e) {
166014
+ console.warn("Stopping bridges during shutdown failed:", e);
166015
+ }
165921
166016
  try {
165922
166017
  await Promise.race([
165923
166018
  backupService.createAutoBackup(),