@glasshome/sync-layer 0.7.1 → 0.8.0

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.
Files changed (58) hide show
  1. package/dist/index.d.ts +1291 -54
  2. package/dist/index.js +215 -308
  3. package/dist/service-C8H42I-Q.d.ts +459 -0
  4. package/dist/solid/index.d.ts +119 -6
  5. package/dist/solid/index.js +20 -15
  6. package/dist/{statistics-CtRfIF6D.js → statistics-DyCCTwv2.js} +83 -95
  7. package/dist/worker/worker-main.d.ts +11 -8
  8. package/dist/worker/worker-main.js +2 -3
  9. package/dist/{worker-main-C-3QM4di.js → worker-main-D_wyGAIG.js} +47 -157
  10. package/package.json +3 -3
  11. package/dist/calendar/track.d.ts +0 -19
  12. package/dist/calendar/types.d.ts +0 -32
  13. package/dist/camera/fetch.d.ts +0 -16
  14. package/dist/camera/query.d.ts +0 -15
  15. package/dist/camera/types.d.ts +0 -85
  16. package/dist/camera/webrtc.d.ts +0 -31
  17. package/dist/commands/service.d.ts +0 -53
  18. package/dist/commands/types.d.ts +0 -113
  19. package/dist/connection/adapter.d.ts +0 -48
  20. package/dist/connection/auth.d.ts +0 -126
  21. package/dist/connection/bridged.d.ts +0 -16
  22. package/dist/connection/manager.d.ts +0 -51
  23. package/dist/connection/subscription-manager.d.ts +0 -48
  24. package/dist/connection/subscriptions.d.ts +0 -31
  25. package/dist/connection/types.d.ts +0 -151
  26. package/dist/core/privileged-conn.d.ts +0 -29
  27. package/dist/core/reducers.d.ts +0 -99
  28. package/dist/core/store.d.ts +0 -100
  29. package/dist/core/types.d.ts +0 -339
  30. package/dist/demo/demo-data.d.ts +0 -24
  31. package/dist/demo/demo-provider.d.ts +0 -40
  32. package/dist/demo/energy-sim.d.ts +0 -112
  33. package/dist/energy/prefs.d.ts +0 -56
  34. package/dist/entities/area-views.d.ts +0 -32
  35. package/dist/entities/index.d.ts +0 -54
  36. package/dist/entities/views.d.ts +0 -35
  37. package/dist/history/constants.d.ts +0 -16
  38. package/dist/history/fetch.d.ts +0 -19
  39. package/dist/history/query.d.ts +0 -27
  40. package/dist/history/statistics.d.ts +0 -55
  41. package/dist/history/types.d.ts +0 -146
  42. package/dist/host-api.d.ts +0 -50
  43. package/dist/media/url.d.ts +0 -14
  44. package/dist/query/area-builder.d.ts +0 -25
  45. package/dist/query/builder.d.ts +0 -18
  46. package/dist/query/helpers.d.ts +0 -21
  47. package/dist/query/memoization.d.ts +0 -18
  48. package/dist/query/projections.d.ts +0 -74
  49. package/dist/query/types.d.ts +0 -260
  50. package/dist/solid/hooks.d.ts +0 -119
  51. package/dist/solid/indices.d.ts +0 -15
  52. package/dist/weather/fetch.d.ts +0 -19
  53. package/dist/weather/query.d.ts +0 -23
  54. package/dist/weather/types.d.ts +0 -126
  55. package/dist/worker/bridge-client.d.ts +0 -47
  56. package/dist/worker/enforcement.d.ts +0 -53
  57. package/dist/worker/protocol.d.ts +0 -129
  58. package/dist/worker/token-store.d.ts +0 -3
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { applyDemoServiceCall, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, callService, createDemoFixtures, demoEnergyPreferences, entityViewEquals, extractDomain, fetchStatisticsDuringPeriod, forceResubscribe, forceResubscribeCalendars, getAllEntityViews, getAreaView, getAreaViews, getEntityView, getEntityViews, isDemoMode, isEnergyEntity, loadDemoData, normalizeStatisticTime, privilegedConn, registerEntity, sendCommand, setManagerConnection, setPrivilegedConn, setResubscribeHandler, setState, startDemoEnergyTicker, state, stopDemoEnergyTicker, synthesizeEnergyHistory, toggle, turnOff, turnOn, unloadDemoData, updateEntity } from "./statistics-CtRfIF6D.js";
2
- import { runHaBridgeWorker } from "./worker-main-C-3QM4di.js";
3
- import { createConnection, getAuth } from "home-assistant-js-websocket";
1
+ import { A as extractDomain, B as state, C as startDemoEnergyTicker, D as demoEnergyPreferences, E as createDemoFixtures, F as forceResubscribeCalendars, L as privilegedConn, M as registerEntity, N as setManagerConnection, O as isEnergyEntity, P as setResubscribeHandler, R as setPrivilegedConn, S as loadDemoData, T as unloadDemoData, _ as turnOff, a as getAreaView, b as applyDemoServiceCall, c as entityViewEquals, d as getEntityViews, f as batchEntityUpdates, g as toggle, h as sendCommand, i as buildDeviceView, j as forceResubscribe, k as synthesizeEnergyHistory, l as getAllEntityViews, m as callService, n as normalizeStatisticTime, o as getAreaViews, p as batchServiceCalls, r as buildAreaView, s as buildEntityView, t as fetchStatisticsDuringPeriod, u as getEntityView, v as turnOn, w as stopDemoEnergyTicker, x as isDemoMode, y as updateEntity, z as setState } from "./statistics-DyCCTwv2.js";
2
+ import { t as runHaBridgeWorker } from "./worker-main-D_wyGAIG.js";
3
+ import "home-assistant-js-websocket";
4
4
  import { produce } from "solid-js/store";
5
-
6
5
  //#region src/core/reducers.ts
7
6
  /**
8
7
  * Bulk update multiple entities
@@ -11,9 +10,9 @@ import { produce } from "solid-js/store";
11
10
  *
12
11
  * @param entities - Array of entity states
13
12
  */
