@riddix/hamh 2.1.0-alpha.819 → 2.1.0-alpha.821

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.
@@ -132611,6 +132611,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
132611
132611
  disableLockPin: config11.disableLockPin,
132612
132612
  lockUsercodeService: config11.lockUsercodeService,
132613
132613
  lockUsercodeSlot: config11.lockUsercodeSlot,
132614
+ lockPinMinLength: config11.lockPinMinLength,
132615
+ lockPinMaxLength: config11.lockPinMaxLength,
132614
132616
  powerEntity: config11.powerEntity,
132615
132617
  energyEntity: config11.energyEntity,
132616
132618
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -132764,6 +132766,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
132764
132766
  disableLockPin: config11.disableLockPin,
132765
132767
  lockUsercodeService: config11.lockUsercodeService,
132766
132768
  lockUsercodeSlot: config11.lockUsercodeSlot,
132769
+ lockPinMinLength: config11.lockPinMinLength,
132770
+ lockPinMaxLength: config11.lockPinMaxLength,
132767
132771
  powerEntity: config11.powerEntity,
132768
132772
  energyEntity: config11.energyEntity,
132769
132773
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -133608,6 +133612,8 @@ function entityMappingApi(mappingStorage) {
133608
133612
  disableLockPin: body.disableLockPin,
133609
133613
  lockUsercodeService: body.lockUsercodeService,
133610
133614
  lockUsercodeSlot: body.lockUsercodeSlot,
133615
+ lockPinMinLength: body.lockPinMinLength,
133616
+ lockPinMaxLength: body.lockPinMaxLength,
133611
133617
  powerEntity: body.powerEntity,
133612
133618
  energyEntity: body.energyEntity,
133613
133619
  suctionLevelEntity: body.suctionLevelEntity,
@@ -134100,6 +134106,8 @@ function configToProfileEntry(config11) {
134100
134106
  disableLockPin: config11.disableLockPin,
134101
134107
  lockUsercodeService: config11.lockUsercodeService,
134102
134108
  lockUsercodeSlot: config11.lockUsercodeSlot,
134109
+ lockPinMinLength: config11.lockPinMinLength,
134110
+ lockPinMaxLength: config11.lockPinMaxLength,
134103
134111
  powerEntity: config11.powerEntity,
134104
134112
  energyEntity: config11.energyEntity,
134105
134113
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -134231,6 +134239,8 @@ function mappingProfileApi(mappingStorage) {
134231
134239
  disableLockPin: entry.disableLockPin,
134232
134240
  lockUsercodeService: entry.lockUsercodeService,
134233
134241
  lockUsercodeSlot: entry.lockUsercodeSlot,
134242
+ lockPinMinLength: entry.lockPinMinLength,
134243
+ lockPinMaxLength: entry.lockPinMaxLength,
134234
134244
  powerEntity: entry.powerEntity,
134235
134245
  energyEntity: entry.energyEntity,
134236
134246
  suctionLevelEntity: entry.suctionLevelEntity,
@@ -138403,6 +138413,10 @@ var EntityMappingStorage = class extends Service {
138403
138413
  this.mappings.set(request.bridgeId, bridgeMap);
138404
138414
  }
138405
138415
  const roomEntities = request.roomEntities?.filter((e) => e?.trim()) || [];
138416
+ const pinLengths = sanitizePinLengths(
138417
+ request.lockPinMinLength,
138418
+ request.lockPinMaxLength
138419
+ );
138406
138420
  const config11 = {
138407
138421
  entityId: request.entityId,
138408
138422
  matterDeviceType: request.matterDeviceType,
@@ -138422,6 +138436,8 @@ var EntityMappingStorage = class extends Service {
138422
138436
  disableLockPin: request.disableLockPin || void 0,
138423
138437
  lockUsercodeService: request.lockUsercodeService?.trim() || void 0,
138424
138438
  lockUsercodeSlot: sanitizeUsercodeSlot(request.lockUsercodeSlot),
138439
+ lockPinMinLength: pinLengths.min,
138440
+ lockPinMaxLength: pinLengths.max,
138425
138441
  powerEntity: request.powerEntity?.trim() || void 0,
138426
138442
  energyEntity: request.energyEntity?.trim() || void 0,
138427
138443
  pressureEntity: request.pressureEntity?.trim() || void 0,
@@ -138451,7 +138467,7 @@ var EntityMappingStorage = class extends Service {
138451
138467
  climateAutoMode: request.climateAutoMode || void 0,
138452
138468
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0
138453
138469
  };
138454
- if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
138470
+ if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
138455
138471
  bridgeMap.delete(request.entityId);
138456
138472
  } else {
138457
138473
  bridgeMap.set(request.entityId, config11);
@@ -138491,6 +138507,24 @@ function sanitizeUsercodeSlot(value) {
138491
138507
  }
138492
138508
  return n;
138493
138509
  }
138510
+ function sanitizePinLength(value) {
138511
+ if (value === void 0 || value === null || value === "") {
138512
+ return void 0;
138513
+ }
138514
+ const n = typeof value === "string" ? Number(value) : value;
138515
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 20) {
138516
+ return void 0;
138517
+ }
138518
+ return n;
138519
+ }
138520
+ function sanitizePinLengths(min, max) {
138521
+ const lo = sanitizePinLength(min);
138522
+ const hi = sanitizePinLength(max);
138523
+ if ((lo ?? 4) > (hi ?? 8)) {
138524
+ return { min: void 0, max: void 0 };
138525
+ }
138526
+ return { min: lo, max: hi };
138527
+ }
138494
138528
  function sanitizeDebounceMs(value) {
138495
138529
  if (value === void 0 || value === null || value === "") {
138496
138530
  return void 0;
@@ -153370,6 +153404,104 @@ function dropUndefined(obj) {
153370
153404
  return result;
153371
153405
  }
153372
153406
 
153407
+ // src/plugins/builtin/camera/camera-tcp-requirement.ts
153408
+ import { readFileSync as readFileSync6 } from "node:fs";
153409
+
153410
+ // src/plugins/plugin-storage.ts
153411
+ init_esm();
153412
+ import * as fs9 from "node:fs";
153413
+ import * as path11 from "node:path";
153414
+ var logger194 = Logger.get("PluginStorage");
153415
+ var SAVE_DEBOUNCE_MS = 500;
153416
+ function pluginStorageFilePath(storageDir, bridgeId, pluginName) {
153417
+ const safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
153418
+ return path11.join(
153419
+ storageDir,
153420
+ `plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
153421
+ );
153422
+ }
153423
+ var FilePluginStorage = class {
153424
+ data = {};
153425
+ dirty = false;
153426
+ filePath;
153427
+ saveTimer;
153428
+ constructor(storageDir, bridgeId, pluginName) {
153429
+ this.filePath = pluginStorageFilePath(storageDir, bridgeId, pluginName);
153430
+ this.load();
153431
+ }
153432
+ async get(key, defaultValue) {
153433
+ const value = this.data[key];
153434
+ return value ?? defaultValue;
153435
+ }
153436
+ async set(key, value) {
153437
+ this.data[key] = value;
153438
+ this.dirty = true;
153439
+ this.scheduleSave();
153440
+ }
153441
+ async delete(key) {
153442
+ delete this.data[key];
153443
+ this.dirty = true;
153444
+ this.scheduleSave();
153445
+ }
153446
+ async keys() {
153447
+ return Object.keys(this.data);
153448
+ }
153449
+ load() {
153450
+ try {
153451
+ if (fs9.existsSync(this.filePath)) {
153452
+ const raw = fs9.readFileSync(this.filePath, "utf-8");
153453
+ this.data = JSON.parse(raw);
153454
+ }
153455
+ } catch (e) {
153456
+ logger194.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
153457
+ this.data = {};
153458
+ }
153459
+ }
153460
+ scheduleSave() {
153461
+ if (this.saveTimer) clearTimeout(this.saveTimer);
153462
+ this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
153463
+ }
153464
+ save() {
153465
+ if (!this.dirty) return;
153466
+ if (this.saveTimer) {
153467
+ clearTimeout(this.saveTimer);
153468
+ this.saveTimer = void 0;
153469
+ }
153470
+ try {
153471
+ const dir = path11.dirname(this.filePath);
153472
+ if (!fs9.existsSync(dir)) {
153473
+ fs9.mkdirSync(dir, { recursive: true });
153474
+ }
153475
+ fs9.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
153476
+ this.dirty = false;
153477
+ } catch (e) {
153478
+ logger194.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
153479
+ }
153480
+ }
153481
+ flush() {
153482
+ this.save();
153483
+ }
153484
+ };
153485
+
153486
+ // src/plugins/builtin/camera/camera-tcp-requirement.ts
153487
+ var CAMERA_TCP_CONFIG = { incoming: true, outgoing: false };
153488
+ function parseCameraList(cameras) {
153489
+ if (typeof cameras !== "string") return [];
153490
+ return cameras.split(",").map((s) => s.trim()).filter(Boolean);
153491
+ }
153492
+ function bridgeNeedsTcpForCameras(storageDir, bridgeId) {
153493
+ try {
153494
+ const raw = readFileSync6(
153495
+ pluginStorageFilePath(storageDir, bridgeId, "camera"),
153496
+ "utf-8"
153497
+ );
153498
+ const json = JSON.parse(raw);
153499
+ return parseCameraList(json.config?.cameras).length > 0;
153500
+ } catch {
153501
+ return false;
153502
+ }
153503
+ }
153504
+
153373
153505
  // src/plugins/plugin-manager.ts
153374
153506
  init_esm();
153375
153507
  import * as fs10 from "node:fs";
@@ -153671,7 +153803,7 @@ init_esm4();
153671
153803
  init_esm3();
153672
153804
  init_access_control();
153673
153805
  init_groupcast();
153674
- var logger194 = Logger.get("GroupcastServer");
153806
+ var logger195 = Logger.get("GroupcastServer");
153675
153807
  var UNMAPPED_KEYSET_ID = 65535;
153676
153808
  var GROUPCAST_IS_PROVISIONAL = true;
153677
153809
  var GroupcastServer = class extends GroupcastBehavior {
@@ -154088,7 +154220,7 @@ var GroupcastServer = class extends GroupcastBehavior {
154088
154220
  if (hasMembership) continue;
154089
154221
  const fabricGroups = gkmState.groupTable.filter((g) => g.fabricIndex === fi);
154090
154222
  if (fabricGroups.length === 0) continue;
154091
- logger194.info(`Migrating ${fabricGroups.length} legacy group(s) for fabric ${fi} to Groupcast`);
154223
+ logger195.info(`Migrating ${fabricGroups.length} legacy group(s) for fabric ${fi} to Groupcast`);
154092
154224
  const newEntries = fabricGroups.map((group) => {
154093
154225
  const keyMapping = gkmState.groupKeyMap.find((m) => m.fabricIndex === fi && m.groupId === group.groupId);
154094
154226
  const mcastAddrPolicy = this.features.perGroup ? Groupcast3.MulticastAddrPolicy.PerGroup : Groupcast3.MulticastAddrPolicy.IanaAddr;
@@ -154110,7 +154242,7 @@ var GroupcastServer = class extends GroupcastBehavior {
154110
154242
  }
154111
154243
  if (migrated) {
154112
154244
  this.#updateUsedMcastAddrCount();
154113
- logger194.info("Groupcast migration complete");
154245
+ logger195.info("Groupcast migration complete");
154114
154246
  }
154115
154247
  }
154116
154248
  async [Symbol.asyncDispose]() {
@@ -154586,7 +154718,7 @@ var IdentifyServer2 = class extends IdentifyServer {
154586
154718
  // src/matter/endpoints/validate-endpoint-type.ts
154587
154719
  init_esm();
154588
154720
  init_esm7();
154589
- var logger195 = Logger.get("EndpointValidation");
154721
+ var logger196 = Logger.get("EndpointValidation");
154590
154722
  function toCamelCase(name) {
154591
154723
  return name.charAt(0).toLowerCase() + name.slice(1);
154592
154724
  }
@@ -154616,12 +154748,12 @@ function validateEndpointType(endpointType, entityId) {
154616
154748
  }
154617
154749
  const prefix = entityId ? `[${entityId}] ` : "";
154618
154750
  if (missingMandatory.length > 0) {
154619
- logger195.warn(
154751
+ logger196.warn(
154620
154752
  `${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): missing mandatory clusters: ${missingMandatory.join(", ")}`
154621
154753
  );
154622
154754
  }
154623
154755
  if (availableOptional.length > 0) {
154624
- logger195.debug(
154756
+ logger196.debug(
154625
154757
  `${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): optional clusters not used: ${availableOptional.join(", ")}`
154626
154758
  );
154627
154759
  }
@@ -154745,7 +154877,7 @@ var BridgeDataProvider = class extends Service {
154745
154877
 
154746
154878
  // src/utils/apply-patch-state.ts
154747
154879
  init_esm();
154748
- var logger196 = Logger.get("ApplyPatchState");
154880
+ var logger197 = Logger.get("ApplyPatchState");
154749
154881
  function applyPatchState(state, patch, options) {
154750
154882
  return applyPatch(state, patch, options?.force);
154751
154883
  }
@@ -154772,23 +154904,23 @@ function applyPatch(state, patch, force = false) {
154772
154904
  if (errorMessage.includes(
154773
154905
  "Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
154774
154906
  )) {
154775
- logger196.debug(
154907
+ logger197.debug(
154776
154908
  `Suppressed endpoint storage error, patch not applied: ${JSON.stringify(actualPatch)}`
154777
154909
  );
154778
154910
  return actualPatch;
154779
154911
  }
154780
154912
  if (errorMessage.includes("synchronous-transaction-conflict")) {
154781
- logger196.warn(
154913
+ logger197.warn(
154782
154914
  `Transaction conflict, state update DROPPED: ${JSON.stringify(actualPatch)}`
154783
154915
  );
154784
154916
  return actualPatch;
154785
154917
  }
154786
154918
  failedKeys.push(key);
154787
- logger196.warn(`Failed to set property '${key}': ${errorMessage}`);
154919
+ logger197.warn(`Failed to set property '${key}': ${errorMessage}`);
154788
154920
  }
154789
154921
  }
154790
154922
  if (failedKeys.length > 0) {
154791
- logger196.warn(
154923
+ logger197.warn(
154792
154924
  `${failedKeys.length} properties failed to update: [${failedKeys.join(", ")}]`
154793
154925
  );
154794
154926
  }
@@ -154858,7 +154990,7 @@ function truncate(maxLength, value) {
154858
154990
  }
154859
154991
 
154860
154992
  // src/plugins/plugin-device-factory.ts
154861
- var logger197 = Logger.get("PluginDeviceFactory");
154993
+ var logger198 = Logger.get("PluginDeviceFactory");
154862
154994
  var deviceTypeMap = {
154863
154995
  on_off_light: () => OnOffLightDevice.with(
154864
154996
  IdentifyServer2,
@@ -154964,7 +155096,7 @@ var deviceTypeMap = {
154964
155096
  function createPluginEndpointType(deviceType) {
154965
155097
  const factory = deviceTypeMap[deviceType];
154966
155098
  if (!factory) {
154967
- logger197.warn(`Unsupported plugin device type: "${deviceType}"`);
155099
+ logger198.warn(`Unsupported plugin device type: "${deviceType}"`);
154968
155100
  return void 0;
154969
155101
  }
154970
155102
  const endpoint = factory();
@@ -154975,79 +155107,6 @@ function getSupportedPluginDeviceTypes() {
154975
155107
  return Object.keys(deviceTypeMap);
154976
155108
  }
154977
155109
 
154978
- // src/plugins/plugin-storage.ts
154979
- init_esm();
154980
- import * as fs9 from "node:fs";
154981
- import * as path11 from "node:path";
154982
- var logger198 = Logger.get("PluginStorage");
154983
- var SAVE_DEBOUNCE_MS = 500;
154984
- var FilePluginStorage = class {
154985
- data = {};
154986
- dirty = false;
154987
- filePath;
154988
- saveTimer;
154989
- constructor(storageDir, bridgeId, pluginName) {
154990
- const safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
154991
- this.filePath = path11.join(
154992
- storageDir,
154993
- `plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
154994
- );
154995
- this.load();
154996
- }
154997
- async get(key, defaultValue) {
154998
- const value = this.data[key];
154999
- return value ?? defaultValue;
155000
- }
155001
- async set(key, value) {
155002
- this.data[key] = value;
155003
- this.dirty = true;
155004
- this.scheduleSave();
155005
- }
155006
- async delete(key) {
155007
- delete this.data[key];
155008
- this.dirty = true;
155009
- this.scheduleSave();
155010
- }
155011
- async keys() {
155012
- return Object.keys(this.data);
155013
- }
155014
- load() {
155015
- try {
155016
- if (fs9.existsSync(this.filePath)) {
155017
- const raw = fs9.readFileSync(this.filePath, "utf-8");
155018
- this.data = JSON.parse(raw);
155019
- }
155020
- } catch (e) {
155021
- logger198.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
155022
- this.data = {};
155023
- }
155024
- }
155025
- scheduleSave() {
155026
- if (this.saveTimer) clearTimeout(this.saveTimer);
155027
- this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
155028
- }
155029
- save() {
155030
- if (!this.dirty) return;
155031
- if (this.saveTimer) {
155032
- clearTimeout(this.saveTimer);
155033
- this.saveTimer = void 0;
155034
- }
155035
- try {
155036
- const dir = path11.dirname(this.filePath);
155037
- if (!fs9.existsSync(dir)) {
155038
- fs9.mkdirSync(dir, { recursive: true });
155039
- }
155040
- fs9.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
155041
- this.dirty = false;
155042
- } catch (e) {
155043
- logger198.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
155044
- }
155045
- }
155046
- flush() {
155047
- this.save();
155048
- }
155049
- };
155050
-
155051
155110
  // src/plugins/safe-plugin-runner.ts
155052
155111
  init_esm();
155053
155112
  var logger199 = Logger.get("SafePluginRunner");
@@ -155763,13 +155822,14 @@ var SecondaryNetworkInterfaceEndpointDefinition = MutableEndpoint({
155763
155822
  Object.freeze(SecondaryNetworkInterfaceEndpointDefinition);
155764
155823
 
155765
155824
  // src/utils/json/create-bridge-server-config.ts
155766
- function createBridgeServerConfig(data) {
155825
+ function createBridgeServerConfig(data, options) {
155767
155826
  return {
155768
155827
  type: ServerNode.RootEndpoint,
155769
155828
  id: data.id,
155770
155829
  network: {
155771
155830
  port: data.port,
155772
- subscriptionOptions: matterSubscriptionOptions()
155831
+ subscriptionOptions: matterSubscriptionOptions(),
155832
+ ...options?.tcp ? { tcp: options.tcp } : {}
155773
155833
  },
155774
155834
  productDescription: {
155775
155835
  name: data.name,
@@ -155800,8 +155860,8 @@ function createBridgeServerConfig(data) {
155800
155860
 
155801
155861
  // src/matter/endpoints/bridge-server-node.ts
155802
155862
  var BridgeServerNode = class extends ServerNode {
155803
- constructor(env, bridgeData, aggregator) {
155804
- const config11 = createBridgeServerConfig(bridgeData);
155863
+ constructor(env, bridgeData, aggregator, options) {
155864
+ const config11 = createBridgeServerConfig(bridgeData, options);
155805
155865
  super({
155806
155866
  ...config11,
155807
155867
  environment: env,
@@ -155941,14 +156001,16 @@ function deadSessionTimeoutMs(flags2) {
155941
156001
  var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
155942
156002
  var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
155943
156003
  var Bridge = class {
155944
- constructor(env, logger245, dataProvider, endpointManager) {
156004
+ constructor(env, logger245, dataProvider, endpointManager, serverOptions) {
155945
156005
  this.dataProvider = dataProvider;
155946
156006
  this.endpointManager = endpointManager;
156007
+ this.serverOptions = serverOptions;
155947
156008
  this.log = logger245.get(`Bridge / ${dataProvider.id}`);
155948
156009
  this.server = new BridgeServerNode(
155949
156010
  env,
155950
156011
  this.dataProvider,
155951
- this.endpointManager.root
156012
+ this.endpointManager.root,
156013
+ this.serverOptions
155952
156014
  );
155953
156015
  const { basicInformation } = this.dataProvider;
155954
156016
  this.log.debugCtx("Root bridge BasicInformation configured", {
@@ -155963,6 +156025,7 @@ var Bridge = class {
155963
156025
  }
155964
156026
  dataProvider;
155965
156027
  endpointManager;
156028
+ serverOptions;
155966
156029
  log;
155967
156030
  server;
155968
156031
  status = {
@@ -156113,7 +156176,44 @@ var Bridge = class {
156113
156176
  return this.endpointManager.getPluginConfigSchema(pluginName);
156114
156177
  }
156115
156178
  async updatePluginConfig(pluginName, config11) {
156116
- return this.endpointManager.updatePluginConfig(pluginName, config11);
156179
+ const result = await this.endpointManager.updatePluginConfig(
156180
+ pluginName,
156181
+ config11
156182
+ );
156183
+ if (result && pluginName === "camera") {
156184
+ await this.applyCameraTcp(config11);
156185
+ }
156186
+ return result;
156187
+ }
156188
+ // #419: cameras need Matter over TCP because the WebRTC offer exceeds the UDP
156189
+ // message size. Match the bridge listener to the saved camera list. Changing
156190
+ // network config takes effect on (re)start, so bounce the bridge if running.
156191
+ async applyCameraTcp(config11) {
156192
+ const want = parseCameraList(config11.cameras).length > 0;
156193
+ const have = !!this.server.state.network.tcp;
156194
+ if (want === have) {
156195
+ return;
156196
+ }
156197
+ this.log.info(
156198
+ want ? "cameras configured, enabling matter over tcp (#419)" : "no cameras left, disabling matter over tcp (#419)"
156199
+ );
156200
+ const running = this.status.code === BridgeStatus.Running;
156201
+ if (running) {
156202
+ await this.stop();
156203
+ }
156204
+ try {
156205
+ await this.server.set({
156206
+ network: { tcp: want ? CAMERA_TCP_CONFIG : void 0 }
156207
+ });
156208
+ } catch (e) {
156209
+ this.log.warn(
156210
+ "Could not apply tcp live, restart matterhub to apply it:",
156211
+ e
156212
+ );
156213
+ }
156214
+ if (running) {
156215
+ await this.start();
156216
+ }
156117
156217
  }
156118
156218
  async initialize() {
156119
156219
  await this.server.construction.ready.then();
@@ -164339,6 +164439,24 @@ function usercodePassthroughFrom(homeAssistant) {
164339
164439
  callAction: (action) => homeAssistant.callAction(action)
164340
164440
  };
164341
164441
  }
164442
+ var DEFAULT_MIN_PIN_LENGTH = 4;
164443
+ var DEFAULT_MAX_PIN_LENGTH = 8;
164444
+ var PIN_LENGTH_SPEC_MIN = 1;
164445
+ var PIN_LENGTH_SPEC_MAX = 20;
164446
+ function clampPinLength(value) {
164447
+ if (value === void 0 || !Number.isInteger(value) || value < PIN_LENGTH_SPEC_MIN || value > PIN_LENGTH_SPEC_MAX) {
164448
+ return void 0;
164449
+ }
164450
+ return value;
164451
+ }
164452
+ function effectivePinCodeLengths(homeAssistant) {
164453
+ const min = clampPinLength(homeAssistant.state.mapping?.lockPinMinLength) ?? DEFAULT_MIN_PIN_LENGTH;
164454
+ const max = clampPinLength(homeAssistant.state.mapping?.lockPinMaxLength) ?? DEFAULT_MAX_PIN_LENGTH;
164455
+ if (min > max) {
164456
+ return { min: DEFAULT_MIN_PIN_LENGTH, max: DEFAULT_MAX_PIN_LENGTH };
164457
+ }
164458
+ return { min, max };
164459
+ }
164342
164460
  function asFabricIndex(value) {
164343
164461
  return value === void 0 || value === 0 ? null : FabricIndex3(value);
164344
164462
  }
@@ -164597,6 +164715,7 @@ var LockServerWithPinBase = class extends PinCredentialBase {
164597
164715
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164598
164716
  const isPinDisabledByMapping = homeAssistant.state.mapping?.disableLockPin === true;
164599
164717
  const hasPinConfigured = !isPinDisabledByMapping && hasStoredCredentialHelper(this.env, homeAssistant.entityId);
164718
+ const pinLengths = effectivePinCodeLengths(homeAssistant);
164600
164719
  applyPatchState(this.state, {
164601
164720
  lockState: this.state.config.getLockState(entity.state, this.agent),
164602
164721
  lockType: DoorLock3.LockType.DeadBolt,
@@ -164616,8 +164735,8 @@ var LockServerWithPinBase = class extends PinCredentialBase {
164616
164735
  numberOfPinUsersSupported: 1,
164617
164736
  numberOfTotalUsersSupported: 1,
164618
164737
  numberOfCredentialsSupportedPerUser: 1,
164619
- maxPinCodeLength: 8,
164620
- minPinCodeLength: 4,
164738
+ maxPinCodeLength: pinLengths.max,
164739
+ minPinCodeLength: pinLengths.min,
164621
164740
  requirePinForRemoteOperation: hasPinConfigured
164622
164741
  });
164623
164742
  }
@@ -164770,6 +164889,7 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
164770
164889
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164771
164890
  const isPinDisabledByMapping = homeAssistant.state.mapping?.disableLockPin === true;
164772
164891
  const hasPinConfigured = !isPinDisabledByMapping && hasStoredCredentialHelper(this.env, homeAssistant.entityId);
164892
+ const pinLengths = effectivePinCodeLengths(homeAssistant);
164773
164893
  applyPatchState(this.state, {
164774
164894
  lockState: this.state.config.getLockState(entity.state, this.agent),
164775
164895
  lockType: DoorLock3.LockType.DeadBolt,
@@ -164789,8 +164909,8 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
164789
164909
  numberOfPinUsersSupported: 1,
164790
164910
  numberOfTotalUsersSupported: 1,
164791
164911
  numberOfCredentialsSupportedPerUser: 1,
164792
- maxPinCodeLength: 8,
164793
- minPinCodeLength: 4,
164912
+ maxPinCodeLength: pinLengths.max,
164913
+ minPinCodeLength: pinLengths.min,
164794
164914
  requirePinForRemoteOperation: hasPinConfigured
164795
164915
  });
164796
164916
  }
@@ -171111,7 +171231,7 @@ var CameraPlugin = class {
171111
171231
  this.log.info("no Home Assistant connection, no cameras exposed");
171112
171232
  return;
171113
171233
  }
171114
- const entityIds = (cameras ?? "").split(",").map((s) => s.trim()).filter(Boolean);
171234
+ const entityIds = parseCameraList(cameras);
171115
171235
  if (entityIds.length === 0) {
171116
171236
  this.log.info("no camera entity ids configured");
171117
171237
  return;
@@ -174364,6 +174484,11 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
174364
174484
  initialData,
174365
174485
  this.storageLocation
174366
174486
  );
174487
+ const loggerService = env.get(LoggerService);
174488
+ const tcp = this.storageLocation && bridgeNeedsTcpForCameras(this.storageLocation, initialData.id) ? CAMERA_TCP_CONFIG : void 0;
174489
+ if (tcp) {
174490
+ loggerService.get("BridgeEnvironmentFactory").info("matter over tcp enabled, cameras configured (#419)");
174491
+ }
174367
174492
  class BridgeWithEnvironment extends Bridge {
174368
174493
  async dispose() {
174369
174494
  await super.dispose();
@@ -174372,9 +174497,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
174372
174497
  }
174373
174498
  const bridge = new BridgeWithEnvironment(
174374
174499
  env,
174375
- env.get(LoggerService),
174500
+ loggerService,
174376
174501
  await env.load(BridgeDataProvider),
174377
- await env.load(BridgeEndpointManager)
174502
+ await env.load(BridgeEndpointManager),
174503
+ tcp ? { tcp } : void 0
174378
174504
  );
174379
174505
  await bridge.initialize();
174380
174506
  return bridge;