@glasshome/sync-layer 0.1.11 → 0.1.13
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/manager.d.ts +7 -0
- package/dist/core/types.d.ts +7 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +96 -2
- package/dist/solid/index.js +8 -23
- package/dist/solid/indices.d.ts +2 -1
- package/package.json +1 -1
|
@@ -36,6 +36,13 @@ export declare function getConnection(): Connection | null;
|
|
|
36
36
|
* Check if currently connected
|
|
37
37
|
*/
|
|
38
38
|
export declare function isConnected(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Get the underlying socket's connected flag.
|
|
41
|
+
*
|
|
42
|
+
* Exposed so apps can render a connection indicator in the UI. Distinct from
|
|
43
|
+
* `isConnected()` which reflects the manager's internal lifecycle state.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getConnectionState(): boolean;
|
|
39
46
|
/**
|
|
40
47
|
* Refresh all data
|
|
41
48
|
*/
|
package/dist/core/types.d.ts
CHANGED
|
@@ -175,8 +175,13 @@ export interface EntityView {
|
|
|
175
175
|
domain: EntityDomain;
|
|
176
176
|
/** Current state value */
|
|
177
177
|
state: string;
|
|
178
|
-
/**
|
|
179
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Entity attributes from Home Assistant, excluding keys that are
|
|
180
|
+
* surfaced as canonical resolved fields elsewhere on EntityView
|
|
181
|
+
* (`deviceClass`, `unitOfMeasurement`, `friendlyName`, `icon`).
|
|
182
|
+
* Use those fields instead of reaching into `attributes`.
|
|
183
|
+
*/
|
|
184
|
+
attributes: Omit<Record<string, any>, "device_class" | "unit_of_measurement" | "friendly_name" | "icon">;
|
|
180
185
|
lastChanged: Date;
|
|
181
186
|
lastUpdated: Date;
|
|
182
187
|
/** Event context */
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { addDebugIncomingMessageListener, addDebugOutgoingMessageListener, } from "./connection/adapter";
|
|
10
10
|
export type { ConnectionOptions } from "./connection/manager";
|
|
11
|
-
export { disconnect, getConnection, initConnection, isConnected } from "./connection/manager";
|
|
11
|
+
export { disconnect, getConnection, getConnectionState, initConnection, isConnected, } from "./connection/manager";
|
|
12
12
|
export { registerEntity } from "./connection/subscription-manager";
|
|
13
13
|
export type { SyncLayerConnection } from "./connection/types";
|
|
14
14
|
export type { OAuthOptions } from "./connection/auth";
|
package/dist/index.js
CHANGED
|
@@ -1154,6 +1154,85 @@ function applyStateDiff(s, entityId, diff) {
|
|
|
1154
1154
|
|
|
1155
1155
|
//#endregion
|
|
1156
1156
|
//#region src/connection/manager.ts
|
|
1157
|
+
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1158
|
+
const HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
1159
|
+
const SUSPEND_DELAY_MS = 5 * 60 * 1e3;
|
|
1160
|
+
let heartbeatInterval = null;
|
|
1161
|
+
let suspendTimeout = null;
|
|
1162
|
+
let visiblePromiseResolve = null;
|
|
1163
|
+
let visibilityHandler = null;
|
|
1164
|
+
let pageshowHandler = null;
|
|
1165
|
+
function promiseTimeout(ms, promise) {
|
|
1166
|
+
return new Promise((resolve, reject) => {
|
|
1167
|
+
const timer = setTimeout(() => reject(new Error("Promise timeout")), ms);
|
|
1168
|
+
promise.then((value) => {
|
|
1169
|
+
clearTimeout(timer);
|
|
1170
|
+
resolve(value);
|
|
1171
|
+
}, (err) => {
|
|
1172
|
+
clearTimeout(timer);
|
|
1173
|
+
reject(err);
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
function clearSuspendTimeout() {
|
|
1178
|
+
if (suspendTimeout !== null) {
|
|
1179
|
+
clearTimeout(suspendTimeout);
|
|
1180
|
+
suspendTimeout = null;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function resolveVisiblePromise() {
|
|
1184
|
+
if (visiblePromiseResolve) {
|
|
1185
|
+
visiblePromiseResolve();
|
|
1186
|
+
visiblePromiseResolve = null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function startHeartbeat(conn$1) {
|
|
1190
|
+
stopHeartbeat();
|
|
1191
|
+
heartbeatInterval = setInterval(() => {
|
|
1192
|
+
promiseTimeout(HEARTBEAT_TIMEOUT_MS, conn$1.ping()).catch(() => {
|
|
1193
|
+
conn$1.reconnect();
|
|
1194
|
+
});
|
|
1195
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
1196
|
+
}
|
|
1197
|
+
function stopHeartbeat() {
|
|
1198
|
+
if (heartbeatInterval !== null) {
|
|
1199
|
+
clearInterval(heartbeatInterval);
|
|
1200
|
+
heartbeatInterval = null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function setupLifecycleListeners(conn$1) {
|
|
1204
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
1205
|
+
teardownLifecycleListeners();
|
|
1206
|
+
visibilityHandler = () => {
|
|
1207
|
+
if (document.visibilityState === "hidden") {
|
|
1208
|
+
const pending = new Promise((resolve) => {
|
|
1209
|
+
visiblePromiseResolve = resolve;
|
|
1210
|
+
});
|
|
1211
|
+
conn$1.suspendReconnectUntil(pending);
|
|
1212
|
+
clearSuspendTimeout();
|
|
1213
|
+
suspendTimeout = setTimeout(() => {
|
|
1214
|
+
conn$1.suspend();
|
|
1215
|
+
suspendTimeout = null;
|
|
1216
|
+
}, SUSPEND_DELAY_MS);
|
|
1217
|
+
} else {
|
|
1218
|
+
clearSuspendTimeout();
|
|
1219
|
+
resolveVisiblePromise();
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
pageshowHandler = (event) => {
|
|
1223
|
+
if (event.persisted) conn$1.reconnect();
|
|
1224
|
+
};
|
|
1225
|
+
document.addEventListener("visibilitychange", visibilityHandler);
|
|
1226
|
+
window.addEventListener("pageshow", pageshowHandler);
|
|
1227
|
+
}
|
|
1228
|
+
function teardownLifecycleListeners() {
|
|
1229
|
+
if (typeof document !== "undefined" && visibilityHandler) document.removeEventListener("visibilitychange", visibilityHandler);
|
|
1230
|
+
if (typeof window !== "undefined" && pageshowHandler) window.removeEventListener("pageshow", pageshowHandler);
|
|
1231
|
+
visibilityHandler = null;
|
|
1232
|
+
pageshowHandler = null;
|
|
1233
|
+
clearSuspendTimeout();
|
|
1234
|
+
resolveVisiblePromise();
|
|
1235
|
+
}
|
|
1157
1236
|
/**
|
|
1158
1237
|
* Initialize connection to Home Assistant
|
|
1159
1238
|
*/
|
|
@@ -1180,6 +1259,8 @@ async function initConnection(options) {
|
|
|
1180
1259
|
setupEventHandlers(conn$1, options);
|
|
1181
1260
|
await loadInitialData(conn$1);
|
|
1182
1261
|
await subscribeToUpdates(wrappedConn);
|
|
1262
|
+
setupLifecycleListeners(conn$1);
|
|
1263
|
+
startHeartbeat(conn$1);
|
|
1183
1264
|
options.onConnect?.();
|
|
1184
1265
|
return conn$1;
|
|
1185
1266
|
} catch (error) {
|
|
@@ -1197,6 +1278,8 @@ async function initConnection(options) {
|
|
|
1197
1278
|
*/
|
|
1198
1279
|
function disconnect() {
|
|
1199
1280
|
const conn$1 = state.conn;
|
|
1281
|
+
stopHeartbeat();
|
|
1282
|
+
teardownLifecycleListeners();
|
|
1200
1283
|
if (conn$1) conn$1.close();
|
|
1201
1284
|
setState(produce((s) => {
|
|
1202
1285
|
s.conn = null;
|
|
@@ -1215,6 +1298,16 @@ function getConnection() {
|
|
|
1215
1298
|
function isConnected() {
|
|
1216
1299
|
return state.connectionState === "connected";
|
|
1217
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Get the underlying socket's connected flag.
|
|
1303
|
+
*
|
|
1304
|
+
* Exposed so apps can render a connection indicator in the UI. Distinct from
|
|
1305
|
+
* `isConnected()` which reflects the manager's internal lifecycle state.
|
|
1306
|
+
*/
|
|
1307
|
+
function getConnectionState() {
|
|
1308
|
+
const conn$1 = state.conn;
|
|
1309
|
+
return conn$1 ? conn$1.connected : false;
|
|
1310
|
+
}
|
|
1218
1311
|
function setupEventHandlers(conn$1, options) {
|
|
1219
1312
|
conn$1.addEventListener("disconnected", () => {
|
|
1220
1313
|
setState(produce((s) => {
|
|
@@ -1304,6 +1397,7 @@ function buildEntityView(entity$1, registry) {
|
|
|
1304
1397
|
const friendlyName = entity$1.attributes.friendly_name ?? registry?.name ?? entity$1.entity_id;
|
|
1305
1398
|
const name = registry?.name ?? friendlyName;
|
|
1306
1399
|
const iconResolution = resolveIcon(entity$1, registry);
|
|
1400
|
+
const { device_class: _dc, unit_of_measurement: _uom, friendly_name: _fn, icon: _icon,...restAttributes } = entity$1.attributes;
|
|
1307
1401
|
let areaId = registry?.area_id ?? null;
|
|
1308
1402
|
if (areaId === null && registry?.device_id) {
|
|
1309
1403
|
const device = state.devices[registry.device_id];
|
|
@@ -1313,7 +1407,7 @@ function buildEntityView(entity$1, registry) {
|
|
|
1313
1407
|
id: entity$1.entity_id,
|
|
1314
1408
|
domain,
|
|
1315
1409
|
state: entity$1.state,
|
|
1316
|
-
attributes:
|
|
1410
|
+
attributes: restAttributes,
|
|
1317
1411
|
lastChanged: new Date(entity$1.last_changed),
|
|
1318
1412
|
lastUpdated: new Date(entity$1.last_updated),
|
|
1319
1413
|
context: {
|
|
@@ -2416,4 +2510,4 @@ async function refreshForecasts(entityId, types) {
|
|
|
2416
2510
|
const VERSION = "0.1.0";
|
|
2417
2511
|
|
|
2418
2512
|
//#endregion
|
|
2419
|
-
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
|
|
2513
|
+
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
|
package/dist/solid/index.js
CHANGED
|
@@ -179,9 +179,6 @@ function useStore(selector) {
|
|
|
179
179
|
|
|
180
180
|
//#endregion
|
|
181
181
|
//#region src/solid/indices.ts
|
|
182
|
-
/**
|
|
183
|
-
* Group items by a key function
|
|
184
|
-
*/
|
|
185
182
|
function groupBy(items, keyFn) {
|
|
186
183
|
const result = {};
|
|
187
184
|
for (const item of items) {
|
|
@@ -190,26 +187,14 @@ function groupBy(items, keyFn) {
|
|
|
190
187
|
}
|
|
191
188
|
return result;
|
|
192
189
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Create a lazily-initialized memo. The actual createMemo runs on first
|
|
195
|
-
* access, inside whichever Solid root is current at that point.
|
|
196
|
-
* This avoids top-level createMemo calls that execute outside any root.
|
|
197
|
-
*/
|
|
198
|
-
function lazyMemo(fn) {
|
|
199
|
-
let memo;
|
|
200
|
-
return () => {
|
|
201
|
-
if (!memo) memo = createMemo(fn);
|
|
202
|
-
return memo();
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
190
|
/** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
|
|
206
|
-
const byDomain =
|
|
191
|
+
const byDomain = () => groupBy(Object.keys(state.entities), (id) => extractDomain(id));
|
|
207
192
|
/** Entity IDs grouped by area (via entity registry area_id) */
|
|
208
|
-
const byArea =
|
|
193
|
+
const byArea = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.area_id ?? null);
|
|
209
194
|
/** Entity IDs grouped by device (via entity registry device_id) */
|
|
210
|
-
const byDevice =
|
|
195
|
+
const byDevice = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.device_id ?? null);
|
|
211
196
|
/** Entity IDs grouped by floor (device -> area -> floor chain) */
|
|
212
|
-
const byFloor =
|
|
197
|
+
const byFloor = () => {
|
|
213
198
|
const result = {};
|
|
214
199
|
for (const [entityId, reg] of Object.entries(state.entityRegistry)) if (reg.device_id) {
|
|
215
200
|
const device = state.devices[reg.device_id];
|
|
@@ -219,15 +204,15 @@ const byFloor = lazyMemo(() => {
|
|
|
219
204
|
}
|
|
220
205
|
}
|
|
221
206
|
return result;
|
|
222
|
-
}
|
|
207
|
+
};
|
|
223
208
|
/** Entity IDs grouped by label */
|
|
224
|
-
const byLabel =
|
|
209
|
+
const byLabel = () => {
|
|
225
210
|
const result = {};
|
|
226
211
|
for (const [entityId, reg] of Object.entries(state.entityRegistry)) for (const label of reg.labels ?? []) (result[label] ??= []).push(entityId);
|
|
227
212
|
return result;
|
|
228
|
-
}
|
|
213
|
+
};
|
|
229
214
|
/** All entity IDs currently in the store */
|
|
230
|
-
const allEntityIds =
|
|
215
|
+
const allEntityIds = () => Object.keys(state.entities);
|
|
231
216
|
|
|
232
217
|
//#endregion
|
|
233
218
|
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
|
package/dist/solid/indices.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** Reactive derived entity indices
|
|
1
|
+
/** Reactive derived entity indices. Each call reads from the SolidJS store directly,
|
|
2
|
+
* so consumers wrap in createMemo within their own reactive root for memoization. */
|
|
2
3
|
import type { Accessor } from "solid-js";
|
|
3
4
|
/** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
|
|
4
5
|
export declare const byDomain: Accessor<Record<string, string[]>>;
|