@glasshome/sync-layer 0.7.1 → 0.8.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/index.d.ts +1291 -54
- package/dist/index.js +215 -308
- package/dist/service-C8H42I-Q.d.ts +459 -0
- package/dist/solid/index.d.ts +119 -6
- package/dist/solid/index.js +20 -15
- package/dist/{statistics-CtRfIF6D.js → statistics-DyCCTwv2.js} +83 -95
- package/dist/worker/worker-main.d.ts +11 -8
- package/dist/worker/worker-main.js +2 -3
- package/dist/{worker-main-C-3QM4di.js → worker-main-D_wyGAIG.js} +47 -157
- package/package.json +3 -3
- package/dist/calendar/track.d.ts +0 -19
- package/dist/calendar/types.d.ts +0 -32
- package/dist/camera/fetch.d.ts +0 -16
- package/dist/camera/query.d.ts +0 -15
- package/dist/camera/types.d.ts +0 -85
- package/dist/camera/webrtc.d.ts +0 -31
- package/dist/commands/service.d.ts +0 -53
- package/dist/commands/types.d.ts +0 -113
- package/dist/connection/adapter.d.ts +0 -48
- package/dist/connection/auth.d.ts +0 -126
- package/dist/connection/bridged.d.ts +0 -16
- package/dist/connection/manager.d.ts +0 -51
- package/dist/connection/subscription-manager.d.ts +0 -48
- package/dist/connection/subscriptions.d.ts +0 -31
- package/dist/connection/types.d.ts +0 -151
- package/dist/core/privileged-conn.d.ts +0 -29
- package/dist/core/reducers.d.ts +0 -99
- package/dist/core/store.d.ts +0 -100
- package/dist/core/types.d.ts +0 -339
- package/dist/demo/demo-data.d.ts +0 -24
- package/dist/demo/demo-provider.d.ts +0 -40
- package/dist/demo/energy-sim.d.ts +0 -112
- package/dist/energy/prefs.d.ts +0 -56
- package/dist/entities/area-views.d.ts +0 -32
- package/dist/entities/index.d.ts +0 -54
- package/dist/entities/views.d.ts +0 -35
- package/dist/history/constants.d.ts +0 -16
- package/dist/history/fetch.d.ts +0 -19
- package/dist/history/query.d.ts +0 -27
- package/dist/history/statistics.d.ts +0 -55
- package/dist/history/types.d.ts +0 -146
- package/dist/host-api.d.ts +0 -50
- package/dist/media/url.d.ts +0 -14
- package/dist/query/area-builder.d.ts +0 -25
- package/dist/query/builder.d.ts +0 -18
- package/dist/query/helpers.d.ts +0 -21
- package/dist/query/memoization.d.ts +0 -18
- package/dist/query/projections.d.ts +0 -74
- package/dist/query/types.d.ts +0 -260
- package/dist/solid/hooks.d.ts +0 -119
- package/dist/solid/indices.d.ts +0 -15
- package/dist/weather/fetch.d.ts +0 -19
- package/dist/weather/query.d.ts +0 -23
- package/dist/weather/types.d.ts +0 -126
- package/dist/worker/bridge-client.d.ts +0 -47
- package/dist/worker/enforcement.d.ts +0 -53
- package/dist/worker/protocol.d.ts +0 -129
- package/dist/worker/token-store.d.ts +0 -3
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Auth, createConnection, createSocket
|
|
1
|
+
import { Auth, ERR_INVALID_AUTH, createConnection, createSocket } from "home-assistant-js-websocket";
|
|
2
2
|
import { matchesCapability } from "@glasshome/widget-contract";
|
|
3
|
-
|
|
4
3
|
//#region src/worker/enforcement.ts
|
|
5
4
|
var RegistryMirror = class {
|
|
6
5
|
entities = /* @__PURE__ */ new Map();
|
|
@@ -65,66 +64,45 @@ function enforceServiceCall(caps, call, registry) {
|
|
|
65
64
|
message: `Call to ${call.domain}.${call.service} is not covered by the widget's granted capabilities`
|
|
66
65
|
};
|
|
67
66
|
}
|
|
68
|
-
|
|
69
67
|
//#endregion
|
|
70
|
-
//#region src/worker/
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return new Promise((resolve, reject) => {
|
|
81
|
-
const req = indexedDB.open(DB_NAME, 1);
|
|
82
|
-
req.onupgradeneeded = () => {
|
|
83
|
-
req.result.createObjectStore(STORE);
|
|
84
|
-
};
|
|
85
|
-
req.onsuccess = () => resolve(req.result);
|
|
86
|
-
req.onerror = () => reject(req.error);
|
|
68
|
+
//#region src/worker/proxy-auth.ts
|
|
69
|
+
/** The relay authenticates upstream; this Auth carries no token and never expires. */
|
|
70
|
+
function proxyAuth(hassUrl) {
|
|
71
|
+
return new Auth({
|
|
72
|
+
hassUrl,
|
|
73
|
+
clientId: null,
|
|
74
|
+
access_token: "",
|
|
75
|
+
refresh_token: "",
|
|
76
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
77
|
+
expires_in: Number.MAX_SAFE_INTEGER
|
|
87
78
|
});
|
|
88
79
|
}
|
|
89
|
-
function
|
|
90
|
-
return
|
|
91
|
-
req.onsuccess = () => resolve(req.result);
|
|
92
|
-
req.onerror = () => reject(req.error);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
async function loadTokens() {
|
|
96
|
-
try {
|
|
97
|
-
const db = await openDb();
|
|
98
|
-
const store = db.transaction(STORE, "readonly").objectStore(STORE);
|
|
99
|
-
const data = await settle(store.get(KEY));
|
|
100
|
-
db.close();
|
|
101
|
-
return data ?? null;
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
80
|
+
function invalidAuthReason(err) {
|
|
81
|
+
return err === ERR_INVALID_AUTH ? { reason: "invalid_auth" } : {};
|
|
105
82
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
83
|
+
/** The lib fires reconnect-error only for ERR_INVALID_AUTH and then stops
|
|
84
|
+
* retrying, so that case is terminal, not a reconnect in progress. */
|
|
85
|
+
function reconnectErrorMessage(err) {
|
|
86
|
+
const reason = invalidAuthReason(err);
|
|
87
|
+
return {
|
|
88
|
+
k: "conn",
|
|
89
|
+
state: "reason" in reason ? "disconnected" : "reconnecting",
|
|
90
|
+
...reason
|
|
91
|
+
};
|
|
113
92
|
}
|
|
114
|
-
|
|
115
93
|
//#endregion
|
|
116
94
|
//#region src/worker/worker-main.ts
|
|
117
95
|
/**
|
|
118
|
-
* HA bridge worker. Owns the socket
|
|
119
|
-
*
|
|
120
|
-
* tunnels arbitrary HA traffic for the host. Widget
|
|
121
|
-
* one verb
|
|
122
|
-
* capabilities with the worker's own registry mirror, so
|
|
123
|
-
* not trust anything computed in the widget's realm.
|
|
96
|
+
* HA bridge worker. Owns the socket, which never reaches the main thread, and
|
|
97
|
+
* holds no HA token: the relay authenticates upstream. The privileged channel
|
|
98
|
+
* (the worker's own port) tunnels arbitrary HA traffic for the host. Widget
|
|
99
|
+
* MessagePorts get exactly one verb, call_service, validated against the
|
|
100
|
+
* widget's granted capabilities with the worker's own registry mirror, so
|
|
101
|
+
* enforcement does not trust anything computed in the widget's realm.
|
|
124
102
|
*/
|
|
125
103
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
126
104
|
const HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
127
|
-
const SUSPEND_DELAY_MS =
|
|
105
|
+
const SUSPEND_DELAY_MS = 3e5;
|
|
128
106
|
function isHaWireMessage(value) {
|
|
129
107
|
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
130
108
|
}
|
|
@@ -140,44 +118,13 @@ function errText(err) {
|
|
|
140
118
|
}
|
|
141
119
|
return String(err);
|
|
142
120
|
}
|
|
143
|
-
var BrokeredAuth = class extends Auth {
|
|
144
|
-
constructor(mintUrl, data, ticket) {
|
|
145
|
-
super(data);
|
|
146
|
-
this.mintUrl = mintUrl;
|
|
147
|
-
this.ticket = ticket;
|
|
148
|
-
}
|
|
149
|
-
async refreshAccessToken() {
|
|
150
|
-
const res = await fetch(this.mintUrl, {
|
|
151
|
-
credentials: "include",
|
|
152
|
-
headers: {
|
|
153
|
-
accept: "application/json",
|
|
154
|
-
...this.ticket ? { "x-proxy-ticket": this.ticket } : {}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
if (!res.ok) throw new Error(`Token broker responded ${res.status}`);
|
|
158
|
-
if (!res.headers.get("content-type")?.includes("application/json")) throw new Error(`Token broker did not return JSON (is ${this.mintUrl} reachable?)`);
|
|
159
|
-
const broker = await res.json();
|
|
160
|
-
this.data = {
|
|
161
|
-
...this.data,
|
|
162
|
-
access_token: broker.accessToken,
|
|
163
|
-
expires_in: broker.expiresInSec,
|
|
164
|
-
expires: genExpires(broker.expiresInSec),
|
|
165
|
-
refresh_token: "",
|
|
166
|
-
hassUrl: broker.haUrl,
|
|
167
|
-
clientId: broker.haClientId
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
121
|
/** Resolve the proxy WS endpoint. Accepts a same-origin path (web) or an
|
|
172
122
|
* absolute ws(s)/http(s) URL (native), and appends the auth ticket if given. */
|
|
173
123
|
function buildProxyWsUrl(scope, proxyWsPath, ticket) {
|
|
174
124
|
let base;
|
|
175
125
|
if (/^wss?:\/\//i.test(proxyWsPath)) base = proxyWsPath;
|
|
176
126
|
else if (/^https?:\/\//i.test(proxyWsPath)) base = proxyWsPath.replace(/^http/i, "ws");
|
|
177
|
-
else {
|
|
178
|
-
const proto = scope.location.protocol === "https:" ? "wss:" : "ws:";
|
|
179
|
-
base = `${proto}//${scope.location.host}${proxyWsPath}`;
|
|
180
|
-
}
|
|
127
|
+
else base = `${scope.location.protocol === "https:" ? "wss:" : "ws:"}//${scope.location.host}${proxyWsPath}`;
|
|
181
128
|
if (ticket) base += `${base.includes("?") ? "&" : "?"}ticket=${encodeURIComponent(ticket)}`;
|
|
182
129
|
return base;
|
|
183
130
|
}
|
|
@@ -266,7 +213,7 @@ function runHaBridgeWorker(scope) {
|
|
|
266
213
|
function startHeartbeat(c) {
|
|
267
214
|
stopHeartbeat();
|
|
268
215
|
heartbeat = setInterval(() => {
|
|
269
|
-
const timer = new Promise((_, reject) => setTimeout(() => reject(new Error("ping timeout")), HEARTBEAT_TIMEOUT_MS));
|
|
216
|
+
const timer = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("ping timeout")), HEARTBEAT_TIMEOUT_MS));
|
|
270
217
|
Promise.race([c.ping(), timer]).catch(() => c.reconnect());
|
|
271
218
|
}, HEARTBEAT_INTERVAL_MS);
|
|
272
219
|
}
|
|
@@ -295,72 +242,25 @@ function runHaBridgeWorker(scope) {
|
|
|
295
242
|
resumeVisible = null;
|
|
296
243
|
}
|
|
297
244
|
}
|
|
298
|
-
async function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
expires_in: 365 * 24 * 3600
|
|
307
|
-
});
|
|
308
|
-
case "brokered": return new BrokeredAuth(mode.mintUrl, {
|
|
309
|
-
hassUrl: url,
|
|
310
|
-
clientId: null,
|
|
311
|
-
access_token: "",
|
|
312
|
-
refresh_token: "",
|
|
313
|
-
expires: 0,
|
|
314
|
-
expires_in: 0
|
|
315
|
-
}, ticket);
|
|
316
|
-
case "oauth": {
|
|
317
|
-
const data = mode.data ?? await loadTokens();
|
|
318
|
-
if (!data) return "needs-auth";
|
|
319
|
-
if (mode.data) await saveTokens(mode.data);
|
|
320
|
-
return new Auth(data, (updated) => {
|
|
321
|
-
saveTokens(updated);
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
async function connect(url, mode, proxyWsPath, proxyTicket) {
|
|
327
|
-
const auth = await buildAuth(url, mode, proxyTicket);
|
|
328
|
-
if (auth === "needs-auth") {
|
|
329
|
-
post({
|
|
330
|
-
k: "connect_result",
|
|
331
|
-
ok: false,
|
|
332
|
-
needsAuth: true
|
|
333
|
-
});
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (mode.kind === "brokered") try {
|
|
337
|
-
await auth.refreshAccessToken();
|
|
338
|
-
} catch (err) {
|
|
339
|
-
post({
|
|
340
|
-
k: "connect_result",
|
|
341
|
-
ok: false,
|
|
342
|
-
error: errText(err)
|
|
343
|
-
});
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const proxyWsUrl = proxyWsPath ? buildProxyWsUrl(scope, proxyWsPath, proxyTicket) : null;
|
|
347
|
-
const socketFactory = proxyWsUrl ? (options) => {
|
|
348
|
-
const proxied = new Proxy(options.auth, { get: (target, prop) => prop === "wsUrl" ? proxyWsUrl : Reflect.get(target, prop) });
|
|
349
|
-
return createSocket({
|
|
350
|
-
...options,
|
|
351
|
-
auth: proxied
|
|
352
|
-
});
|
|
353
|
-
} : void 0;
|
|
245
|
+
async function connect(url, proxyWsPath, proxyTicket) {
|
|
246
|
+
const auth = proxyAuth(url);
|
|
247
|
+
const proxyWsUrl = buildProxyWsUrl(scope, proxyWsPath, proxyTicket);
|
|
248
|
+
const proxiedAuth = new Proxy(auth, { get: (target, prop) => prop === "wsUrl" ? proxyWsUrl : Reflect.get(target, prop) });
|
|
249
|
+
const socketFactory = (options) => createSocket({
|
|
250
|
+
...options,
|
|
251
|
+
auth: proxiedAuth
|
|
252
|
+
});
|
|
354
253
|
try {
|
|
355
254
|
conn = await createConnection({
|
|
356
255
|
auth,
|
|
357
|
-
|
|
256
|
+
createSocket: socketFactory
|
|
358
257
|
});
|
|
359
258
|
} catch (err) {
|
|
360
259
|
post({
|
|
361
260
|
k: "connect_result",
|
|
362
261
|
ok: false,
|
|
363
|
-
error: errText(err)
|
|
262
|
+
error: errText(err),
|
|
263
|
+
...invalidAuthReason(err)
|
|
364
264
|
});
|
|
365
265
|
return;
|
|
366
266
|
}
|
|
@@ -368,10 +268,7 @@ function runHaBridgeWorker(scope) {
|
|
|
368
268
|
k: "conn",
|
|
369
269
|
state: "disconnected"
|
|
370
270
|
}));
|
|
371
|
-
conn.addEventListener("reconnect-error", () => post(
|
|
372
|
-
k: "conn",
|
|
373
|
-
state: "reconnecting"
|
|
374
|
-
}));
|
|
271
|
+
conn.addEventListener("reconnect-error", (_c, err) => post(reconnectErrorMessage(err)));
|
|
375
272
|
let everReady = false;
|
|
376
273
|
conn.addEventListener("ready", () => {
|
|
377
274
|
post({
|
|
@@ -399,9 +296,9 @@ function runHaBridgeWorker(scope) {
|
|
|
399
296
|
const msg = ev.data;
|
|
400
297
|
switch (msg.k) {
|
|
401
298
|
case "connect":
|
|
402
|
-
await connect(msg.url, msg.
|
|
299
|
+
await connect(msg.url, msg.proxyWsPath, msg.proxyTicket);
|
|
403
300
|
break;
|
|
404
|
-
case "send":
|
|
301
|
+
case "send":
|
|
405
302
|
if (!conn || !isHaWireMessage(msg.message)) {
|
|
406
303
|
post({
|
|
407
304
|
k: "result",
|
|
@@ -428,8 +325,7 @@ function runHaBridgeWorker(scope) {
|
|
|
428
325
|
});
|
|
429
326
|
}
|
|
430
327
|
break;
|
|
431
|
-
|
|
432
|
-
case "sub": {
|
|
328
|
+
case "sub":
|
|
433
329
|
if (!conn) {
|
|
434
330
|
post({
|
|
435
331
|
k: "sub_end",
|
|
@@ -466,7 +362,6 @@ function runHaBridgeWorker(scope) {
|
|
|
466
362
|
});
|
|
467
363
|
}
|
|
468
364
|
break;
|
|
469
|
-
}
|
|
470
365
|
case "unsub": {
|
|
471
366
|
const unsub = subs.get(msg.id);
|
|
472
367
|
subs.delete(msg.id);
|
|
@@ -493,9 +388,6 @@ function runHaBridgeWorker(scope) {
|
|
|
493
388
|
state: "disconnected"
|
|
494
389
|
});
|
|
495
390
|
break;
|
|
496
|
-
case "clear_tokens":
|
|
497
|
-
await saveTokens(null);
|
|
498
|
-
break;
|
|
499
391
|
case "register_widget": {
|
|
500
392
|
const port = ev.ports[0];
|
|
501
393
|
if (port) attachWidgetPort(msg.widgetId, msg.caps, port);
|
|
@@ -504,10 +396,8 @@ function runHaBridgeWorker(scope) {
|
|
|
504
396
|
case "unregister_widget":
|
|
505
397
|
widgets.get(msg.widgetId)?.port.close();
|
|
506
398
|
widgets.delete(msg.widgetId);
|
|
507
|
-
break;
|
|
508
399
|
}
|
|
509
400
|
};
|
|
510
401
|
}
|
|
511
|
-
|
|
512
402
|
//#endregion
|
|
513
|
-
export { runHaBridgeWorker };
|
|
403
|
+
export { runHaBridgeWorker as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasshome/sync-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Type-safe, reactive state synchronization layer for Home Assistant",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"@glasshome/ha-types": "0.1.2",
|
|
58
58
|
"@types/bun": "latest",
|
|
59
59
|
"solid-js": "^1.9.0",
|
|
60
|
-
"tsdown": "^0.
|
|
61
|
-
"typescript": "^
|
|
60
|
+
"tsdown": "^0.22.14",
|
|
61
|
+
"typescript": "^7.0.2"
|
|
62
62
|
},
|
|
63
63
|
"homepage": "https://github.com/glasshome/sync-layer#readme",
|
|
64
64
|
"bugs": {
|
package/dist/calendar/track.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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;
|
package/dist/calendar/types.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/camera/fetch.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Camera Stream Fetching
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
import type { EntityId } from "../core/types";
|
|
7
|
-
import type { CameraStreamData, StreamFormat, StreamQueryOptions } from "./types";
|
|
8
|
-
/**
|
|
9
|
-
* Fetch stream for a camera entity.
|
|
10
|
-
* Returns a public demo HLS stream when not connected.
|
|
11
|
-
*/
|
|
12
|
-
export declare function fetchStream(entityId: EntityId, format?: StreamFormat): Promise<string | null>;
|
|
13
|
-
/**
|
|
14
|
-
* Fetch stream data for a camera entity
|
|
15
|
-
*/
|
|
16
|
-
export declare function fetchStreamData(entityId: EntityId, options?: StreamQueryOptions): Promise<CameraStreamData>;
|
package/dist/camera/query.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Camera Stream Query Builder
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
import type { EntityId } from "../core/types";
|
|
7
|
-
import type { CameraStreamData, EntityStreamQueryOptions, StreamFormat } from "./types";
|
|
8
|
-
/**
|
|
9
|
-
* Get stream for a camera entity
|
|
10
|
-
*/
|
|
11
|
-
export declare function getStream(entityId: EntityId, options?: EntityStreamQueryOptions): Promise<CameraStreamData>;
|
|
12
|
-
/**
|
|
13
|
-
* Refresh stream for a camera entity
|
|
14
|
-
*/
|
|
15
|
-
export declare function refreshStream(entityId: EntityId, format?: StreamFormat): Promise<CameraStreamData>;
|
package/dist/camera/types.d.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Camera Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions for camera stream data.
|
|
5
|
-
*
|
|
6
|
-
* @packageDocumentation
|
|
7
|
-
*/
|
|
8
|
-
import type { EntityId } from "../core/types";
|
|
9
|
-
/**
|
|
10
|
-
* Stream format
|
|
11
|
-
* Supported stream formats from Home Assistant Camera API
|
|
12
|
-
*/
|
|
13
|
-
export type StreamFormat = "hls" | "webrtc" | "mjpeg";
|
|
14
|
-
/**
|
|
15
|
-
* Camera stream data
|
|
16
|
-
*/
|
|
17
|
-
export interface CameraStream {
|
|
18
|
-
/** Stream URL */
|
|
19
|
-
url: string | null;
|
|
20
|
-
/** Loading state */
|
|
21
|
-
loading: boolean;
|
|
22
|
-
/** Error if any */
|
|
23
|
-
error: Error | null;
|
|
24
|
-
/** Last fetched timestamp */
|
|
25
|
-
lastFetched: number | null;
|
|
26
|
-
/** Stream format */
|
|
27
|
-
format: StreamFormat;
|
|
28
|
-
/** Stream expiration time (if available) */
|
|
29
|
-
expiresAt?: number | null;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Camera stream data for an entity
|
|
33
|
-
*/
|
|
34
|
-
export interface CameraStreamData {
|
|
35
|
-
/** Entity ID */
|
|
36
|
-
entityId: EntityId;
|
|
37
|
-
/** Stream data */
|
|
38
|
-
stream: CameraStream;
|
|
39
|
-
/** Loading state */
|
|
40
|
-
loading: boolean;
|
|
41
|
-
/** Error if any */
|
|
42
|
-
error: Error | null;
|
|
43
|
-
/** Last fetched timestamp */
|
|
44
|
-
lastFetched: number | null;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Camera stream data for multiple entities
|
|
48
|
-
*/
|
|
49
|
-
export interface CameraStreams {
|
|
50
|
-
/** Entity ID -> Stream data */
|
|
51
|
-
[entityId: EntityId]: CameraStreamData;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Options for stream queries
|
|
55
|
-
*/
|
|
56
|
-
export interface StreamQueryOptions {
|
|
57
|
-
/** Stream format (default: "hls") */
|
|
58
|
-
format?: StreamFormat;
|
|
59
|
-
/** Auto-refresh stream before expiration */
|
|
60
|
-
autoRefresh?: boolean;
|
|
61
|
-
/** Stream refresh interval in milliseconds (default: 5 minutes) */
|
|
62
|
-
refreshInterval?: number;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Single entity stream query options
|
|
66
|
-
*/
|
|
67
|
-
export interface EntityStreamQueryOptions {
|
|
68
|
-
/** Stream format (default: "hls") */
|
|
69
|
-
format?: StreamFormat;
|
|
70
|
-
/** Auto-refresh stream before expiration */
|
|
71
|
-
autoRefresh?: boolean;
|
|
72
|
-
/** Stream refresh interval in milliseconds (default: 5 minutes) */
|
|
73
|
-
refreshInterval?: number;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Stream query result
|
|
77
|
-
*/
|
|
78
|
-
export interface StreamResult {
|
|
79
|
-
/** Stream data */
|
|
80
|
-
data: CameraStreamData | null;
|
|
81
|
-
/** Loading state */
|
|
82
|
-
loading: boolean;
|
|
83
|
-
/** Error if any */
|
|
84
|
-
error: Error | null;
|
|
85
|
-
}
|
package/dist/camera/webrtc.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { EntityId } from "../core/types";
|
|
2
|
-
/** Minimal RTC types for sync-layer (no DOM lib dependency) */
|
|
3
|
-
type RtcConfiguration = Record<string, unknown>;
|
|
4
|
-
type RtcIceCandidateInit = Record<string, unknown>;
|
|
5
|
-
interface WebRtcClientConfig {
|
|
6
|
-
configuration: RtcConfiguration;
|
|
7
|
-
dataChannel?: string;
|
|
8
|
-
}
|
|
9
|
-
interface WebRtcSession {
|
|
10
|
-
sessionId: string | null;
|
|
11
|
-
unsubscribe: (() => Promise<void>) | null;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Get WebRTC client configuration (STUN/TURN servers) from HA
|
|
15
|
-
*/
|
|
16
|
-
export declare function getWebRtcClientConfig(entityId: EntityId): Promise<WebRtcClientConfig>;
|
|
17
|
-
/**
|
|
18
|
-
* Start WebRTC session with HA.
|
|
19
|
-
* Returns a promise that resolves when the answer SDP is received.
|
|
20
|
-
* ICE candidates from HA are forwarded to the provided callback.
|
|
21
|
-
* Local ICE candidates should be sent via sendWebRtcCandidate().
|
|
22
|
-
*/
|
|
23
|
-
export declare function startWebRtcSession(entityId: EntityId, sdpOffer: string, onCandidate: (candidate: RtcIceCandidateInit) => void): Promise<{
|
|
24
|
-
answer: string;
|
|
25
|
-
session: WebRtcSession;
|
|
26
|
-
}>;
|
|
27
|
-
/**
|
|
28
|
-
* Send a local ICE candidate to HA
|
|
29
|
-
*/
|
|
30
|
-
export declare function sendWebRtcCandidate(entityId: EntityId, sessionId: string, candidate: RtcIceCandidateInit): Promise<void>;
|
|
31
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Service calls and commands
|
|
3
|
-
*
|
|
4
|
-
* Type-safe wrappers for Home Assistant service calls and commands.
|
|
5
|
-
*
|
|
6
|
-
* @packageDocumentation
|
|
7
|
-
*/
|
|
8
|
-
import type { Domain, ServiceCall, ServiceName, WsCommandType } from "@glasshome/ha-types";
|
|
9
|
-
import type { EntityId } from "../core/types";
|
|
10
|
-
import type { EntityUpdateFields, ServiceTarget } from "./types";
|
|
11
|
-
/**
|
|
12
|
-
* Call a Home Assistant service
|
|
13
|
-
*/
|
|
14
|
-
export declare function callService<D extends Domain, S extends ServiceName<D>>(domain: D, service: S, serviceData?: ServiceCall<D, S>, target?: ServiceTarget): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Turn on entity or entities
|
|
17
|
-
*/
|
|
18
|
-
export declare function turnOn(entityId: EntityId | EntityId[], serviceData?: Record<string, any>): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
* Turn off entity or entities
|
|
21
|
-
*/
|
|
22
|
-
export declare function turnOff(entityId: EntityId | EntityId[], serviceData?: Record<string, any>): Promise<void>;
|
|
23
|
-
/**
|
|
24
|
-
* Toggle entity or entities
|
|
25
|
-
*/
|
|
26
|
-
export declare function toggle(entityId: EntityId | EntityId[], serviceData?: Record<string, any>): Promise<void>;
|
|
27
|
-
/**
|
|
28
|
-
* Update entity registry entry
|
|
29
|
-
*/
|
|
30
|
-
export declare function updateEntity(entityId: EntityId, updates: EntityUpdateFields): Promise<any>;
|
|
31
|
-
/**
|
|
32
|
-
* Send a generic WebSocket command
|
|
33
|
-
*/
|
|
34
|
-
export declare function sendCommand<T = unknown>(command: {
|
|
35
|
-
type: WsCommandType | string;
|
|
36
|
-
[key: string]: unknown;
|
|
37
|
-
}): Promise<T>;
|
|
38
|
-
/**
|
|
39
|
-
* Execute multiple service calls in parallel
|
|
40
|
-
*/
|
|
41
|
-
export declare function batchServiceCalls(calls: Array<{
|
|
42
|
-
domain: Domain;
|
|
43
|
-
service: ServiceName<Domain>;
|
|
44
|
-
serviceData?: ServiceCall<Domain, ServiceName<Domain>>;
|
|
45
|
-
target?: ServiceTarget;
|
|
46
|
-
}>): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Execute multiple entity updates in parallel
|
|
49
|
-
*/
|
|
50
|
-
export declare function batchEntityUpdates(updates: Array<{
|
|
51
|
-
entityId: EntityId;
|
|
52
|
-
updates: EntityUpdateFields;
|
|
53
|
-
}>): Promise<any[]>;
|