14
- function bulkUpdateEntities(entities$1) {
13
+ function bulkUpdateEntities(entities) {
15
14
  setState(produce((s) => {
16
- for (const entity$1 of entities$1) s.entities[entity$1.entity_id] = entity$1;
15
+ for (const entity of entities) s.entities[entity.entity_id] = entity;
17
16
  }));
18
17
  }
19
18
  /**
@@ -71,9 +70,9 @@ function removeDevice(deviceId) {
71
70
  *
72
71
  * @param area - Area registry entry
73
72
  */
74
- function updateArea(area$1) {
73
+ function updateArea(area) {
75
74
  setState(produce((s) => {
76
- s.areas[area$1.id] = area$1;
75
+ s.areas[area.id] = area;
77
76
  }));
78
77
  }
79
78
  /**
@@ -136,121 +135,16 @@ function updateStatisticsMetadata(metadata) {
136
135
  for (const meta of metadata) s.statisticsMetadata[meta.statistic_id] = meta;
137
136
  }));
138
137
  }
139
-
140
138
  //#endregion
141
- //#region src/connection/auth.ts
139
+ //#region src/history/constants.ts
142
140
  /**
143
- * Authenticate using OAuth flow
144
- *
145
- * This function uses `getAuth` from `home-assistant-js-websocket` which:
146
- * 1. Redirects the whole page to the Home Assistant authorization page
147
- * 2. Waits for user authorization
148
- * 3. On return, HA redirects back to the app URL with `?auth_callback=1&code=…`
149
- * 4. Exchanges the authorization code for tokens and returns an Auth object
150
- *
151
- * Note: For a complete OAuth flow, you typically need to:
152
- * - Set up a redirect URI endpoint in your app
153
- * - Handle the OAuth callback with the authorization code
154
- * - Use `getAuthCode` callback to provide the code
155
- *
156
- * In browser environments `getAuth` handles this automatically via a full-page
157
- * redirect (not a popup); native platforms supply `getAuthCode` instead.
158
- *
159
- * @param options - OAuth options
160
- * @returns Authenticated Auth object
141
+ * History Constants
161
142
  *
162
- * @example
163
- * ```typescript
164
- * // Basic OAuth flow (browser only)
165
- * const auth = await authenticateWithOAuth({
166
- * url: 'https://homeassistant.local:8123',
167
- * clientId: 'https://homeassistant.local:8123',
168
- * redirectUri: 'https://myapp.com/auth/callback',
169
- * });
170
- * ```
143
+ * Constants and utilities for history fetching and processing.
144
+ * Based on Home Assistant frontend implementation.
171
145
  *
172
- * @example
173
- * ```typescript
174
- * // OAuth with custom code handling
175
- * const auth = await authenticateWithOAuth({
176
- * url: 'https://homeassistant.local:8123',
177
- * clientId: 'https://homeassistant.local:8123',
178
- * redirectUri: 'https://myapp.com/auth/callback',
179
- * getAuthCode: async () => {
180
- * // Wait for user to authorize and get code from callback
181
- * return await waitForAuthCode();
182
- * },
183
- * });
184
- * ```
146
+ * @packageDocumentation
185
147
  */
186
- async function authenticateWithOAuth(options) {
187
- const { url, clientId, redirectUri, authCode, getAuthCode, saveAuth, loadAuth, limitHassInstance } = options;
188
- try {
189
- const auth$1 = await getAuth({
190
- hassUrl: url,
191
- clientId: clientId ?? null,
192
- redirectUrl: redirectUri,
193
- authCode,
194
- limitHassInstance,
195
- async loadTokens() {
196
- if (loadAuth) {
197
- const saved = await loadAuth();
198
- if (saved?.data) return saved.data;
199
- }
200
- return null;
201
- },
202
- saveTokens(tokens) {
203
- if (saveAuth && tokens) {
204
- const authObj = {
205
- hassUrl: url,
206
- clientId,
207
- redirectUrl: redirectUri,
208
- data: tokens,
209
- async refreshAccessToken() {
210
- return Promise.resolve();
211
- }
212
- };
213
- saveAuth(authObj);
214
- }
215
- }
216
- });
217
- return auth$1;
218
- } catch {}
219
- let finalAuthCode = authCode;
220
- if (!finalAuthCode && getAuthCode) finalAuthCode = await getAuthCode();
221
- const auth = await getAuth({
222
- hassUrl: url,
223
- clientId: clientId ?? null,
224
- redirectUrl: redirectUri,
225
- authCode: finalAuthCode,
226
- limitHassInstance,
227
- async loadTokens() {
228
- if (loadAuth) {
229
- const saved = await loadAuth();
230
- if (saved?.data) return saved.data;
231
- }
232
- return null;
233
- },
234
- saveTokens(tokens) {
235
- if (saveAuth && tokens) {
236
- const authObj = {
237
- hassUrl: url,
238
- clientId,
239
- redirectUrl: redirectUri,
240
- data: tokens,
241
- async refreshAccessToken() {
242
- return Promise.resolve();
243
- }
244
- };
245
- saveAuth(authObj);
246
- }
247
- }
248
- });
249
- return auth;
250
- }
251
-
252
- //#endregion
253
- //#region src/history/constants.ts
254
148
  const DOMAINS_USE_LAST_UPDATED = [
255
149
  "climate",
256
150
  "humidifier",
@@ -283,10 +177,14 @@ function entityIdHistoryNeedsAttributes(entityId) {
283
177
  if (!state.entities[entityId]) return NEED_ATTRIBUTE_DOMAINS.includes(domain);
284
178
  return NEED_ATTRIBUTE_DOMAINS.includes(domain);
285
179
  }
286
-
287
180
  //#endregion
288
181
  //#region src/history/fetch.ts
289
182
  /**
183
+ * History Fetching
184
+ *
185
+ * @packageDocumentation
186
+ */
187
+ /**
290
188
  * Fetch history for entities during a time period
291
189
  */
292
190
  async function fetchHistory(options) {
@@ -306,19 +204,17 @@ async function fetchHistory(options) {
306
204
  }
307
205
  const startTimeStr = startTime.toISOString();
308
206
  const endTimeStr = endTime?.toISOString();
309
- const noAttributes = noAttributesOverride ?? (entityIds.length === 0 ? false : !entityIds.some((entityId) => entityIdHistoryNeedsAttributes(entityId)));
310
207
  const params = {
311
208
  type: "history/history_during_period",
312
209
  start_time: startTimeStr,
313
210
  minimal_response: minimalResponse,
314
- no_attributes: noAttributes,
211
+ no_attributes: noAttributesOverride ?? (entityIds.length === 0 ? false : !entityIds.some((entityId) => entityIdHistoryNeedsAttributes(entityId))),
315
212
  include_start_time_state: includeStartTimeState,
316
213
  significant_changes_only: significantChangesOnly
317
214
  };
318
215
  if (endTimeStr) params.end_time = endTimeStr;
319
216
  if (entityIds.length !== 0) params.entity_ids = entityIds;
320
- const response = await sendCommand(params);
321
- return response ?? {};
217
+ return await sendCommand(params) ?? {};
322
218
  }
323
219
  /**
324
220
  * Fetch history for a single entity
@@ -326,7 +222,7 @@ async function fetchHistory(options) {
326
222
  async function fetchEntityHistory(entityId, options) {
327
223
  const { startTime, endTime, includeStartTimeState = true, significantChangesOnly = true, minimalResponse = false, noAttributes = false } = options;
328
224
  try {
329
- const historyData = await fetchHistory({
225
+ const entityHistory = (await fetchHistory({
330
226
  startTime,
331
227
  endTime,
332
228
  entityIds: [entityId],
@@ -334,18 +230,16 @@ async function fetchEntityHistory(entityId, options) {
334
230
  significantChangesOnly,
335
231
  minimalResponse,
336
232
  noAttributes
337
- });
338
- const entityHistory = historyData[entityId] ?? [];
339
- const timeline = entityHistory.map((s) => ({
340
- timestamp: s.lu,
341
- state: s.s,
342
- attributes: s.a,
343
- lastChanged: s.lc,
344
- lastUpdated: s.lu
345
- }));
233
+ }))[entityId] ?? [];
346
234
  return {
347
235
  entityId,
348
- timeline,
236
+ timeline: entityHistory.map((s) => ({
237
+ timestamp: s.lu,
238
+ state: s.s,
239
+ attributes: s.a,
240
+ lastChanged: s.lc,
241
+ lastUpdated: s.lu
242
+ })),
349
243
  entityHistory,
350
244
  loading: false,
351
245
  error: null,
@@ -374,28 +268,35 @@ function historyStateToTimeline(s) {
374
268
  lastUpdated: s.lu
375
269
  };
376
270
  }
377
-
378
271
  //#endregion
379
272
  //#region src/history/query.ts
380
273
  /** Hard cap on retained points per entity timeline. */
381
274
  const MAX_HISTORY_POINTS = 5760;
382
- /** Set of entity IDs with active history tracking. */
383
- const trackedEntities = /* @__PURE__ */ new Set();
275
+ /** Live tracker count per entity. Ref-counted because a timeline is shared: two
276
+ * widgets on the same entity must not let the first unmount delete the store
277
+ * data the second is still rendering (appendHistoryPoint would then silently
278
+ * drop every update and the survivor's chart would freeze). */
279
+ const trackedEntities = /* @__PURE__ */ new Map();
384
280
  /** Check if an entity has active history tracking. */
385
281
  function isHistoryTracked(entityId) {
386
282
  return trackedEntities.has(entityId);
387
283
  }
388
284
  /** Start tracking history for an entity. Fetches initial backfill. */
389
285
  async function trackEntityHistory(entityId, options) {
390
- trackedEntities.add(entityId);
286
+ trackedEntities.set(entityId, (trackedEntities.get(entityId) ?? 0) + 1);
391
287
  const historyData = await fetchEntityHistory(entityId, options);
392
288
  setState(produce((s) => {
393
289
  s.history[entityId] = historyData;
394
290
  }));
395
291
  return historyData;
396
292
  }
397
- /** Stop tracking history for an entity and clear its data. */
293
+ /** Release one tracker. The entity's data is cleared only when the last one goes. */
398
294
  function untrackEntityHistory(entityId) {
295
+ const remaining = (trackedEntities.get(entityId) ?? 0) - 1;
296
+ if (remaining > 0) {
297
+ trackedEntities.set(entityId, remaining);
298
+ return;
299
+ }
399
300
  trackedEntities.delete(entityId);
400
301
  setState(produce((s) => {
401
302
  delete s.history[entityId];
@@ -454,12 +355,11 @@ function bulkAppendHistoryPoints(points) {
454
355
  for (const entityId of touched) {
455
356
  const history = s.history[entityId];
456
357
  if (!history) continue;
457
- if (history.entityHistory.length > MAX_HISTORY_POINTS) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
458
- if (history.timeline.length > MAX_HISTORY_POINTS) history.timeline.splice(0, history.timeline.length - MAX_HISTORY_POINTS);
358
+ if (history.entityHistory.length > 5760) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
359
+ if (history.timeline.length > 5760) history.timeline.splice(0, history.timeline.length - MAX_HISTORY_POINTS);
459
360
  }
460
361
  }));
461
362
  }
462
-
463
363
  //#endregion
464
364
  //#region src/connection/subscriptions.ts
465
365
  /** Registry subscription cleanup functions (entity sub managed by subscription-manager) */
@@ -471,8 +371,7 @@ function bufferMessage(message) {
471
371
  messageBuffer.push(message);
472
372
  if (!rafScheduled) {
473
373
  rafScheduled = true;
474
- const schedule = typeof requestAnimationFrame === "function" ? requestAnimationFrame : queueMicrotask;
475
- schedule(flushMessageBuffer);
374
+ (typeof requestAnimationFrame === "function" ? requestAnimationFrame : queueMicrotask)(flushMessageBuffer);
476
375
  }
477
376
  }
478
377
  function flushMessageBuffer() {
@@ -503,10 +402,9 @@ function flushMessageBuffer() {
503
402
  async function subscribeEntities(connection, entityIds) {
504
403
  const subscribeMsg = { type: "subscribe_entities" };
505
404
  if (entityIds.length > 0) subscribeMsg.entity_ids = entityIds;
506
- const unsub = await connection.subscribeMessage((message) => {
405
+ return await connection.subscribeMessage((message) => {
507
406
  bufferMessage(message);
508
407
  }, subscribeMsg);
509
- return unsub;
510
408
  }
511
409
  /**
512
410
  * Subscribe to all relevant events from Home Assistant
@@ -584,8 +482,7 @@ async function handleDeviceRegistryUpdate(event) {
584
482
  const conn = privilegedConn();
585
483
  if (!conn) return;
586
484
  try {
587
- const devices = await conn.sendMessagePromise({ type: "config/device_registry/list" });
588
- const device = devices.find((d) => d.id === device_id);
485
+ const device = (await conn.sendMessagePromise({ type: "config/device_registry/list" })).find((d) => d.id === device_id);
589
486
  if (device) updateDevice(device);
590
487
  } catch (error) {
591
488
  console.error("Error fetching device:", error);
@@ -598,9 +495,8 @@ async function handleAreaRegistryUpdate(event) {
598
495
  const conn = privilegedConn();
599
496
  if (!conn) return;
600
497
  try {
601
- const areas$1 = await conn.sendMessagePromise({ type: "config/area_registry/list" });
602
- const area$1 = areas$1.find((a) => a.id === area_id);
603
- if (area$1) updateArea(area$1);
498
+ const area = (await conn.sendMessagePromise({ type: "config/area_registry/list" })).find((a) => a.id === area_id);
499
+ if (area) updateArea(area);
604
500
  } catch (error) {
605
501
  console.error("Error fetching area:", error);
606
502
  }
@@ -612,8 +508,7 @@ async function handleFloorRegistryUpdate(event) {
612
508
  const conn = privilegedConn();
613
509
  if (!conn) return;
614
510
  try {
615
- const floors = await conn.sendMessagePromise({ type: "config/floor_registry/list" });
616
- const floor = floors.find((f) => f.floor_id === floor_id);
511
+ const floor = (await conn.sendMessagePromise({ type: "config/floor_registry/list" })).find((f) => f.floor_id === floor_id);
617
512
  if (floor) updateFloor(floor);
618
513
  } catch (error) {
619
514
  console.error("Error fetching floor:", error);
@@ -626,8 +521,7 @@ async function handleLabelRegistryUpdate(event) {
626
521
  const conn = privilegedConn();
627
522
  if (!conn) return;
628
523
  try {
629
- const labels = await conn.sendMessagePromise({ type: "config/label_registry/list" });
630
- const label = labels.find((l) => l.label_id === label_id);
524
+ const label = (await conn.sendMessagePromise({ type: "config/label_registry/list" })).find((l) => l.label_id === label_id);
631
525
  if (label) updateLabel(label);
632
526
  } catch (error) {
633
527
  console.error("Error fetching label:", error);
@@ -652,7 +546,7 @@ async function handleStatisticsUpdate(event) {
652
546
  function timestampToIso(timestamp) {
653
547
  if (timestamp === void 0) return (/* @__PURE__ */ new Date()).toISOString();
654
548
  if (typeof timestamp === "string") return timestamp;
655
- return new Date(timestamp * 1e3).toISOString();
549
+ return (/* @__PURE__ */ new Date(timestamp * 1e3)).toISOString();
656
550
  }
657
551
  function collectHistoryFromCompressed(points, entityId, compressed) {
658
552
  if (!isHistoryTracked(entityId)) return;
@@ -745,10 +639,8 @@ function applyStateDiff(s, entityId, diff) {
745
639
  const removals = diff["-"];
746
640
  if (removals?.a) for (const key of removals.a) delete existing.attributes[key];
747
641
  }
748
-
749
642
  //#endregion
750
643
  //#region src/connection/manager.ts
751
- const SUSPEND_DELAY_MS = 5 * 60 * 1e3;
752
644
  let activeConn = null;
753
645
  let heartbeatInterval = null;
754
646
  let suspendTimeout = null;
@@ -826,7 +718,7 @@ async function loadInitialData(conn) {
826
718
  setState(produce((s) => {
827
719
  for (const device of deviceRegistry) s.devices[device.id] = device;
828
720
  for (const areaApi of areaRegistry) {
829
- const area$1 = {
721
+ const area = {
830
722
  id: areaApi.area_id || areaApi.id,
831
723
  name: areaApi.name,
832
724
  normalized_name: areaApi.normalized_name || areaApi.name.toLowerCase().replace(/\s+/g, "_"),
@@ -837,10 +729,10 @@ async function loadInitialData(conn) {
837
729
  labels: Array.isArray(areaApi.labels) ? areaApi.labels : [],
838
730
  picture: areaApi.picture ?? null,
839
731
  temperature_entity_id: areaApi.temperature_entity_id ?? null,
840
- created_at: typeof areaApi.created_at === "number" ? new Date(areaApi.created_at * 1e3).toISOString() : areaApi.created_at,
841
- modified_at: typeof areaApi.modified_at === "number" ? new Date(areaApi.modified_at * 1e3).toISOString() : areaApi.modified_at
732
+ created_at: typeof areaApi.created_at === "number" ? (/* @__PURE__ */ new Date(areaApi.created_at * 1e3)).toISOString() : areaApi.created_at,
733
+ modified_at: typeof areaApi.modified_at === "number" ? (/* @__PURE__ */ new Date(areaApi.modified_at * 1e3)).toISOString() : areaApi.modified_at
842
734
  };
843
- s.areas[area$1.id] = area$1;
735
+ s.areas[area.id] = area;
844
736
  }
845
737
  for (const floor of floorRegistry) s.floors[floor.floor_id] = floor;
846
738
  for (const label of labelRegistry) s.labels[label.label_id] = label;
@@ -850,7 +742,6 @@ async function loadInitialData(conn) {
850
742
  throw error;
851
743
  }
852
744
  }
853
-
854
745
  //#endregion
855
746
  //#region src/query/area-builder.ts
856
747
  var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
@@ -889,8 +780,7 @@ var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
889
780
  return this.execute();
890
781
  }
891
782
  first() {
892
- const results = this.execute();
893
- return results[0];
783
+ return this.execute()[0];
894
784
  }
895
785
  subscribe(callback) {
896
786
  callback(this.get());
@@ -903,15 +793,15 @@ var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
903
793
  });
904
794
  }
905
795
  execute() {
906
- let areas$1 = getAreaViews();
907
- for (const filter of this.queryState.filters) if (filter.type === "floor") areas$1 = areas$1.filter((area$1) => area$1.floorId === filter.value);
908
- else if (filter.type === "label") areas$1 = areas$1.filter((area$1) => area$1.labels.includes(filter.value));
909
- else if (filter.type === "predicate") areas$1 = areas$1.filter(filter.value);
910
- if (this.queryState.sorts.length > 0) areas$1 = this.sortAreas(areas$1);
911
- return areas$1;
912
- }
913
- sortAreas(areas$1) {
914
- return [...areas$1].sort((a, b) => {
796
+ let areas = getAreaViews();
797
+ for (const filter of this.queryState.filters) if (filter.type === "floor") areas = areas.filter((area) => area.floorId === filter.value);
798
+ else if (filter.type === "label") areas = areas.filter((area) => area.labels.includes(filter.value));
799
+ else if (filter.type === "predicate") areas = areas.filter(filter.value);
800
+ if (this.queryState.sorts.length > 0) areas = this.sortAreas(areas);
801
+ return areas;
802
+ }
803
+ sortAreas(areas) {
804
+ return [...areas].sort((a, b) => {
915
805
  for (const sort of this.queryState.sorts) {
916
806
  const aValue = this.getNestedValue(a, sort.key);
917
807
  const bValue = this.getNestedValue(b, sort.key);
@@ -943,10 +833,18 @@ function areas() {
943
833
  function area(areaId) {
944
834
  return getAreaView(areaId);
945
835
  }
946
-
947
836
  //#endregion
948
837
  //#region src/entities/index.ts
949
838
  /**
839
+ * Entity Index Queries
840
+ *
841
+ * Provides utilities for querying entities by various criteria.
842
+ * In the SolidJS port, indices are NOT stored in state -- they are
843
+ * computed on the fly here (and will be derived reactively in Plan 03).
844
+ *
845
+ * @packageDocumentation
846
+ */
847
+ /**
950
848
  * Get all entity IDs
951
849
  */
952
850
  function getAllEntityIds() {
@@ -968,10 +866,7 @@ function getEntitiesViaDevices(areaId) {
968
866
  const entityIds = [];
969
867
  for (const device of devicesInArea) {
970
868
  const deviceEntities = getEntitiesByDevice(device.id);
971
- for (const entityId of deviceEntities) {
972
- const registry = state.entityRegistry[entityId];
973
- if (!registry?.area_id) entityIds.push(entityId);
974
- }
869
+ for (const entityId of deviceEntities) if (!state.entityRegistry[entityId]?.area_id) entityIds.push(entityId);
975
870
  }
976
871
  return entityIds;
977
872
  }
@@ -984,7 +879,7 @@ function getEntitiesByArea(areaId) {
984
879
  const directEntityIds = [];
985
880
  for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
986
881
  const deviceEntityIds = getEntitiesViaDevices(areaId);
987
- return Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
882
+ return Array.from(/* @__PURE__ */ new Set([...directEntityIds, ...deviceEntityIds]));
988
883
  }
989
884
  /**
990
885
  * Get entities by device
@@ -999,9 +894,9 @@ function getEntitiesByDevice(deviceId) {
999
894
  */
1000
895
  function getEntitiesByFloor(floorId) {
1001
896
  const entityIds = [];
1002
- const areasOnFloor = Object.values(state.areas).filter((area$1) => area$1.floor_id === floorId);
1003
- for (const area$1 of areasOnFloor) {
1004
- const areaEntities = getEntitiesByArea(area$1.id);
897
+ const areasOnFloor = Object.values(state.areas).filter((area) => area.floor_id === floorId);
898
+ for (const area of areasOnFloor) {
899
+ const areaEntities = getEntitiesByArea(area.id);
1005
900
  entityIds.push(...areaEntities);
1006
901
  }
1007
902
  return entityIds;
@@ -1014,7 +909,6 @@ function getEntitiesByLabel(labelId) {
1014
909
  for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.labels?.includes(labelId)) entityIds.push(entityId);
1015
910
  return entityIds;
1016
911
  }
1017
-
1018
912
  //#endregion
1019
913
  //#region src/query/memoization.ts
1020
914
  /**
@@ -1035,7 +929,6 @@ function buildQuerySignature(query) {
1035
929
  function memoizedExecute(_signature, compute) {
1036
930
  return compute();
1037
931
  }
1038
-
1039
932
  //#endregion
1040
933
  //#region src/query/projections.ts
1041
934
  /**
@@ -1044,8 +937,8 @@ function memoizedExecute(_signature, compute) {
1044
937
  * @param entities - Array of entity views
1045
938
  * @returns Array of entity IDs
1046
939
  */
1047
- function projectToIds(entities$1) {
1048
- return entities$1.map((e) => e.id);
940
+ function projectToIds(entities) {
941
+ return entities.map((e) => e.id);
1049
942
  }
1050
943
  /**
1051
944
  * Pick specific fields from entities
@@ -1054,10 +947,10 @@ function projectToIds(entities$1) {
1054
947
  * @param keys - Array of field names to include
1055
948
  * @returns Array of objects with only specified fields
1056
949
  */
1057
- function projectPick(entities$1, keys) {
1058
- return entities$1.map((entity$1) => {
950
+ function projectPick(entities, keys) {
951
+ return entities.map((entity) => {
1059
952
  const result = {};
1060
- for (const key of keys) result[key] = entity$1[key];
953
+ for (const key of keys) result[key] = entity[key];
1061
954
  return result;
1062
955
  });
1063
956
  }
@@ -1070,8 +963,8 @@ function projectPick(entities$1, keys) {
1070
963
  * @param key - Field name or dot-path to extract
1071
964
  * @returns Array of field values
1072
965
  */
1073
- function projectPluck(entities$1, key) {
1074
- return entities$1.map((entity$1) => getNestedValue(entity$1, key));
966
+ function projectPluck(entities, key) {
967
+ return entities.map((entity) => getNestedValue(entity, key));
1075
968
  }
1076
969
  /**
1077
970
  * Map entities using a custom function
@@ -1080,8 +973,8 @@ function projectPluck(entities$1, key) {
1080
973
  * @param mapper - Function to transform each entity
1081
974
  * @returns Array of mapped values
1082
975
  */
1083
- function projectMap(entities$1, mapper) {
1084
- return entities$1.map(mapper);
976
+ function projectMap(entities, mapper) {
977
+ return entities.map(mapper);
1085
978
  }
1086
979
  /**
1087
980
  * Get nested value from object using dot-notation
@@ -1100,12 +993,11 @@ function getNestedValue(obj, path) {
1100
993
  const parts = path.split(".");
1101
994
  let current = obj;
1102
995
  for (const part of parts) {
1103
- if (current === null || current === void 0) return void 0;
996
+ if (current === null || current === void 0) return;
1104
997
  current = current[part];
1105
998
  }
1106
999
  return current;
1107
1000
  }
1108
-
1109
1001
  //#endregion
1110
1002
  //#region src/query/builder.ts
1111
1003
  var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
@@ -1196,12 +1088,10 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1196
1088
  return this.clone({ distinct: key });
1197
1089
  }
1198
1090
  get() {
1199
- const signature = buildQuerySignature(this.queryState);
1200
- return memoizedExecute(signature, () => this.execute());
1091
+ return memoizedExecute(buildQuerySignature(this.queryState), () => this.execute());
1201
1092
  }
1202
1093
  first() {
1203
- const results = this.get();
1204
- return results[0];
1094
+ return this.get()[0];
1205
1095
  }
1206
1096
  subscribe(callback) {
1207
1097
  callback(this.get());
@@ -1215,14 +1105,14 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1215
1105
  }
1216
1106
  execute() {
1217
1107
  const entityIds = this.getEntityIdsFromIndices();
1218
- let entities$1 = getEntityViews(entityIds);
1219
- for (const filter of this.queryState.predicateFilters) entities$1 = entities$1.filter(filter);
1220
- if (this.queryState.sorts.length > 0) entities$1 = this.sortEntities(entities$1);
1221
- if (this.queryState.distinct) entities$1 = this.applyDistinct(entities$1);
1222
- if (this.queryState.skip !== void 0) entities$1 = entities$1.slice(this.queryState.skip);
1223
- if (this.queryState.take !== void 0) entities$1 = entities$1.slice(0, this.queryState.take);
1224
- if (this.queryState.projection) return this.applyProjection(entities$1);
1225
- return entities$1;
1108
+ let entities = getEntityViews(entityIds);
1109
+ for (const filter of this.queryState.predicateFilters) entities = entities.filter(filter);
1110
+ if (this.queryState.sorts.length > 0) entities = this.sortEntities(entities);
1111
+ if (this.queryState.distinct) entities = this.applyDistinct(entities);
1112
+ if (this.queryState.skip !== void 0) entities = entities.slice(this.queryState.skip);
1113
+ if (this.queryState.take !== void 0) entities = entities.slice(0, this.queryState.take);
1114
+ if (this.queryState.projection) return this.applyProjection(entities);
1115
+ return entities;
1226
1116
  }
1227
1117
  getEntityIdsFromIndices() {
1228
1118
  if (this.queryState.indexFilters.length === 0) return getAllEntityIds();
@@ -1249,8 +1139,8 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1249
1139
  }
1250
1140
  return result;
1251
1141
  }
1252
- sortEntities(entities$1) {
1253
- const sorted = [...entities$1];
1142
+ sortEntities(entities) {
1143
+ const sorted = [...entities];
1254
1144
  sorted.sort((a, b) => {
1255
1145
  for (const sort of this.queryState.sorts) {
1256
1146
  const aVal = getNestedValue(a, sort.key);
@@ -1270,32 +1160,33 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1270
1160
  if (typeof a === "number" && typeof b === "number") return a - b;
1271
1161
  return String(a).localeCompare(String(b));
1272
1162
  }
1273
- applyProjection(entities$1) {
1274
- if (!this.queryState.projection) return entities$1;
1163
+ applyProjection(entities) {
1164
+ if (!this.queryState.projection) return entities;
1275
1165
  switch (this.queryState.projection.type) {
1276
- case "ids": return projectToIds(entities$1);
1277
- case "pick": return projectPick(entities$1, this.queryState.projection.keys);
1278
- case "pluck": return projectPluck(entities$1, this.queryState.projection.key);
1279
- case "map": return projectMap(entities$1, this.queryState.projection.mapper);
1280
- default: return entities$1;
1166
+ case "ids": return projectToIds(entities);
1167
+ case "pick": return projectPick(entities, this.queryState.projection.keys);
1168
+ case "pluck": return projectPluck(entities, this.queryState.projection.key);
1169
+ case "map": return projectMap(entities, this.queryState.projection.mapper);
1170
+ default: return entities;
1281
1171
  }
1282
1172
  }
1283
- applyDistinct(entities$1) {
1284
- if (!this.queryState.distinct) return entities$1;
1173
+ applyDistinct(entities) {
1174
+ if (!this.queryState.distinct) return entities;
1285
1175
  const seen = /* @__PURE__ */ new Set();
1286
1176
  const result = [];
1287
- for (const entity$1 of entities$1) {
1288
- const value = getNestedValue(entity$1, this.queryState.distinct);
1177
+ for (const entity of entities) {
1178
+ const value = getNestedValue(entity, this.queryState.distinct);
1289
1179
  const key = JSON.stringify(value);
1290
1180
  if (!seen.has(key)) {
1291
1181
  seen.add(key);
1292
- result.push(entity$1);
1182
+ result.push(entity);
1293
1183
  }
1294
1184
  }
1295
1185
  return result;
1296
1186
  }
1297
1187
  };
1298
1188
  var EntityQueryBuilderImpl = class {
1189
+ entityId;
1299
1190
  constructor(entityId) {
1300
1191
  this.entityId = entityId;
1301
1192
  }
@@ -1323,7 +1214,6 @@ function entities() {
1323
1214
  function entity(entityId) {
1324
1215
  return new EntityQueryBuilderImpl(entityId);
1325
1216
  }
1326
-
1327
1217
  //#endregion
1328
1218
  //#region src/query/helpers.ts
1329
1219
  /**
@@ -1345,9 +1235,13 @@ function entitiesByAreaAndDomain(areaId, domain) {
1345
1235
  function entitiesWhere(predicate) {
1346
1236
  return entities().where(predicate);
1347
1237
  }
1348
-
1349
1238
  //#endregion
1350
1239
  //#region src/camera/fetch.ts
1240
+ /**
1241
+ * Camera Stream Fetching
1242
+ *
1243
+ * @packageDocumentation
1244
+ */
1351
1245
  /** Public HLS test stream for demo/disconnected mode */
1352
1246
  const DEMO_HLS_STREAM = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
1353
1247
  /**
@@ -1357,12 +1251,11 @@ const DEMO_HLS_STREAM = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
1357
1251
  async function fetchStream(entityId, format = "hls") {
1358
1252
  if (isDemoMode()) return format === "hls" ? DEMO_HLS_STREAM : null;
1359
1253
  if (!privilegedConn()) return null;
1360
- const response = await sendCommand({
1254
+ return (await sendCommand({
1361
1255
  type: "camera/stream",
1362
1256
  entity_id: entityId,
1363
1257
  format
1364
- });
1365
- return response?.url ?? null;
1258
+ }))?.url ?? null;
1366
1259
  }
1367
1260
  /**
1368
1261
  * Transform relative stream URL to absolute URL
@@ -1376,8 +1269,7 @@ function transformStreamUrl(streamUrl) {
1376
1269
  console.warn("[fetchStreamData] No Home Assistant URL available to resolve relative stream URL");
1377
1270
  return streamUrl;
1378
1271
  }
1379
- const baseUrl = hassUrl.replace(/\/$/, "");
1380
- return `${baseUrl}${streamUrl}`;
1272
+ return `${hassUrl.replace(/\/$/, "")}${streamUrl}`;
1381
1273
  }
1382
1274
  return streamUrl;
1383
1275
  }
@@ -1387,49 +1279,48 @@ function transformStreamUrl(streamUrl) {
1387
1279
  async function fetchStreamData(entityId, options = {}) {
1388
1280
  const { format = "hls" } = options;
1389
1281
  try {
1390
- const streamUrl = await fetchStream(entityId, format);
1391
- const absoluteStreamUrl = transformStreamUrl(streamUrl);
1392
- const stream = {
1393
- url: absoluteStreamUrl,
1394
- loading: false,
1395
- error: null,
1396
- lastFetched: Date.now(),
1397
- format,
1398
- expiresAt: null
1399
- };
1400
1282
  return {
1401
1283
  entityId,
1402
- stream,
1284
+ stream: {
1285
+ url: transformStreamUrl(await fetchStream(entityId, format)),
1286
+ loading: false,
1287
+ error: null,
1288
+ lastFetched: Date.now(),
1289
+ format,
1290
+ expiresAt: null
1291
+ },
1403
1292
  loading: false,
1404
1293
  error: null,
1405
1294
  lastFetched: Date.now()
1406
1295
  };
1407
1296
  } catch (error) {
1408
- const stream = {
1409
- url: null,
1410
- loading: false,
1411
- error: error instanceof Error ? error : new Error(String(error)),
1412
- lastFetched: null,
1413
- format,
1414
- expiresAt: null
1415
- };
1416
1297
  return {
1417
1298
  entityId,
1418
- stream,
1299
+ stream: {
1300
+ url: null,
1301
+ loading: false,
1302
+ error: error instanceof Error ? error : new Error(String(error)),
1303
+ lastFetched: null,
1304
+ format,
1305
+ expiresAt: null
1306
+ },
1419
1307
  loading: false,
1420
1308
  error: error instanceof Error ? error : new Error(String(error)),
1421
1309
  lastFetched: null
1422
1310
  };
1423
1311
  }
1424
1312
  }
1425
-
1426
1313
  //#endregion
1427
1314
  //#region src/camera/query.ts
1428
- const DEFAULT_REFRESH_INTERVAL = 4 * 60 * 1e3;
1315
+ /**
1316
+ * Camera Stream Query Builder
1317
+ *
1318
+ * @packageDocumentation
1319
+ */
1320
+ const DEFAULT_REFRESH_INTERVAL = 24e4;
1429
1321
  function needsStreamRefresh(streamData, refreshInterval = DEFAULT_REFRESH_INTERVAL) {
1430
1322
  if (!streamData || !streamData.lastFetched) return true;
1431
- const age = Date.now() - streamData.lastFetched;
1432
- return age >= refreshInterval;
1323
+ return Date.now() - streamData.lastFetched >= refreshInterval;
1433
1324
  }
1434
1325
  /**
1435
1326
  * Get stream for a camera entity
@@ -1442,9 +1333,9 @@ async function getStream(entityId, options = {}) {
1442
1333
  format,
1443
1334
  autoRefresh,
1444
1335
  refreshInterval
1445
- }).then((streamData$1) => {
1336
+ }).then((streamData) => {
1446
1337
  setState(produce((s) => {
1447
- s.streams[entityId] = streamData$1;
1338
+ s.streams[entityId] = streamData;
1448
1339
  }));
1449
1340
  });
1450
1341
  return cached;
@@ -1468,7 +1359,6 @@ async function refreshStream(entityId, format = "hls") {
1468
1359
  }));
1469
1360
  return getStream(entityId, { format });
1470
1361
  }
1471
-
1472
1362
  //#endregion
1473
1363
  //#region src/energy/prefs.ts
1474
1364
  function isRecord(value) {
@@ -1505,7 +1395,6 @@ async function fetchEnergyPreferences(connection) {
1505
1395
  device_consumption: devices
1506
1396
  };
1507
1397
  }
1508
-
1509
1398
  //#endregion
1510
1399
  //#region src/media/url.ts
1511
1400
  /**
@@ -1529,7 +1418,6 @@ function hassMediaUrl(path) {
1529
1418
  if (base) return `${base}?path=${encodeURIComponent(path)}`;
1530
1419
  return state.hassUrl ? `${state.hassUrl}${path}` : path;
1531
1420
  }
1532
-
1533
1421
  //#endregion
1534
1422
  //#region src/camera/webrtc.ts
1535
1423
  /**
@@ -1551,7 +1439,7 @@ async function getWebRtcClientConfig(entityId) {
1551
1439
  */
1552
1440
  function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1553
1441
  const conn = privilegedConn();
1554
- if (!conn) return Promise.reject(new Error("Not connected"));
1442
+ if (!conn) return Promise.reject(/* @__PURE__ */ new Error("Not connected"));
1555
1443
  return new Promise((resolve, reject) => {
1556
1444
  const session = {
1557
1445
  sessionId: null,
@@ -1561,7 +1449,7 @@ function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1561
1449
  const timeout = setTimeout(() => {
1562
1450
  if (!answered) {
1563
1451
  session.unsubscribe?.();
1564
- reject(new Error("WebRTC offer timed out"));
1452
+ reject(/* @__PURE__ */ new Error("WebRTC offer timed out"));
1565
1453
  }
1566
1454
  }, 15e3);
1567
1455
  conn.subscribeMessage((event) => {
@@ -1586,7 +1474,6 @@ function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1586
1474
  clearTimeout(timeout);
1587
1475
  session.unsubscribe?.();
1588
1476
  reject(new Error(event.message || event.code || "WebRTC offer failed"));
1589
- break;
1590
1477
  }
1591
1478
  }, {
1592
1479
  type: "camera/webrtc/offer",
@@ -1613,17 +1500,21 @@ async function sendWebRtcCandidate(entityId, sessionId, candidate) {
1613
1500
  candidate
1614
1501
  });
1615
1502
  }
1616
-
1617
1503
  //#endregion
1618
1504
  //#region src/weather/fetch.ts
1619
1505
  /**
1506
+ * Weather Forecast Fetching
1507
+ *
1508
+ * @packageDocumentation
1509
+ */
1510
+ /**
1620
1511
  * In demo mode, derive forecasts from the entity's `forecast` attribute.
1621
1512
  * Hourly and daily are synthesised from the same source (daily entries
1622
1513
  * expanded into 12 hourly steps for the chart).
1623
1514
  */
1624
1515
  function buildDemoForecast(entityId, type) {
1625
- const entity$1 = state.entities[entityId];
1626
- const raw = entity$1?.attributes?.forecast ?? [];
1516
+ const entity = state.entities[entityId];
1517
+ const raw = entity?.attributes?.forecast ?? [];
1627
1518
  if (raw.length === 0) return [];
1628
1519
  if (type === "daily" || type === "twice_daily") return raw.map((d) => {
1629
1520
  const templow = d.templow;
@@ -1651,7 +1542,7 @@ function buildDemoForecast(entityId, type) {
1651
1542
  out.push({
1652
1543
  datetime: dt.toISOString(),
1653
1544
  temperature: Math.round((mid + Math.sin(phase) * swing) * 10) / 10,
1654
- condition: today?.condition ?? entity$1?.state ?? "cloudy"
1545
+ condition: today?.condition ?? entity?.state ?? "cloudy"
1655
1546
  });
1656
1547
  }
1657
1548
  return out;
@@ -1669,7 +1560,7 @@ async function fetchForecast(entityId, type) {
1669
1560
  const timeout = setTimeout(() => {
1670
1561
  if (!resolved && unsubscribe) {
1671
1562
  resolved = true;
1672
- unsubscribe().then(() => reject(new Error("Forecast subscription timeout"))).catch(() => reject(new Error("Forecast subscription timeout")));
1563
+ unsubscribe().then(() => reject(/* @__PURE__ */ new Error("Forecast subscription timeout"))).catch(() => reject(/* @__PURE__ */ new Error("Forecast subscription timeout")));
1673
1564
  }
1674
1565
  }, 1e4);
1675
1566
  conn.subscribeMessage((event) => {
@@ -1732,11 +1623,10 @@ async function fetchForecasts(entityId, types) {
1732
1623
  */
1733
1624
  async function fetchForecastData(entityId, type) {
1734
1625
  try {
1735
- const forecast = await fetchForecast(entityId, type);
1736
1626
  return {
1737
1627
  entityId,
1738
1628
  type,
1739
- forecast,
1629
+ forecast: await fetchForecast(entityId, type),
1740
1630
  loading: false,
1741
1631
  error: null,
1742
1632
  lastFetched: Date.now()
@@ -1752,10 +1642,14 @@ async function fetchForecastData(entityId, type) {
1752
1642
  };
1753
1643
  }
1754
1644
  }
1755
-
1756
1645
  //#endregion
1757
1646
  //#region src/weather/query.ts
1758
1647
  /**
1648
+ * Weather Forecast Query Builder
1649
+ *
1650
+ * @packageDocumentation
1651
+ */
1652
+ /**
1759
1653
  * Get forecast for a single entity and type
1760
1654
  */
1761
1655
  async function getForecast(entityId, type) {
@@ -1870,12 +1764,11 @@ async function refreshForecasts(entityId, types) {
1870
1764
  }));
1871
1765
  return getForecasts(entityId, types);
1872
1766
  }
1873
-
1874
1767
  //#endregion
1875
1768
  //#region src/worker/bridge-client.ts
1876
- var BridgeNeedsAuthError = class extends Error {
1769
+ var BridgeInvalidAuthError = class extends Error {
1877
1770
  constructor() {
1878
- super("No stored Home Assistant tokens; interactive sign-in required");
1771
+ super("Home Assistant rejected the connection; an admin needs to sign in to Home Assistant again");
1879
1772
  }
1880
1773
  };
1881
1774
  function createHaBridge(worker, events = {}) {
@@ -1899,12 +1792,13 @@ function createHaBridge(worker, events = {}) {
1899
1792
  switch (msg.k) {
1900
1793
  case "connect_result":
1901
1794
  if (msg.ok) pendingConnect?.resolve();
1902
- else pendingConnect?.reject(msg.needsAuth ? new BridgeNeedsAuthError() : new Error(msg.error ?? "Connect failed"));
1795
+ else pendingConnect?.reject(msg.reason === "invalid_auth" ? new BridgeInvalidAuthError() : new Error(msg.error ?? "Connect failed"));
1903
1796
  pendingConnect = null;
1904
1797
  break;
1905
1798
  case "conn":
1906
1799
  connState = msg.state;
1907
1800
  events.onConnState?.(msg.state);
1801
+ if (msg.reason === "invalid_auth") events.onInvalidAuth?.();
1908
1802
  break;
1909
1803
  case "ready_after_reconnect":
1910
1804
  events.onReadyAfterReconnect?.();
@@ -1917,17 +1811,14 @@ function createHaBridge(worker, events = {}) {
1917
1811
  else pending.reject(new Error(msg.error ?? "Request failed"));
1918
1812
  break;
1919
1813
  }
1920
- case "event": {
1814
+ case "event":
1921
1815
  subCallbacks.get(msg.id)?.(msg.payload);
1922
1816
  for (const listener of rawListeners) listener(msg.payload);
1923
1817
  break;
1924
- }
1925
1818
  case "sub_end":
1926
1819
  subCallbacks.delete(msg.id);
1927
1820
  break;
1928
- case "denial":
1929
- events.onDenial?.(msg);
1930
- break;
1821
+ case "denial": events.onDenial?.(msg);
1931
1822
  }
1932
1823
  };
1933
1824
  function sendMessagePromise(message) {
@@ -1962,24 +1853,6 @@ function createHaBridge(worker, events = {}) {
1962
1853
  });
1963
1854
  };
1964
1855
  }
1965
- const conn = {
1966
- connect: () => Promise.resolve(),
1967
- disconnect: () => post({ k: "disconnect" }),
1968
- get connected$() {
1969
- return connState === "connected";
1970
- },
1971
- authenticate: () => Promise.resolve(),
1972
- get authState() {
1973
- return connState === "connected" ? "authenticated" : "pending";
1974
- },
1975
- subscribeEvents: (callback, eventType) => Promise.resolve(subscribe("events", callback, eventType)),
1976
- subscribeMessage: (callback, message) => Promise.resolve(subscribe("message", callback, void 0, message)),
1977
- sendMessagePromise,
1978
- addEventListener: (handler) => {
1979
- rawListeners.add(handler);
1980
- return () => rawListeners.delete(handler);
1981
- }
1982
- };
1983
1856
  return {
1984
1857
  connect(opts) {
1985
1858
  return new Promise((resolve, reject) => {
@@ -1990,7 +1863,6 @@ function createHaBridge(worker, events = {}) {
1990
1863
  post({
1991
1864
  k: "connect",
1992
1865
  url: opts.url,
1993
- mode: opts.mode,
1994
1866
  proxyWsPath: opts.proxyWsPath,
1995
1867
  proxyTicket: opts.proxyTicket
1996
1868
  });
@@ -1998,8 +1870,24 @@ function createHaBridge(worker, events = {}) {
1998
1870
  },
1999
1871
  disconnect: () => post({ k: "disconnect" }),
2000
1872
  reconnect: () => post({ k: "reconnect" }),
2001
- clearTokens: () => post({ k: "clear_tokens" }),
2002
- conn,
1873
+ conn: {
1874
+ connect: () => Promise.resolve(),
1875
+ disconnect: () => post({ k: "disconnect" }),
1876
+ get connected$() {
1877
+ return connState === "connected";
1878
+ },
1879
+ authenticate: () => Promise.resolve(),
1880
+ get authState() {
1881
+ return connState === "connected" ? "authenticated" : "pending";
1882
+ },
1883
+ subscribeEvents: (callback, eventType) => Promise.resolve(subscribe("events", callback, eventType)),
1884
+ subscribeMessage: (callback, message) => Promise.resolve(subscribe("message", callback, void 0, message)),
1885
+ sendMessagePromise,
1886
+ addEventListener: (handler) => {
1887
+ rawListeners.add(handler);
1888
+ return () => rawListeners.delete(handler);
1889
+ }
1890
+ },
2003
1891
  registerWidget(widgetId, caps) {
2004
1892
  const channel = new MessageChannel();
2005
1893
  post({
@@ -2034,7 +1922,6 @@ function createHaBridge(worker, events = {}) {
2034
1922
  }
2035
1923
  };
2036
1924
  }
2037
-
2038
1925
  //#endregion
2039
1926
  //#region src/connection/bridged.ts
2040
1927
  /**
@@ -2058,10 +1945,10 @@ async function attachBridgeToStore(bridge, hassUrl, mediaProxyBase) {
2058
1945
  await forceResubscribeCalendars();
2059
1946
  }
2060
1947
  /** Map worker connection-state messages onto the store. */
2061
- function applyBridgeConnState(state$1) {
1948
+ function applyBridgeConnState(state) {
2062
1949
  setState(produce((s) => {
2063
- s.connectionState = state$1;
2064
- if (state$1 === "connected") s.connectionError = null;
1950
+ s.connectionState = state;
1951
+ if (state === "connected") s.connectionError = null;
2065
1952
  }));
2066
1953
  }
2067
1954
  /** Re-sync after the worker reports a completed reconnect. */
@@ -2077,16 +1964,38 @@ function detachBridgeFromStore() {
2077
1964
  s.connectionState = "disconnected";
2078
1965
  }));
2079
1966
  }
2080
-
2081
1967
  //#endregion
2082
1968
  //#region src/host-api.ts
1969
+ /**
1970
+ * One-shot handoff of the host-only surface.
1971
+ *
1972
+ * The problem this solves: the host import map serves this package's entry to
1973
+ * every widget bundle, so everything exported there is callable by untrusted
1974
+ * widget code. But the host needs privileged operations from the same package —
1975
+ * connect the bridge, authenticate, disconnect — and it cannot import them
1976
+ * through a private subpath, because the store must be ONE instance shared with
1977
+ * widgets. An unserved subpath gets bundled into the host and forks the store
1978
+ * (finding 45); a served one is reachable by widgets again (finding 46). A
1979
+ * subpath cannot be both, so hiding is not available.
1980
+ *
1981
+ * So the surface stays on the shared entry and is protected by ORDER instead of
1982
+ * by visibility: `claimHostApi()` yields the API to its first caller and null to
1983
+ * everyone after. The host claims it while booting; widget bundles are fetched
1984
+ * and evaluated later, by which point there is nothing left to take.
1985
+ *
1986
+ * Why these members are dangerous in a widget's hands:
1987
+ * - attachBridgeToStore / createHaBridge: install a connection, i.e. replace the
1988
+ * host's link with one the widget controls — every subsequent service call is
1989
+ * intercepted and every entity the dashboard renders can be fabricated.
1990
+ * - disconnect / detachBridgeFromStore: drop the household's connection.
1991
+ * - loadDemoData / unloadDemoData: overwrite live state with fixtures.
1992
+ */
2083
1993
  let unclaimed = {
2084
1994
  attachBridgeToStore,
2085
1995
  detachBridgeFromStore,
2086
1996
  applyBridgeConnState,
2087
1997
  reloadAfterBridgeReconnect,
2088
1998
  createHaBridge,
2089
- authenticateWithOAuth,
2090
1999
  disconnect,
2091
2000
  loadDemoData,
2092
2001
  unloadDemoData
@@ -2104,10 +2013,8 @@ function claimHostApi() {
2104
2013
  unclaimed = null;
2105
2014
  return api;
2106
2015
  }
2107
-
2108
2016
  //#endregion
2109
2017
  //#region src/index.ts
2110
2018
  const VERSION = "0.2.1";
2111
-
2112
2019
  //#endregion
2113
- export { BridgeNeedsAuthError, DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, MAX_HISTORY_POINTS, NEED_ATTRIBUTE_DOMAINS, VERSION, appendHistoryPoint, applyDemoServiceCall, area, areas, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, claimHostApi, createDemoFixtures, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEnergyPreferences, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStatisticsDuringPeriod, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, hassMediaUrl, historyStateToTimeline, isConnected, isDemoMode, isHistoryTracked, normalizeStatisticTime, refreshForecast, refreshForecasts, refreshStream, registerEntity, runHaBridgeWorker, searchEntities, sendCommand, sendWebRtcCandidate, startDemoEnergyTicker, startWebRtcSession, state, stopDemoEnergyTicker, toggle, trackEntityHistory, turnOff, turnOn, untrackEntityHistory, updateEntity };
2020
+ export { BridgeInvalidAuthError, DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, MAX_HISTORY_POINTS, NEED_ATTRIBUTE_DOMAINS, VERSION, appendHistoryPoint, applyDemoServiceCall, area, areas, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, claimHostApi, createDemoFixtures, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEnergyPreferences, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStatisticsDuringPeriod, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, hassMediaUrl, historyStateToTimeline, isConnected, isDemoMode, isHistoryTracked, normalizeStatisticTime, refreshForecast, refreshForecasts, refreshStream, registerEntity, runHaBridgeWorker, searchEntities, sendCommand, sendWebRtcCandidate, startDemoEnergyTicker, startWebRtcSession, state, stopDemoEnergyTicker, toggle, trackEntityHistory, turnOff, turnOn, untrackEntityHistory, updateEntity };