@mega-yfue/eufy-sdk 0.5.0-beta.6 → 0.5.0-beta.60
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/client/device-registry.d.ts +24 -2
- package/dist/client/eufy-mega.d.ts +75 -10
- package/dist/client/types.d.ts +1 -1
- package/dist/core/contracts.d.ts +65 -30
- package/dist/core/store.d.ts +4 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/index.js +3018 -425
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/arming.d.ts +37 -4
- package/dist/model/capabilities/camera.d.ts +24 -10
- package/dist/model/capabilities/dp-catalog.d.ts +4 -3
- package/dist/model/capabilities/index.d.ts +12 -3
- package/dist/model/capabilities/light.d.ts +3 -0
- package/dist/model/capabilities/motion.d.ts +5 -0
- package/dist/model/capabilities/mower.d.ts +230 -0
- package/dist/model/capabilities/suction.d.ts +5 -5
- package/dist/model/capabilities/types.d.ts +6 -0
- package/dist/model/capabilities/vacuum-clean.d.ts +19 -13
- package/dist/model/classify.d.ts +1 -2
- package/dist/model/device-family.d.ts +4 -0
- package/dist/model/param-dictionary.d.ts +6 -0
- package/dist/model/param-namespace.d.ts +1 -1
- package/dist/model/registry.d.ts +3 -2
- package/dist/model/types.d.ts +21 -8
- package/dist/transport/abortable.d.ts +8 -0
- package/dist/transport/http/mega-client.d.ts +34 -2
- package/dist/transport/mqtt/broker-discovery.d.ts +2 -1
- package/dist/transport/mqtt/command-router.d.ts +3 -2
- package/dist/transport/p2p/command-router.d.ts +14 -26
- package/dist/transport/p2p/fmp4.d.ts +12 -2
- package/dist/transport/p2p/live-stream.d.ts +3 -0
- package/dist/transport/p2p/p2p-session.d.ts +21 -1
- package/dist/transport/p2p/readable-egress.d.ts +4 -0
- package/dist/transport/p2p/recording-download.d.ts +4 -3
- package/dist/transport/p2p/shared-live-source.d.ts +9 -0
- package/dist/transport/p2p/talkback.d.ts +21 -10
- package/dist/transport/p2p/video.d.ts +5 -2
- package/dist/transport/rtc/command-router.d.ts +128 -0
- package/dist/transport/rtc/live.d.ts +69 -0
- package/dist/transport/rtc/peer.d.ts +104 -0
- package/dist/transport/rtc/portal-keepalive.d.ts +11 -0
- package/dist/transport/rtc/portal-packet.d.ts +72 -0
- package/dist/transport/rtc/ptcs-framer.d.ts +86 -0
- package/dist/transport/rtc/scall-sdp.d.ts +42 -0
- package/dist/transport/rtc/session.d.ts +90 -0
- package/dist/transport/rtc/signaling.d.ts +139 -0
- package/dist/transport/token-echo.d.ts +8 -0
- package/dist/transport/tuya/account.d.ts +8 -5
- package/dist/transport/tuya/client.d.ts +19 -5
- package/dist/transport/tuya/command-router.d.ts +10 -14
- package/dist/transport/tuya/dp-codec.d.ts +2 -23
- package/dist/transport/tuya/et3.d.ts +22 -0
- package/dist/transport/tuya/request.d.ts +18 -8
- package/dist/transport/tuya/sign.d.ts +7 -0
- package/package.json +2 -1
package/dist/model/types.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type Codec = "station" | "camera" | "sensor" | "lock" | "keypad" | "vacuu
|
|
|
39
39
|
* it maps to a {@link CapabilityModule} that owns its property schema. Extend this union as
|
|
40
40
|
* new capabilities are modelled — adding one never requires a subclass.
|
|
41
41
|
*/
|
|
42
|
-
export type Capability = "video" | "snapshot" | "motion" | "person_detection" | "battery" | "light" | "ptz" | "doorbell" | "contact" | "leak" | "smoke" | "co" | "siren" | "lock" | "keypad" | "arming" | "storage" | "rtsp" | "camera" | "audio" | "vacuum_clean" | "vacuum_dock" | "suction" | "locate" | "smart_light" | "display" | "info";
|
|
42
|
+
export type Capability = "video" | "snapshot" | "motion" | "person_detection" | "battery" | "light" | "ptz" | "doorbell" | "contact" | "leak" | "smoke" | "co" | "siren" | "lock" | "keypad" | "arming" | "storage" | "rtsp" | "camera" | "audio" | "vacuum_clean" | "vacuum_dock" | "suction" | "locate" | "mower" | "smart_light" | "display" | "info";
|
|
43
43
|
/** Value type of a property. */
|
|
44
44
|
export type PropertyValueType = "bool" | "number" | "string" | "enum";
|
|
45
45
|
/**
|
|
@@ -52,11 +52,12 @@ export type PropertyValueType = "bool" | "number" | "string" | "enum";
|
|
|
52
52
|
* one as the other is wrong in a way no type check catches.
|
|
53
53
|
*
|
|
54
54
|
* - `boolean` — an on/off state (always paired with `type: "bool"`).
|
|
55
|
-
* - `percent` / `celsius` / `dbm` / `seconds` / `hours` / `
|
|
56
|
-
* in the unit the device reports it in; each pairs with the
|
|
57
|
-
* never converted on the way out — a converted reading is an invented
|
|
58
|
-
* are separate kinds for exactly that reason: a robot reports
|
|
59
|
-
*
|
|
55
|
+
* - `percent` / `celsius` / `dbm` / `seconds` / `hours` / `minutes` / `millimetres` / `megabytes` /
|
|
56
|
+
* `degrees` — a measured quantity in the unit the device reports it in; each pairs with the
|
|
57
|
+
* matching `unit`. Values are never converted on the way out — a converted reading is an invented
|
|
58
|
+
* one. `seconds`, `minutes` and `hours` are separate kinds for exactly that reason: a robot reports
|
|
59
|
+
* a run in seconds, a mower its blade wear in minutes and a vacuum's consumable wear in hours, and
|
|
60
|
+
* normalising one into another would publish a number the device never sent.
|
|
60
61
|
* - `scalar` — a plain number in no unit at all: a step on a ladder, a mode index, a raw level, a
|
|
61
62
|
* segment count. Ordered and comparable, but its range and direction are the device's, so nothing
|
|
62
63
|
* but the device says what a given value means.
|
|
@@ -83,7 +84,7 @@ export type PropertyValueType = "bool" | "number" | "string" | "enum";
|
|
|
83
84
|
* decodes them and are exported beside that decoder; unlike an enum's options they are not carried
|
|
84
85
|
* here, which is a gap this vocabulary does not close on its own.
|
|
85
86
|
*/
|
|
86
|
-
export declare const KNOWN_VALUE_KINDS: readonly ["boolean", "percent", "celsius", "dbm", "seconds", "hours", "megabytes", "degrees", "scalar", "bitfield", "enum", "identifier", "timestamp", "text"];
|
|
87
|
+
export declare const KNOWN_VALUE_KINDS: readonly ["boolean", "percent", "celsius", "dbm", "seconds", "hours", "minutes", "millimetres", "megabytes", "degrees", "scalar", "bitfield", "enum", "identifier", "timestamp", "text"];
|
|
87
88
|
/** One of the kinds this version models — the closed half of {@link ValueKind}. */
|
|
88
89
|
export type KnownValueKind = (typeof KNOWN_VALUE_KINDS)[number];
|
|
89
90
|
/**
|
|
@@ -106,7 +107,8 @@ export declare function isKnownValueKind(kind: ValueKind): kind is KnownValueKin
|
|
|
106
107
|
/**
|
|
107
108
|
* How much a param's name and meaning can be trusted, most-trusted first. This is the one definition;
|
|
108
109
|
* the param dictionary and every `provenance` field use it.
|
|
109
|
-
* - `mega` — the vendor's cloud names it: its data-point catalog (`get_product_data_point`),
|
|
110
|
+
* - `mega` — the vendor's cloud names it: its data-point catalog (`get_product_data_point`), the
|
|
111
|
+
* Tuya product schema of an `eufy_home_tuya` device (`thing.m.device.ref.info.list`), or a
|
|
110
112
|
* reported value that matches what the cloud record already says (a model name or code).
|
|
111
113
|
* - `verified` — confirmed on a real device by this project: our own capture or observation, or
|
|
112
114
|
* identified by someone who has the hardware.
|
|
@@ -140,6 +142,11 @@ export interface PropertySpec {
|
|
|
140
142
|
kind?: ValueKind;
|
|
141
143
|
/** Whether the value can be written back to the device (a setter exists). */
|
|
142
144
|
writable: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Whether the device has reported this value: its own param, a read alias, or the payload it reads
|
|
147
|
+
* from. `false` means it never has. Absent when the manifest was built without the device's params.
|
|
148
|
+
*/
|
|
149
|
+
reported?: boolean;
|
|
143
150
|
/** Allowed values for `type: "enum"` (raw → label). */
|
|
144
151
|
enumValues?: Record<number, string>;
|
|
145
152
|
/**
|
|
@@ -286,6 +293,12 @@ export interface CloudRecord {
|
|
|
286
293
|
parentSn?: string;
|
|
287
294
|
/** Reported param_type → raw value. Presence of a param is a capability signal. */
|
|
288
295
|
params?: Record<number, string>;
|
|
296
|
+
/**
|
|
297
|
+
* Params the device reported over its realtime channel, keyed in the device's own namespace. A line
|
|
298
|
+
* whose state exists only on that channel carries none of it in {@link params}, so this is the
|
|
299
|
+
* evidence an availability gate on such a param is decided from.
|
|
300
|
+
*/
|
|
301
|
+
dpParams?: Record<number, string>;
|
|
289
302
|
}
|
|
290
303
|
/** The fully-resolved device shape produced by the 3-tier resolver. */
|
|
291
304
|
export interface ResolvedDevice {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settle `work` as it settles, or reject the moment `signal` aborts, whichever comes first.
|
|
3
|
+
*
|
|
4
|
+
* The underlying wait is left to finish on its own: it may be shared work other callers are waiting on, so a
|
|
5
|
+
* caller abandoning its own call does not cancel the work itself. This abandons WAITING, which is the only
|
|
6
|
+
* part that belonged to the caller.
|
|
7
|
+
*/
|
|
8
|
+
export declare function abortable<T>(work: Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
@@ -209,6 +209,8 @@ export interface SignedRetry {
|
|
|
209
209
|
export declare class MegaHttpClient {
|
|
210
210
|
private readonly cfg;
|
|
211
211
|
private region;
|
|
212
|
+
/** The host `estimate_domain` answered, persisted with the session. */
|
|
213
|
+
private estimatedDomain;
|
|
212
214
|
private bootstrapDomain?;
|
|
213
215
|
private sessionKey?;
|
|
214
216
|
/** Per-host ECDH session keys for non-mega gateways (e.g. eufylife) keyed by host. */
|
|
@@ -228,6 +230,8 @@ export declare class MegaHttpClient {
|
|
|
228
230
|
/** True while a 2FA code is outstanding: `auth_` holds only the limited pre-verify token, so the
|
|
229
231
|
* restored-session short-circuit must NOT treat it as a usable session. Cleared on Ok/reset. */
|
|
230
232
|
private pending2fa;
|
|
233
|
+
/** An autonomous re-login answered captcha/2FA; see {@link canReauthenticate}. */
|
|
234
|
+
private reloginChallenged;
|
|
231
235
|
private tokenExpiresAt;
|
|
232
236
|
/**
|
|
233
237
|
* Stable per-install device id: `openudid` as configured, as restored from the session store, or as
|
|
@@ -259,10 +263,12 @@ export declare class MegaHttpClient {
|
|
|
259
263
|
constructor(cfg: MegaClientConfig);
|
|
260
264
|
/**
|
|
261
265
|
* Install the session the store holds, if it holds a usable one: the token + its bound ECDH key, skipping
|
|
262
|
-
* estimate/key-exchange/login/2FA entirely. Answers the token adopted, or `undefined`.
|
|
266
|
+
* estimate/key-exchange/login/2FA entirely. Answers the token adopted, or `undefined`. A session logged in
|
|
267
|
+
* with a country other than the configured one is not usable: its shard is the other country's.
|
|
263
268
|
*
|
|
264
269
|
* Read at construction, and again when a rejection is being recovered from — a store SHARED with another
|
|
265
270
|
* client may already hold the session that client obtained, which is cheaper to adopt than to compete with.
|
|
271
|
+
* An adopted session is a complete one, so it ends any 2FA or challenged re-login this client was left with.
|
|
266
272
|
*
|
|
267
273
|
* Note: the device identity (openudid + phone model + media UA) is NOT restored here — it is resolved once
|
|
268
274
|
* in the constructor, where an explicit config wins over the stored value, and must not be re-derived from a
|
|
@@ -275,6 +281,21 @@ export declare class MegaHttpClient {
|
|
|
275
281
|
} | undefined;
|
|
276
282
|
/** The active region shard (e.g. `"eu-pr"`, `"us-pr"`), set after {@link login} or a region override. */
|
|
277
283
|
get regionShard(): RegionShard;
|
|
284
|
+
/**
|
|
285
|
+
* The shard the RTC signalling signs on: the regional prefix of the estimated domain
|
|
286
|
+
* (`…-ie-…`/`…-ie.` → `ie-pr`), else {@link regionShard}. The eu/us classification cannot name shards
|
|
287
|
+
* like `ie-pr`, whose accounts need the IE sign host and cluster.
|
|
288
|
+
*/
|
|
289
|
+
get rtcShard(): string;
|
|
290
|
+
/**
|
|
291
|
+
* The credentials the RTC signalling needs; `undefined` while logged out. The sign is refused
|
|
292
|
+
* ("gtoken not equal userid") unless its gtoken is the one every authed HTTP call carries.
|
|
293
|
+
*/
|
|
294
|
+
rtcIdentity(): {
|
|
295
|
+
authToken: string;
|
|
296
|
+
userId: string;
|
|
297
|
+
gtoken: string;
|
|
298
|
+
} | undefined;
|
|
278
299
|
/**
|
|
279
300
|
* The name commands attribute themselves to — {@link MegaClientConfig.accountName} when the config
|
|
280
301
|
* pins one (trimmed; blank counts as unset), otherwise the logged-in account's display name, which
|
|
@@ -367,6 +388,15 @@ export declare class MegaHttpClient {
|
|
|
367
388
|
* params instead, which also carry `{param_type, param_value, update_time}` and are not owner-gated.
|
|
368
389
|
*/
|
|
369
390
|
getDeviceParamList<T = unknown>(deviceSn: string): Promise<T>;
|
|
391
|
+
/**
|
|
392
|
+
* Fetch the account's devices as the `devicerelation` service lists them, `{attribute: 7, house_id: ""}`.
|
|
393
|
+
*
|
|
394
|
+
* Each entry is `{device: {device_sn, dps, …}}`, and `dps` is the cloud's last-known value of every
|
|
395
|
+
* data point that device has reported, keyed by DP number. Answered for an account a device is shared
|
|
396
|
+
* with as well as for its owner. Sent with `category: eufy_home`, without which the service answers
|
|
397
|
+
* `10000`.
|
|
398
|
+
*/
|
|
399
|
+
getDeviceRelationList<T = unknown>(): Promise<T>;
|
|
370
400
|
/**
|
|
371
401
|
* Fetch one page of a device's **cleaning history** from the mega `clean` service.
|
|
372
402
|
*
|
|
@@ -563,7 +593,9 @@ export declare class MegaHttpClient {
|
|
|
563
593
|
* Structural rather than a list of paths: a login round trip is itself an authenticated call while a limited
|
|
564
594
|
* token is held, so re-logging in from inside one would recurse — and a path list would have to be
|
|
565
595
|
* maintained alongside every request the login flow makes. A 2FA code already outstanding is a login a human
|
|
566
|
-
* is part-way through, and restarting it silently would discard it.
|
|
596
|
+
* is part-way through, and restarting it silently would discard it. A re-login that already came back needing
|
|
597
|
+
* a captcha or a 2FA code would need the same again, and each attempt sends the account another code, so
|
|
598
|
+
* none is tried until a login completes or a stored session is adopted. No credentials means nothing to try,
|
|
567
599
|
* so the rejection surfaces instead.
|
|
568
600
|
*/
|
|
569
601
|
private canReauthenticate;
|
|
@@ -8,8 +8,9 @@ export interface BrokerCredentials {
|
|
|
8
8
|
}
|
|
9
9
|
export interface ProbeResult {
|
|
10
10
|
ip: string;
|
|
11
|
-
/** True iff SUBSCRIBE to the target topic was granted (qos 0/1/2
|
|
11
|
+
/** True iff SUBSCRIBE to the target topic was granted (qos 0/1/2). */
|
|
12
12
|
granted: boolean;
|
|
13
|
+
/** The SUBACK return code for the topic, the 0x80 deny code included. */
|
|
13
14
|
grantedQos?: number;
|
|
14
15
|
error?: string;
|
|
15
16
|
ms: number;
|
|
@@ -136,8 +136,9 @@ export declare class MqttCommandRouter {
|
|
|
136
136
|
* One exception is known: a single garage unit whose own-cert SUBSCRIBE was denied on every candidate
|
|
137
137
|
* while a phone-extracted cert granted on the same one, with a sibling of the same model on the same
|
|
138
138
|
* account granting fine. That is a per-device authorization gap on the vendor's backend, not a
|
|
139
|
-
* broker-instance problem and not something this SDK works around —
|
|
140
|
-
* (
|
|
139
|
+
* broker-instance problem and not something this SDK works around — the throw below names each
|
|
140
|
+
* candidate's outcome (its SUBACK deny code, a timeout or a connection error) until the backend grants
|
|
141
|
+
* the account's own credentials.
|
|
141
142
|
*
|
|
142
143
|
* One fresh, explicitly non-reconnecting (`reconnectPeriod: 0`) connection per call — it repays the
|
|
143
144
|
* full mTLS handshake every command and tears the socket down right after (see
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P2P command router — the transport-side owner of the ThroughTek PPCS sessions and every wire
|
|
3
|
-
* operation over them: opening sessions, resolving a serial to its session + routing params,
|
|
4
|
-
* mapping a transport-neutral {@link Command} to a concrete frame (encryption level, wire shape),
|
|
5
|
-
* the fire-and-forget control senders, request/reply queries, and the media provider.
|
|
6
|
-
*
|
|
7
|
-
* Layering: this module knows P2P bytes; it does NOT know capabilities. Frame → semantic-event
|
|
8
|
-
* decoding is a model concern, so raw frames are handed back to the client via {@link P2PRouterDeps.onFrame}
|
|
9
|
-
* (the client gates them on device capabilities and emits typed events). This keeps transport free of
|
|
10
|
-
* any `model/` import — the capability↔transport decorrelation invariant.
|
|
11
|
-
*/
|
|
12
1
|
import type { MegaHttpClient } from "../http/mega-client.js";
|
|
13
2
|
import type { EufyDevice } from "../../core/types.js";
|
|
14
3
|
import type { Command, AutoLockSnapshot, MediaProvider, SharedSourceHints, AbortableCall } from "../../core/contracts.js";
|
|
@@ -258,11 +247,14 @@ export declare class P2PCommandRouter {
|
|
|
258
247
|
*/
|
|
259
248
|
dispatchCommand(sn: string, cmd: Command): Promise<void>;
|
|
260
249
|
/**
|
|
261
|
-
* Restart
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
250
|
+
* Restart the station that owns `sn`'s P2P session. `RESTART_HUB` (1034) is a station-scalar on the
|
|
251
|
+
* broadcast channel 255 whose body is `[u32 value][account_id padded]`, value `0`. ✅ Wire-confirmed
|
|
252
|
+
* byte-exact as a level-2 frame from a capture of the app's own HomeBase Restart (2026-08-03).
|
|
253
|
+
*
|
|
254
|
+
* The seal follows the session, as {@link sendBySessionLevel} defines it: a keyed session takes the
|
|
255
|
+
* level-2 frame, a keyless one — a standalone camera, which never negotiates a key — the same body
|
|
256
|
+
* sealed level-1. Both are replayed {@link DIRECT_CMD_SENDS}× at 200ms, as every other unacknowledged
|
|
257
|
+
* control here is.
|
|
266
258
|
*/
|
|
267
259
|
rebootStation(sn: string): Promise<void>;
|
|
268
260
|
/**
|
|
@@ -308,6 +300,11 @@ export declare class P2PCommandRouter {
|
|
|
308
300
|
* stream costs nothing extra and a talkback on an otherwise idle camera opens the session it needs
|
|
309
301
|
* instead of playing into silence.
|
|
310
302
|
*
|
|
303
|
+
* **On a model in {@link ELD_TALKBACK_MODELS}, the speaker plays the codec the camera sends.** So the first
|
|
304
|
+
* audio frame of the media session this talkback holds decides its codec ({@link speakerCodec}), within
|
|
305
|
+
* {@link TALKBACK_CODEC_WAIT_MS} of the session's first frame, and the claim is re-checked after that wait as
|
|
306
|
+
* after the warm-up. Every other model opens `aac-lc` at once.
|
|
307
|
+
*
|
|
311
308
|
* The level-2 key is waited for softly: only the HomeBase-attached path requires it, and
|
|
312
309
|
* {@link Talkback.start} reports that failure precisely, so a hard wait here would reject an
|
|
313
310
|
* own-session camera that legitimately never negotiates one.
|
|
@@ -453,15 +450,6 @@ export declare class P2PCommandRouter {
|
|
|
453
450
|
* A still with no live sibling re-asserts, so a tile refreshing on a quiet station is
|
|
454
451
|
* unaffected.
|
|
455
452
|
*/
|
|
456
|
-
/**
|
|
457
|
-
* Attach a consumer, unless the caller has already abandoned the call.
|
|
458
|
-
*
|
|
459
|
-
* The acquisition it just waited through can outlast the caller's interest, and a consumer attached for
|
|
460
|
-
* somebody who has gone keeps the pull warm for nobody. Detaching immediately gives the pull back, which
|
|
461
|
-
* lets it linger and fall away if this was the only thing holding it, and leaves it untouched if it was
|
|
462
|
-
* not.
|
|
463
|
-
*/
|
|
464
|
-
private attachUnlessAborted;
|
|
465
453
|
/**
|
|
466
454
|
* Dispose one cached live source and forget it, so the next acquisition builds a fresh one. Its claim
|
|
467
455
|
* on a session goes with it, and a media session opened for this source alone is closed: nothing else
|
|
@@ -687,7 +675,7 @@ export declare class P2PCommandRouter {
|
|
|
687
675
|
* **`set-json-raw` intent** — bare JSON, no envelope: outer P2P cmd = `outerCmd` itself, plaintext
|
|
688
676
|
* exactly `{account_id,...data}` (`session.sendRawLevel2` with no wrapper). Reversed from a live
|
|
689
677
|
* capture of the app's own SET_SNOOZE_TIME (1271) frame — see `param-dictionary.ts`'s `1271` entry
|
|
690
|
-
* (`snoozeTime`); the alarm-delay config (1255, `arming.ts`'s `ARMING_CMD.
|
|
678
|
+
* (`snoozeTime`); the alarm-delay config (1255, `arming.ts`'s `ARMING_CMD.SET_ALL_ACTION`) reuses
|
|
691
679
|
* the same bare-JSON shape.
|
|
692
680
|
*/
|
|
693
681
|
private sendJsonRaw;
|
|
@@ -17,9 +17,12 @@ export declare class Fmp4Muxer {
|
|
|
17
17
|
private baseDecodeTime;
|
|
18
18
|
private fragTicks;
|
|
19
19
|
private samples;
|
|
20
|
-
|
|
20
|
+
/** Ticks from the first video sample's start to the newest sample's end, at its provisional duration. */
|
|
21
|
+
private videoTicks;
|
|
21
22
|
private readonly audioRequested;
|
|
22
23
|
private audioCodec?;
|
|
24
|
+
/** Decoder config of an AAC-ELD track, taken from its frames; absent for AAC-LC, whose `esds` config is fixed. */
|
|
25
|
+
private audioConfig?;
|
|
23
26
|
private audioDisabled;
|
|
24
27
|
private audioSamples;
|
|
25
28
|
private audioBaseDecodeTime;
|
|
@@ -35,9 +38,16 @@ export declare class Fmp4Muxer {
|
|
|
35
38
|
* Push one access unit. Returns any completed segments: the `init` on the first keyframe, and/or a
|
|
36
39
|
* media fragment when this frame closed the open one. Returns `undefined` if nothing is emitted yet
|
|
37
40
|
* (e.g. delta frames before the first keyframe).
|
|
41
|
+
*
|
|
42
|
+
* A sample lasts until the next frame's `timestampMs`, measured from the first keyframe's, so the video
|
|
43
|
+
* timeline follows the arrival clock; a frame that shares the previous one's timestamp leaves that
|
|
44
|
+
* sample one tick long, and the next frame's duration absorbs the difference.
|
|
38
45
|
*/
|
|
39
46
|
push(frame: LiveVideoFrame, timestampMs?: number): MediaFragment | undefined;
|
|
40
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Add one station-declared AAC access unit to the audio track: an ADTS frame for AAC-LC, a raw access unit
|
|
49
|
+
* described by its frame's config for AAC-ELD. G.711 remains available via `live()`.
|
|
50
|
+
*/
|
|
41
51
|
pushAudio(frame: LiveAudioFrame, timestampMs?: number): MediaFragment | undefined;
|
|
42
52
|
/** Permanently omit incompatible audio while allowing the video recording to continue. */
|
|
43
53
|
private disableAudio;
|
|
@@ -103,6 +103,9 @@ export declare class LiveStream extends EventEmitter {
|
|
|
103
103
|
private lastCodec;
|
|
104
104
|
/** Rebuilds an access unit the station split across several frames — see {@link AccessUnitAssembler}. */
|
|
105
105
|
private readonly units;
|
|
106
|
+
/** Whether a missing video datagram or incomplete unit invalidated the current reference chain. */
|
|
107
|
+
private awaitingKeyframeAfterGap;
|
|
108
|
+
private readonly videoGapHandler;
|
|
106
109
|
/** The channel inbound media must be tagged with, once {@link acceptsMedia} trusts the station's tag. */
|
|
107
110
|
private mediaChannel?;
|
|
108
111
|
private tracedFirstVideoCommand;
|
|
@@ -435,6 +435,11 @@ export declare class P2PSession extends EventEmitter {
|
|
|
435
435
|
* "direct" commands (e.g. CAMERA_SWITCH 1035) carry a binary struct, not JSON.
|
|
436
436
|
*/
|
|
437
437
|
sendRawLevel2Bytes(payload: Buffer, channel: number, outerCmd?: number, signCode?: number): boolean;
|
|
438
|
+
/**
|
|
439
|
+
* The level-1 twin of {@link sendRawLevel2Bytes}: the plaintext `payload` sealed AES-128-ECB under the
|
|
440
|
+
* level-1 key, signCode 1 — the seal of the level-1 media start. Returns `false` when not connected.
|
|
441
|
+
*/
|
|
442
|
+
sendRawLevel1Bytes(payload: Buffer, channel: number, outerCmd: number): boolean;
|
|
438
443
|
/**
|
|
439
444
|
* Stop the realtime media stream (`CMD_STOP_REALTIME_MEDIA`, 1004) on a camera `channel`, in the shape
|
|
440
445
|
* its runtime topology takes, as {@link startLiveMedia} selects its start:
|
|
@@ -606,6 +611,18 @@ export declare class P2PSession extends EventEmitter {
|
|
|
606
611
|
accountId?: string;
|
|
607
612
|
channel?: number;
|
|
608
613
|
}): void;
|
|
614
|
+
/**
|
|
615
|
+
* Read a date-ranged page of `history_record_info` from the station. The reply's `data` array groups
|
|
616
|
+
* recording rows with companion tables; each group retains its `table_name` and `payload`.
|
|
617
|
+
* `startTime` is `"0"` for the newest page or the oldest row's `yyyyMMddHHmmss` cursor for older rows.
|
|
618
|
+
*/
|
|
619
|
+
queryRecordPage(opts: {
|
|
620
|
+
accountId?: string;
|
|
621
|
+
startDate: string;
|
|
622
|
+
endDate: string;
|
|
623
|
+
startTime?: string;
|
|
624
|
+
count?: number;
|
|
625
|
+
}): Promise<unknown[]>;
|
|
609
626
|
/**
|
|
610
627
|
* Query one on-station table and answer its rows, once the reply is whole.
|
|
611
628
|
*
|
|
@@ -613,13 +630,15 @@ export declare class P2PSession extends EventEmitter {
|
|
|
613
630
|
* several frames whose decrypted text is a fragment of one document, so the fragments are
|
|
614
631
|
* accumulated here and scanned after each one. Answers that document's `data` rows. Rejects when
|
|
615
632
|
* `signal` aborts, and when `timeoutMs` (default {@link DB_TABLE_TIMEOUT_MS}) elapses with no
|
|
616
|
-
* complete reply
|
|
633
|
+
* complete reply, and when the station answers a non-zero `mIntRet`.
|
|
617
634
|
*
|
|
618
635
|
* One read at a time per session: while one is accumulating, every {@link queryDatabase} on the
|
|
619
636
|
* session throws, so a second reply cannot land in this buffer.
|
|
620
637
|
*/
|
|
621
638
|
readDatabase(table: string, opts?: {
|
|
622
639
|
accountId?: string;
|
|
640
|
+
innerCmd?: number;
|
|
641
|
+
query?: Record<string, unknown>;
|
|
623
642
|
timeoutMs?: number;
|
|
624
643
|
signal?: AbortSignal;
|
|
625
644
|
}): Promise<unknown[]>;
|
|
@@ -699,6 +718,7 @@ export declare class P2PSession extends EventEmitter {
|
|
|
699
718
|
* Frames are packed back to back, so a frame header can itself be cut by a datagram boundary. The start
|
|
700
719
|
* of a header left at the end of a datagram is carried into the next one rather than discarded; dropping
|
|
701
720
|
* it would lose that frame and every frame after it until a datagram happened to begin on a header.
|
|
721
|
+
* Payload fragments retain their original slices and are copied once, when the complete frame is delivered.
|
|
702
722
|
*/
|
|
703
723
|
private reassemble;
|
|
704
724
|
/**
|
|
@@ -17,11 +17,15 @@ import type { Consumer } from "./shared-live-source.js";
|
|
|
17
17
|
export interface ReadableEgressOptions {
|
|
18
18
|
/** Emit {@link LiveVideoFrame} objects instead of raw Annex-B bytes (default false = bytes). */
|
|
19
19
|
objectMode?: boolean;
|
|
20
|
+
/** Yield ReceivedVideoFrame envelopes with original source-arrival times; ignored in raw mode. */
|
|
21
|
+
arrivalTime?: boolean;
|
|
20
22
|
/** Readable highWaterMark (bytes, or object count in objectMode). */
|
|
21
23
|
highWaterMark?: number;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* Wrap a shared-source {@link Consumer} in a fresh Readable. The consumer is detached when the
|
|
25
27
|
* Readable is destroyed/ended, so callers own the lifetime by owning the stream.
|
|
28
|
+
* Frames arrive through onMedia; an empty video listener releases the keyframe-prime
|
|
29
|
+
* because ConsumerImpl replays it when the first video listener is added.
|
|
26
30
|
*/
|
|
27
31
|
export declare function openReadableFromConsumer(consumer: Consumer, opts?: ReadableEgressOptions): Readable;
|
|
@@ -46,9 +46,10 @@ export declare function receiveRecording(session: P2PSession, request: {
|
|
|
46
46
|
signal?: AbortSignal;
|
|
47
47
|
}): RecordingTransfer;
|
|
48
48
|
/**
|
|
49
|
-
* Decode a recording's frames, in arrival order, into elementary streams.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* Decode a recording's frames, in arrival order, into elementary streams. Split pictures are assembled
|
|
50
|
+
* before they enter the output; an orphan continuation cannot become its own picture. Keyframes go
|
|
51
|
+
* through one {@link VideoFrameDecoder}, whose media key then opens the audio; a keyframe that does not
|
|
52
|
+
* open under `eccPrivateKeyHex`, and every audio frame before the first media key, are dropped. Rejects with
|
|
52
53
|
* {@link RecordingDownloadError} `undecodable` when no video frame decodes.
|
|
53
54
|
*/
|
|
54
55
|
export declare function decodeRecording(frames: readonly RecordingFrame[], eccPrivateKeyHex: string): RecordingDownload;
|
|
@@ -438,3 +438,12 @@ export declare class SharedLiveSource {
|
|
|
438
438
|
*/
|
|
439
439
|
dispose(): void;
|
|
440
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Attach a consumer, unless the caller has already abandoned the call.
|
|
443
|
+
*
|
|
444
|
+
* The acquisition it just waited through can outlast the caller's interest, and a consumer attached for
|
|
445
|
+
* somebody who has gone keeps the pull warm for nobody. Detaching immediately gives the pull back, which
|
|
446
|
+
* lets it linger and fall away if this was the only thing holding it, and leaves it untouched if it was
|
|
447
|
+
* not.
|
|
448
|
+
*/
|
|
449
|
+
export declare function attachUnlessAborted(source: SharedLiveSource, signal?: AbortSignal): Consumer;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Managed talkback session over P2P — the send-side twin of `live-stream.ts`. Takes
|
|
3
|
-
*
|
|
4
|
-
* bracketed by the device's
|
|
2
|
+
* Managed talkback session over P2P — the send-side twin of `live-stream.ts`. Takes audio from a host in
|
|
3
|
+
* the codec the camera's speaker plays (AAC-LC ADTS, or raw AAC-ELD access units), recovers frame
|
|
4
|
+
* boundaries, and paces the frames onto the wire at their own playback rate, bracketed by the device's
|
|
5
|
+
* start/stop control frames.
|
|
5
6
|
*
|
|
6
7
|
* Pacing is the reason this exists rather than a bare `sendAudioFrame` loop. Each AAC-LC frame at
|
|
7
|
-
* 16 kHz is 1024 samples — exactly 64 ms of audio — and
|
|
8
|
+
* 16 kHz is 1024 samples — exactly 64 ms of audio — and each AAC-ELD access unit is 512 samples, 32 ms.
|
|
9
|
+
* The device plays what arrives when it arrives.
|
|
8
10
|
* A host piping a file would otherwise deliver a minute of audio in a few hundred milliseconds and
|
|
9
11
|
* lose all but the tail. A live source paces itself and simply keeps the queue near-empty.
|
|
10
12
|
*
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
*/
|
|
18
20
|
import { EventEmitter } from "node:events";
|
|
19
21
|
import { Writable } from "node:stream";
|
|
20
|
-
import type { AacEncoder, StreamBudgetNotice, TalkbackHandle } from "../../core/contracts.js";
|
|
22
|
+
import type { AacEncoder, StreamBudgetNotice, TalkbackCodec, TalkbackHandle } from "../../core/contracts.js";
|
|
21
23
|
import { type Logger } from "../../core/logger.js";
|
|
22
24
|
/**
|
|
23
25
|
* The subset of the session this needs — start/stop the path, push one frame, and report a frame the
|
|
@@ -66,7 +68,7 @@ export interface TalkbackOptions {
|
|
|
66
68
|
export declare const DEFAULT_IDLE_TIMEOUT_MS = 30000;
|
|
67
69
|
/**
|
|
68
70
|
* A talkback session bound to one camera channel. Opens the device's path on construction via
|
|
69
|
-
* {@link start}, then drains queued frames on a
|
|
71
|
+
* {@link start}, then drains queued frames on a tick of one frame's duration until {@link stop}.
|
|
70
72
|
*/
|
|
71
73
|
export declare class Talkback extends EventEmitter implements TalkbackHandle {
|
|
72
74
|
private readonly sink;
|
|
@@ -74,7 +76,8 @@ export declare class Talkback extends EventEmitter implements TalkbackHandle {
|
|
|
74
76
|
private readonly reader;
|
|
75
77
|
private readonly queue;
|
|
76
78
|
private readonly logger;
|
|
77
|
-
|
|
79
|
+
/** Playback duration of one queued frame, the pacer's step: 64 ms for `aac-lc`, 32 ms for `aac-eld`. */
|
|
80
|
+
private frameMs;
|
|
78
81
|
private readonly idleTimeoutMs;
|
|
79
82
|
private timer?;
|
|
80
83
|
private started;
|
|
@@ -94,17 +97,24 @@ export declare class Talkback extends EventEmitter implements TalkbackHandle {
|
|
|
94
97
|
private chain;
|
|
95
98
|
private readonly onAudioGap;
|
|
96
99
|
constructor(sink: TalkbackSink, opts: TalkbackOptions);
|
|
100
|
+
/** Frames the pacing queue holds before a writable applies backpressure: {@link HIGH_WATER_MS} of this codec. */
|
|
101
|
+
private get highWater();
|
|
102
|
+
/** The codec this talkback sends, set by {@link start}. */
|
|
103
|
+
codec: TalkbackCodec;
|
|
97
104
|
/** Frames still queued for the wire. */
|
|
98
105
|
get pending(): number;
|
|
99
106
|
/**
|
|
100
107
|
* Open the device's talkback path and begin the pacing tick. Throws when the topology's control
|
|
101
108
|
* frame could not be sent — for a HomeBase-attached camera that means the level-2 key was never
|
|
102
109
|
* negotiated, which would otherwise leave a silent session that accepts audio nobody hears.
|
|
110
|
+
*
|
|
111
|
+
* `codec` is the codec the camera's speaker plays; it fixes the input this talkback accepts and the pacing step.
|
|
103
112
|
*/
|
|
104
|
-
start(): this;
|
|
113
|
+
start(codec: TalkbackCodec): this;
|
|
105
114
|
/**
|
|
106
|
-
* Queue audio.
|
|
107
|
-
*
|
|
115
|
+
* Queue audio in the codec this talkback sends. For `aac-lc` the bytes are ADTS, or PCM when an encoder was
|
|
116
|
+
* supplied, read as a stream, so a chunk carrying part of a frame is held until the rest arrives. For `aac-eld`
|
|
117
|
+
* each call is one whole access unit; a unit over the device's length limit is dropped with an `error`.
|
|
108
118
|
*
|
|
109
119
|
* Audio written after {@link end} is dropped with an `error` rather than queued: `finished` has
|
|
110
120
|
* either already fired or is owed on the frames written before it, so a late chunk would either play
|
|
@@ -160,6 +170,7 @@ export declare class Talkback extends EventEmitter implements TalkbackHandle {
|
|
|
160
170
|
/**
|
|
161
171
|
* A Writable over {@link write} that withholds its callback while the queue is at the high-water
|
|
162
172
|
* mark — which is what turns `pipe()` from a file into playback at speed rather than a memory spike.
|
|
173
|
+
* For `aac-eld` it is object-mode: each write is one access unit.
|
|
163
174
|
*/
|
|
164
175
|
writable(): Writable;
|
|
165
176
|
/** Drain an encoder's partial trailing block, so the last syllable is not lost. */
|
|
@@ -71,6 +71,8 @@ export declare class VideoFrameDecoder {
|
|
|
71
71
|
}
|
|
72
72
|
/** One whole access unit recovered from the frame (or frames) the station sent it in. */
|
|
73
73
|
export interface AssembledAccessUnit {
|
|
74
|
+
/** Unsigned 32-bit header timestamp from the first frame of this access unit. */
|
|
75
|
+
sourceTimestamp: number;
|
|
74
76
|
/** True when the unit's own header flagged it a keyframe — a point a consumer may begin decoding at. */
|
|
75
77
|
keyframe: boolean;
|
|
76
78
|
/** Frame width the unit's header declared. */
|
|
@@ -144,9 +146,10 @@ export declare class AccessUnitAssembler {
|
|
|
144
146
|
* `decode` extracts the payload of a frame, whatever the stream's encryption: it is called for EVERY
|
|
145
147
|
* frame, including a continuation, because a continuation carries its own wrapped key ahead of its share
|
|
146
148
|
* of the payload (measured: 129 bytes beyond what its header declares, exactly as a unit's first frame
|
|
147
|
-
* carries).
|
|
149
|
+
* carries). A continuation without its matching open unit has no Annex-B start code and cannot be emitted
|
|
150
|
+
* as a complete unit, even if its repeated header says keyframe.
|
|
148
151
|
*/
|
|
149
152
|
push(payload: Buffer, decode: (payload: Buffer) => Buffer | undefined): AssembledAccessUnit[];
|
|
150
153
|
/** Forget an incomplete unit, counting and reporting it. */
|
|
151
|
-
|
|
154
|
+
discard(): void;
|
|
152
155
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTC command router: drives a station over the portal's WebRTC data channel instead of P2P.
|
|
3
|
+
*
|
|
4
|
+
* The T9000 answers no P2P lookup, but it accepts the portal's control channel: sign → WS join → scall →
|
|
5
|
+
* SDP answer → ICE over the relay the hub grants → DTLS → SCTP data channels, with commands riding
|
|
6
|
+
* `WebrtcDataChannel` as portal packets (see {@link buildPortalPacket}).
|
|
7
|
+
*
|
|
8
|
+
* `set-payload` rides the `1350` SET_PAYLOAD envelope, inner `{account_id, cmd, mValue3, payload}`;
|
|
9
|
+
* `set-json` rides the `1700` CONTROL_PAYLOAD envelope, inner `{account_id, cmd, commandType, data}`.
|
|
10
|
+
* A command to the station goes on the station channel `255`; an attached device's command shares the
|
|
11
|
+
* station's session and keeps the device's channel. Other command kinds are refused.
|
|
12
|
+
*
|
|
13
|
+
* One session per station, reused across commands and closed after {@link IDLE_CLOSE_MS} without one;
|
|
14
|
+
* a live pull holds it open. Sends on a session are serialised; an ACK is correlated by its outer command
|
|
15
|
+
* and segment, since it carries no inner command. An ACK is not an observation of the resulting state.
|
|
16
|
+
*
|
|
17
|
+
* Live view of an attached camera rides the same session (the hub accepts one RTC session per account),
|
|
18
|
+
* through one shared live source per station: the station serves one camera at a time, so a second
|
|
19
|
+
* camera is refused until the first one's view ends.
|
|
20
|
+
*/
|
|
21
|
+
import { type Command, type MediaProvider } from "../../core/contracts.js";
|
|
22
|
+
import type { Logger } from "../../core/logger.js";
|
|
23
|
+
import { RtcSession, type RtcSessionOptions } from "./session.js";
|
|
24
|
+
export interface RtcIdentity {
|
|
25
|
+
authToken: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
/** The `gtoken` header value the mega session's authed HTTP calls carry. */
|
|
28
|
+
gtoken: string;
|
|
29
|
+
}
|
|
30
|
+
/** Where a command goes: the station's session, and whether it addresses the station or a device on it. */
|
|
31
|
+
export interface RtcRoute {
|
|
32
|
+
/** The station's signaling exchange; defaults to compact SDP. */
|
|
33
|
+
signalingMode?: "call" | "scall";
|
|
34
|
+
/** Candidate policy used by the station's exchange; defaults to relay-only. */
|
|
35
|
+
iceTransportPolicy?: "relay" | "all";
|
|
36
|
+
stationSn: string;
|
|
37
|
+
/**
|
|
38
|
+
* The station's `member.admin_user_id`, the account the session and the payload name; the logged-in
|
|
39
|
+
* user when the station's record carries none.
|
|
40
|
+
*/
|
|
41
|
+
adminUserId?: string;
|
|
42
|
+
/** True for a device attached to the station, which keeps the command's own channel. */
|
|
43
|
+
attached: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The camera a live view is for, on its station's route. */
|
|
46
|
+
export interface RtcLiveRoute extends RtcRoute {
|
|
47
|
+
cameraSn: string;
|
|
48
|
+
/** The camera's `device_channel` on the station. */
|
|
49
|
+
channel: number;
|
|
50
|
+
}
|
|
51
|
+
export interface RtcCommandRouterDeps {
|
|
52
|
+
/** The logged-in session's credentials; `undefined` while logged out. */
|
|
53
|
+
identity: () => RtcIdentity | undefined;
|
|
54
|
+
/** The mega shard the account signs on (`"ie-pr"`, `"eu-pr"`, `"us-pr"`); picks the smart host. */
|
|
55
|
+
shard: () => string;
|
|
56
|
+
/** ISO country sent on the sign request (default `US`). */
|
|
57
|
+
country?: string;
|
|
58
|
+
logger?: Logger;
|
|
59
|
+
onError?: (e: Error) => void;
|
|
60
|
+
/** ffmpeg for the live still (`ffmpeg` on PATH by default). */
|
|
61
|
+
ffmpegPath?: string;
|
|
62
|
+
createSession?: (opts: RtcSessionOptions) => RtcSession;
|
|
63
|
+
}
|
|
64
|
+
export declare class RtcCommandRouter {
|
|
65
|
+
private readonly deps;
|
|
66
|
+
private readonly sessions;
|
|
67
|
+
private readonly lives;
|
|
68
|
+
private readonly warm;
|
|
69
|
+
constructor(deps: RtcCommandRouterDeps);
|
|
70
|
+
dispatchCommand(route: RtcRoute, cmd: Command): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* The media of a camera on a T9000: live video, a readable feed and a live still over the station's
|
|
73
|
+
* session. Recording has no wire here and is refused; the optional members it has no wire for are absent.
|
|
74
|
+
*/
|
|
75
|
+
mediaProviderFor(route: RtcLiveRoute): MediaProvider;
|
|
76
|
+
/**
|
|
77
|
+
* Tear down every station session (logout / shutdown). Each live source is disposed before its session
|
|
78
|
+
* closes, so its consumers end cleanly and its stop still rides the session.
|
|
79
|
+
*/
|
|
80
|
+
close(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Hand `use` the station's live source for this camera, in the same turn the source is created or reused,
|
|
83
|
+
* so a consumer it attaches is counted before any other caller looks.
|
|
84
|
+
*
|
|
85
|
+
* The station streams one camera at a time. Another camera whose source has consumers refuses this one,
|
|
86
|
+
* before the session is awaited and again after; one with none (never attached or lingering)
|
|
87
|
+
* holds nothing a viewer needs, so it is disposed and the slot taken. The first caller's
|
|
88
|
+
* {@link SharedSourceHints} configure a new source.
|
|
89
|
+
*/
|
|
90
|
+
private withLiveSource;
|
|
91
|
+
/** Refuse a camera while another camera on its station has consumers. */
|
|
92
|
+
private refuseIfWatched;
|
|
93
|
+
private newLiveSource;
|
|
94
|
+
/**
|
|
95
|
+
* Completes on the command ACK or a correlated control-result notification. The notification must
|
|
96
|
+
* match the request channel, parameter and segment and contain a structured payload. Receipt does
|
|
97
|
+
* not establish physical actuation. The command is sent once, bounded by {@link ACK_TIMEOUT_MS}.
|
|
98
|
+
*/
|
|
99
|
+
private sendAwaitAck;
|
|
100
|
+
private stationSession;
|
|
101
|
+
/**
|
|
102
|
+
* One bounded bring-up: the session is up when its command channel opens, and it fails, with the
|
|
103
|
+
* session closed and every listener gone, when `connect()` throws, the session closes first, or
|
|
104
|
+
* {@link CONNECT_TIMEOUT_MS} passes. A single promise, so an early failure cannot leave a second one
|
|
105
|
+
* rejecting unheard.
|
|
106
|
+
*/
|
|
107
|
+
private bringUp;
|
|
108
|
+
/**
|
|
109
|
+
* Hold a station's command session open: one hold per station, its idle-close ({@link IDLE_CLOSE_MS})
|
|
110
|
+
* suppressed, {@link PORTAL_KEEPALIVE} sent every {@link PORTAL_KEEPALIVE_MS}, and the session dropped
|
|
111
|
+
* on a tick that finds the previous keepalive unechoed. A closed session ends the hold.
|
|
112
|
+
*
|
|
113
|
+
* Answers an idempotent disposer that releases the hold. Throws when no account is logged in.
|
|
114
|
+
*/
|
|
115
|
+
keepWarm(route: RtcRoute): () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Bring a station session up for a {@link keepWarm} hold and start its keepalive. A bring-up that fails,
|
|
118
|
+
* or a hold disposed before it completes, clears the hold and arms no timer. Each tick first checks
|
|
119
|
+
* that the hub echoed the previous keepalive, and drops the session when it did not. The keepalive
|
|
120
|
+
* stops when the session closes, which also clears the hold so the next command or `keepWarm`
|
|
121
|
+
* rebuilds cleanly.
|
|
122
|
+
*/
|
|
123
|
+
private warmUp;
|
|
124
|
+
/** Stop a hold's keepalive and remove its listeners from the session it watches. */
|
|
125
|
+
private unwatch;
|
|
126
|
+
private touch;
|
|
127
|
+
private drop;
|
|
128
|
+
}
|