@riddix/hamh 2.1.0-alpha.839 → 2.1.0-alpha.841

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.
@@ -1767,8 +1767,8 @@ var init_Cancelable = __esm({
1767
1767
  };
1768
1768
  return result;
1769
1769
  }
1770
- static set logger(logger248) {
1771
- this.#logger = logger248;
1770
+ static set logger(logger251) {
1771
+ this.#logger = logger251;
1772
1772
  }
1773
1773
  static get logger() {
1774
1774
  return this.#logger;
@@ -3475,13 +3475,13 @@ var init_Logger = __esm({
3475
3475
  *
3476
3476
  * @deprecated use {@link destinations}
3477
3477
  */
3478
- static addLogger(identifier, logger248, options) {
3478
+ static addLogger(identifier, logger251, options) {
3479
3479
  if (identifier in this.destinations) {
3480
3480
  throw new ImplementationError(`Logger "${identifier}" already exists`);
3481
3481
  }
3482
3482
  const dest = LogDestination({ name: identifier });
3483
3483
  const legacy = adaptDestinationToLegacy(dest);
3484
- legacy.log = logger248;
3484
+ legacy.log = logger251;
3485
3485
  if (options?.defaultLogLevel !== void 0) {
3486
3486
  legacy.defaultLogLevel = options.defaultLogLevel;
3487
3487
  }
@@ -5687,8 +5687,8 @@ var init_Construction = __esm({
5687
5687
  }
5688
5688
  }
5689
5689
  #unhandledError(...args) {
5690
- const logger248 = Logger.get(this.#subject.constructor.name);
5691
- logger248.error(...args);
5690
+ const logger251 = Logger.get(this.#subject.constructor.name);
5691
+ logger251.error(...args);
5692
5692
  }
5693
5693
  #createErrorHandler(name) {
5694
5694
  return (e) => {
@@ -11712,18 +11712,18 @@ var init_FileHandleTracker = __esm({
11712
11712
  logger12 = Logger.get("FileHandleTracker");
11713
11713
  ((FileHandleTracker2) => {
11714
11714
  const openHandles = /* @__PURE__ */ new Set();
11715
- const registry2 = new FinalizationRegistry((info) => {
11715
+ const registry3 = new FinalizationRegistry((info) => {
11716
11716
  logger12.warn(`File handle GC'd without close: "${info.purpose}" for ${info.path}`);
11717
11717
  });
11718
11718
  function register(handle) {
11719
11719
  const info = { purpose: handle.purpose, path: handle.path };
11720
- registry2.register(handle, info, handle);
11720
+ registry3.register(handle, info, handle);
11721
11721
  const ref = new WeakRef(handle);
11722
11722
  openHandles.add(ref);
11723
11723
  }
11724
11724
  FileHandleTracker2.register = register;
11725
11725
  function unregister(handle) {
11726
- registry2.unregister(handle);
11726
+ registry3.unregister(handle);
11727
11727
  for (const ref of openHandles) {
11728
11728
  if (ref.deref() === handle) {
11729
11729
  openHandles.delete(ref);
@@ -64070,8 +64070,8 @@ var init_NodeSession = __esm({
64070
64070
  return session?.type === SessionType.Unicast;
64071
64071
  }
64072
64072
  NodeSession2.is = is;
64073
- function logNew(logger248, operation, session, messenger, fabric, peerNodeId) {
64074
- logger248.info(
64073
+ function logNew(logger251, operation, session, messenger, fabric, peerNodeId) {
64074
+ logger251.info(
64075
64075
  session.via,
64076
64076
  `${operation} session with`,
64077
64077
  Diagnostic.strong(PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId: peerNodeId }).toString()),
@@ -117714,11 +117714,11 @@ var init_Api = __esm({
117714
117714
  }
117715
117715
  Api2.resourceFor = resourceFor;
117716
117716
  function log(level, facility, id, ...message) {
117717
- let logger248 = loggers.get(facility);
117718
- if (!logger248) {
117719
- loggers.set(facility, logger248 = Logger.get(facility));
117717
+ let logger251 = loggers.get(facility);
117718
+ if (!logger251) {
117719
+ loggers.set(facility, logger251 = Logger.get(facility));
117720
117720
  }
117721
- logger248[level](Diagnostic.via(id || "(anon)"), message);
117721
+ logger251[level](Diagnostic.via(id || "(anon)"), message);
117722
117722
  }
117723
117723
  Api2.log = log;
117724
117724
  function logRequest(facility, id, method, target) {
@@ -132631,10 +132631,10 @@ var init_home_assistant_actions = __esm({
132631
132631
  circuitBreakerResetMs: 3e4
132632
132632
  };
132633
132633
  HomeAssistantActions = class extends Service {
132634
- constructor(logger248, client, config11) {
132634
+ constructor(logger251, client, config11) {
132635
132635
  super("HomeAssistantActions");
132636
132636
  this.client = client;
132637
- this.log = logger248.get(this);
132637
+ this.log = logger251.get(this);
132638
132638
  this.config = { ...defaultConfig, ...config11 };
132639
132639
  this.circuitBreaker = new CircuitBreaker(
132640
132640
  this.config.circuitBreakerThreshold,
@@ -132980,10 +132980,10 @@ var DiagnosticService = class {
132980
132980
  };
132981
132981
 
132982
132982
  // src/api/access-log.ts
132983
- function accessLogger(logger248) {
132983
+ function accessLogger(logger251) {
132984
132984
  return (req, res, next) => {
132985
132985
  res.on("finish", () => {
132986
- logger248.debug(
132986
+ logger251.debug(
132987
132987
  `${req.method} ${decodeURI(req.originalUrl)} ${res.statusCode} ${res.statusMessage} from ${req.socket.remoteAddress}`
132988
132988
  );
132989
132989
  });
@@ -136617,7 +136617,7 @@ var BLOCKED_PREFIXES = [
136617
136617
  function pluginApi(bridgeService, storageLocation) {
136618
136618
  const router = express14.Router();
136619
136619
  const installer = new PluginInstaller(storageLocation);
136620
- const registry2 = new PluginRegistry(storageLocation);
136620
+ const registry3 = new PluginRegistry(storageLocation);
136621
136621
  router.get("/", (_req, res) => {
136622
136622
  const result = [];
136623
136623
  for (const bridge of bridgeService.bridges) {
@@ -136702,7 +136702,7 @@ function pluginApi(bridgeService, storageLocation) {
136702
136702
  res.json({ success: true, pluginName, reset: true });
136703
136703
  });
136704
136704
  router.get("/installed", (_req, res) => {
136705
- const registered = registry2.getAll();
136705
+ const registered = registry3.getAll();
136706
136706
  const npmInstalled = installer.listInstalled();
136707
136707
  const result = registered.map((entry) => {
136708
136708
  const npm = npmInstalled.find((p) => p.name === entry.packageName);
@@ -136732,7 +136732,7 @@ function pluginApi(bridgeService, storageLocation) {
136732
136732
  });
136733
136733
  return;
136734
136734
  }
136735
- registry2.add(packageName, config11 ?? {});
136735
+ registry3.add(packageName, config11 ?? {});
136736
136736
  res.json({
136737
136737
  success: true,
136738
136738
  packageName,
@@ -136760,7 +136760,7 @@ function pluginApi(bridgeService, storageLocation) {
136760
136760
  });
136761
136761
  return;
136762
136762
  }
136763
- registry2.remove(packageName);
136763
+ registry3.remove(packageName);
136764
136764
  res.json({
136765
136765
  success: true,
136766
136766
  packageName,
@@ -136800,7 +136800,7 @@ function pluginApi(bridgeService, storageLocation) {
136800
136800
  });
136801
136801
  return;
136802
136802
  }
136803
- registry2.add(result.packageName, {});
136803
+ registry3.add(result.packageName, {});
136804
136804
  res.json({
136805
136805
  success: true,
136806
136806
  packageName: result.packageName,
@@ -136833,7 +136833,7 @@ function pluginApi(bridgeService, storageLocation) {
136833
136833
  });
136834
136834
  return;
136835
136835
  }
136836
- registry2.add(result.packageName, {});
136836
+ registry3.add(result.packageName, {});
136837
136837
  res.json({
136838
136838
  success: true,
136839
136839
  packageName: result.packageName,
@@ -137530,7 +137530,7 @@ var WebSocketApi = class {
137530
137530
 
137531
137531
  // src/api/web-api.ts
137532
137532
  var WebApi = class extends Service {
137533
- constructor(logger248, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
137533
+ constructor(logger251, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
137534
137534
  super("WebApi");
137535
137535
  this.bridgeService = bridgeService;
137536
137536
  this.haClient = haClient;
@@ -137542,8 +137542,8 @@ var WebApi = class extends Service {
137542
137542
  this.settingsStorage = settingsStorage;
137543
137543
  this.backupService = backupService;
137544
137544
  this.props = props;
137545
- this.logger = logger248;
137546
- this.log = logger248.get(this);
137545
+ this.logger = logger251;
137546
+ this.log = logger251.get(this);
137547
137547
  this.accessLogger = accessLogger(this.log.createChild("Access Log"));
137548
137548
  this.startTime = Date.now();
137549
137549
  this.wsApi = new WebSocketApi(
@@ -138734,10 +138734,10 @@ function isTransientConnectError(reason) {
138734
138734
  return msg.includes("socket hang up") || msg.includes("tls") || msg.includes("TLS");
138735
138735
  }
138736
138736
  var HomeAssistantClient = class extends Service {
138737
- constructor(logger248, options) {
138737
+ constructor(logger251, options) {
138738
138738
  super("HomeAssistantClient");
138739
138739
  this.options = options;
138740
- this.log = logger248.get(this);
138740
+ this.log = logger251.get(this);
138741
138741
  }
138742
138742
  options;
138743
138743
  static Options = /* @__PURE__ */ Symbol.for("HomeAssistantClientProps");
@@ -156524,8 +156524,8 @@ var PluginManager = class {
156524
156524
  this.storageDir = storageDir;
156525
156525
  this.homeAssistant = homeAssistant;
156526
156526
  }
156527
- setRegistry(registry2) {
156528
- this.registry = registry2;
156527
+ setRegistry(registry3) {
156528
+ this.registry = registry3;
156529
156529
  }
156530
156530
  /**
156531
156531
  * Load and register a built-in plugin instance.
@@ -157335,11 +157335,11 @@ var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
157335
157335
  var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
157336
157336
  var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
157337
157337
  var Bridge = class {
157338
- constructor(env, logger248, dataProvider, endpointManager, serverOptions) {
157338
+ constructor(env, logger251, dataProvider, endpointManager, serverOptions) {
157339
157339
  this.dataProvider = dataProvider;
157340
157340
  this.endpointManager = endpointManager;
157341
157341
  this.serverOptions = serverOptions;
157342
- this.log = logger248.get(`Bridge / ${dataProvider.id}`);
157342
+ this.log = logger251.get(`Bridge / ${dataProvider.id}`);
157343
157343
  this.server = new BridgeServerNode(
157344
157344
  env,
157345
157345
  this.dataProvider,
@@ -158471,9 +158471,9 @@ function resolveBatteryPercent(state) {
158471
158471
  return BATTERY_ENUM_PERCENT[state.toLowerCase()] ?? null;
158472
158472
  }
158473
158473
  var EntityStateProvider = class extends Service {
158474
- constructor(registry2) {
158474
+ constructor(registry3) {
158475
158475
  super("EntityStateProvider");
158476
- this.registry = registry2;
158476
+ this.registry = registry3;
158477
158477
  }
158478
158478
  registry;
158479
158479
  /**
@@ -160391,11 +160391,11 @@ function createEndpointId2(entityId, customName) {
160391
160391
  const baseName = customName || entityId;
160392
160392
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
160393
160393
  }
160394
- function buildEntityPayload(registry2, entityId) {
160395
- const state = registry2.initialState(entityId);
160394
+ function buildEntityPayload(registry3, entityId) {
160395
+ const state = registry3.initialState(entityId);
160396
160396
  if (!state) return void 0;
160397
- const entity = registry2.entity(entityId);
160398
- const deviceRegistry = registry2.deviceOf(entityId);
160397
+ const entity = registry3.entity(entityId);
160398
+ const deviceRegistry = registry3.deviceOf(entityId);
160399
160399
  return {
160400
160400
  entity_id: entityId,
160401
160401
  state,
@@ -160410,8 +160410,8 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
160410
160410
  lastStates = /* @__PURE__ */ new Map();
160411
160411
  debouncedUpdates = /* @__PURE__ */ new Map();
160412
160412
  static async create(config11) {
160413
- const { registry: registry2, primaryEntityId } = config11;
160414
- const primaryPayload = buildEntityPayload(registry2, primaryEntityId);
160413
+ const { registry: registry3, primaryEntityId } = config11;
160414
+ const primaryPayload = buildEntityPayload(registry3, primaryEntityId);
160415
160415
  if (!primaryPayload) return void 0;
160416
160416
  const airPurifierAttributes = primaryPayload.state.attributes;
160417
160417
  const supportedFeatures = airPurifierAttributes.supported_features ?? 0;
@@ -160499,7 +160499,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
160499
160499
  let tempSub;
160500
160500
  if (config11.temperatureEntityId) {
160501
160501
  const tempPayload = buildEntityPayload(
160502
- registry2,
160502
+ registry3,
160503
160503
  config11.temperatureEntityId
160504
160504
  );
160505
160505
  if (tempPayload) {
@@ -160514,7 +160514,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
160514
160514
  }
160515
160515
  let humSub;
160516
160516
  if (config11.humidityEntityId) {
160517
- const humPayload = buildEntityPayload(registry2, config11.humidityEntityId);
160517
+ const humPayload = buildEntityPayload(registry3, config11.humidityEntityId);
160518
160518
  if (humPayload) {
160519
160519
  humSub = new Endpoint(
160520
160520
  HumiditySubType.set({
@@ -161968,14 +161968,14 @@ function createEndpointId3(entityId, customName) {
161968
161968
  const baseName = customName || entityId;
161969
161969
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
161970
161970
  }
161971
- function buildEntityPayload2(registry2, entityId) {
161972
- const state = registry2.initialState(entityId);
161971
+ function buildEntityPayload2(registry3, entityId) {
161972
+ const state = registry3.initialState(entityId);
161973
161973
  if (!state) return void 0;
161974
161974
  return {
161975
161975
  entity_id: entityId,
161976
161976
  state,
161977
- registry: registry2.entity(entityId),
161978
- deviceRegistry: registry2.deviceOf(entityId)
161977
+ registry: registry3.entity(entityId),
161978
+ deviceRegistry: registry3.deviceOf(entityId)
161979
161979
  };
161980
161980
  }
161981
161981
  var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpoint {
@@ -161985,8 +161985,8 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
161985
161985
  lastStates = /* @__PURE__ */ new Map();
161986
161986
  debouncedUpdates = /* @__PURE__ */ new Map();
161987
161987
  static async create(config11) {
161988
- const { registry: registry2, primaryEntityId } = config11;
161989
- const payload = buildEntityPayload2(registry2, primaryEntityId);
161988
+ const { registry: registry3, primaryEntityId } = config11;
161989
+ const payload = buildEntityPayload2(registry3, primaryEntityId);
161990
161990
  if (!payload) return void 0;
161991
161991
  const endpointId = config11.endpointId ?? createEndpointId3(primaryEntityId, config11.customName);
161992
161992
  const mapping = config11.mapping ?? {
@@ -162244,11 +162244,11 @@ function createEndpointId4(entityId, customName) {
162244
162244
  const baseName = customName || entityId;
162245
162245
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
162246
162246
  }
162247
- function buildEntityPayload3(registry2, entityId) {
162248
- const state = registry2.initialStateIncludingUnfiltered(entityId);
162247
+ function buildEntityPayload3(registry3, entityId) {
162248
+ const state = registry3.initialStateIncludingUnfiltered(entityId);
162249
162249
  if (!state) return void 0;
162250
- const entity = registry2.entityIncludingUnfiltered(entityId);
162251
- const deviceRegistry = registry2.deviceOfIncludingUnfiltered(entityId);
162250
+ const entity = registry3.entityIncludingUnfiltered(entityId);
162251
+ const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
162252
162252
  return {
162253
162253
  entity_id: entityId,
162254
162254
  state,
@@ -162263,8 +162263,8 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
162263
162263
  lastStates = /* @__PURE__ */ new Map();
162264
162264
  debouncedUpdates = /* @__PURE__ */ new Map();
162265
162265
  static async create(config11) {
162266
- const { registry: registry2, primaryEntityId } = config11;
162267
- const primaryPayload = buildEntityPayload3(registry2, primaryEntityId);
162266
+ const { registry: registry3, primaryEntityId } = config11;
162267
+ const primaryPayload = buildEntityPayload3(registry3, primaryEntityId);
162268
162268
  if (!primaryPayload) return void 0;
162269
162269
  let parentType = BridgedNodeEndpoint.with(
162270
162270
  BasicInformationServer2,
@@ -162308,7 +162308,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
162308
162308
  parts.push(tempSub);
162309
162309
  let humSub;
162310
162310
  if (config11.humidityEntityId) {
162311
- const humPayload = buildEntityPayload3(registry2, config11.humidityEntityId);
162311
+ const humPayload = buildEntityPayload3(registry3, config11.humidityEntityId);
162312
162312
  if (humPayload) {
162313
162313
  humSub = new Endpoint(
162314
162314
  HumiditySubType2.set({
@@ -162322,7 +162322,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
162322
162322
  let pressSub;
162323
162323
  if (config11.pressureEntityId) {
162324
162324
  const pressPayload = buildEntityPayload3(
162325
- registry2,
162325
+ registry3,
162326
162326
  config11.pressureEntityId
162327
162327
  );
162328
162328
  if (pressPayload) {
@@ -169774,8 +169774,8 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
169774
169774
  #findIdentifyButton(homeAssistant) {
169775
169775
  const deviceId = homeAssistant.entity.registry?.device_id;
169776
169776
  if (!deviceId) return void 0;
169777
- const registry2 = this.env.get(HomeAssistantRegistry);
169778
- for (const entity of Object.values(registry2.entities)) {
169777
+ const registry3 = this.env.get(HomeAssistantRegistry);
169778
+ for (const entity of Object.values(registry3.entities)) {
169779
169779
  if (entity.device_id !== deviceId) continue;
169780
169780
  if (!entity.entity_id.startsWith("button.")) continue;
169781
169781
  const uniqueId = entity.unique_id ?? "";
@@ -172178,11 +172178,11 @@ function createEndpointId5(entityId, customName) {
172178
172178
  const baseName = customName || entityId;
172179
172179
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
172180
172180
  }
172181
- function buildEntityPayload4(registry2, entityId) {
172182
- const state = registry2.initialStateIncludingUnfiltered(entityId);
172181
+ function buildEntityPayload4(registry3, entityId) {
172182
+ const state = registry3.initialStateIncludingUnfiltered(entityId);
172183
172183
  if (!state) return void 0;
172184
- const entity = registry2.entityIncludingUnfiltered(entityId);
172185
- const deviceRegistry = registry2.deviceOfIncludingUnfiltered(entityId);
172184
+ const entity = registry3.entityIncludingUnfiltered(entityId);
172185
+ const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
172186
172186
  return {
172187
172187
  entity_id: entityId,
172188
172188
  state,
@@ -172198,8 +172198,8 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
172198
172198
  lastMappedStates = /* @__PURE__ */ new Map();
172199
172199
  debouncedUpdates = /* @__PURE__ */ new Map();
172200
172200
  static async create(config11) {
172201
- const { registry: registry2, primaryEntityId, composedEntities } = config11;
172202
- const primaryPayload = buildEntityPayload4(registry2, primaryEntityId);
172201
+ const { registry: registry3, primaryEntityId, composedEntities } = config11;
172202
+ const primaryPayload = buildEntityPayload4(registry3, primaryEntityId);
172203
172203
  if (!primaryPayload) return void 0;
172204
172204
  let parentType = BridgedNodeEndpoint.with(
172205
172205
  BasicInformationServer2,
@@ -172241,7 +172241,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
172241
172241
  primarySubPayload,
172242
172242
  primaryMapping,
172243
172243
  config11.areaName,
172244
- { vacuumOnOff: registry2.isVacuumOnOffEnabled() }
172244
+ { vacuumOnOff: registry3.isVacuumOnOffEnabled() }
172245
172245
  );
172246
172246
  if (!primaryType) {
172247
172247
  logger242.warn(
@@ -172257,7 +172257,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
172257
172257
  for (let i = 0; i < composedEntities.length; i++) {
172258
172258
  const sub = composedEntities[i];
172259
172259
  if (!sub.entityId) continue;
172260
- const subPayload = buildEntityPayload4(registry2, sub.entityId);
172260
+ const subPayload = buildEntityPayload4(registry3, sub.entityId);
172261
172261
  if (!subPayload) {
172262
172262
  logger242.warn(
172263
172263
  `Cannot find state for composed sub-entity ${sub.entityId}, it does not exist in Home Assistant (removed or renamed?)`
@@ -172419,32 +172419,32 @@ function asStandaloneEndpointType(type) {
172419
172419
  // src/matter/endpoints/legacy/legacy-endpoint.ts
172420
172420
  var logger243 = Logger.get("LegacyEndpoint");
172421
172421
  var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172422
- static async create(registry2, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
172423
- const deviceRegistry = registry2.deviceOf(entityId);
172424
- let state = registry2.initialState(entityId);
172425
- const entity = registry2.entity(entityId);
172422
+ static async create(registry3, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
172423
+ const deviceRegistry = registry3.deviceOf(entityId);
172424
+ let state = registry3.initialState(entityId);
172425
+ const entity = registry3.entity(entityId);
172426
172426
  if (!state) {
172427
172427
  return;
172428
172428
  }
172429
- if (registry2.isAutoBatteryMappingEnabled() && registry2.isBatteryEntityUsed(entityId)) {
172429
+ if (registry3.isAutoBatteryMappingEnabled() && registry3.isBatteryEntityUsed(entityId)) {
172430
172430
  logger243.debug(
172431
172431
  `Skipping ${entityId} - already auto-assigned as battery to another device`
172432
172432
  );
172433
172433
  return;
172434
172434
  }
172435
- if (registry2.isAutoHumidityMappingEnabled() && registry2.isHumidityEntityUsed(entityId)) {
172435
+ if (registry3.isAutoHumidityMappingEnabled() && registry3.isHumidityEntityUsed(entityId)) {
172436
172436
  logger243.debug(
172437
172437
  `Skipping ${entityId} - already auto-assigned as humidity to a temperature sensor`
172438
172438
  );
172439
172439
  return;
172440
172440
  }
172441
- if (registry2.isAutoPressureMappingEnabled() && registry2.isPressureEntityUsed(entityId)) {
172441
+ if (registry3.isAutoPressureMappingEnabled() && registry3.isPressureEntityUsed(entityId)) {
172442
172442
  logger243.debug(
172443
172443
  `Skipping ${entityId} - already auto-assigned as pressure to a temperature sensor`
172444
172444
  );
172445
172445
  return;
172446
172446
  }
172447
- if (registry2.isAutoComposedDevicesEnabled() && registry2.isComposedSubEntityUsed(entityId)) {
172447
+ if (registry3.isAutoComposedDevicesEnabled() && registry3.isComposedSubEntityUsed(entityId)) {
172448
172448
  logger243.debug(
172449
172449
  `Skipping ${entityId} - already consumed by a composed device`
172450
172450
  );
@@ -172452,10 +172452,10 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172452
172452
  }
172453
172453
  let effectiveMapping = mapping;
172454
172454
  if (entity.device_id) {
172455
- if (registry2.isAutoHumidityMappingEnabled()) {
172455
+ if (registry3.isAutoHumidityMappingEnabled()) {
172456
172456
  const attrs = state.attributes;
172457
172457
  if (!mapping?.humidityEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
172458
- const humidityEntityId = registry2.findHumidityEntityForDevice(
172458
+ const humidityEntityId = registry3.findHumidityEntityForDevice(
172459
172459
  entity.device_id
172460
172460
  );
172461
172461
  if (humidityEntityId && humidityEntityId !== entityId) {
@@ -172464,17 +172464,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172464
172464
  entityId: effectiveMapping?.entityId ?? entityId,
172465
172465
  humidityEntity: humidityEntityId
172466
172466
  };
172467
- registry2.markHumidityEntityUsed(humidityEntityId);
172467
+ registry3.markHumidityEntityUsed(humidityEntityId);
172468
172468
  logger243.debug(
172469
172469
  `Auto-assigned humidity ${humidityEntityId} to ${entityId}`
172470
172470
  );
172471
172471
  }
172472
172472
  }
172473
172473
  }
172474
- if (registry2.isAutoPressureMappingEnabled()) {
172474
+ if (registry3.isAutoPressureMappingEnabled()) {
172475
172475
  const attrs = state.attributes;
172476
172476
  if (!mapping?.pressureEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
172477
- const pressureEntityId = registry2.findPressureEntityForDevice(
172477
+ const pressureEntityId = registry3.findPressureEntityForDevice(
172478
172478
  entity.device_id
172479
172479
  );
172480
172480
  if (pressureEntityId && pressureEntityId !== entityId) {
@@ -172483,7 +172483,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172483
172483
  entityId: effectiveMapping?.entityId ?? entityId,
172484
172484
  pressureEntity: pressureEntityId
172485
172485
  };
172486
- registry2.markPressureEntityUsed(pressureEntityId);
172486
+ registry3.markPressureEntityUsed(pressureEntityId);
172487
172487
  logger243.debug(
172488
172488
  `Auto-assigned pressure ${pressureEntityId} to ${entityId}`
172489
172489
  );
@@ -172491,8 +172491,8 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172491
172491
  }
172492
172492
  }
172493
172493
  const isVacuum = entityId.startsWith("vacuum.");
172494
- if ((registry2.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
172495
- const batteryEntityId = registry2.findBatteryEntityForDevice(
172494
+ if ((registry3.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
172495
+ const batteryEntityId = registry3.findBatteryEntityForDevice(
172496
172496
  entity.device_id
172497
172497
  );
172498
172498
  if (batteryEntityId && batteryEntityId !== entityId) {
@@ -172501,7 +172501,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172501
172501
  entityId: effectiveMapping?.entityId ?? entityId,
172502
172502
  batteryEntity: batteryEntityId
172503
172503
  };
172504
- registry2.markBatteryEntityUsed(batteryEntityId);
172504
+ registry3.markBatteryEntityUsed(batteryEntityId);
172505
172505
  logger243.debug(
172506
172506
  `Auto-assigned battery ${batteryEntityId} to ${entityId}`
172507
172507
  );
@@ -172509,8 +172509,8 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172509
172509
  }
172510
172510
  const alarmDeviceClass = state.attributes.device_class;
172511
172511
  const isSmokeCoAlarm = mapping?.matterDeviceType === "smoke_co_alarm" || entityId.startsWith("binary_sensor.") && (alarmDeviceClass === "smoke" || alarmDeviceClass === "carbon_monoxide" || alarmDeviceClass === "gas");
172512
- if (registry2.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
172513
- const faultEntityId = registry2.findProblemEntityForDevice(
172512
+ if (registry3.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
172513
+ const faultEntityId = registry3.findProblemEntityForDevice(
172514
172514
  entity.device_id
172515
172515
  );
172516
172516
  if (faultEntityId && faultEntityId !== entityId) {
@@ -172525,7 +172525,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172525
172525
  if (!mapping?.powerEntity) {
172526
172526
  const domain = entityId.split(".")[0];
172527
172527
  if (domain === "switch") {
172528
- const powerEntityId = registry2.findPowerEntityForDevice(
172528
+ const powerEntityId = registry3.findPowerEntityForDevice(
172529
172529
  entity.device_id
172530
172530
  );
172531
172531
  if (powerEntityId && powerEntityId !== entityId) {
@@ -172534,7 +172534,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172534
172534
  entityId: effectiveMapping?.entityId ?? entityId,
172535
172535
  powerEntity: powerEntityId
172536
172536
  };
172537
- registry2.markPowerEntityUsed(powerEntityId);
172537
+ registry3.markPowerEntityUsed(powerEntityId);
172538
172538
  logger243.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
172539
172539
  }
172540
172540
  }
@@ -172542,7 +172542,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172542
172542
  if (!mapping?.energyEntity) {
172543
172543
  const domain = entityId.split(".")[0];
172544
172544
  if (domain === "switch") {
172545
- const energyEntityId = registry2.findEnergyEntityForDevice(
172545
+ const energyEntityId = registry3.findEnergyEntityForDevice(
172546
172546
  entity.device_id
172547
172547
  );
172548
172548
  if (energyEntityId && energyEntityId !== entityId) {
@@ -172551,7 +172551,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172551
172551
  entityId: effectiveMapping?.entityId ?? entityId,
172552
172552
  energyEntity: energyEntityId
172553
172553
  };
172554
- registry2.markEnergyEntityUsed(energyEntityId);
172554
+ registry3.markEnergyEntityUsed(energyEntityId);
172555
172555
  logger243.debug(
172556
172556
  `Auto-assigned energy ${energyEntityId} to ${entityId}`
172557
172557
  );
@@ -172559,7 +172559,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172559
172559
  }
172560
172560
  }
172561
172561
  if (entityId.startsWith("vacuum.")) {
172562
- const vacuumEntities = registry2.findVacuumSelectEntities(
172562
+ const vacuumEntities = registry3.findVacuumSelectEntities(
172563
172563
  entity.device_id
172564
172564
  );
172565
172565
  if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
@@ -172603,7 +172603,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172603
172603
  );
172604
172604
  }
172605
172605
  const supportedFeatures = state.attributes.supported_features ?? 0;
172606
- const cleanAreaRooms = await registry2.resolveCleanAreaRooms(
172606
+ const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
172607
172607
  entityId,
172608
172608
  supportedFeatures
172609
172609
  );
@@ -172619,7 +172619,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172619
172619
  }
172620
172620
  const vacAttrs = state.attributes;
172621
172621
  if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
172622
- const valetudoRooms = registry2.findValetudoMapSegments(
172622
+ const valetudoRooms = registry3.findValetudoMapSegments(
172623
172623
  entity.device_id
172624
172624
  );
172625
172625
  if (valetudoRooms.length > 0) {
@@ -172638,7 +172638,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172638
172638
  `Auto-detected ${valetudoRooms.length} Valetudo segments for ${entityId}`
172639
172639
  );
172640
172640
  } else {
172641
- const roborockRooms = await registry2.resolveRoborockRooms(entityId);
172641
+ const roborockRooms = await registry3.resolveRoborockRooms(entityId);
172642
172642
  if (roborockRooms.length > 0) {
172643
172643
  const roomsObj = {};
172644
172644
  for (const r of roborockRooms) {
@@ -172664,10 +172664,10 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172664
172664
  `Composed mappings are not supported in server mode, exposing ${entityId} as a flat standalone endpoint`
172665
172665
  );
172666
172666
  }
172667
- if (!standalone && registry2.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
172668
- const composedAreaName = registry2.getAreaName(entityId);
172667
+ if (!standalone && registry3.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
172668
+ const composedAreaName = registry3.getAreaName(entityId);
172669
172669
  const composed = await UserComposedEndpoint.create({
172670
- registry: registry2,
172670
+ registry: registry3,
172671
172671
  primaryEntityId: entityId,
172672
172672
  mapping: effectiveMapping,
172673
172673
  composedEntities: effectiveMapping.composedEntities,
@@ -172683,12 +172683,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172683
172683
  `User composed device creation failed for ${entityId}, falling back to standalone`
172684
172684
  );
172685
172685
  }
172686
- if (!standalone && registry2.isAutoComposedDevicesEnabled()) {
172686
+ if (!standalone && registry3.isAutoComposedDevicesEnabled()) {
172687
172687
  const attrs = state.attributes;
172688
172688
  if (entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature && (effectiveMapping?.humidityEntity || effectiveMapping?.pressureEntity)) {
172689
- const composedAreaName = registry2.getAreaName(entityId);
172689
+ const composedAreaName = registry3.getAreaName(entityId);
172690
172690
  const composed = await ComposedSensorEndpoint.create({
172691
- registry: registry2,
172691
+ registry: registry3,
172692
172692
  primaryEntityId: entityId,
172693
172693
  humidityEntityId: effectiveMapping?.humidityEntity,
172694
172694
  pressureEntityId: effectiveMapping?.pressureEntity,
@@ -172704,12 +172704,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172704
172704
  }
172705
172705
  const resolvedMatterType = mapping?.matterDeviceType ?? (entityId.startsWith("fan.") ? "fan" : void 0);
172706
172706
  if (resolvedMatterType === "air_purifier") {
172707
- const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry2.findTemperatureEntityForDevice(entity.device_id) : void 0);
172708
- const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry2.findHumidityEntityForDevice(entity.device_id) : void 0);
172707
+ const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry3.findTemperatureEntityForDevice(entity.device_id) : void 0);
172708
+ const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry3.findHumidityEntityForDevice(entity.device_id) : void 0);
172709
172709
  if (temperatureEntityId || humidityEntityId) {
172710
- const composedAreaName = registry2.getAreaName(entityId);
172710
+ const composedAreaName = registry3.getAreaName(entityId);
172711
172711
  const composed = await ComposedAirPurifierEndpoint.create({
172712
- registry: registry2,
172712
+ registry: registry3,
172713
172713
  primaryEntityId: entityId,
172714
172714
  temperatureEntityId,
172715
172715
  humidityEntityId,
@@ -172731,9 +172731,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172731
172731
  if (!standalone && entityId.startsWith("climate.") && effectiveMapping?.climateExposeFan === true) {
172732
172732
  const climateFeatures = state.attributes.supported_features ?? 0;
172733
172733
  if ((climateFeatures & ClimateDeviceFeature.FAN_MODE) !== 0) {
172734
- const composedAreaName = registry2.getAreaName(entityId);
172734
+ const composedAreaName = registry3.getAreaName(entityId);
172735
172735
  const composed = await ComposedClimateFanEndpoint.create({
172736
- registry: registry2,
172736
+ registry: registry3,
172737
172737
  primaryEntityId: entityId,
172738
172738
  mapping: effectiveMapping,
172739
172739
  customName: effectiveMapping?.customName,
@@ -172758,7 +172758,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172758
172758
  let cleaningModeOptions;
172759
172759
  if (entityId.startsWith("vacuum.")) {
172760
172760
  if (effectiveMapping?.cleaningModeEntity) {
172761
- const cmState = registry2.initialState(
172761
+ const cmState = registry3.initialState(
172762
172762
  effectiveMapping.cleaningModeEntity
172763
172763
  );
172764
172764
  cleaningModeOptions = cmState?.attributes?.options;
@@ -172772,13 +172772,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
172772
172772
  ];
172773
172773
  }
172774
172774
  }
172775
- const areaName = registry2.getAreaName(entityId);
172775
+ const areaName = registry3.getAreaName(entityId);
172776
172776
  let type = createLegacyEndpointType(
172777
172777
  payload,
172778
172778
  effectiveMapping,
172779
172779
  areaName,
172780
172780
  {
172781
- vacuumOnOff: registry2.isVacuumOnOffEnabled(),
172781
+ vacuumOnOff: registry3.isVacuumOnOffEnabled(),
172782
172782
  cleaningModeOptions,
172783
172783
  pluginDomainMappings
172784
172784
  },
@@ -172957,18 +172957,186 @@ var CameraAvStreamServer = class extends Base4 {
172957
172957
  })(CameraAvStreamServer || (CameraAvStreamServer = {}));
172958
172958
 
172959
172959
  // src/plugins/builtin/camera/webrtc-provider-server.ts
172960
+ init_esm();
172960
172961
  init_esm7();
172961
172962
  init_esm3();
172963
+
172964
+ // src/plugins/builtin/camera/requestor-client.ts
172965
+ init_esm();
172966
+ init_esm4();
172967
+ var logger244 = Logger.get("CameraWebRtcRequestor");
172968
+ var registry2 = /* @__PURE__ */ new Map();
172969
+ var pendingDeliveries = /* @__PURE__ */ new Map();
172970
+ var invokeTransport = defaultInvoke;
172971
+ function registerRequestor(sessionId, registration) {
172972
+ registry2.set(sessionId, registration);
172973
+ }
172974
+ function unregisterRequestor(sessionId) {
172975
+ registry2.delete(sessionId);
172976
+ const timer = pendingDeliveries.get(sessionId);
172977
+ if (timer) {
172978
+ clearTimeout(timer);
172979
+ pendingDeliveries.delete(sessionId);
172980
+ }
172981
+ }
172982
+ function unregisterAllRequestors() {
172983
+ for (const id of [...registry2.keys()]) unregisterRequestor(id);
172984
+ for (const timer of pendingDeliveries.values()) clearTimeout(timer);
172985
+ pendingDeliveries.clear();
172986
+ }
172987
+ function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
172988
+ const prior = pendingDeliveries.get(sessionId);
172989
+ if (prior) clearTimeout(prior);
172990
+ const owner = registry2.get(sessionId);
172991
+ const timer = setTimeout(() => {
172992
+ void (async () => {
172993
+ try {
172994
+ for (const delayMs of [0, 250, 500]) {
172995
+ if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
172996
+ if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
172997
+ return;
172998
+ }
172999
+ if (await sendAnswer(sessionId, sdp)) {
173000
+ logger244.info(`answer delivered for session ${sessionId}`);
173001
+ return;
173002
+ }
173003
+ }
173004
+ if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
173005
+ return;
173006
+ }
173007
+ logger244.info(
173008
+ `answer delivery failed for session ${sessionId}, giving up`
173009
+ );
173010
+ await onGiveUp();
173011
+ } catch (err) {
173012
+ logger244.info(
173013
+ `answer delivery for session ${sessionId} threw: ${errText(err)}`
173014
+ );
173015
+ } finally {
173016
+ if (pendingDeliveries.get(sessionId) === timer) {
173017
+ pendingDeliveries.delete(sessionId);
173018
+ }
173019
+ }
173020
+ })();
173021
+ }, 0);
173022
+ timer.unref?.();
173023
+ pendingDeliveries.set(sessionId, timer);
173024
+ }
173025
+ function hasRequestor(sessionId) {
173026
+ return registry2.has(sessionId);
173027
+ }
173028
+ function sendAnswer(sessionId, sdp) {
173029
+ return invokeRequestor(sessionId, "answer", {
173030
+ webRtcSessionId: sessionId,
173031
+ sdp
173032
+ });
173033
+ }
173034
+ async function invokeRequestor(sessionId, command, fields) {
173035
+ const registration = registry2.get(sessionId);
173036
+ if (!registration) {
173037
+ logger244.info(`requestor ${command}: no session ${sessionId} registered`);
173038
+ return false;
173039
+ }
173040
+ if (registration.session.isClosed) {
173041
+ logger244.info(`requestor ${command}: session ${sessionId} already closed`);
173042
+ return false;
173043
+ }
173044
+ const request = {
173045
+ endpoint: registration.requestorEndpoint,
173046
+ cluster: WebRtcTransportRequestor3.Cluster,
173047
+ command,
173048
+ fields
173049
+ };
173050
+ try {
173051
+ return await invokeTransport({ registration, request });
173052
+ } catch (err) {
173053
+ logger244.info(
173054
+ `requestor ${command} failed for session ${sessionId}: ${errText(err)}`
173055
+ );
173056
+ return false;
173057
+ }
173058
+ }
173059
+ async function defaultInvoke({
173060
+ registration,
173061
+ request
173062
+ }) {
173063
+ const { session, env } = registration;
173064
+ const exchangeManager = env.get(ExchangeManager);
173065
+ const exchangeProvider = new DedicatedChannelExchangeProvider(
173066
+ exchangeManager,
173067
+ session
173068
+ );
173069
+ const client = new ClientInteraction({ environment: env, exchangeProvider });
173070
+ try {
173071
+ const result = client.invoke(
173072
+ Invoke({
173073
+ commands: [
173074
+ {
173075
+ endpoint: request.endpoint,
173076
+ cluster: request.cluster,
173077
+ command: request.command,
173078
+ // biome-ignore lint/suspicious/noExplicitAny: fields typed per command by caller
173079
+ fields: request.fields
173080
+ }
173081
+ ]
173082
+ })
173083
+ );
173084
+ for await (const chunk of result) {
173085
+ for (const entry of chunk) {
173086
+ if (entry.kind === "cmd-status") {
173087
+ if (entry.status !== Status2.Success) {
173088
+ logger244.info(
173089
+ `requestor ${request.command} status ${entry.status} for session ${String(request.fields.webRtcSessionId)}`
173090
+ );
173091
+ return false;
173092
+ }
173093
+ return true;
173094
+ }
173095
+ }
173096
+ }
173097
+ return true;
173098
+ } finally {
173099
+ await client.close().catch(() => {
173100
+ });
173101
+ }
173102
+ }
173103
+ function errText(err) {
173104
+ return err instanceof Error ? err.message : String(err);
173105
+ }
173106
+
173107
+ // src/plugins/builtin/camera/webrtc-provider-server.ts
173108
+ var logger245 = Logger.get("CameraWebRtc");
173109
+ var nextGlobalSessionId = 0;
173110
+ function mintSessionId() {
173111
+ for (let i = 0; i <= 65534; i++) {
173112
+ const id = nextGlobalSessionId;
173113
+ nextGlobalSessionId = nextGlobalSessionId >= 65534 ? 0 : nextGlobalSessionId + 1;
173114
+ if (!hasRequestor(id)) return id;
173115
+ }
173116
+ return nextGlobalSessionId;
173117
+ }
172962
173118
  var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
172963
173119
  solicitOffer(request) {
172964
- const id = this.state.nextSessionId++;
173120
+ const id = mintSessionId();
172965
173121
  this.trackSession(id, request.streamUsage, request.originatingEndpointId);
172966
- void this.state.bridge.startSession(id, this.state.entityId).catch(() => {
172967
- });
173122
+ logger245.info(
173123
+ `solicitOffer session=${id} (${this.state.entityId}), deferred offer`
173124
+ );
173125
+ void this.state.bridge.startSession(id, this.state.entityId, {
173126
+ iceServers: request.iceServers,
173127
+ iceTransportPolicy: request.iceTransportPolicy
173128
+ }).catch(
173129
+ (err) => logger245.info(
173130
+ `solicitOffer startSession failed for ${this.state.entityId}: ${errText2(err)}`
173131
+ )
173132
+ );
172968
173133
  return { webRtcSessionId: id, deferredOffer: true };
172969
173134
  }
172970
173135
  async provideOffer(request) {
172971
- const id = request.webRtcSessionId ?? this.state.nextSessionId++;
173136
+ const id = request.webRtcSessionId ?? mintSessionId();
173137
+ logger245.info(
173138
+ `provideOffer entry: entityId=${this.state.entityId} session=${id} (sdp ${request.sdp.length} chars)`
173139
+ );
172972
173140
  if (request.webRtcSessionId == null) {
172973
173141
  this.trackSession(
172974
173142
  id,
@@ -172976,37 +173144,96 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
172976
173144
  request.originatingEndpointId ?? EndpointNumber(0)
172977
173145
  );
172978
173146
  }
172979
- const answerSdp = await this.state.bridge.acceptControllerOffer(
172980
- id,
172981
- this.state.entityId,
172982
- request.sdp
173147
+ const requestorEndpoint = request.originatingEndpointId ?? EndpointNumber(0);
173148
+ const session = this.context.session;
173149
+ if (session) {
173150
+ registerRequestor(id, {
173151
+ session,
173152
+ requestorEndpoint,
173153
+ env: this.env
173154
+ });
173155
+ }
173156
+ let answerSdp;
173157
+ try {
173158
+ answerSdp = await this.state.bridge.acceptControllerOffer(
173159
+ id,
173160
+ this.state.entityId,
173161
+ request.sdp,
173162
+ {
173163
+ iceServers: request.iceServers,
173164
+ iceTransportPolicy: request.iceTransportPolicy
173165
+ }
173166
+ );
173167
+ } catch (err) {
173168
+ const message = errText2(err);
173169
+ logger245.info(
173170
+ `provideOffer failed for ${this.state.entityId} session=${id}: ${message}`
173171
+ );
173172
+ unregisterRequestor(id);
173173
+ this.state.currentSessions = this.state.currentSessions.filter(
173174
+ (s) => s.id !== id
173175
+ );
173176
+ throw new StatusResponseError(
173177
+ `WebRTC offer failed: ${message}`,
173178
+ StatusCode.Failure
173179
+ );
173180
+ }
173181
+ logger245.info(
173182
+ `provideOffer answer computed for ${this.state.entityId} session=${id} (${answerSdp.length} chars); delivering via requestor`
172983
173183
  );
172984
- void answerSdp;
173184
+ const bridge = this.state.bridge;
173185
+ const state = this.state;
173186
+ deliverAnswerDeferred(id, answerSdp, async () => {
173187
+ await bridge.endSession(id).catch(() => {
173188
+ });
173189
+ unregisterRequestor(id);
173190
+ try {
173191
+ state.currentSessions = state.currentSessions.filter(
173192
+ (s) => s.id !== id
173193
+ );
173194
+ } catch {
173195
+ }
173196
+ });
172985
173197
  return { webRtcSessionId: id };
172986
173198
  }
172987
173199
  provideAnswer(request) {
173200
+ logger245.info(
173201
+ `provideAnswer session=${request.webRtcSessionId} (sdp ${request.sdp.length} chars, ${this.state.entityId})`
173202
+ );
172988
173203
  return this.state.bridge.acceptControllerAnswer(
172989
173204
  request.webRtcSessionId,
172990
173205
  request.sdp
172991
173206
  );
172992
173207
  }
172993
173208
  async provideIceCandidates(request) {
173209
+ logger245.info(
173210
+ `provideIceCandidates session=${request.webRtcSessionId}: ${request.iceCandidates.length} candidate(s) (${this.state.entityId})`
173211
+ );
172994
173212
  for (const c of request.iceCandidates) {
172995
173213
  await this.state.bridge.addControllerIceCandidate(
172996
173214
  request.webRtcSessionId,
172997
173215
  c.candidate,
172998
- c.sdpMid
173216
+ c.sdpMid,
173217
+ c.sdpmLineIndex
172999
173218
  );
173000
173219
  }
173001
173220
  }
173002
173221
  async endSession(request) {
173222
+ logger245.info(
173223
+ `endSession session=${request.webRtcSessionId} (${this.state.entityId})`
173224
+ );
173003
173225
  await this.state.bridge.endSession(request.webRtcSessionId);
173226
+ unregisterRequestor(request.webRtcSessionId);
173004
173227
  this.state.currentSessions = this.state.currentSessions.filter(
173005
173228
  (s) => s.id !== request.webRtcSessionId
173006
173229
  );
173007
173230
  }
173008
173231
  trackSession(id, streamUsage, peerEndpointId) {
173009
173232
  const session = this.context.session;
173233
+ const fabricIndex = session?.associatedFabric?.fabricIndex;
173234
+ if (fabricIndex == null) {
173235
+ return;
173236
+ }
173010
173237
  this.state.currentSessions = [
173011
173238
  ...this.state.currentSessions,
173012
173239
  {
@@ -173015,16 +173242,18 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
173015
173242
  peerEndpointId,
173016
173243
  streamUsage,
173017
173244
  metadataEnabled: false,
173018
- fabricIndex: session?.associatedFabric?.fabricIndex ?? FabricIndex3(0)
173245
+ fabricIndex
173019
173246
  }
173020
173247
  ];
173021
173248
  }
173022
173249
  };
173250
+ function errText2(err) {
173251
+ return err instanceof Error ? err.message : String(err);
173252
+ }
173023
173253
  ((CameraWebRtcProviderServer2) => {
173024
173254
  class State extends WebRtcTransportProviderServer.State {
173025
173255
  bridge;
173026
173256
  entityId;
173027
- nextSessionId = 1;
173028
173257
  }
173029
173258
  CameraWebRtcProviderServer2.State = State;
173030
173259
  })(CameraWebRtcProviderServer || (CameraWebRtcProviderServer = {}));
@@ -173043,8 +173272,7 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
173043
173272
  webRtcTransportProvider: {
173044
173273
  bridge,
173045
173274
  entityId,
173046
- currentSessions: [],
173047
- nextSessionId: 1
173275
+ currentSessions: []
173048
173276
  },
173049
173277
  cameraAvStreamManagement: {
173050
173278
  bridge,
@@ -173106,128 +173334,232 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
173106
173334
  }
173107
173335
 
173108
173336
  // src/plugins/builtin/camera/webrtc-bridge.ts
173337
+ init_esm();
173109
173338
  import {
173110
173339
  createConnection as createConnection3,
173111
173340
  createLongLivedTokenAuth as createLongLivedTokenAuth2
173112
173341
  } from "home-assistant-js-websocket";
173113
173342
  import { RTCPeerConnection } from "werift";
173343
+ var logger246 = Logger.get("CameraWebRtc");
173344
+ var DEFAULT_HA_WEBRTC_TIMEOUT_MS = 15e3;
173345
+ var haWebRtcTimeoutMs = DEFAULT_HA_WEBRTC_TIMEOUT_MS;
173114
173346
  var WebRtcBridge = class {
173115
- constructor(config11) {
173347
+ constructor(config11, deps = {}) {
173116
173348
  this.config = config11;
173349
+ this.deps = deps;
173117
173350
  }
173118
173351
  config;
173352
+ deps;
173119
173353
  connection;
173120
173354
  sessions = /* @__PURE__ */ new Map();
173121
173355
  async ha() {
173122
173356
  if (this.connection) return this.connection;
173123
- const auth = createLongLivedTokenAuth2(
173124
- this.config.haUrl,
173125
- this.config.haToken
173126
- );
173127
- this.connection = await createConnection3({ auth });
173357
+ if (this.deps.connect) {
173358
+ this.connection = await this.deps.connect(this.config);
173359
+ } else {
173360
+ const auth = createLongLivedTokenAuth2(
173361
+ this.config.haUrl,
173362
+ this.config.haToken
173363
+ );
173364
+ this.connection = await createConnection3({ auth });
173365
+ }
173128
173366
  return this.connection;
173129
173367
  }
173130
173368
  // We offer to the controller; it answers. Pulls HA media first, then forwards.
173131
- async startSession(matterSessionId, entityId) {
173369
+ async startSession(matterSessionId, entityId, ice) {
173132
173370
  const haPeer = new RTCPeerConnection();
173133
- const controllerPeer = new RTCPeerConnection();
173134
- haPeer.onTrack.subscribe((track) => {
173135
- const transceiver = controllerPeer.addTransceiver(
173136
- track.kind,
173137
- { direction: "sendonly" }
173371
+ const controllerPeer = new RTCPeerConnection(
173372
+ this.controllerConfig(entityId, ice)
173373
+ );
173374
+ this.wireStateLogging(haPeer, "haPeer", entityId);
173375
+ this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
173376
+ const senders = /* @__PURE__ */ new Map();
173377
+ senders.set(
173378
+ "video",
173379
+ controllerPeer.addTransceiver("video", { direction: "sendonly" })
173380
+ );
173381
+ this.forwardHaTracks(haPeer, senders, entityId);
173382
+ await this.closeExistingPeers(matterSessionId);
173383
+ this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
173384
+ try {
173385
+ haPeer.addTransceiver("video", { direction: "recvonly" });
173386
+ haPeer.addTransceiver("audio", { direction: "recvonly" });
173387
+ const haOffer = await haPeer.createOffer();
173388
+ await haPeer.setLocalDescription(haOffer);
173389
+ const haOfferSdp = this.localSdp(haPeer);
173390
+ logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
173391
+ const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
173392
+ entityId,
173393
+ haOfferSdp,
173394
+ haPeer
173138
173395
  );
173139
- track.onReceiveRtp.subscribe((rtp) => transceiver.sender.sendRtp(rtp));
173140
- });
173141
- haPeer.addTransceiver("video", { direction: "recvonly" });
173142
- haPeer.addTransceiver("audio", { direction: "recvonly" });
173143
- const haOffer = await haPeer.createOffer();
173144
- await haPeer.setLocalDescription(haOffer);
173145
- const haAnswer = await this.requestHaWebRtc(entityId, haOffer.sdp);
173146
- await haPeer.setRemoteDescription({ type: "answer", sdp: haAnswer });
173147
- const controllerOffer = await controllerPeer.createOffer();
173148
- await controllerPeer.setLocalDescription(controllerOffer);
173149
- const iceCandidates = [];
173150
- controllerPeer.onIceCandidate.subscribe((c) => {
173151
- if (c?.candidate)
173152
- iceCandidates.push({
173153
- candidate: c.candidate,
173154
- sdpMid: c.sdpMid ?? null
173155
- });
173156
- });
173157
- this.sessions.set(matterSessionId, {
173158
- entityId,
173159
- haPeer,
173160
- controllerPeer
173161
- });
173162
- return { sdp: controllerOffer.sdp, iceCandidates };
173396
+ const session = this.sessions.get(matterSessionId);
173397
+ if (session) {
173398
+ session.haSessionId = sessionId;
173399
+ session.haUnsubscribe = unsubscribe;
173400
+ }
173401
+ await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
173402
+ const iceCandidates = [];
173403
+ controllerPeer.onIceCandidate.subscribe((c) => {
173404
+ if (c?.candidate)
173405
+ iceCandidates.push({
173406
+ candidate: c.candidate,
173407
+ sdpMid: c.sdpMid ?? null
173408
+ });
173409
+ });
173410
+ const controllerOffer = await controllerPeer.createOffer();
173411
+ await controllerPeer.setLocalDescription(controllerOffer);
173412
+ const sdp = this.localSdp(controllerPeer);
173413
+ logger246.info(
173414
+ `controller offer created for ${entityId} (${sdp.length} chars)`
173415
+ );
173416
+ return { sdp, iceCandidates };
173417
+ } catch (err) {
173418
+ logger246.info(`startSession failed for ${entityId}: ${errText3(err)}`);
173419
+ await this.endSession(matterSessionId);
173420
+ throw err;
173421
+ }
173163
173422
  }
173164
173423
  // ProvideOffer flow: controller sent its offer. Pull HA media, answer it.
173165
173424
  // Returning the answer to the controller is the unverified Requestor path.
173166
- async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp) {
173425
+ async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp, ice) {
173167
173426
  const haPeer = new RTCPeerConnection();
173168
- const controllerPeer = new RTCPeerConnection();
173169
- haPeer.onTrack.subscribe((track) => {
173170
- const transceiver = controllerPeer.addTransceiver(track.kind, {
173171
- direction: "sendonly"
173172
- });
173173
- track.onReceiveRtp.subscribe((rtp) => transceiver.sender.sendRtp(rtp));
173174
- });
173427
+ const controllerPeer = new RTCPeerConnection(
173428
+ this.controllerConfig(entityId, ice)
173429
+ );
173430
+ this.wireStateLogging(haPeer, "haPeer", entityId);
173431
+ this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
173432
+ const senders = /* @__PURE__ */ new Map();
173433
+ for (const kind of offerKinds(controllerOfferSdp)) {
173434
+ senders.set(
173435
+ kind,
173436
+ controllerPeer.addTransceiver(kind, { direction: "sendonly" })
173437
+ );
173438
+ }
173439
+ this.forwardHaTracks(haPeer, senders, entityId);
173440
+ await this.closeExistingPeers(matterSessionId);
173175
173441
  this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
173176
- haPeer.addTransceiver("video", { direction: "recvonly" });
173177
- haPeer.addTransceiver("audio", { direction: "recvonly" });
173178
- const haOffer = await haPeer.createOffer();
173179
- await haPeer.setLocalDescription(haOffer);
173180
- const haAnswer = await this.requestHaWebRtc(entityId, haOffer.sdp);
173181
- await haPeer.setRemoteDescription({ type: "answer", sdp: haAnswer });
173182
- await controllerPeer.setRemoteDescription({
173183
- type: "offer",
173184
- sdp: controllerOfferSdp
173185
- });
173186
- const answer = await controllerPeer.createAnswer();
173187
- await controllerPeer.setLocalDescription(answer);
173188
- return answer.sdp;
173442
+ try {
173443
+ haPeer.addTransceiver("video", { direction: "recvonly" });
173444
+ haPeer.addTransceiver("audio", { direction: "recvonly" });
173445
+ const haOffer = await haPeer.createOffer();
173446
+ await haPeer.setLocalDescription(haOffer);
173447
+ const haOfferSdp = this.localSdp(haPeer);
173448
+ logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
173449
+ const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
173450
+ entityId,
173451
+ haOfferSdp,
173452
+ haPeer
173453
+ );
173454
+ const session = this.sessions.get(matterSessionId);
173455
+ if (session) {
173456
+ session.haSessionId = sessionId;
173457
+ session.haUnsubscribe = unsubscribe;
173458
+ }
173459
+ await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
173460
+ await controllerPeer.setRemoteDescription({
173461
+ type: "offer",
173462
+ sdp: controllerOfferSdp
173463
+ });
173464
+ const controllerAnswer = await controllerPeer.createAnswer();
173465
+ await controllerPeer.setLocalDescription(controllerAnswer);
173466
+ const answerSdp = this.localSdp(controllerPeer);
173467
+ logger246.info(
173468
+ `controller answer created for ${entityId} (${answerSdp.length} chars)`
173469
+ );
173470
+ return answerSdp;
173471
+ } catch (err) {
173472
+ logger246.info(
173473
+ `acceptControllerOffer failed for ${entityId}: ${errText3(err)}`
173474
+ );
173475
+ await this.endSession(matterSessionId);
173476
+ throw err;
173477
+ }
173189
173478
  }
173190
173479
  /** Apply the Matter controller's SDP answer to the controller peer. */
173191
173480
  async acceptControllerAnswer(matterSessionId, sdp) {
173192
173481
  const session = this.sessions.get(matterSessionId);
173193
173482
  if (!session) return;
173483
+ logger246.debug(`controller answer applied for ${session.entityId}`);
173194
173484
  await session.controllerPeer.setRemoteDescription({ type: "answer", sdp });
173195
173485
  }
173196
173486
  /** Add a remote ICE candidate from the Matter controller. */
173197
- async addControllerIceCandidate(matterSessionId, candidate, sdpMid) {
173487
+ async addControllerIceCandidate(matterSessionId, candidate, sdpMid, sdpMLineIndex) {
173198
173488
  const session = this.sessions.get(matterSessionId);
173199
173489
  if (!session) return;
173490
+ logger246.debug(
173491
+ `controller ICE candidate for ${session.entityId}: ${candidate}`
173492
+ );
173200
173493
  await session.controllerPeer.addIceCandidate({
173201
173494
  candidate,
173202
- sdpMid: sdpMid ?? void 0
173495
+ sdpMid: sdpMid ?? void 0,
173496
+ sdpMLineIndex: sdpMLineIndex ?? void 0
173497
+ });
173498
+ }
173499
+ // Close the peers of a session we are about to overwrite. Leaves no map
173500
+ // entry; the caller replaces it with fresh peers under the same id.
173501
+ async closeExistingPeers(matterSessionId) {
173502
+ const prior = this.sessions.get(matterSessionId);
173503
+ if (!prior) return;
173504
+ logger246.info(
173505
+ `replacing session ${matterSessionId} (${prior.entityId}), closing prior peers`
173506
+ );
173507
+ if (prior.haUnsubscribe) {
173508
+ await Promise.resolve(prior.haUnsubscribe()).catch(
173509
+ (err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
173510
+ );
173511
+ }
173512
+ await prior.haPeer.close().catch(() => {
173513
+ });
173514
+ await prior.controllerPeer.close().catch(() => {
173515
+ });
173516
+ await this.stopHaStream(prior).catch(() => {
173203
173517
  });
173204
173518
  }
173205
173519
  async endSession(matterSessionId) {
173206
173520
  const session = this.sessions.get(matterSessionId);
173207
173521
  if (!session) return;
173522
+ logger246.info(`ending session ${matterSessionId} (${session.entityId})`);
173208
173523
  this.sessions.delete(matterSessionId);
173209
- await session.haPeer.close();
173210
- await session.controllerPeer.close();
173211
- if (session.haSessionId) {
173212
- try {
173213
- const conn = await this.ha();
173214
- await conn.sendMessagePromise({
173215
- type: "camera/webrtc/candidate",
173216
- entity_id: session.entityId,
173217
- session_id: session.haSessionId,
173218
- candidate: { candidate: "" }
173219
- });
173220
- } catch {
173221
- }
173524
+ if (session.haUnsubscribe) {
173525
+ await Promise.resolve(session.haUnsubscribe()).catch(
173526
+ (err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
173527
+ );
173222
173528
  }
173529
+ await session.haPeer.close().catch(() => {
173530
+ });
173531
+ await session.controllerPeer.close().catch(() => {
173532
+ });
173533
+ await this.stopHaStream(session).catch(() => {
173534
+ });
173535
+ }
173536
+ // Tell HA to stop the stream if we tracked an HA session id. Best effort.
173537
+ async stopHaStream(session) {
173538
+ if (!session.haSessionId) return;
173539
+ const conn = await this.ha();
173540
+ await conn.sendMessagePromise({
173541
+ type: "camera/webrtc/candidate",
173542
+ entity_id: session.entityId,
173543
+ session_id: session.haSessionId,
173544
+ candidate: { candidate: "" }
173545
+ });
173223
173546
  }
173224
173547
  // Grab a still JPEG via HA's camera proxy (for CaptureSnapshot).
173225
173548
  async snapshot(entityId) {
173226
173549
  const url2 = `${this.config.haUrl}/api/camera_proxy/${entityId}`;
173227
- const res = await fetch(url2, {
173228
- headers: { Authorization: `Bearer ${this.config.haToken}` }
173229
- });
173550
+ let res;
173551
+ try {
173552
+ res = await fetch(url2, {
173553
+ headers: { Authorization: `Bearer ${this.config.haToken}` }
173554
+ });
173555
+ } catch (err) {
173556
+ logger246.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
173557
+ throw err;
173558
+ }
173230
173559
  if (!res.ok) {
173560
+ logger246.info(
173561
+ `snapshot fetch failed for ${entityId}: camera_proxy ${res.status}`
173562
+ );
173231
173563
  throw new Error(`HA camera_proxy ${entityId}: ${res.status}`);
173232
173564
  }
173233
173565
  return new Uint8Array(await res.arrayBuffer());
@@ -173239,29 +173571,157 @@ var WebRtcBridge = class {
173239
173571
  this.connection?.close();
173240
173572
  this.connection = void 0;
173241
173573
  }
173242
- // Offer to HA's WebRTC, resolve on the first answer event off the same sub.
173243
- async requestHaWebRtc(entityId, offerSdp) {
173574
+ // Forward every track HA sends into the matching pre-added controller sender.
173575
+ forwardHaTracks(haPeer, senders, entityId) {
173576
+ haPeer.onTrack.subscribe((track) => {
173577
+ logger246.info(`onTrack ${track.kind} from HA (${entityId})`);
173578
+ const transceiver = senders.get(track.kind);
173579
+ if (!transceiver) {
173580
+ logger246.info(
173581
+ `no controller transceiver for ${track.kind} (${entityId})`
173582
+ );
173583
+ return;
173584
+ }
173585
+ let firstLogged = false;
173586
+ track.onReceiveRtp.subscribe((rtp) => {
173587
+ if (!firstLogged) {
173588
+ firstLogged = true;
173589
+ logger246.info(`first RTP forwarded for ${track.kind} (${entityId})`);
173590
+ }
173591
+ transceiver.sender.sendRtp(rtp);
173592
+ });
173593
+ });
173594
+ }
173595
+ // Log connection/ice state transitions (these events fire on change only).
173596
+ wireStateLogging(peer, label, entityId) {
173597
+ peer.connectionStateChange.subscribe(
173598
+ (s) => logger246.info(`${label} connectionState=${s} (${entityId})`)
173599
+ );
173600
+ peer.iceConnectionStateChange.subscribe(
173601
+ (s) => logger246.info(`${label} iceConnectionState=${s} (${entityId})`)
173602
+ );
173603
+ }
173604
+ // Map the Matter ICE struct onto werift's config. Only the controller peer
173605
+ // uses controller-supplied servers; the HA peer keeps werift defaults.
173606
+ controllerConfig(entityId, ice) {
173607
+ const iceServers = (ice?.iceServers ?? []).flatMap(
173608
+ (s) => (s.urLs ?? []).map((url2) => ({
173609
+ urls: url2,
173610
+ username: s.username,
173611
+ credential: s.credential
173612
+ }))
173613
+ );
173614
+ if (iceServers.length === 0) {
173615
+ logger246.info(`controller peer using default ICE servers (${entityId})`);
173616
+ return void 0;
173617
+ }
173618
+ const policy = ice?.iceTransportPolicy === "relay" || ice?.iceTransportPolicy === "all" ? ice.iceTransportPolicy : void 0;
173619
+ logger246.info(
173620
+ `controller peer using ${iceServers.length} controller ICE server(s)${policy ? ` policy=${policy}` : ""} (${entityId})`
173621
+ );
173622
+ return policy ? { iceServers, iceTransportPolicy: policy } : { iceServers };
173623
+ }
173624
+ localSdp(peer) {
173625
+ const local = peer.localDescription;
173626
+ if (!local) throw new Error("no local description");
173627
+ return local.sdp;
173628
+ }
173629
+ // Feed an HA trickle-ICE candidate into the HA peer.
173630
+ feedHaCandidate(entityId, haPeer, candidate) {
173631
+ const value = candidate?.candidate;
173632
+ if (!value) return;
173633
+ logger246.debug(`HA ICE candidate for ${entityId}: ${value}`);
173634
+ void haPeer.addIceCandidate({
173635
+ candidate: value,
173636
+ sdpMid: candidate?.sdpMid ?? void 0,
173637
+ sdpMLineIndex: candidate?.sdpMLineIndex ?? void 0
173638
+ }).catch(
173639
+ (e) => logger246.debug(
173640
+ `HA addIceCandidate failed for ${entityId}: ${errText3(e)}`
173641
+ )
173642
+ );
173643
+ }
173644
+ // Offer to HA's WebRTC and resolve on the first answer. Rejects on an error
173645
+ // reply or after a timeout; keeps the subscription open past the answer so HA
173646
+ // trickle candidates keep flowing into the HA peer until the session ends.
173647
+ async requestHaWebRtc(entityId, offerSdp, haPeer) {
173244
173648
  const conn = await this.ha();
173245
- return new Promise((resolve11, reject) => {
173649
+ let unsubscribe = () => {
173650
+ };
173651
+ let sessionId;
173652
+ const answer = await new Promise((resolve11, reject) => {
173246
173653
  let settled = false;
173654
+ let rejected = false;
173655
+ let timer;
173656
+ const settle = (fn) => {
173657
+ if (settled) return;
173658
+ settled = true;
173659
+ clearTimeout(timer);
173660
+ fn();
173661
+ };
173662
+ const fail = (err) => settle(() => {
173663
+ rejected = true;
173664
+ unsubscribe();
173665
+ reject(err);
173666
+ });
173667
+ timer = setTimeout(() => {
173668
+ logger246.info(
173669
+ `HA WebRTC timed out after ${haWebRtcTimeoutMs}ms for ${entityId}`
173670
+ );
173671
+ fail(
173672
+ new Error(`HA WebRTC request timed out after ${haWebRtcTimeoutMs}ms`)
173673
+ );
173674
+ }, haWebRtcTimeoutMs);
173247
173675
  conn.subscribeMessage(
173248
173676
  (msg) => {
173249
- if (msg.type === "answer" && msg.answer && !settled) {
173250
- settled = true;
173251
- const session = [...this.sessions.values()].find(
173252
- (s) => s.entityId === entityId
173253
- );
173254
- if (session && msg.session_id) {
173255
- session.haSessionId = msg.session_id;
173677
+ switch (msg.type) {
173678
+ case "session":
173679
+ if (msg.session_id) sessionId = msg.session_id;
173680
+ break;
173681
+ case "answer":
173682
+ if (msg.answer) {
173683
+ if (msg.session_id) sessionId = msg.session_id;
173684
+ const ans = msg.answer;
173685
+ settle(() => {
173686
+ logger246.info(
173687
+ `HA answer received for ${entityId} (${ans.length} chars)`
173688
+ );
173689
+ resolve11(ans);
173690
+ });
173691
+ }
173692
+ break;
173693
+ case "error": {
173694
+ const detail = msg.message ?? msg.code ?? "unknown error";
173695
+ logger246.info(`HA WebRTC error for ${entityId}: ${detail}`);
173696
+ fail(new Error(`HA WebRTC error: ${detail}`));
173697
+ break;
173256
173698
  }
173257
- resolve11(msg.answer);
173699
+ case "candidate":
173700
+ this.feedHaCandidate(entityId, haPeer, msg.candidate);
173701
+ break;
173258
173702
  }
173259
173703
  },
173260
173704
  { type: "camera/webrtc/offer", entity_id: entityId, offer: offerSdp }
173261
- ).catch(reject);
173705
+ ).then((unsub) => {
173706
+ unsubscribe = unsub;
173707
+ if (rejected) unsub();
173708
+ }).catch((err) => {
173709
+ fail(err instanceof Error ? err : new Error(String(err)));
173710
+ });
173262
173711
  });
173712
+ return { answer, sessionId, unsubscribe };
173263
173713
  }
173264
173714
  };
173715
+ function offerKinds(sdp) {
173716
+ const kinds = [];
173717
+ for (const m of sdp.matchAll(/^m=(video|audio)/gm)) {
173718
+ kinds.push(m[1]);
173719
+ }
173720
+ return kinds;
173721
+ }
173722
+ function errText3(err) {
173723
+ return err instanceof Error ? err.message : String(err);
173724
+ }
173265
173725
 
173266
173726
  // src/plugins/builtin/camera/camera-plugin.ts
173267
173727
  var CONFIG_KEY = "config";
@@ -173394,6 +173854,7 @@ var CameraPlugin = class {
173394
173854
  await this.bridge?.close().catch(() => {
173395
173855
  });
173396
173856
  this.bridge = void 0;
173857
+ unregisterAllRequestors();
173397
173858
  }
173398
173859
  };
173399
173860
 
@@ -173407,7 +173868,7 @@ import {
173407
173868
  getCollection
173408
173869
  } from "home-assistant-js-websocket";
173409
173870
  import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
173410
- var logger244 = Logger.get("SubscribeEntities");
173871
+ var logger247 = Logger.get("SubscribeEntities");
173411
173872
  function processEvent(store, updates) {
173412
173873
  const state = { ...store.state };
173413
173874
  if (updates.a) {
@@ -173433,7 +173894,7 @@ function processEvent(store, updates) {
173433
173894
  for (const entityId in updates.c) {
173434
173895
  let entityState = state[entityId];
173435
173896
  if (!entityState) {
173436
- logger244.warn("Received state update for unknown entity", entityId);
173897
+ logger247.warn("Received state update for unknown entity", entityId);
173437
173898
  continue;
173438
173899
  }
173439
173900
  entityState = { ...entityState };
@@ -173522,7 +173983,7 @@ var subscribeEntities = (conn, onChange, entityIds) => {
173522
173983
  // src/services/bridges/entity-isolation-service.ts
173523
173984
  init_esm();
173524
173985
  init_diagnostic_event_bus();
173525
- var logger245 = Logger.get("EntityIsolation");
173986
+ var logger248 = Logger.get("EntityIsolation");
173526
173987
  var EntityIsolationServiceImpl = class {
173527
173988
  isolatedEntities = /* @__PURE__ */ new Map();
173528
173989
  isolationCallbacks = /* @__PURE__ */ new Map();
@@ -173587,13 +174048,13 @@ var EntityIsolationServiceImpl = class {
173587
174048
  }
173588
174049
  const parsed = this.parseEndpointPath(msg);
173589
174050
  if (!parsed) {
173590
- logger245.warn("Could not parse entity from error:", msg);
174051
+ logger248.warn("Could not parse entity from error:", msg);
173591
174052
  return false;
173592
174053
  }
173593
174054
  const { bridgeId, entityName } = parsed;
173594
174055
  const callback = this.isolationCallbacks.get(bridgeId);
173595
174056
  if (!callback) {
173596
- logger245.warn(
174057
+ logger248.warn(
173597
174058
  `No isolation callback registered for bridge ${bridgeId}, entity: ${entityName}`
173598
174059
  );
173599
174060
  return false;
@@ -173608,7 +174069,7 @@ var EntityIsolationServiceImpl = class {
173608
174069
  reason,
173609
174070
  failedAt: (/* @__PURE__ */ new Date()).toISOString()
173610
174071
  });
173611
- logger245.warn(
174072
+ logger248.warn(
173612
174073
  `Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
173613
174074
  );
173614
174075
  diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
@@ -173620,7 +174081,7 @@ var EntityIsolationServiceImpl = class {
173620
174081
  await callback(entityName);
173621
174082
  return true;
173622
174083
  } catch (e) {
173623
- logger245.error(`Failed to isolate entity ${entityName}:`, e);
174084
+ logger248.error(`Failed to isolate entity ${entityName}:`, e);
173624
174085
  return false;
173625
174086
  }
173626
174087
  }
@@ -173653,10 +174114,10 @@ var EntityIsolationService = new EntityIsolationServiceImpl();
173653
174114
  var MAX_ENTITY_ID_LENGTH = 150;
173654
174115
  var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
173655
174116
  var BridgeEndpointManager = class extends Service {
173656
- constructor(client, registry2, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
174117
+ constructor(client, registry3, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
173657
174118
  super("BridgeEndpointManager");
173658
174119
  this.client = client;
173659
- this.registry = registry2;
174120
+ this.registry = registry3;
173660
174121
  this.mappingStorage = mappingStorage;
173661
174122
  this.identityStorage = identityStorage;
173662
174123
  this.bridgeId = bridgeId;
@@ -174367,8 +174828,8 @@ init_send_ha_message();
174367
174828
  import { callService as callService2 } from "home-assistant-js-websocket";
174368
174829
  import { keys as keys2, pickBy, values as values3 } from "lodash-es";
174369
174830
  var BridgeRegistry = class _BridgeRegistry {
174370
- constructor(registry2, dataProvider, client) {
174371
- this.registry = registry2;
174831
+ constructor(registry3, dataProvider, client) {
174832
+ this.registry = registry3;
174372
174833
  this.dataProvider = dataProvider;
174373
174834
  this.client = client;
174374
174835
  this.refresh();
@@ -175138,11 +175599,11 @@ function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOStrin
175138
175599
  return { ...state, last_updated: now };
175139
175600
  }
175140
175601
  var ServerModeBridge = class {
175141
- constructor(logger248, dataProvider, endpointManager, server) {
175602
+ constructor(logger251, dataProvider, endpointManager, server) {
175142
175603
  this.dataProvider = dataProvider;
175143
175604
  this.endpointManager = endpointManager;
175144
175605
  this.server = server;
175145
- this.log = logger248.get(`ServerModeBridge / ${dataProvider.id}`);
175606
+ this.log = logger251.get(`ServerModeBridge / ${dataProvider.id}`);
175146
175607
  }
175147
175608
  dataProvider;
175148
175609
  endpointManager;
@@ -176172,12 +176633,12 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
176172
176633
  }
176173
176634
 
176174
176635
  // src/matter/endpoints/server-mode-vacuum-endpoint.ts
176175
- var logger246 = Logger.get("ServerModeVacuumEndpoint");
176636
+ var logger249 = Logger.get("ServerModeVacuumEndpoint");
176176
176637
  var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
176177
- static async create(registry2, entityId, mapping, endpointId) {
176178
- const deviceRegistry = registry2.deviceOf(entityId);
176179
- let state = registry2.initialState(entityId);
176180
- const entity = registry2.entity(entityId);
176638
+ static async create(registry3, entityId, mapping, endpointId) {
176639
+ const deviceRegistry = registry3.deviceOf(entityId);
176640
+ let state = registry3.initialState(entityId);
176641
+ const entity = registry3.entity(entityId);
176181
176642
  if (!state) {
176182
176643
  return void 0;
176183
176644
  }
@@ -176185,12 +176646,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176185
176646
  if (mapping?.disableBatteryMapping && mapping.batteryEntity) {
176186
176647
  effectiveMapping = { ...mapping, batteryEntity: void 0 };
176187
176648
  }
176188
- logger246.info(
176649
+ logger249.info(
176189
176650
  `${entityId}: device_id=${entity.device_id}, manualBattery=${mapping?.batteryEntity ?? "none"}`
176190
176651
  );
176191
176652
  if (entity.device_id) {
176192
176653
  if (!mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
176193
- const batteryEntityId = registry2.findBatteryEntityForDevice(
176654
+ const batteryEntityId = registry3.findBatteryEntityForDevice(
176194
176655
  entity.device_id
176195
176656
  );
176196
176657
  if (batteryEntityId && batteryEntityId !== entityId) {
@@ -176199,22 +176660,22 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176199
176660
  entityId: effectiveMapping?.entityId ?? entityId,
176200
176661
  batteryEntity: batteryEntityId
176201
176662
  };
176202
- registry2.markBatteryEntityUsed(batteryEntityId);
176203
- logger246.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
176663
+ registry3.markBatteryEntityUsed(batteryEntityId);
176664
+ logger249.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
176204
176665
  } else {
176205
176666
  const attrs = state.attributes;
176206
176667
  if (attrs.battery_level != null || attrs.battery != null) {
176207
- logger246.info(
176668
+ logger249.info(
176208
176669
  `${entityId}: No battery entity found, using battery attribute from vacuum state`
176209
176670
  );
176210
176671
  } else {
176211
- logger246.warn(
176672
+ logger249.warn(
176212
176673
  `${entityId}: No battery entity found for device ${entity.device_id}`
176213
176674
  );
176214
176675
  }
176215
176676
  }
176216
176677
  }
176217
- const vacuumEntities = registry2.findVacuumSelectEntities(
176678
+ const vacuumEntities = registry3.findVacuumSelectEntities(
176218
176679
  entity.device_id
176219
176680
  );
176220
176681
  if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
@@ -176223,7 +176684,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176223
176684
  entityId: effectiveMapping?.entityId ?? entityId,
176224
176685
  cleaningModeEntity: vacuumEntities.cleaningModeEntity
176225
176686
  };
176226
- logger246.info(
176687
+ logger249.info(
176227
176688
  `${entityId}: Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity}`
176228
176689
  );
176229
176690
  }
@@ -176233,7 +176694,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176233
176694
  entityId: effectiveMapping?.entityId ?? entityId,
176234
176695
  suctionLevelEntity: vacuumEntities.suctionLevelEntity
176235
176696
  };
176236
- logger246.info(
176697
+ logger249.info(
176237
176698
  `${entityId}: Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity}`
176238
176699
  );
176239
176700
  }
@@ -176243,7 +176704,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176243
176704
  entityId: effectiveMapping?.entityId ?? entityId,
176244
176705
  mopIntensityEntity: vacuumEntities.mopIntensityEntity
176245
176706
  };
176246
- logger246.info(
176707
+ logger249.info(
176247
176708
  `${entityId}: Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity}`
176248
176709
  );
176249
176710
  }
@@ -176253,12 +176714,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176253
176714
  entityId: effectiveMapping?.entityId ?? entityId,
176254
176715
  currentRoomEntity: vacuumEntities.currentRoomEntity
176255
176716
  };
176256
- logger246.info(
176717
+ logger249.info(
176257
176718
  `${entityId}: Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity}`
176258
176719
  );
176259
176720
  }
176260
176721
  const supportedFeatures = state.attributes.supported_features ?? 0;
176261
- const cleanAreaRooms = await registry2.resolveCleanAreaRooms(
176722
+ const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
176262
176723
  entityId,
176263
176724
  supportedFeatures
176264
176725
  );
@@ -176268,13 +176729,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176268
176729
  entityId: effectiveMapping?.entityId ?? entityId,
176269
176730
  cleanAreaRooms
176270
176731
  };
176271
- logger246.info(
176732
+ logger249.info(
176272
176733
  `${entityId}: Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA`
176273
176734
  );
176274
176735
  }
176275
176736
  const vacAttrs = state.attributes;
176276
176737
  if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
176277
- const valetudoRooms = registry2.findValetudoMapSegments(
176738
+ const valetudoRooms = registry3.findValetudoMapSegments(
176278
176739
  entity.device_id
176279
176740
  );
176280
176741
  if (valetudoRooms.length > 0) {
@@ -176289,11 +176750,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176289
176750
  rooms: roomsObj
176290
176751
  }
176291
176752
  };
176292
- logger246.info(
176753
+ logger249.info(
176293
176754
  `${entityId}: Auto-detected ${valetudoRooms.length} Valetudo segments`
176294
176755
  );
176295
176756
  } else {
176296
- const roborockRooms = await registry2.resolveRoborockRooms(entityId);
176757
+ const roborockRooms = await registry3.resolveRoborockRooms(entityId);
176297
176758
  if (roborockRooms.length > 0) {
176298
176759
  const roomsObj = {};
176299
176760
  for (const r of roborockRooms) {
@@ -176306,14 +176767,14 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176306
176767
  rooms: roomsObj
176307
176768
  }
176308
176769
  };
176309
- logger246.info(
176770
+ logger249.info(
176310
176771
  `${entityId}: Auto-detected ${roborockRooms.length} Roborock rooms`
176311
176772
  );
176312
176773
  }
176313
176774
  }
176314
176775
  }
176315
176776
  } else {
176316
- logger246.warn(`${entityId}: No device_id, cannot auto-assign battery`);
176777
+ logger249.warn(`${entityId}: No device_id, cannot auto-assign battery`);
176317
176778
  }
176318
176779
  const payload = {
176319
176780
  entity_id: entityId,
@@ -176324,7 +176785,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176324
176785
  const vacAttrsForClean = state.attributes;
176325
176786
  let cleaningModeOptions;
176326
176787
  if (effectiveMapping?.cleaningModeEntity) {
176327
- const cmState = registry2.initialState(
176788
+ const cmState = registry3.initialState(
176328
176789
  effectiveMapping.cleaningModeEntity
176329
176790
  );
176330
176791
  cleaningModeOptions = cmState?.attributes?.options;
@@ -176344,7 +176805,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176344
176805
  customName,
176345
176806
  mapping: effectiveMapping
176346
176807
  },
176347
- registry2.isServerModeVacuumOnOffEnabled(),
176808
+ registry3.isServerModeVacuumOnOffEnabled(),
176348
176809
  cleaningModeOptions
176349
176810
  );
176350
176811
  if (!endpointType) {
@@ -176386,11 +176847,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176386
176847
  }
176387
176848
  if (mappedChanged) {
176388
176849
  this.pendingMappedChange = true;
176389
- logger246.debug(
176850
+ logger249.debug(
176390
176851
  `Mapped entity change detected for ${this.entityId}, forcing update`
176391
176852
  );
176392
176853
  }
176393
- logger246.debug(
176854
+ logger249.debug(
176394
176855
  `State update received for ${this.entityId}: state=${state.state}`
176395
176856
  );
176396
176857
  this.lastState = state;
@@ -176430,11 +176891,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
176430
176891
  // src/services/bridges/server-mode-endpoint-manager.ts
176431
176892
  var MAX_SERVER_MODE_DEVICES = 10;
176432
176893
  var ServerModeEndpointManager = class extends Service {
176433
- constructor(serverNode, client, registry2, mappingStorage, identityStorage, dataProvider, log) {
176894
+ constructor(serverNode, client, registry3, mappingStorage, identityStorage, dataProvider, log) {
176434
176895
  super("ServerModeEndpointManager");
176435
176896
  this.serverNode = serverNode;
176436
176897
  this.client = client;
176437
- this.registry = registry2;
176898
+ this.registry = registry3;
176438
176899
  this.mappingStorage = mappingStorage;
176439
176900
  this.identityStorage = identityStorage;
176440
176901
  this.dataProvider = dataProvider;
@@ -176968,10 +177429,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
176968
177429
  // src/core/ioc/app-environment.ts
176969
177430
  var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176970
177431
  constructor(rootEnv, options) {
176971
- const logger248 = rootEnv.get(LoggerService);
177432
+ const logger251 = rootEnv.get(LoggerService);
176972
177433
  super({
176973
177434
  id: "App",
176974
- log: logger248.get("AppContainer"),
177435
+ log: logger251.get("AppContainer"),
176975
177436
  parent: rootEnv
176976
177437
  });
176977
177438
  this.options = options;
@@ -176985,8 +177446,8 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176985
177446
  }
176986
177447
  construction;
176987
177448
  async init() {
176988
- const logger248 = this.get(LoggerService);
176989
- this.set(LoggerService, logger248);
177449
+ const logger251 = this.get(LoggerService);
177450
+ this.set(LoggerService, logger251);
176990
177451
  this.set(AppStorage, new AppStorage(await this.load(StorageService)));
176991
177452
  this.set(BridgeStorage, new BridgeStorage(await this.load(AppStorage)));
176992
177453
  this.set(
@@ -177007,7 +177468,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
177007
177468
  );
177008
177469
  this.set(
177009
177470
  HomeAssistantClient,
177010
- new HomeAssistantClient(logger248, this.options.homeAssistant)
177471
+ new HomeAssistantClient(logger251, this.options.homeAssistant)
177011
177472
  );
177012
177473
  this.set(
177013
177474
  HomeAssistantConfig,
@@ -177015,7 +177476,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
177015
177476
  );
177016
177477
  this.set(
177017
177478
  HomeAssistantActions,
177018
- new HomeAssistantActions(logger248, await this.load(HomeAssistantClient))
177479
+ new HomeAssistantActions(logger251, await this.load(HomeAssistantClient))
177019
177480
  );
177020
177481
  this.set(
177021
177482
  HomeAssistantRegistry,
@@ -177051,7 +177512,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
177051
177512
  this.set(
177052
177513
  WebApi,
177053
177514
  new WebApi(
177054
- logger248,
177515
+ logger251,
177055
177516
  await this.load(BridgeService),
177056
177517
  await this.load(HomeAssistantClient),
177057
177518
  await this.load(HomeAssistantRegistry),
@@ -177072,7 +177533,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
177072
177533
 
177073
177534
  // src/matter/patches/patch-level-control-tlv.ts
177074
177535
  init_esm();
177075
- var logger247 = Logger.get("PatchLevelControlTlv");
177536
+ var logger250 = Logger.get("PatchLevelControlTlv");
177076
177537
  var optionalFieldModels = /* @__PURE__ */ new WeakSet();
177077
177538
  var fieldModelMandatoryGetterPatched = false;
177078
177539
  function patchLevelControlTlv() {
@@ -177128,11 +177589,11 @@ function patchLevelControlTlv() {
177128
177589
  patched++;
177129
177590
  }
177130
177591
  if (patched > 0) {
177131
- logger247.info(
177592
+ logger250.info(
177132
177593
  `Patched ${patched} LevelControl TLV schema(s): transitionTime is now optional (Google Home compatibility)`
177133
177594
  );
177134
177595
  } else {
177135
- logger247.warn(
177596
+ logger250.warn(
177136
177597
  "Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work.",
177137
177598
  {
177138
177599
  moveToLevel: describeFields(