@glasshome/sync-layer 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -43,7 +43,7 @@ export { fetchForecast, fetchForecastData, fetchForecasts } from "./weather/fetc
43
43
  export { getForecast, getForecasts, refreshForecast, refreshForecasts } from "./weather/query";
44
44
  export type { EntityForecastQueryOptions, ForecastQueryOptions, ForecastResult, ForecastsResult, ForecastType, WeatherForecast, WeatherForecastData, WeatherForecasts, WeatherForecastsData, } from "./weather/types";
45
45
  export { applyDemoServiceCall, createDemoFixtures, isDemoMode, loadDemoData, startDemoEnergyTicker, stopDemoEnergyTicker, unloadDemoData, } from "./demo/demo-provider";
46
- export declare const VERSION = "0.2.0";
46
+ export declare const VERSION = "0.2.1";
47
47
  export { applyBridgeConnState, attachBridgeToStore, detachBridgeFromStore, reloadAfterBridgeReconnect, } from "./connection/bridged";
48
48
  export { BridgeNeedsAuthError, createHaBridge, type BridgeConnectOptions, type BridgeEvents, type HaBridge, } from "./worker/bridge-client";
49
49
  export type { AuthMode, ConnState, OAuthTokenData } from "./worker/protocol";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ENERGY_ENTITY_IDS, createDemoFixtures, demoEnergyPreferences, energyEntityValue, formatEnergyState, isEnergyEntity, isSunUp, resetStore, setState, simulateEnergy, state, sunEvents, synthesizeEnergyHistory, synthesizeEnergyStatistics } from "./demo-data-CavpxBw3.js";
2
- import { runHaBridgeWorker } from "./worker-main-VfHPLX2h.js";
2
+ import { runHaBridgeWorker } from "./worker-main-BYGgbA7k.js";
3
3
  import { createConnection, createLongLivedTokenAuth, getAuth } from "home-assistant-js-websocket";
4
4
  import { produce, reconcile } from "solid-js/store";
5
5
 
@@ -2870,7 +2870,8 @@ function createHaBridge(worker, events = {}) {
2870
2870
  k: "connect",
2871
2871
  url: opts.url,
2872
2872
  mode: opts.mode,
2873
- proxyWsPath: opts.proxyWsPath
2873
+ proxyWsPath: opts.proxyWsPath,
2874
+ proxyTicket: opts.proxyTicket
2874
2875
  });
2875
2876
  });
2876
2877
  },
