@glasshome/sync-layer 0.2.1 → 0.3.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.
- package/dist/connection/bridged.d.ts +1 -1
- package/dist/core/store.d.ts +3 -0
- package/dist/{demo-data-CavpxBw3.js → demo-data-DiGSP4EH.js} +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31 -5
- package/dist/media/url.d.ts +14 -0
- package/dist/testing/index.js +1 -1
- package/dist/worker/bridge-client.d.ts +1 -0
- package/dist/worker/protocol.d.ts +6 -1
- package/dist/worker/worker-main.js +1 -1
- package/dist/{worker-main-C93MR-AO.js → worker-main-BYGgbA7k.js} +28 -11
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import type { HaBridge } from "../worker/bridge-client";
|
|
|
7
7
|
*
|
|
8
8
|
* Call after bridge.connect() resolved. Returns when initial data is loaded.
|
|
9
9
|
*/
|
|
10
|
-
export declare function attachBridgeToStore(bridge: HaBridge, hassUrl: string): Promise<void>;
|
|
10
|
+
export declare function attachBridgeToStore(bridge: HaBridge, hassUrl: string, mediaProxyBase?: string): Promise<void>;
|
|
11
11
|
/** Map worker connection-state messages onto the store. */
|
|
12
12
|
export declare function applyBridgeConnState(state: "connected" | "disconnected" | "reconnecting"): void;
|
|
13
13
|
/** Re-sync after the worker reports a completed reconnect. */
|
package/dist/core/store.d.ts
CHANGED
|
@@ -32,6 +32,9 @@ export interface GlassHomeState {
|
|
|
32
32
|
connectionError: Error | null;
|
|
33
33
|
/** Home Assistant base URL */
|
|
34
34
|
hassUrl: string | null;
|
|
35
|
+
/** Same-origin prefix for the HA media proxy (e.g. "/ha-proxy/media"), or null
|
|
36
|
+
* to resolve media directly against hassUrl. Set by the host at connect. */
|
|
37
|
+
mediaProxyBase: string | null;
|
|
35
38
|
/** Home Assistant core config (unit system, locale, currency); null until loaded */
|
|
36
39
|
config: import("@glasshome/ha-types").HassConfig | null;
|
|
37
40
|
/** Live entity state (keyed by entity_id) */
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityR
|
|
|
27
27
|
export { fetchStream, fetchStreamData } from "./camera/fetch";
|
|
28
28
|
export { getStream, refreshStream } from "./camera/query";
|
|
29
29
|
export { fetchEnergyPreferences } from "./energy/prefs";
|
|
30
|
+
export { hassMediaUrl } from "./media/url";
|
|
30
31
|
export type { EnergyDeviceConsumption, EnergyPreferences, EnergySource, } from "./energy/prefs";
|
|
31
32
|
export type { CameraStream, CameraStreamData, CameraStreams, EntityStreamQueryOptions, StreamFormat, StreamQueryOptions, StreamResult, } from "./camera/types";
|
|
32
33
|
export { getWebRtcClientConfig, sendWebRtcCandidate, startWebRtcSession, } from "./camera/webrtc";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ENERGY_ENTITY_IDS, createDemoFixtures, demoEnergyPreferences, energyEntityValue, formatEnergyState, isEnergyEntity, isSunUp, resetStore, setState, simulateEnergy, state, sunEvents, synthesizeEnergyHistory, synthesizeEnergyStatistics } from "./demo-data-
|
|
2
|
-
import { runHaBridgeWorker } from "./worker-main-
|
|
1
|
+
import { ENERGY_ENTITY_IDS, createDemoFixtures, demoEnergyPreferences, energyEntityValue, formatEnergyState, isEnergyEntity, isSunUp, resetStore, setState, simulateEnergy, state, sunEvents, synthesizeEnergyHistory, synthesizeEnergyStatistics } from "./demo-data-DiGSP4EH.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
|
|
|
@@ -2298,6 +2298,30 @@ async function fetchEnergyPreferences(connection) {
|
|
|
2298
2298
|
};
|
|
2299
2299
|
}
|
|
2300
2300
|
|
|
2301
|
+
//#endregion
|
|
2302
|
+
//#region src/media/url.ts
|
|
2303
|
+
/**
|
|
2304
|
+
* Resolve an HA media reference (e.g. an entity's `entity_picture`, a camera
|
|
2305
|
+
* proxy path) to a URL the page can actually load.
|
|
2306
|
+
*
|
|
2307
|
+
* HA gives these as origin-relative paths (`/api/media_player_proxy/...?token=`)
|
|
2308
|
+
* carrying their own signed token. A bare `<img src>` would resolve them against
|
|
2309
|
+
* the dash origin and 404. When the host set `mediaProxyBase`, route through the
|
|
2310
|
+
* same-origin media proxy so the page CSP (`img-src 'self'`) covers it in every
|
|
2311
|
+
* mode, LAN and remote. Otherwise fall back to prefixing `hassUrl` directly
|
|
2312
|
+
* (the LAN-only path, used until a transport sets the proxy base).
|
|
2313
|
+
*
|
|
2314
|
+
* Absolute URLs and non-path values pass through untouched.
|
|
2315
|
+
*/
|
|
2316
|
+
function hassMediaUrl(path) {
|
|
2317
|
+
if (!path) return void 0;
|
|
2318
|
+
if (/^https?:\/\//i.test(path)) return path;
|
|
2319
|
+
if (!path.startsWith("/")) return path;
|
|
2320
|
+
const base = state.mediaProxyBase;
|
|
2321
|
+
if (base) return `${base}?path=${encodeURIComponent(path)}`;
|
|
2322
|
+
return state.hassUrl ? `${state.hassUrl}${path}` : path;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2301
2325
|
//#endregion
|
|
2302
2326
|
//#region src/camera/webrtc.ts
|
|
2303
2327
|
/**
|
|
@@ -2720,10 +2744,11 @@ async function refreshForecasts(entityId, types) {
|
|
|
2720
2744
|
*
|
|
2721
2745
|
* Call after bridge.connect() resolved. Returns when initial data is loaded.
|
|
2722
2746
|
*/
|
|
2723
|
-
async function attachBridgeToStore(bridge, hassUrl) {
|
|
2747
|
+
async function attachBridgeToStore(bridge, hassUrl, mediaProxyBase) {
|
|
2724
2748
|
setState(produce((s) => {
|
|
2725
2749
|
s.conn = bridge.conn;
|
|
2726
2750
|
s.hassUrl = hassUrl.replace(/\/$/, "");
|
|
2751
|
+
s.mediaProxyBase = mediaProxyBase ?? null;
|
|
2727
2752
|
s.connectionState = "connected";
|
|
2728
2753
|
s.connectionError = null;
|
|
2729
2754
|
}));
|
|
@@ -2870,7 +2895,8 @@ function createHaBridge(worker, events = {}) {
|
|
|
2870
2895
|
k: "connect",
|
|
2871
2896
|
url: opts.url,
|
|
2872
2897
|
mode: opts.mode,
|
|
2873
|
-
proxyWsPath: opts.proxyWsPath
|
|
2898
|
+
proxyWsPath: opts.proxyWsPath,
|
|
2899
|
+
proxyTicket: opts.proxyTicket
|
|
2874
2900
|
});
|
|
2875
2901
|
});
|
|
2876
2902
|
},
|
|
@@ -2918,4 +2944,4 @@ function createHaBridge(worker, events = {}) {
|
|
|
2918
2944
|
const VERSION = "0.2.1";
|
|
2919
2945
|
|
|
2920
2946
|
//#endregion
|
|
2921
|
-
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 };
|
|
2947
|
+
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, hassMediaUrl, 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 };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an HA media reference (e.g. an entity's `entity_picture`, a camera
|
|
3
|
+
* proxy path) to a URL the page can actually load.
|
|
4
|
+
*
|
|
5
|
+
* HA gives these as origin-relative paths (`/api/media_player_proxy/...?token=`)
|
|
6
|
+
* carrying their own signed token. A bare `<img src>` would resolve them against
|
|
7
|
+
* the dash origin and 404. When the host set `mediaProxyBase`, route through the
|
|
8
|
+
* same-origin media proxy so the page CSP (`img-src 'self'`) covers it in every
|
|
9
|
+
* mode, LAN and remote. Otherwise fall back to prefixing `hassUrl` directly
|
|
10
|
+
* (the LAN-only path, used until a transport sets the proxy base).
|
|
11
|
+
*
|
|
12
|
+
* Absolute URLs and non-path values pass through untouched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hassMediaUrl(path: string | null | undefined): string | undefined;
|
package/dist/testing/index.js
CHANGED
|
@@ -31,8 +31,13 @@ export type MainToWorker = {
|
|
|
31
31
|
k: "connect";
|
|
32
32
|
url: string;
|
|
33
33
|
mode: AuthMode;
|
|
34
|
-
/**
|
|
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;
|
|
@@ -129,14 +129,18 @@ 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
138
|
const res = await fetch(this.mintUrl, {
|
|
138
|
-
credentials: "
|
|
139
|
-
headers: {
|
|
139
|
+
credentials: "include",
|
|
140
|
+
headers: {
|
|
141
|
+
accept: "application/json",
|
|
142
|
+
...this.ticket ? { "x-proxy-ticket": this.ticket } : {}
|
|
143
|
+
}
|
|
140
144
|
});
|
|
141
145
|
if (!res.ok) throw new Error(`Token broker responded ${res.status}`);
|
|
142
146
|
if (!res.headers.get("content-type")?.includes("application/json")) throw new Error(`Token broker did not return JSON (is ${this.mintUrl} reachable?)`);
|
|
@@ -152,6 +156,19 @@ var BrokeredAuth = class extends Auth {
|
|
|
152
156
|
};
|
|
153
157
|
}
|
|
154
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
|
+
}
|
|
155
172
|
function runHaBridgeWorker(scope) {
|
|
156
173
|
let conn = null;
|
|
157
174
|
const widgets = /* @__PURE__ */ new Map();
|
|
@@ -264,7 +281,7 @@ function runHaBridgeWorker(scope) {
|
|
|
264
281
|
resumeVisible = null;
|
|
265
282
|
}
|
|
266
283
|
}
|
|
267
|
-
async function buildAuth(url, mode) {
|
|
284
|
+
async function buildAuth(url, mode, ticket) {
|
|
268
285
|
switch (mode.kind) {
|
|
269
286
|
case "token": return new Auth({
|
|
270
287
|
hassUrl: url,
|
|
@@ -281,7 +298,7 @@ function runHaBridgeWorker(scope) {
|
|
|
281
298
|
refresh_token: "",
|
|
282
299
|
expires: 0,
|
|
283
300
|
expires_in: 0
|
|
284
|
-
});
|
|
301
|
+
}, ticket);
|
|
285
302
|
case "oauth": {
|
|
286
303
|
const data = mode.data ?? await loadTokens();
|
|
287
304
|
if (!data) return "needs-auth";
|
|
@@ -292,8 +309,8 @@ function runHaBridgeWorker(scope) {
|
|
|
292
309
|
}
|
|
293
310
|
}
|
|
294
311
|
}
|
|
295
|
-
async function connect(url, mode, proxyWsPath) {
|
|
296
|
-
const auth = await buildAuth(url, mode);
|
|
312
|
+
async function connect(url, mode, proxyWsPath, proxyTicket) {
|
|
313
|
+
const auth = await buildAuth(url, mode, proxyTicket);
|
|
297
314
|
if (auth === "needs-auth") {
|
|
298
315
|
post({
|
|
299
316
|
k: "connect_result",
|
|
@@ -312,9 +329,9 @@ function runHaBridgeWorker(scope) {
|
|
|
312
329
|
});
|
|
313
330
|
return;
|
|
314
331
|
}
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const proxied = new Proxy(options.auth, { get: (target, prop) => prop === "wsUrl" ?
|
|
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) });
|
|
318
335
|
return createSocket({
|
|
319
336
|
...options,
|
|
320
337
|
auth: proxied
|
|
@@ -368,7 +385,7 @@ function runHaBridgeWorker(scope) {
|
|
|
368
385
|
const msg = ev.data;
|
|
369
386
|
switch (msg.k) {
|
|
370
387
|
case "connect":
|
|
371
|
-
await connect(msg.url, msg.mode, msg.proxyWsPath);
|
|
388
|
+
await connect(msg.url, msg.mode, msg.proxyWsPath, msg.proxyTicket);
|
|
372
389
|
break;
|
|
373
390
|
case "send": {
|
|
374
391
|
if (!conn || !isHaWireMessage(msg.message)) {
|