@glasshome/sync-layer 0.5.0 → 0.7.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/calendar/track.d.ts +19 -0
- package/dist/calendar/types.d.ts +32 -0
- package/dist/core/privileged-conn.d.ts +29 -0
- package/dist/core/store.d.ts +3 -2
- package/dist/host-api.d.ts +50 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +168 -2369
- package/dist/solid/hooks.d.ts +11 -1
- package/dist/solid/index.d.ts +1 -1
- package/dist/solid/index.js +21 -3
- package/dist/statistics-CtRfIF6D.js +2175 -0
- package/dist/worker/protocol.d.ts +4 -0
- package/dist/worker/worker-main.js +1 -1
- package/dist/{worker-main-Cf-k4f2N.js → worker-main-C-3QM4di.js} +5 -3
- package/package.json +2 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EntityId } from "../core/types";
|
|
2
|
+
import type { CalendarWindowOptions } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Start tracking live events for a calendar entity. Returns the untrack
|
|
5
|
+
* function. Safe while disconnected: the subscription activates on the next
|
|
6
|
+
* `forceResubscribeCalendars()` (called by the connection layer on connect
|
|
7
|
+
* and reconnect).
|
|
8
|
+
*/
|
|
9
|
+
export declare function trackCalendarEvents(entityId: EntityId, options?: CalendarWindowOptions): () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Reconcile every tracked calendar, dropping current subscription handles
|
|
12
|
+
* first. The connection layer calls this on connect and after reconnect:
|
|
13
|
+
* an unchanged window is no proof the old subscription is alive, so each
|
|
14
|
+
* tracked entity resubscribes (HA re-pushes the snapshot, refreshing the
|
|
15
|
+
* store through any missed changes).
|
|
16
|
+
*/
|
|
17
|
+
export declare function forceResubscribeCalendars(): Promise<void>;
|
|
18
|
+
/** Test-only: drop all tracking state and timers. */
|
|
19
|
+
export declare function resetCalendarTracking(): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EntityId } from "../core/types";
|
|
2
|
+
/**
|
|
3
|
+
* One calendar event as HA serializes it over `calendar/event/subscribe`:
|
|
4
|
+
* dates/datetimes are ISO strings (date-only for all-day events), and
|
|
5
|
+
* None-valued fields are omitted.
|
|
6
|
+
*/
|
|
7
|
+
export interface CalendarEvent {
|
|
8
|
+
start: string;
|
|
9
|
+
end: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
location?: string;
|
|
13
|
+
uid?: string;
|
|
14
|
+
recurrence_id?: string;
|
|
15
|
+
rrule?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Live event window for one calendar entity, held in the store. */
|
|
18
|
+
export interface CalendarEventsData {
|
|
19
|
+
entityId: EntityId;
|
|
20
|
+
events: CalendarEvent[];
|
|
21
|
+
/** True until the first snapshot for the current subscription arrives. */
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
/** Subscribed window bounds (ISO). */
|
|
25
|
+
start: string;
|
|
26
|
+
end: string;
|
|
27
|
+
}
|
|
28
|
+
/** Per-tracker window request, in whole days relative to now. */
|
|
29
|
+
export interface CalendarWindowOptions {
|
|
30
|
+
daysBehind?: number;
|
|
31
|
+
daysAhead?: number;
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The privileged Home Assistant connection, held outside the shared store.
|
|
3
|
+
*
|
|
4
|
+
* This handle tunnels arbitrary HA traffic: the worker's `case "send"` forwards
|
|
5
|
+
* whatever arrives on it after a shape check only, because capability
|
|
6
|
+
* validation belongs on the per-widget MessagePorts, not here.
|
|
7
|
+
*
|
|
8
|
+
* It therefore must not be reachable from widget code. It used to live at
|
|
9
|
+
* `state.conn`, and `state` is exported from this package's entry, which the
|
|
10
|
+
* host serves to every widget bundle through its import map — so any widget
|
|
11
|
+
* could read the handle and call any service on any entity, regardless of the
|
|
12
|
+
* capabilities it declared and the user approved. Proven by execution, not
|
|
13
|
+
* argued: a widget-side `useService()` emitted `call_service`/`lock.unlock`
|
|
14
|
+
* onto the host's own channel (finding 46).
|
|
15
|
+
*
|
|
16
|
+
* Module-local and deliberately absent from `src/index.ts`. Not being exported
|
|
17
|
+
* is the entire mechanism, so exporting it — directly, or by parking the value
|
|
18
|
+
* on anything that is exported — reopens the hole. `check-widget-reachable-
|
|
19
|
+
* surface.ts` fails the build if it ever becomes reachable again.
|
|
20
|
+
*
|
|
21
|
+
* Plain variable rather than store state: every reader takes it imperatively
|
|
22
|
+
* inside an async fetcher or command, so none of them tracked it reactively.
|
|
23
|
+
* `state.connectionState` remains the reactive signal for connection status.
|
|
24
|
+
*/
|
|
25
|
+
import type { HaLink } from "./store";
|
|
26
|
+
/** Read the live connection. Null before connect and after disconnect. */
|
|
27
|
+
export declare function privilegedConn(): HaLink | null;
|
|
28
|
+
/** Set by the connection layer on connect/disconnect. Host-side callers only. */
|
|
29
|
+
export declare function setPrivilegedConn(conn: HaLink | null): void;
|
package/dist/core/store.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Central reactive state store for sync-layer using SolidJS createStore. */
|
|
2
2
|
import type { SetStoreFunction, Store } from "solid-js/store";
|
|
3
|
+
import type { CalendarEventsData } from "../calendar/types";
|
|
3
4
|
import type { CameraStreamData } from "../camera/types";
|
|
4
5
|
import type { EntityHistoryData } from "../history/types";
|
|
5
6
|
import type { WeatherForecastsData } from "../weather/types";
|
|
@@ -24,8 +25,6 @@ export interface HaLink {
|
|
|
24
25
|
subscribeEvents(callback: (event: unknown) => void, eventType?: string): Promise<() => void | Promise<void>>;
|
|
25
26
|
}
|
|
26
27
|
export interface GlassHomeState {
|
|
27
|
-
/** Live HA link (real socket, worker bridge facade, or mock) */
|
|
28
|
-
conn: HaLink | null;
|
|
29
28
|
/** Current connection state */
|
|
30
29
|
connectionState: ConnectionState;
|
|
31
30
|
/** Connection error (if any) */
|
|
@@ -57,6 +56,8 @@ export interface GlassHomeState {
|
|
|
57
56
|
history: Record<EntityId, EntityHistoryData>;
|
|
58
57
|
/** Weather forecasts data (keyed by entity ID) */
|
|
59
58
|
forecasts: Record<EntityId, WeatherForecastsData>;
|
|
59
|
+
/** Live calendar events (keyed by entity ID), maintained by calendar/track */
|
|
60
|
+
calendars: Record<EntityId, CalendarEventsData>;
|
|
60
61
|
/** Camera stream data (keyed by entity ID) */
|
|
61
62
|
streams: Record<EntityId, CameraStreamData>;
|
|
62
63
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot handoff of the host-only surface.
|
|
3
|
+
*
|
|
4
|
+
* The problem this solves: the host import map serves this package's entry to
|
|
5
|
+
* every widget bundle, so everything exported there is callable by untrusted
|
|
6
|
+
* widget code. But the host needs privileged operations from the same package —
|
|
7
|
+
* connect the bridge, authenticate, disconnect — and it cannot import them
|
|
8
|
+
* through a private subpath, because the store must be ONE instance shared with
|
|
9
|
+
* widgets. An unserved subpath gets bundled into the host and forks the store
|
|
10
|
+
* (finding 45); a served one is reachable by widgets again (finding 46). A
|
|
11
|
+
* subpath cannot be both, so hiding is not available.
|
|
12
|
+
*
|
|
13
|
+
* So the surface stays on the shared entry and is protected by ORDER instead of
|
|
14
|
+
* by visibility: `claimHostApi()` yields the API to its first caller and null to
|
|
15
|
+
* everyone after. The host claims it while booting; widget bundles are fetched
|
|
16
|
+
* and evaluated later, by which point there is nothing left to take.
|
|
17
|
+
*
|
|
18
|
+
* Why these members are dangerous in a widget's hands:
|
|
19
|
+
* - attachBridgeToStore / createHaBridge: install a connection, i.e. replace the
|
|
20
|
+
* host's link with one the widget controls — every subsequent service call is
|
|
21
|
+
* intercepted and every entity the dashboard renders can be fabricated.
|
|
22
|
+
* - authenticateWithOAuth: returns an `Auth` carrying a live access token.
|
|
23
|
+
* - disconnect / detachBridgeFromStore: drop the household's connection.
|
|
24
|
+
* - loadDemoData / unloadDemoData: overwrite live state with fixtures.
|
|
25
|
+
*/
|
|
26
|
+
import { authenticateWithOAuth } from "./connection/auth";
|
|
27
|
+
import { applyBridgeConnState, attachBridgeToStore, detachBridgeFromStore, reloadAfterBridgeReconnect } from "./connection/bridged";
|
|
28
|
+
import { disconnect } from "./connection/manager";
|
|
29
|
+
import { loadDemoData, unloadDemoData } from "./demo/demo-provider";
|
|
30
|
+
import { createHaBridge } from "./worker/bridge-client";
|
|
31
|
+
export interface HostApi {
|
|
32
|
+
attachBridgeToStore: typeof attachBridgeToStore;
|
|
33
|
+
detachBridgeFromStore: typeof detachBridgeFromStore;
|
|
34
|
+
applyBridgeConnState: typeof applyBridgeConnState;
|
|
35
|
+
reloadAfterBridgeReconnect: typeof reloadAfterBridgeReconnect;
|
|
36
|
+
createHaBridge: typeof createHaBridge;
|
|
37
|
+
authenticateWithOAuth: typeof authenticateWithOAuth;
|
|
38
|
+
disconnect: typeof disconnect;
|
|
39
|
+
loadDemoData: typeof loadDemoData;
|
|
40
|
+
unloadDemoData: typeof unloadDemoData;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Take the host-only surface. Returns it once; null on every later call.
|
|
44
|
+
*
|
|
45
|
+
* Call it exactly once, during host boot, and keep the result in a module the
|
|
46
|
+
* import map does not serve. Calling it from anywhere else is what this guards
|
|
47
|
+
* against, so a null return means something already claimed it — treat that as
|
|
48
|
+
* a fatal misconfiguration, not a retry.
|
|
49
|
+
*/
|
|
50
|
+
export declare function claimHostApi(): HostApi | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
* Type-safe, reactive state synchronization layer for Home Assistant
|
|
5
5
|
* Uses SolidJS createStore for reactive state management.
|
|
6
6
|
*
|
|
7
|
+
* THIS ENTRY IS AN ACCESS-CONTROL SURFACE, not an ordinary barrel. The host
|
|
8
|
+
* import map serves it to every untrusted widget bundle, so anything exported
|
|
9
|
+
* here is callable by widget code. Before adding an export, ask who outside
|
|
10
|
+
* this package needs it: in-package callers use relative imports and need
|
|
11
|
+
* nothing here, host-only lifecycle belongs on claimHostApi(), and widget
|
|
12
|
+
* hooks belong on the ./solid subpath. Types are always safe; they carry no
|
|
13
|
+
* authority. scripts/check-widget-reachable-surface.ts guards this, but it is
|
|
14
|
+
* a DENYLIST of known-dangerous names and is blind to any handle invented
|
|
15
|
+
* after it was written — it cannot be relied on to catch a new mistake.
|
|
16
|
+
*
|
|
7
17
|
* @packageDocumentation
|
|
8
18
|
*/
|
|
9
|
-
export { addDebugIncomingMessageListener, addDebugOutgoingMessageListener, } from "./connection/adapter";
|
|
10
19
|
export type { ConnectionOptions } from "./connection/manager";
|
|
11
|
-
export {
|
|
20
|
+
export { getConnectionState, isConnected } from "./connection/manager";
|
|
12
21
|
export { registerEntity } from "./connection/subscription-manager";
|
|
13
22
|
export type { SyncLayerConnection } from "./connection/types";
|
|
14
23
|
export type { OAuthOptions } from "./connection/auth";
|
|
15
|
-
export { authenticateWithOAuth, authenticateWithToken, isAuthValid, refreshAuth, } from "./connection/auth";
|
|
16
24
|
export { area, areas } from "./query/area-builder";
|
|
17
25
|
export { entities, entity } from "./query/builder";
|
|
18
26
|
export { entitiesByAreaAndDomain, entitiesWhere, searchEntities, } from "./query/helpers";
|
|
@@ -22,7 +30,7 @@ export type { CommandOptions, CommandResult, EntityUpdateFields, ServiceCallCont
|
|
|
22
30
|
export type { GlassHomeState } from "./core/store";
|
|
23
31
|
export type { AreaId, AreaView, ConnectionState, DeviceId, DeviceView, EntityDomain, EntityId, EntityView, HassEntity, } from "./core/types";
|
|
24
32
|
export { extractDomain } from "./core/types";
|
|
25
|
-
export {
|
|
33
|
+
export { state } from "./core/store";
|
|
26
34
|
export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, HassConfig, HassUnitSystem, RegistryEntryDisabler, RegistryEntryHider, ServiceCall, ServiceName, WsCommandType, WsResult, } from "@glasshome/ha-types";
|
|
27
35
|
export { fetchStream, fetchStreamData } from "./camera/fetch";
|
|
28
36
|
export { getStream, refreshStream } from "./camera/query";
|
|
@@ -41,11 +49,12 @@ export { fetchStatisticsDuringPeriod, normalizeStatisticTime, } from "./history/
|
|
|
41
49
|
export type { StatisticsPeriod, StatisticsQueryOptions, StatisticValue, } from "./history/statistics";
|
|
42
50
|
export type { EntityHistoryData, EntityHistoryQueryOptions, EntityHistoryResult, EntityHistoryState, HistoryData, HistoryQueryOptions, HistoryResult, HistoryStates, TimelineState, } from "./history/types";
|
|
43
51
|
export { fetchForecast, fetchForecastData, fetchForecasts } from "./weather/fetch";
|
|
52
|
+
export type { CalendarEvent, CalendarEventsData, CalendarWindowOptions } from "./calendar/types";
|
|
44
53
|
export { getForecast, getForecasts, refreshForecast, refreshForecasts } from "./weather/query";
|
|
45
54
|
export type { EntityForecastQueryOptions, ForecastQueryOptions, ForecastResult, ForecastsResult, ForecastType, WeatherForecast, WeatherForecastData, WeatherForecasts, WeatherForecastsData, } from "./weather/types";
|
|
46
|
-
export { applyDemoServiceCall, createDemoFixtures, isDemoMode,
|
|
55
|
+
export { applyDemoServiceCall, createDemoFixtures, isDemoMode, startDemoEnergyTicker, stopDemoEnergyTicker, } from "./demo/demo-provider";
|
|
47
56
|
export declare const VERSION = "0.2.1";
|
|
48
|
-
export {
|
|
49
|
-
export {
|
|
57
|
+
export { BridgeNeedsAuthError, type BridgeConnectOptions, type BridgeEvents, type HaBridge, } from "./worker/bridge-client";
|
|
58
|
+
export { claimHostApi, type HostApi } from "./host-api";
|
|
50
59
|
export type { AuthMode, ConnState, OAuthTokenData } from "./worker/protocol";
|
|
51
60
|
export { runHaBridgeWorker, type WorkerScope } from "./worker/worker-main";
|