@@ -2915,7 +2916,7 @@ function createHaBridge(worker, events = {}) {
2915
2916
 
2916
2917
  //#endregion
2917
2918
  //#region src/index.ts
2918
- const VERSION = "0.2.0";
2919
+ const VERSION = "0.2.1";
2919
2920
 
2920
2921
  //#endregion
2921
2922
  export { BridgeNeedsAuthError, DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, MAX_HISTORY_POINTS, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyBridgeConnState, applyDemoServiceCall, area, areas, attachBridgeToStore, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, createHaBridge, detachBridgeFromStore, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEnergyPreferences, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStatisticsDuringPeriod, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, normalizeStatisticTime, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, reloadAfterBridgeReconnect, resetStore, runHaBridgeWorker, searchEntities, sendCommand, sendWebRtcCandidate, setState, startDemoEnergyTicker, startWebRtcSession, state, stopDemoEnergyTicker, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
@@ -15,6 +15,7 @@ export interface BridgeConnectOptions {
15
15
  url: string;
16
16
  mode: AuthMode;
17
17
  proxyWsPath?: string;
18
+ proxyTicket?: string;
18
19
  }
19
20
  export interface BridgeEvents {
20
21
  onConnState?: (state: ConnState) => void;
@@ -31,8 +31,13 @@ export type MainToWorker = {
31
31
  k: "connect";
32
32
  url: string;
33
33
  mode: AuthMode;
34
- /** Same-origin WS path to use instead of the HA host (tunnel proxy). */
34
+ /** Proxy WS endpoint to use instead of the HA host. A path (web, resolved
35
+ * same-origin) or an absolute ws(s)/http(s) URL (native, where the worker
36
+ * origin is localhost, not dash-server). */
35
37
  proxyWsPath?: string;
38
+ /** Short-lived ticket authorizing the proxy WS + broker mint when a
39
+ * cross-origin cookie/bearer can't ride them (native). */
40
+ proxyTicket?: string;
36
41
  } | {
37
42
  k: "send";
38
43
  id: number;
@@ -1,3 +1,3 @@
1
- import { runHaBridgeWorker } from "../worker-main-VfHPLX2h.js";
1
+ import { runHaBridgeWorker } from "../worker-main-BYGgbA7k.js";
2
2
 
3
3
  export { runHaBridgeWorker };
@@ -129,13 +129,21 @@ function isHaWireMessage(value) {
129
129
  return typeof value === "object" && value !== null && typeof value.type === "string";
130
130
  }
131
131
  var BrokeredAuth = class extends Auth {
132
- constructor(mintUrl, data) {
132
+ constructor(mintUrl, data, ticket) {
133
133
  super(data);
134
134
  this.mintUrl = mintUrl;
135
+ this.ticket = ticket;
135
136
  }
136
137
  async refreshAccessToken() {
137
- const res = await fetch(this.mintUrl, { credentials: "same-origin" });
138
+ const res = await fetch(this.mintUrl, {
139
+ credentials: "include",
140
+ headers: {
141
+ accept: "application/json",
142
+ ...this.ticket ? { "x-proxy-ticket": this.ticket } : {}
143
+ }
144
+ });
138
145
  if (!res.ok) throw new Error(`Token broker responded ${res.status}`);
146
+ if (!res.headers.get("content-type")?.includes("application/json")) throw new Error(`Token broker did not return JSON (is ${this.mintUrl} reachable?)`);
139
147
  const broker = await res.json();
140
148
  this.data = {
141
149
  ...this.data,
@@ -148,6 +156,19 @@ var BrokeredAuth = class extends Auth {
148
156
  };
149
157
  }
150
158
  };
159
+ /** Resolve the proxy WS endpoint. Accepts a same-origin path (web) or an
160
+ * absolute ws(s)/http(s) URL (native), and appends the auth ticket if given. */
161
+ function buildProxyWsUrl(scope, proxyWsPath, ticket) {
162
+ let base;
163
+ if (/^wss?:\/\//i.test(proxyWsPath)) base = proxyWsPath;
164
+ else if (/^https?:\/\//i.test(proxyWsPath)) base = proxyWsPath.replace(/^http/i, "ws");
165
+ else {
166
+ const proto = scope.location.protocol === "https:" ? "wss:" : "ws:";
167
+ base = `${proto}//${scope.location.host}${proxyWsPath}`;
168
+ }
169
+ if (ticket) base += `${base.includes("?") ? "&" : "?"}ticket=${encodeURIComponent(ticket)}`;
170
+ return base;
171
+ }
151
172
  function runHaBridgeWorker(scope) {
152
173
  let conn = null;
153
174
  const widgets = /* @__PURE__ */ new Map();
@@ -260,7 +281,7 @@ function runHaBridgeWorker(scope) {
260
281
  resumeVisible = null;
261
282
  }
262
283
  }
263
- async function buildAuth(url, mode) {
284
+ async function buildAuth(url, mode, ticket) {
264
285
  switch (mode.kind) {
265
286
  case "token": return new Auth({
266
287
  hassUrl: url,
@@ -277,7 +298,7 @@ function runHaBridgeWorker(scope) {
277
298
  refresh_token: "",
278
299
  expires: 0,
279
300
  expires_in: 0
280
- });
301
+ }, ticket);
281
302
  case "oauth": {
282
303
  const data = mode.data ?? await loadTokens();
283
304
  if (!data) return "needs-auth";
@@ -288,8 +309,8 @@ function runHaBridgeWorker(scope) {
288
309
  }
289
310
  }
290
311
  }
291
- async function connect(url, mode, proxyWsPath) {
292
- const auth = await buildAuth(url, mode);
312
+ async function connect(url, mode, proxyWsPath, proxyTicket) {
313
+ const auth = await buildAuth(url, mode, proxyTicket);
293
314
  if (auth === "needs-auth") {
294
315
  post({
295
316
  k: "connect_result",
@@ -308,9 +329,9 @@ function runHaBridgeWorker(scope) {
308
329
  });
309
330
  return;
310
331
  }
311
- const socketFactory = proxyWsPath ? (options) => {
312
- const proto = scope.location.protocol === "https:" ? "wss:" : "ws:";
313
- const proxied = new Proxy(options.auth, { get: (target, prop) => prop === "wsUrl" ? `${proto}//${scope.location.host}${proxyWsPath}` : Reflect.get(target, prop) });
332
+ const proxyWsUrl = proxyWsPath ? buildProxyWsUrl(scope, proxyWsPath, proxyTicket) : null;
333
+ const socketFactory = proxyWsUrl ? (options) => {
334
+ const proxied = new Proxy(options.auth, { get: (target, prop) => prop === "wsUrl" ? proxyWsUrl : Reflect.get(target, prop) });
314
335
  return createSocket({
315
336
  ...options,
316
337
  auth: proxied
@@ -364,7 +385,7 @@ function runHaBridgeWorker(scope) {
364
385
  const msg = ev.data;
365
386
  switch (msg.k) {
366
387
  case "connect":
367
- await connect(msg.url, msg.mode, msg.proxyWsPath);
388
+ await connect(msg.url, msg.mode, msg.proxyWsPath, msg.proxyTicket);
368
389
  break;
369
390
  case "send": {
370
391
  if (!conn || !isHaWireMessage(msg.message)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/sync-layer",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Type-safe, reactive state synchronization layer for Home Assistant",
5
5
  "repository": {
6
6
  "type": "git",