@glasshome/sync-layer 0.7.2 → 0.8.1

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 +203 -304
  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 +4 -4
  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,7 +268,6 @@ 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. */
@@ -462,12 +355,11 @@ function bulkAppendHistoryPoints(points) {
462
355
  for (const entityId of touched) {
463
356
  const history = s.history[entityId];
464
357
  if (!history) continue;
465
- if (history.entityHistory.length > MAX_HISTORY_POINTS) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
466
- 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);
467
360
  }
468
361
  }));
469
362
  }
470
-
471
363
  //#endregion
472
364
  //#region src/connection/subscriptions.ts
473
365
  /** Registry subscription cleanup functions (entity sub managed by subscription-manager) */
@@ -479,8 +371,7 @@ function bufferMessage(message) {
479
371
  messageBuffer.push(message);
480
372
  if (!rafScheduled) {
481
373
  rafScheduled = true;
482
- const schedule = typeof requestAnimationFrame === "function" ? requestAnimationFrame : queueMicrotask;
483
- schedule(flushMessageBuffer);
374
+ (typeof requestAnimationFrame === "function" ? requestAnimationFrame : queueMicrotask)(flushMessageBuffer);
484
375
  }
485
376
  }
486
377
  function flushMessageBuffer() {
@@ -511,10 +402,9 @@ function flushMessageBuffer() {
511
402
  async function subscribeEntities(connection, entityIds) {
512
403
  const subscribeMsg = { type: "subscribe_entities" };
513
404
  if (entityIds.length > 0) subscribeMsg.entity_ids = entityIds;
514
- const unsub = await connection.subscribeMessage((message) => {
405
+ return await connection.subscribeMessage((message) => {
515
406
  bufferMessage(message);
516
407
  }, subscribeMsg);
517
- return unsub;
518
408
  }
519
409
  /**
520
410
  * Subscribe to all relevant events from Home Assistant
@@ -592,8 +482,7 @@ async function handleDeviceRegistryUpdate(event) {
592
482
  const conn = privilegedConn();
593
483
  if (!conn) return;
594
484
  try {
595
- const devices = await conn.sendMessagePromise({ type: "config/device_registry/list" });
596
- 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);
597
486
  if (device) updateDevice(device);
598
487
  } catch (error) {
599
488
  console.error("Error fetching device:", error);
@@ -606,9 +495,8 @@ async function handleAreaRegistryUpdate(event) {
606
495
  const conn = privilegedConn();
607
496
  if (!conn) return;
608
497
  try {
609
- const areas$1 = await conn.sendMessagePromise({ type: "config/area_registry/list" });
610
- const area$1 = areas$1.find((a) => a.id === area_id);
611
- 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);
612
500
  } catch (error) {
613
501
  console.error("Error fetching area:", error);
614
502
  }
@@ -620,8 +508,7 @@ async function handleFloorRegistryUpdate(event) {
620
508
  const conn = privilegedConn();
621
509
  if (!conn) return;
622
510
  try {
623
- const floors = await conn.sendMessagePromise({ type: "config/floor_registry/list" });
624
- 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);
625
512
  if (floor) updateFloor(floor);
626
513
  } catch (error) {
627
514
  console.error("Error fetching floor:", error);
@@ -634,8 +521,7 @@ async function handleLabelRegistryUpdate(event) {
634
521
  const conn = privilegedConn();
635
522
  if (!conn) return;
636
523
  try {
637
- const labels = await conn.sendMessagePromise({ type: "config/label_registry/list" });
638
- 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);
639
525
  if (label) updateLabel(label);
640
526
  } catch (error) {
641
527
  console.error("Error fetching label:", error);
@@ -660,7 +546,7 @@ async function handleStatisticsUpdate(event) {
660
546
  function timestampToIso(timestamp) {
661
547
  if (timestamp === void 0) return (/* @__PURE__ */ new Date()).toISOString();
662
548
  if (typeof timestamp === "string") return timestamp;
663
- return new Date(timestamp * 1e3).toISOString();
549
+ return (/* @__PURE__ */ new Date(timestamp * 1e3)).toISOString();
664
550
  }
665
551
  function collectHistoryFromCompressed(points, entityId, compressed) {
666
552
  if (!isHistoryTracked(entityId)) return;
@@ -753,10 +639,8 @@ function applyStateDiff(s, entityId, diff) {
753
639
  const removals = diff["-"];
754
640
  if (removals?.a) for (const key of removals.a) delete existing.attributes[key];
755
641
  }
756
-
757
642
  //#endregion
758
643
  //#region src/connection/manager.ts
759
- const SUSPEND_DELAY_MS = 5 * 60 * 1e3;
760
644
  let activeConn = null;
761
645
  let heartbeatInterval = null;
762
646
  let suspendTimeout = null;
@@ -834,7 +718,7 @@ async function loadInitialData(conn) {
834
718
  setState(produce((s) => {
835
719
  for (const device of deviceRegistry) s.devices[device.id] = device;
836
720
  for (const areaApi of areaRegistry) {
837
- const area$1 = {
721
+ const area = {
838
722
  id: areaApi.area_id || areaApi.id,
839
723
  name: areaApi.name,
840
724
  normalized_name: areaApi.normalized_name || areaApi.name.toLowerCase().replace(/\s+/g, "_"),
@@ -845,10 +729,10 @@ async function loadInitialData(conn) {
845
729
  labels: Array.isArray(areaApi.labels) ? areaApi.labels : [],
846
730
  picture: areaApi.picture ?? null,
847
731
  temperature_entity_id: areaApi.temperature_entity_id ?? null,
848
- created_at: typeof areaApi.created_at === "number" ? new Date(areaApi.created_at * 1e3).toISOString() : areaApi.created_at,
849
- 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
850
734
  };
851
- s.areas[area$1.id] = area$1;
735
+ s.areas[area.id] = area;
852
736
  }
853
737
  for (const floor of floorRegistry) s.floors[floor.floor_id] = floor;
854
738
  for (const label of labelRegistry) s.labels[label.label_id] = label;
@@ -858,7 +742,6 @@ async function loadInitialData(conn) {
858
742
  throw error;
859
743
  }
860
744
  }
861
-
862
745
  //#endregion
863
746
  //#region src/query/area-builder.ts
864
747
  var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
@@ -897,8 +780,7 @@ var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
897
780
  return this.execute();
898
781
  }
899
782
  first() {
900
- const results = this.execute();
901
- return results[0];
783
+ return this.execute()[0];
902
784
  }
903
785
  subscribe(callback) {
904
786
  callback(this.get());
@@ -911,15 +793,15 @@ var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
911
793
  });
912
794
  }
913
795
  execute() {
914
- let areas$1 = getAreaViews();
915
- for (const filter of this.queryState.filters) if (filter.type === "floor") areas$1 = areas$1.filter((area$1) => area$1.floorId === filter.value);
916
- else if (filter.type === "label") areas$1 = areas$1.filter((area$1) => area$1.labels.includes(filter.value));
917
- else if (filter.type === "predicate") areas$1 = areas$1.filter(filter.value);
918
- if (this.queryState.sorts.length > 0) areas$1 = this.sortAreas(areas$1);
919
- return areas$1;
920
- }
921
- sortAreas(areas$1) {
922
- 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) => {
923
805
  for (const sort of this.queryState.sorts) {
924
806
  const aValue = this.getNestedValue(a, sort.key);
925
807
  const bValue = this.getNestedValue(b, sort.key);
@@ -951,10 +833,18 @@ function areas() {
951
833
  function area(areaId) {
952
834
  return getAreaView(areaId);
953
835
  }
954
-
955
836
  //#endregion
956
837
  //#region src/entities/index.ts
957
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
+ /**
958
848
  * Get all entity IDs
959
849
  */
960
850
  function getAllEntityIds() {
@@ -976,10 +866,7 @@ function getEntitiesViaDevices(areaId) {
976
866
  const entityIds = [];
977
867
  for (const device of devicesInArea) {
978
868
  const deviceEntities = getEntitiesByDevice(device.id);
979
- for (const entityId of deviceEntities) {
980
- const registry = state.entityRegistry[entityId];
981
- if (!registry?.area_id) entityIds.push(entityId);
982
- }
869
+ for (const entityId of deviceEntities) if (!state.entityRegistry[entityId]?.area_id) entityIds.push(entityId);
983
870
  }
984
871
  return entityIds;
985
872
  }
@@ -992,7 +879,7 @@ function getEntitiesByArea(areaId) {
992
879
  const directEntityIds = [];
993
880
  for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
994
881
  const deviceEntityIds = getEntitiesViaDevices(areaId);
995
- return Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
882
+ return Array.from(/* @__PURE__ */ new Set([...directEntityIds, ...deviceEntityIds]));
996
883
  }
997
884
  /**
998
885
  * Get entities by device
@@ -1007,9 +894,9 @@ function getEntitiesByDevice(deviceId) {
1007
894
  */
1008
895
  function getEntitiesByFloor(floorId) {
1009
896
  const entityIds = [];
1010
- const areasOnFloor = Object.values(state.areas).filter((area$1) => area$1.floor_id === floorId);
1011
- for (const area$1 of areasOnFloor) {
1012
- 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);
1013
900
  entityIds.push(...areaEntities);
1014
901
  }
1015
902
  return entityIds;
@@ -1022,7 +909,6 @@ function getEntitiesByLabel(labelId) {
1022
909
  for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.labels?.includes(labelId)) entityIds.push(entityId);
1023
910
  return entityIds;
1024
911
  }
1025
-
1026
912
  //#endregion
1027
913
  //#region src/query/memoization.ts
1028
914
  /**
@@ -1043,7 +929,6 @@ function buildQuerySignature(query) {
1043
929
  function memoizedExecute(_signature, compute) {
1044
930
  return compute();
1045
931
  }
1046
-
1047
932
  //#endregion
1048
933
  //#region src/query/projections.ts
1049
934
  /**
@@ -1052,8 +937,8 @@ function memoizedExecute(_signature, compute) {
1052
937
  * @param entities - Array of entity views
1053
938
  * @returns Array of entity IDs
1054
939
  */
1055
- function projectToIds(entities$1) {
1056
- return entities$1.map((e) => e.id);
940
+ function projectToIds(entities) {
941
+ return entities.map((e) => e.id);
1057
942
  }
1058
943
  /**
1059
944
  * Pick specific fields from entities
@@ -1062,10 +947,10 @@ function projectToIds(entities$1) {
1062
947
  * @param keys - Array of field names to include
1063
948
  * @returns Array of objects with only specified fields
1064
949
  */
1065
- function projectPick(entities$1, keys) {
1066
- return entities$1.map((entity$1) => {
950
+ function projectPick(entities, keys) {
951
+ return entities.map((entity) => {
1067
952
  const result = {};
1068
- for (const key of keys) result[key] = entity$1[key];
953
+ for (const key of keys) result[key] = entity[key];
1069
954
  return result;
1070
955
  });
1071
956
  }
@@ -1078,8 +963,8 @@ function projectPick(entities$1, keys) {
1078
963
  * @param key - Field name or dot-path to extract
1079
964
  * @returns Array of field values
1080
965
  */
1081
- function projectPluck(entities$1, key) {
1082
- return entities$1.map((entity$1) => getNestedValue(entity$1, key));
966
+ function projectPluck(entities, key) {
967
+ return entities.map((entity) => getNestedValue(entity, key));
1083
968
  }
1084
969
  /**
1085
970
  * Map entities using a custom function
@@ -1088,8 +973,8 @@ function projectPluck(entities$1, key) {
1088
973
  * @param mapper - Function to transform each entity
1089
974
  * @returns Array of mapped values
1090
975
  */
1091
- function projectMap(entities$1, mapper) {
1092
- return entities$1.map(mapper);
976
+ function projectMap(entities, mapper) {
977
+ return entities.map(mapper);
1093
978
  }
1094
979
  /**
1095
980
  * Get nested value from object using dot-notation
@@ -1108,12 +993,11 @@ function getNestedValue(obj, path) {
1108
993
  const parts = path.split(".");
1109
994
  let current = obj;
1110
995
  for (const part of parts) {
1111
- if (current === null || current === void 0) return void 0;
996
+ if (current === null || current === void 0) return;
1112
997
  current = current[part];
1113
998
  }
1114
999
  return current;
1115
1000
  }
1116
-
1117
1001
  //#endregion
1118
1002
  //#region src/query/builder.ts
1119
1003
  var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
@@ -1204,12 +1088,10 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1204
1088
  return this.clone({ distinct: key });
1205
1089
  }
1206
1090
  get() {
1207
- const signature = buildQuerySignature(this.queryState);
1208
- return memoizedExecute(signature, () => this.execute());
1091
+ return memoizedExecute(buildQuerySignature(this.queryState), () => this.execute());
1209
1092
  }
1210
1093
  first() {
1211
- const results = this.get();
1212
- return results[0];
1094
+ return this.get()[0];
1213
1095
  }
1214
1096
  subscribe(callback) {
1215
1097
  callback(this.get());
@@ -1223,14 +1105,14 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1223
1105
  }
1224
1106
  execute() {
1225
1107
  const entityIds = this.getEntityIdsFromIndices();
1226
- let entities$1 = getEntityViews(entityIds);
1227
- for (const filter of this.queryState.predicateFilters) entities$1 = entities$1.filter(filter);
1228
- if (this.queryState.sorts.length > 0) entities$1 = this.sortEntities(entities$1);
1229
- if (this.queryState.distinct) entities$1 = this.applyDistinct(entities$1);
1230
- if (this.queryState.skip !== void 0) entities$1 = entities$1.slice(this.queryState.skip);
1231
- if (this.queryState.take !== void 0) entities$1 = entities$1.slice(0, this.queryState.take);
1232
- if (this.queryState.projection) return this.applyProjection(entities$1);
1233
- 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;
1234
1116
  }
1235
1117
  getEntityIdsFromIndices() {
1236
1118
  if (this.queryState.indexFilters.length === 0) return getAllEntityIds();
@@ -1257,8 +1139,8 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1257
1139
  }
1258
1140
  return result;
1259
1141
  }
1260
- sortEntities(entities$1) {
1261
- const sorted = [...entities$1];
1142
+ sortEntities(entities) {
1143
+ const sorted = [...entities];
1262
1144
  sorted.sort((a, b) => {
1263
1145
  for (const sort of this.queryState.sorts) {
1264
1146
  const aVal = getNestedValue(a, sort.key);
@@ -1278,32 +1160,33 @@ var EntitiesQueryBuilderImpl = class EntitiesQueryBuilderImpl {
1278
1160
  if (typeof a === "number" && typeof b === "number") return a - b;
1279
1161
  return String(a).localeCompare(String(b));
1280
1162
  }
1281
- applyProjection(entities$1) {
1282
- if (!this.queryState.projection) return entities$1;
1163
+ applyProjection(entities) {
1164
+ if (!this.queryState.projection) return entities;
1283
1165
  switch (this.queryState.projection.type) {
1284
- case "ids": return projectToIds(entities$1);
1285
- case "pick": return projectPick(entities$1, this.queryState.projection.keys);
1286
- case "pluck": return projectPluck(entities$1, this.queryState.projection.key);
1287
- case "map": return projectMap(entities$1, this.queryState.projection.mapper);
1288
- 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;
1289
1171
  }
1290
1172
  }
1291
- applyDistinct(entities$1) {
1292
- if (!this.queryState.distinct) return entities$1;
1173
+ applyDistinct(entities) {
1174
+ if (!this.queryState.distinct) return entities;
1293
1175
  const seen = /* @__PURE__ */ new Set();
1294
1176
  const result = [];
1295
- for (const entity$1 of entities$1) {
1296
- const value = getNestedValue(entity$1, this.queryState.distinct);
1177
+ for (const entity of entities) {
1178
+ const value = getNestedValue(entity, this.queryState.distinct);
1297
1179
  const key = JSON.stringify(value);
1298
1180
  if (!seen.has(key)) {
1299
1181
  seen.add(key);
1300
- result.push(entity$1);
1182
+ result.push(entity);
1301
1183
  }
1302
1184
  }
1303
1185
  return result;
1304
1186
  }
1305
1187
  };
1306
1188
  var EntityQueryBuilderImpl = class {
1189
+ entityId;
1307
1190
  constructor(entityId) {
1308
1191
  this.entityId = entityId;
1309
1192
  }
@@ -1331,7 +1214,6 @@ function entities() {
1331
1214
  function entity(entityId) {
1332
1215
  return new EntityQueryBuilderImpl(entityId);
1333
1216
  }
1334
-
1335
1217
  //#endregion
1336
1218
  //#region src/query/helpers.ts
1337
1219
  /**
@@ -1353,9 +1235,13 @@ function entitiesByAreaAndDomain(areaId, domain) {
1353
1235
  function entitiesWhere(predicate) {
1354
1236
  return entities().where(predicate);
1355
1237
  }
1356
-
1357
1238
  //#endregion
1358
1239
  //#region src/camera/fetch.ts
1240
+ /**
1241
+ * Camera Stream Fetching
1242
+ *
1243
+ * @packageDocumentation
1244
+ */
1359
1245
  /** Public HLS test stream for demo/disconnected mode */
1360
1246
  const DEMO_HLS_STREAM = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
1361
1247
  /**
@@ -1365,12 +1251,11 @@ const DEMO_HLS_STREAM = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
1365
1251
  async function fetchStream(entityId, format = "hls") {
1366
1252
  if (isDemoMode()) return format === "hls" ? DEMO_HLS_STREAM : null;
1367
1253
  if (!privilegedConn()) return null;
1368
- const response = await sendCommand({
1254
+ return (await sendCommand({
1369
1255
  type: "camera/stream",
1370
1256
  entity_id: entityId,
1371
1257
  format
1372
- });
1373
- return response?.url ?? null;
1258
+ }))?.url ?? null;
1374
1259
  }
1375
1260
  /**
1376
1261
  * Transform relative stream URL to absolute URL
@@ -1384,8 +1269,7 @@ function transformStreamUrl(streamUrl) {
1384
1269
  console.warn("[fetchStreamData] No Home Assistant URL available to resolve relative stream URL");
1385
1270
  return streamUrl;
1386
1271
  }
1387
- const baseUrl = hassUrl.replace(/\/$/, "");
1388
- return `${baseUrl}${streamUrl}`;
1272
+ return `${hassUrl.replace(/\/$/, "")}${streamUrl}`;
1389
1273
  }
1390
1274
  return streamUrl;
1391
1275
  }
@@ -1395,49 +1279,48 @@ function transformStreamUrl(streamUrl) {
1395
1279
  async function fetchStreamData(entityId, options = {}) {
1396
1280
  const { format = "hls" } = options;
1397
1281
  try {
1398
- const streamUrl = await fetchStream(entityId, format);
1399
- const absoluteStreamUrl = transformStreamUrl(streamUrl);
1400
- const stream = {
1401
- url: absoluteStreamUrl,
1402
- loading: false,
1403
- error: null,
1404
- lastFetched: Date.now(),
1405
- format,
1406
- expiresAt: null
1407
- };
1408
1282
  return {
1409
1283
  entityId,
1410
- 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
+ },
1411
1292
  loading: false,
1412
1293
  error: null,
1413
1294
  lastFetched: Date.now()
1414
1295
  };
1415
1296
  } catch (error) {
1416
- const stream = {
1417
- url: null,
1418
- loading: false,
1419
- error: error instanceof Error ? error : new Error(String(error)),
1420
- lastFetched: null,
1421
- format,
1422
- expiresAt: null
1423
- };
1424
1297
  return {
1425
1298
  entityId,
1426
- 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
+ },
1427
1307
  loading: false,
1428
1308
  error: error instanceof Error ? error : new Error(String(error)),
1429
1309
  lastFetched: null
1430
1310
  };
1431
1311
  }
1432
1312
  }
1433
-
1434
1313
  //#endregion
1435
1314
  //#region src/camera/query.ts
1436
- const DEFAULT_REFRESH_INTERVAL = 4 * 60 * 1e3;
1315
+ /**
1316
+ * Camera Stream Query Builder
1317
+ *
1318
+ * @packageDocumentation
1319
+ */
1320
+ const DEFAULT_REFRESH_INTERVAL = 24e4;
1437
1321
  function needsStreamRefresh(streamData, refreshInterval = DEFAULT_REFRESH_INTERVAL) {
1438
1322
  if (!streamData || !streamData.lastFetched) return true;
1439
- const age = Date.now() - streamData.lastFetched;
1440
- return age >= refreshInterval;
1323
+ return Date.now() - streamData.lastFetched >= refreshInterval;
1441
1324
  }
1442
1325
  /**
1443
1326
  * Get stream for a camera entity
@@ -1450,9 +1333,9 @@ async function getStream(entityId, options = {}) {
1450
1333
  format,
1451
1334
  autoRefresh,
1452
1335
  refreshInterval
1453
- }).then((streamData$1) => {
1336
+ }).then((streamData) => {
1454
1337
  setState(produce((s) => {
1455
- s.streams[entityId] = streamData$1;
1338
+ s.streams[entityId] = streamData;
1456
1339
  }));
1457
1340
  });
1458
1341
  return cached;
@@ -1476,7 +1359,6 @@ async function refreshStream(entityId, format = "hls") {
1476
1359
  }));
1477
1360
  return getStream(entityId, { format });
1478
1361
  }
1479
-
1480
1362
  //#endregion
1481
1363
  //#region src/energy/prefs.ts
1482
1364
  function isRecord(value) {
@@ -1513,7 +1395,6 @@ async function fetchEnergyPreferences(connection) {
1513
1395
  device_consumption: devices
1514
1396
  };
1515
1397
  }
1516
-
1517
1398
  //#endregion
1518
1399
  //#region src/media/url.ts
1519
1400
  /**
@@ -1537,7 +1418,6 @@ function hassMediaUrl(path) {
1537
1418
  if (base) return `${base}?path=${encodeURIComponent(path)}`;
1538
1419
  return state.hassUrl ? `${state.hassUrl}${path}` : path;
1539
1420
  }
1540
-
1541
1421
  //#endregion
1542
1422
  //#region src/camera/webrtc.ts
1543
1423
  /**
@@ -1559,7 +1439,7 @@ async function getWebRtcClientConfig(entityId) {
1559
1439
  */
1560
1440
  function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1561
1441
  const conn = privilegedConn();
1562
- if (!conn) return Promise.reject(new Error("Not connected"));
1442
+ if (!conn) return Promise.reject(/* @__PURE__ */ new Error("Not connected"));
1563
1443
  return new Promise((resolve, reject) => {
1564
1444
  const session = {
1565
1445
  sessionId: null,
@@ -1569,7 +1449,7 @@ function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1569
1449
  const timeout = setTimeout(() => {
1570
1450
  if (!answered) {
1571
1451
  session.unsubscribe?.();
1572
- reject(new Error("WebRTC offer timed out"));
1452
+ reject(/* @__PURE__ */ new Error("WebRTC offer timed out"));
1573
1453
  }
1574
1454
  }, 15e3);
1575
1455
  conn.subscribeMessage((event) => {
@@ -1594,7 +1474,6 @@ function startWebRtcSession(entityId, sdpOffer, onCandidate) {
1594
1474
  clearTimeout(timeout);
1595
1475
  session.unsubscribe?.();
1596
1476
  reject(new Error(event.message || event.code || "WebRTC offer failed"));
1597
- break;
1598
1477
  }
1599
1478
  }, {
1600
1479
  type: "camera/webrtc/offer",
@@ -1621,17 +1500,21 @@ async function sendWebRtcCandidate(entityId, sessionId, candidate) {
1621
1500
  candidate
1622
1501
  });
1623
1502
  }
1624
-
1625
1503
  //#endregion
1626
1504
  //#region src/weather/fetch.ts
1627
1505
  /**
1506
+ * Weather Forecast Fetching
1507
+ *
1508
+ * @packageDocumentation
1509
+ */
1510
+ /**
1628
1511
  * In demo mode, derive forecasts from the entity's `forecast` attribute.
1629
1512
  * Hourly and daily are synthesised from the same source (daily entries
1630
1513
  * expanded into 12 hourly steps for the chart).
1631
1514
  */
1632
1515
  function buildDemoForecast(entityId, type) {
1633
- const entity$1 = state.entities[entityId];
1634
- const raw = entity$1?.attributes?.forecast ?? [];
1516
+ const entity = state.entities[entityId];
1517
+ const raw = entity?.attributes?.forecast ?? [];
1635
1518
  if (raw.length === 0) return [];
1636
1519
  if (type === "daily" || type === "twice_daily") return raw.map((d) => {
1637
1520
  const templow = d.templow;
@@ -1659,7 +1542,7 @@ function buildDemoForecast(entityId, type) {
1659
1542
  out.push({
1660
1543
  datetime: dt.toISOString(),
1661
1544
  temperature: Math.round((mid + Math.sin(phase) * swing) * 10) / 10,
1662
- condition: today?.condition ?? entity$1?.state ?? "cloudy"
1545
+ condition: today?.condition ?? entity?.state ?? "cloudy"
1663
1546
  });
1664
1547
  }
1665
1548
  return out;
@@ -1677,7 +1560,7 @@ async function fetchForecast(entityId, type) {
1677
1560
  const timeout = setTimeout(() => {
1678
1561
  if (!resolved && unsubscribe) {
1679
1562
  resolved = true;
1680
- 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")));
1681
1564
  }
1682
1565
  }, 1e4);
1683
1566
  conn.subscribeMessage((event) => {
@@ -1740,11 +1623,10 @@ async function fetchForecasts(entityId, types) {
1740
1623
  */
1741
1624
  async function fetchForecastData(entityId, type) {
1742
1625
  try {
1743
- const forecast = await fetchForecast(entityId, type);
1744
1626
  return {
1745
1627
  entityId,
1746
1628
  type,
1747
- forecast,
1629
+ forecast: await fetchForecast(entityId, type),
1748
1630
  loading: false,
1749
1631
  error: null,
1750
1632
  lastFetched: Date.now()
@@ -1760,10 +1642,14 @@ async function fetchForecastData(entityId, type) {
1760
1642
  };
1761
1643
  }
1762
1644
  }
1763
-
1764
1645
  //#endregion
1765
1646
  //#region src/weather/query.ts
1766
1647
  /**
1648
+ * Weather Forecast Query Builder
1649
+ *
1650
+ * @packageDocumentation
1651
+ */
1652
+ /**
1767
1653
  * Get forecast for a single entity and type
1768
1654
  */
1769
1655
  async function getForecast(entityId, type) {
@@ -1878,12 +1764,11 @@ async function refreshForecasts(entityId, types) {
1878
1764
  }));
1879
1765
  return getForecasts(entityId, types);
1880
1766
  }
1881
-
1882
1767
  //#endregion
1883
1768
  //#region src/worker/bridge-client.ts
1884
- var BridgeNeedsAuthError = class extends Error {
1769
+ var BridgeInvalidAuthError = class extends Error {
1885
1770
  constructor() {
1886
- 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");
1887
1772
  }
1888
1773
  };
1889
1774
  function createHaBridge(worker, events = {}) {
@@ -1907,12 +1792,13 @@ function createHaBridge(worker, events = {}) {
1907
1792
  switch (msg.k) {
1908
1793
  case "connect_result":
1909
1794
  if (msg.ok) pendingConnect?.resolve();
1910
- 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"));
1911
1796
  pendingConnect = null;
1912
1797
  break;
1913
1798
  case "conn":
1914
1799
  connState = msg.state;
1915
1800
  events.onConnState?.(msg.state);
1801
+ if (msg.reason === "invalid_auth") events.onInvalidAuth?.();
1916
1802
  break;
1917
1803
  case "ready_after_reconnect":
1918
1804
  events.onReadyAfterReconnect?.();
@@ -1925,17 +1811,14 @@ function createHaBridge(worker, events = {}) {
1925
1811
  else pending.reject(new Error(msg.error ?? "Request failed"));
1926
1812
  break;
1927
1813
  }
1928
- case "event": {
1814
+ case "event":
1929
1815
  subCallbacks.get(msg.id)?.(msg.payload);
1930
1816
  for (const listener of rawListeners) listener(msg.payload);
1931
1817
  break;
1932
- }
1933
1818
  case "sub_end":
1934
1819
  subCallbacks.delete(msg.id);
1935
1820
  break;
1936
- case "denial":
1937
- events.onDenial?.(msg);
1938
- break;
1821
+ case "denial": events.onDenial?.(msg);
1939
1822
  }
1940
1823
  };
1941
1824
  function sendMessagePromise(message) {
@@ -1970,24 +1853,6 @@ function createHaBridge(worker, events = {}) {
1970
1853
  });
1971
1854
  };
1972
1855
  }
1973
- const conn = {
1974
- connect: () => Promise.resolve(),
1975
- disconnect: () => post({ k: "disconnect" }),
1976
- get connected$() {
1977
- return connState === "connected";
1978
- },
1979
- authenticate: () => Promise.resolve(),
1980
- get authState() {
1981
- return connState === "connected" ? "authenticated" : "pending";
1982
- },
1983
- subscribeEvents: (callback, eventType) => Promise.resolve(subscribe("events", callback, eventType)),
1984
- subscribeMessage: (callback, message) => Promise.resolve(subscribe("message", callback, void 0, message)),
1985
- sendMessagePromise,
1986
- addEventListener: (handler) => {
1987
- rawListeners.add(handler);
1988
- return () => rawListeners.delete(handler);
1989
- }
1990
- };
1991
1856
  return {
1992
1857
  connect(opts) {
1993
1858
  return new Promise((resolve, reject) => {
@@ -1998,7 +1863,6 @@ function createHaBridge(worker, events = {}) {
1998
1863
  post({
1999
1864
  k: "connect",
2000
1865
  url: opts.url,
2001
- mode: opts.mode,
2002
1866
  proxyWsPath: opts.proxyWsPath,
2003
1867
  proxyTicket: opts.proxyTicket
2004
1868
  });
@@ -2006,8 +1870,24 @@ function createHaBridge(worker, events = {}) {
2006
1870
  },
2007
1871
  disconnect: () => post({ k: "disconnect" }),
2008
1872
  reconnect: () => post({ k: "reconnect" }),
2009
- clearTokens: () => post({ k: "clear_tokens" }),
2010
- 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
+ },
2011
1891
  registerWidget(widgetId, caps) {
2012
1892
  const channel = new MessageChannel();
2013
1893
  post({
@@ -2042,7 +1922,6 @@ function createHaBridge(worker, events = {}) {
2042
1922
  }
2043
1923
  };
2044
1924
  }
2045
-
2046
1925
  //#endregion
2047
1926
  //#region src/connection/bridged.ts
2048
1927
  /**
@@ -2066,10 +1945,10 @@ async function attachBridgeToStore(bridge, hassUrl, mediaProxyBase) {
2066
1945
  await forceResubscribeCalendars();
2067
1946
  }
2068
1947
  /** Map worker connection-state messages onto the store. */
2069
- function applyBridgeConnState(state$1) {
1948
+ function applyBridgeConnState(state) {
2070
1949
  setState(produce((s) => {
2071
- s.connectionState = state$1;
2072
- if (state$1 === "connected") s.connectionError = null;
1950
+ s.connectionState = state;
1951
+ if (state === "connected") s.connectionError = null;
2073
1952
  }));
2074
1953
  }
2075
1954
  /** Re-sync after the worker reports a completed reconnect. */
@@ -2085,16 +1964,38 @@ function detachBridgeFromStore() {
2085
1964
  s.connectionState = "disconnected";
2086
1965
  }));
2087
1966
  }
2088
-
2089
1967
  //#endregion
2090
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
+ */
2091
1993
  let unclaimed = {
2092
1994
  attachBridgeToStore,
2093
1995
  detachBridgeFromStore,
2094
1996
  applyBridgeConnState,
2095
1997
  reloadAfterBridgeReconnect,
2096
1998
  createHaBridge,
2097
- authenticateWithOAuth,
2098
1999
  disconnect,
2099
2000
  loadDemoData,
2100
2001
  unloadDemoData
@@ -2112,10 +2013,8 @@ function claimHostApi() {
2112
2013
  unclaimed = null;
2113
2014
  return api;
2114
2015
  }
2115
-
2116
2016
  //#endregion
2117
2017
  //#region src/index.ts
2118
2018
  const VERSION = "0.2.1";
2119
-
2120
2019
  //#endregion
2121
- 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 };