@riddix/hamh 2.1.0-alpha.711 → 2.1.0-alpha.713
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/backend/cli.js
CHANGED
|
@@ -124211,8 +124211,7 @@ var init_bridge_config_schema = __esm({
|
|
|
124211
124211
|
sessionMaxAgeHours: {
|
|
124212
124212
|
title: "Session Rotation Max Age (hours)",
|
|
124213
124213
|
type: "number",
|
|
124214
|
-
description: "
|
|
124215
|
-
default: 4,
|
|
124214
|
+
description: "Rotate matter sessions older than this many hours so controllers re-establish and re-subscribe. Server Mode rotates every 4h by default; standard bridges only rotate when you set a value here. Set 0 to disable. Range 0 to 168. (#287)",
|
|
124216
124215
|
minimum: 0,
|
|
124217
124216
|
maximum: 168
|
|
124218
124217
|
},
|
|
@@ -145361,6 +145360,15 @@ var ServerModeServerNode = class extends ServerNode {
|
|
|
145361
145360
|
);
|
|
145362
145361
|
}
|
|
145363
145362
|
}
|
|
145363
|
+
// align the pairing device-type hint with the real device (default is vacuum)
|
|
145364
|
+
async updateAdvertisedDeviceType(deviceType) {
|
|
145365
|
+
try {
|
|
145366
|
+
await this.set({ productDescription: { deviceType } });
|
|
145367
|
+
} catch (e) {
|
|
145368
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
145369
|
+
logger180.warn(`Failed to set server-mode device type: ${msg}`);
|
|
145370
|
+
}
|
|
145371
|
+
}
|
|
145364
145372
|
async factoryReset() {
|
|
145365
145373
|
await this.cancel();
|
|
145366
145374
|
await this.erase();
|
|
@@ -147389,6 +147397,30 @@ function ensureCommissioningConfig(server) {
|
|
|
147389
147397
|
|
|
147390
147398
|
// src/services/bridges/bridge.ts
|
|
147391
147399
|
init_diagnostic_event_bus();
|
|
147400
|
+
|
|
147401
|
+
// src/services/bridges/session-rotation.ts
|
|
147402
|
+
var DEFAULT_SESSION_MAX_AGE_HOURS = 4;
|
|
147403
|
+
var SESSION_MAX_AGE_HOURS_RANGE = { min: 1, max: 168 };
|
|
147404
|
+
var ROTATION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
147405
|
+
function parseSessionMaxAgeHours(raw) {
|
|
147406
|
+
if (raw == null || raw === "") return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
147407
|
+
const n = Number.parseInt(raw, 10);
|
|
147408
|
+
if (Number.isNaN(n) || n < 0) return null;
|
|
147409
|
+
if (n === 0) return 0;
|
|
147410
|
+
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
147411
|
+
if (n < min) return min;
|
|
147412
|
+
if (n > max) return max;
|
|
147413
|
+
return n;
|
|
147414
|
+
}
|
|
147415
|
+
function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
|
|
147416
|
+
for (const session of sessions) {
|
|
147417
|
+
if (!startedAt.has(session.id)) {
|
|
147418
|
+
startedAt.set(session.id, now);
|
|
147419
|
+
}
|
|
147420
|
+
}
|
|
147421
|
+
}
|
|
147422
|
+
|
|
147423
|
+
// src/services/bridges/bridge.ts
|
|
147392
147424
|
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
147393
147425
|
var DEAD_SESSION_TIMEOUT_MS = 6e4;
|
|
147394
147426
|
var Bridge = class {
|
|
@@ -147427,6 +147459,11 @@ var Bridge = class {
|
|
|
147427
147459
|
autoForceSyncTimer = null;
|
|
147428
147460
|
deadSessionTimer = null;
|
|
147429
147461
|
staleSessionTimers = /* @__PURE__ */ new Map();
|
|
147462
|
+
// Age-based session rotation (#287): track when each session opened so an
|
|
147463
|
+
// aged controller session can be rotated, forcing it to re-subscribe.
|
|
147464
|
+
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
147465
|
+
rotationTimer = null;
|
|
147466
|
+
maxSessionAgeMs = 0;
|
|
147430
147467
|
// Serialize concurrent lifecycle calls so auto-recovery and a manual
|
|
147431
147468
|
// restartBridge can't race past each other's Starting/Stopping states.
|
|
147432
147469
|
startInFlight;
|
|
@@ -147579,6 +147616,7 @@ var Bridge = class {
|
|
|
147579
147616
|
});
|
|
147580
147617
|
}
|
|
147581
147618
|
this.wireSessionDiagnostics();
|
|
147619
|
+
this.startSessionRotation();
|
|
147582
147620
|
logMemoryUsage(this.log, "bridge running");
|
|
147583
147621
|
diagnosticEventBus.emit("bridge_started", `Bridge started`, {
|
|
147584
147622
|
bridgeId: this.id,
|
|
@@ -147636,6 +147674,7 @@ ${e?.toString()}`);
|
|
|
147636
147674
|
wireSessionDiagnostics() {
|
|
147637
147675
|
try {
|
|
147638
147676
|
const sessionManager = this.server.env.get(SessionManager);
|
|
147677
|
+
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
147639
147678
|
this.sessionDiagHandler = (session) => {
|
|
147640
147679
|
const sessions = [...sessionManager.sessions];
|
|
147641
147680
|
let totalSubs = 0;
|
|
@@ -147694,6 +147733,7 @@ ${e?.toString()}`);
|
|
|
147694
147733
|
};
|
|
147695
147734
|
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
147696
147735
|
this.sessionAddedHandler = (newSession) => {
|
|
147736
|
+
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
147697
147737
|
this.log.info(
|
|
147698
147738
|
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
147699
147739
|
);
|
|
@@ -147719,6 +147759,7 @@ ${e?.toString()}`);
|
|
|
147719
147759
|
}
|
|
147720
147760
|
};
|
|
147721
147761
|
this.sessionDeletedHandler = (session) => {
|
|
147762
|
+
this.sessionStartedAt.delete(session.id);
|
|
147722
147763
|
const sessions = [...sessionManager.sessions];
|
|
147723
147764
|
this.log.warn(
|
|
147724
147765
|
`Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
|
|
@@ -147826,6 +147867,8 @@ ${e?.toString()}`);
|
|
|
147826
147867
|
clearTimeout(timer);
|
|
147827
147868
|
}
|
|
147828
147869
|
this.staleSessionTimers.clear();
|
|
147870
|
+
this.stopSessionRotation();
|
|
147871
|
+
this.sessionStartedAt.clear();
|
|
147829
147872
|
}
|
|
147830
147873
|
stopAutoForceSync() {
|
|
147831
147874
|
if (this.autoForceSyncTimer) {
|
|
@@ -147833,12 +147876,100 @@ ${e?.toString()}`);
|
|
|
147833
147876
|
this.autoForceSyncTimer = null;
|
|
147834
147877
|
}
|
|
147835
147878
|
}
|
|
147879
|
+
// Start periodic age-based session rotation (#287). Aging out a controller's
|
|
147880
|
+
// session forces it to re-establish and re-subscribe, recovering a wedged
|
|
147881
|
+
// Alexa subscription that would otherwise stay stuck until a restart.
|
|
147882
|
+
startSessionRotation() {
|
|
147883
|
+
this.stopSessionRotation();
|
|
147884
|
+
const hours = this.readSessionMaxAgeHours();
|
|
147885
|
+
if (hours === 0) {
|
|
147886
|
+
this.log.info(
|
|
147887
|
+
"Session rotation disabled (HAMH_MATTER_SESSION_MAX_AGE_HOURS=0)"
|
|
147888
|
+
);
|
|
147889
|
+
return;
|
|
147890
|
+
}
|
|
147891
|
+
this.maxSessionAgeMs = hours * 60 * 60 * 1e3;
|
|
147892
|
+
this.rotationTimer = setInterval(
|
|
147893
|
+
() => this.rotateAgedSessions(),
|
|
147894
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
147895
|
+
);
|
|
147896
|
+
this.log.info(
|
|
147897
|
+
`Session rotation: max age ${hours}h, check every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
147898
|
+
);
|
|
147899
|
+
}
|
|
147900
|
+
stopSessionRotation() {
|
|
147901
|
+
if (this.rotationTimer) {
|
|
147902
|
+
clearInterval(this.rotationTimer);
|
|
147903
|
+
this.rotationTimer = null;
|
|
147904
|
+
}
|
|
147905
|
+
}
|
|
147906
|
+
// Resolve the rotation max age. Bridge config wins, then the env var. An
|
|
147907
|
+
// aggregator bridge holds many devices on one controller session, so
|
|
147908
|
+
// rotating it re-subscribes them all at once. Rotation is therefore opt-in
|
|
147909
|
+
// here: it stays disabled unless set via config or the env var.
|
|
147910
|
+
readSessionMaxAgeHours() {
|
|
147911
|
+
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
147912
|
+
const fromConfig = this.dataProvider.sessionMaxAgeHours;
|
|
147913
|
+
if (fromConfig != null && Number.isFinite(fromConfig) && fromConfig >= 0) {
|
|
147914
|
+
if (fromConfig === 0) return 0;
|
|
147915
|
+
if (fromConfig < min) return min;
|
|
147916
|
+
if (fromConfig > max) return max;
|
|
147917
|
+
return fromConfig;
|
|
147918
|
+
}
|
|
147919
|
+
const raw = process.env.HAMH_MATTER_SESSION_MAX_AGE_HOURS;
|
|
147920
|
+
if (raw == null || raw === "") {
|
|
147921
|
+
return 0;
|
|
147922
|
+
}
|
|
147923
|
+
const parsed = parseSessionMaxAgeHours(raw);
|
|
147924
|
+
if (parsed == null) {
|
|
147925
|
+
this.log.warn(
|
|
147926
|
+
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, disabling session rotation`
|
|
147927
|
+
);
|
|
147928
|
+
return 0;
|
|
147929
|
+
}
|
|
147930
|
+
return parsed;
|
|
147931
|
+
}
|
|
147932
|
+
// Gracefully close sessions older than maxSessionAgeMs that still hold
|
|
147933
|
+
// subscriptions, so the controller re-establishes CASE and re-subscribes.
|
|
147934
|
+
// 0-sub sessions are handled by the dead/stale-session path.
|
|
147935
|
+
rotateAgedSessions() {
|
|
147936
|
+
if (this.maxSessionAgeMs === 0) return;
|
|
147937
|
+
try {
|
|
147938
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
147939
|
+
const now = Date.now();
|
|
147940
|
+
const closes = [];
|
|
147941
|
+
for (const s of [...sessionManager.sessions]) {
|
|
147942
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
147943
|
+
if (startedAt == null) continue;
|
|
147944
|
+
const ageMs = now - startedAt;
|
|
147945
|
+
if (ageMs < this.maxSessionAgeMs || s.isClosing || s.subscriptions.size === 0) {
|
|
147946
|
+
continue;
|
|
147947
|
+
}
|
|
147948
|
+
const ageMin = Math.round(ageMs / 6e4);
|
|
147949
|
+
this.log.info(
|
|
147950
|
+
`Rotating session ${s.id} (peer ${s.peerNodeId}, age ${ageMin}min, subs ${s.subscriptions.size})`
|
|
147951
|
+
);
|
|
147952
|
+
closes.push(
|
|
147953
|
+
s.initiateClose().catch(() => {
|
|
147954
|
+
return s.initiateForceClose({
|
|
147955
|
+
cause: new Error("session rotation, forcing")
|
|
147956
|
+
});
|
|
147957
|
+
})
|
|
147958
|
+
);
|
|
147959
|
+
}
|
|
147960
|
+
if (closes.length > 0) {
|
|
147961
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
147962
|
+
}
|
|
147963
|
+
} catch {
|
|
147964
|
+
}
|
|
147965
|
+
}
|
|
147836
147966
|
async update(update) {
|
|
147837
147967
|
try {
|
|
147838
147968
|
this.dataProvider.update(update);
|
|
147839
147969
|
await this.refreshDevices();
|
|
147840
147970
|
if (this.status.code === BridgeStatus.Running) {
|
|
147841
147971
|
this.startAutoForceSyncIfEnabled();
|
|
147972
|
+
this.startSessionRotation();
|
|
147842
147973
|
}
|
|
147843
147974
|
} catch (e) {
|
|
147844
147975
|
const reason = "Failed to update bridge due to error:";
|
|
@@ -160218,10 +160349,29 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
160218
160349
|
}
|
|
160219
160350
|
};
|
|
160220
160351
|
|
|
160352
|
+
// src/matter/endpoints/standalone-endpoint-type.ts
|
|
160353
|
+
init_esm7();
|
|
160354
|
+
var BRIDGED_INFO_ID = "bridgedDeviceBasicInformation";
|
|
160355
|
+
function asStandaloneEndpointType(type) {
|
|
160356
|
+
const behaviors = type.behaviors;
|
|
160357
|
+
if (!(BRIDGED_INFO_ID in behaviors)) {
|
|
160358
|
+
return type;
|
|
160359
|
+
}
|
|
160360
|
+
const kept = Object.entries(behaviors).filter(([id]) => id !== BRIDGED_INFO_ID).map(([, behavior]) => behavior);
|
|
160361
|
+
return MutableEndpoint({
|
|
160362
|
+
name: type.name,
|
|
160363
|
+
deviceType: type.deviceType,
|
|
160364
|
+
deviceRevision: type.deviceRevision,
|
|
160365
|
+
deviceClass: type.deviceClass,
|
|
160366
|
+
requirements: type.requirements,
|
|
160367
|
+
behaviors: SupportedBehaviors(...kept)
|
|
160368
|
+
});
|
|
160369
|
+
}
|
|
160370
|
+
|
|
160221
160371
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
160222
160372
|
var logger225 = Logger.get("LegacyEndpoint");
|
|
160223
160373
|
var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
160224
|
-
static async create(registry2, entityId, mapping, pluginDomainMappings) {
|
|
160374
|
+
static async create(registry2, entityId, mapping, pluginDomainMappings, standalone = false) {
|
|
160225
160375
|
const deviceRegistry = registry2.deviceOf(entityId);
|
|
160226
160376
|
let state = registry2.initialState(entityId);
|
|
160227
160377
|
const entity = registry2.entity(entityId);
|
|
@@ -160547,7 +160697,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
160547
160697
|
}
|
|
160548
160698
|
}
|
|
160549
160699
|
const areaName = registry2.getAreaName(entityId);
|
|
160550
|
-
|
|
160700
|
+
let type = createLegacyEndpointType(payload, effectiveMapping, areaName, {
|
|
160551
160701
|
vacuumOnOff: registry2.isVacuumOnOffEnabled(),
|
|
160552
160702
|
cleaningModeOptions,
|
|
160553
160703
|
pluginDomainMappings
|
|
@@ -160555,6 +160705,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
160555
160705
|
if (!type) {
|
|
160556
160706
|
return;
|
|
160557
160707
|
}
|
|
160708
|
+
if (standalone) {
|
|
160709
|
+
type = asStandaloneEndpointType(type);
|
|
160710
|
+
}
|
|
160558
160711
|
const customName = effectiveMapping?.customName;
|
|
160559
160712
|
const mappedIds = getMappedEntityIds(effectiveMapping);
|
|
160560
160713
|
return new _LegacyEndpoint(type, entityId, customName, mappedIds);
|
|
@@ -160879,6 +161032,7 @@ var EntityIsolationService = new EntityIsolationServiceImpl();
|
|
|
160879
161032
|
|
|
160880
161033
|
// src/services/bridges/bridge-endpoint-manager.ts
|
|
160881
161034
|
var MAX_ENTITY_ID_LENGTH = 150;
|
|
161035
|
+
var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
|
|
160882
161036
|
var BridgeEndpointManager = class extends Service {
|
|
160883
161037
|
constructor(client, registry2, mappingStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
|
|
160884
161038
|
super("BridgeEndpointManager");
|
|
@@ -160912,6 +161066,9 @@ var BridgeEndpointManager = class extends Service {
|
|
|
160912
161066
|
unsubscribe;
|
|
160913
161067
|
_failedEntities = [];
|
|
160914
161068
|
mappingFingerprints = /* @__PURE__ */ new Map();
|
|
161069
|
+
// entityId -> first time it went missing from the registry (grace window)
|
|
161070
|
+
pendingRemovals = /* @__PURE__ */ new Map();
|
|
161071
|
+
removalRecheckTimer = null;
|
|
160915
161072
|
pluginEndpoints = /* @__PURE__ */ new Map();
|
|
160916
161073
|
pluginStateUpdating = /* @__PURE__ */ new Set();
|
|
160917
161074
|
pluginListeners = /* @__PURE__ */ new Map();
|
|
@@ -161114,7 +161271,25 @@ var BridgeEndpointManager = class extends Service {
|
|
|
161114
161271
|
} catch (e) {
|
|
161115
161272
|
this.log.error(`Failed to delete isolated endpoint:`, e);
|
|
161116
161273
|
}
|
|
161274
|
+
this.pendingRemovals.delete(endpoint.entityId);
|
|
161275
|
+
this.mappingFingerprints.delete(endpoint.entityId);
|
|
161276
|
+
}
|
|
161277
|
+
}
|
|
161278
|
+
// refreshDevices only runs on registry-fingerprint changes, which may not
|
|
161279
|
+
// recur, so drive any held removals to completion ourselves once the grace
|
|
161280
|
+
// window has passed.
|
|
161281
|
+
scheduleRemovalRecheck() {
|
|
161282
|
+
if (this.removalRecheckTimer) {
|
|
161283
|
+
clearTimeout(this.removalRecheckTimer);
|
|
161284
|
+
this.removalRecheckTimer = null;
|
|
161117
161285
|
}
|
|
161286
|
+
if (this.pendingRemovals.size === 0) return;
|
|
161287
|
+
this.removalRecheckTimer = setTimeout(() => {
|
|
161288
|
+
this.removalRecheckTimer = null;
|
|
161289
|
+
this.refreshDevices().catch(
|
|
161290
|
+
(e) => this.log.warn("Endpoint removal recheck failed:", e)
|
|
161291
|
+
);
|
|
161292
|
+
}, ENDPOINT_REMOVAL_GRACE_MS + 5e3);
|
|
161118
161293
|
}
|
|
161119
161294
|
getPluginDomainMappings() {
|
|
161120
161295
|
if (!this.pluginManager) return void 0;
|
|
@@ -161135,6 +161310,11 @@ var BridgeEndpointManager = class extends Service {
|
|
|
161135
161310
|
}
|
|
161136
161311
|
async dispose() {
|
|
161137
161312
|
this.stopObserving();
|
|
161313
|
+
if (this.removalRecheckTimer) {
|
|
161314
|
+
clearTimeout(this.removalRecheckTimer);
|
|
161315
|
+
this.removalRecheckTimer = null;
|
|
161316
|
+
}
|
|
161317
|
+
this.pendingRemovals.clear();
|
|
161138
161318
|
EntityIsolationService.unregisterIsolationCallback(this.bridgeId);
|
|
161139
161319
|
EntityIsolationService.clearIsolatedEntities(this.bridgeId);
|
|
161140
161320
|
const endpoints = this.root.parts.map((p) => p);
|
|
@@ -161201,14 +161381,30 @@ var BridgeEndpointManager = class extends Service {
|
|
|
161201
161381
|
}
|
|
161202
161382
|
}
|
|
161203
161383
|
const existingEndpoints = [];
|
|
161384
|
+
const now = Date.now();
|
|
161204
161385
|
for (const endpoint of endpoints) {
|
|
161205
|
-
|
|
161386
|
+
const present = this.entityIds.includes(endpoint.entityId);
|
|
161387
|
+
if (present) {
|
|
161388
|
+
this.pendingRemovals.delete(endpoint.entityId);
|
|
161389
|
+
}
|
|
161390
|
+
if (!present) {
|
|
161391
|
+
const since = this.pendingRemovals.get(endpoint.entityId);
|
|
161392
|
+
if (since == null) {
|
|
161393
|
+
this.pendingRemovals.set(endpoint.entityId, now);
|
|
161394
|
+
existingEndpoints.push(endpoint);
|
|
161395
|
+
continue;
|
|
161396
|
+
}
|
|
161397
|
+
if (now - since < ENDPOINT_REMOVAL_GRACE_MS) {
|
|
161398
|
+
existingEndpoints.push(endpoint);
|
|
161399
|
+
continue;
|
|
161400
|
+
}
|
|
161206
161401
|
try {
|
|
161207
161402
|
await endpoint.delete();
|
|
161208
161403
|
} catch (e) {
|
|
161209
161404
|
this.log.warn(`Failed to delete endpoint ${endpoint.entityId}:`, e);
|
|
161210
161405
|
}
|
|
161211
161406
|
this.mappingFingerprints.delete(endpoint.entityId);
|
|
161407
|
+
this.pendingRemovals.delete(endpoint.entityId);
|
|
161212
161408
|
} else if (this.registry.isAutoComposedDevicesEnabled() && this.registry.isComposedSubEntityUsed(endpoint.entityId)) {
|
|
161213
161409
|
this.log.info(
|
|
161214
161410
|
`Deleting standalone endpoint ${endpoint.entityId}, consumed by composed device`
|
|
@@ -161244,6 +161440,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
161244
161440
|
}
|
|
161245
161441
|
}
|
|
161246
161442
|
}
|
|
161443
|
+
this.scheduleRemovalRecheck();
|
|
161247
161444
|
let memoryLimitReached = false;
|
|
161248
161445
|
for (const entityId of this.entityIds) {
|
|
161249
161446
|
if (!memoryLimitReached && isHeapUnderPressure()) {
|
|
@@ -162128,26 +162325,6 @@ init_dist();
|
|
|
162128
162325
|
init_diagnostic_event_bus();
|
|
162129
162326
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
162130
162327
|
var DEAD_SESSION_TIMEOUT_MS2 = 6e4;
|
|
162131
|
-
var DEFAULT_SESSION_MAX_AGE_HOURS = 4;
|
|
162132
|
-
var SESSION_MAX_AGE_HOURS_RANGE = { min: 1, max: 168 };
|
|
162133
|
-
var ROTATION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
162134
|
-
function parseSessionMaxAgeHours(raw) {
|
|
162135
|
-
if (raw == null || raw === "") return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
162136
|
-
const n = Number.parseInt(raw, 10);
|
|
162137
|
-
if (Number.isNaN(n) || n < 0) return null;
|
|
162138
|
-
if (n === 0) return 0;
|
|
162139
|
-
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
162140
|
-
if (n < min) return min;
|
|
162141
|
-
if (n > max) return max;
|
|
162142
|
-
return n;
|
|
162143
|
-
}
|
|
162144
|
-
function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
|
|
162145
|
-
for (const session of sessions) {
|
|
162146
|
-
if (!startedAt.has(session.id)) {
|
|
162147
|
-
startedAt.set(session.id, now);
|
|
162148
|
-
}
|
|
162149
|
-
}
|
|
162150
|
-
}
|
|
162151
162328
|
function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
162152
162329
|
return { ...state, last_updated: now };
|
|
162153
162330
|
}
|
|
@@ -163243,7 +163420,9 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
163243
163420
|
const endpoint = await LegacyEndpoint.create(
|
|
163244
163421
|
this.registry,
|
|
163245
163422
|
entityId,
|
|
163246
|
-
mapping
|
|
163423
|
+
mapping,
|
|
163424
|
+
void 0,
|
|
163425
|
+
true
|
|
163247
163426
|
);
|
|
163248
163427
|
if (!endpoint) {
|
|
163249
163428
|
this._failedEntities.push({
|
|
@@ -163286,6 +163465,10 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
163286
163465
|
mapping,
|
|
163287
163466
|
friendlyName
|
|
163288
163467
|
);
|
|
163468
|
+
const deviceType = this.deviceEndpoint?.type?.deviceType;
|
|
163469
|
+
if (deviceType != null) {
|
|
163470
|
+
await this.serverNode.updateAdvertisedDeviceType(deviceType);
|
|
163471
|
+
}
|
|
163289
163472
|
}
|
|
163290
163473
|
/**
|
|
163291
163474
|
* Creates a Server Mode Vacuum endpoint without BridgedDeviceBasicInformation.
|