@mega-yfue/eufy-sdk 0.5.0-beta.7 → 0.5.0-beta.71

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.
Files changed (60) hide show
  1. package/dist/client/device-registry.d.ts +24 -2
  2. package/dist/client/eufy-mega.d.ts +83 -10
  3. package/dist/client/types.d.ts +1 -1
  4. package/dist/core/contracts.d.ts +71 -30
  5. package/dist/core/store.d.ts +4 -0
  6. package/dist/core/types.d.ts +2 -0
  7. package/dist/index.js +3611 -603
  8. package/dist/index.js.map +4 -4
  9. package/dist/model/capabilities/arming.d.ts +39 -4
  10. package/dist/model/capabilities/camera.d.ts +24 -10
  11. package/dist/model/capabilities/dp-catalog.d.ts +4 -3
  12. package/dist/model/capabilities/index.d.ts +16 -3
  13. package/dist/model/capabilities/light.d.ts +3 -0
  14. package/dist/model/capabilities/lock.d.ts +21 -7
  15. package/dist/model/capabilities/motion.d.ts +5 -0
  16. package/dist/model/capabilities/mower.d.ts +230 -0
  17. package/dist/model/capabilities/rtsp.d.ts +16 -3
  18. package/dist/model/capabilities/smart-drop.d.ts +20 -0
  19. package/dist/model/capabilities/suction.d.ts +5 -5
  20. package/dist/model/capabilities/types.d.ts +10 -0
  21. package/dist/model/capabilities/vacuum-clean.d.ts +19 -13
  22. package/dist/model/classify.d.ts +1 -2
  23. package/dist/model/device-family.d.ts +8 -0
  24. package/dist/model/param-dictionary.d.ts +6 -0
  25. package/dist/model/param-namespace.d.ts +1 -1
  26. package/dist/model/registry.d.ts +3 -2
  27. package/dist/model/types.d.ts +26 -12
  28. package/dist/transport/abortable.d.ts +8 -0
  29. package/dist/transport/http/mega-client.d.ts +34 -2
  30. package/dist/transport/mqtt/broker-discovery.d.ts +2 -1
  31. package/dist/transport/mqtt/command-router.d.ts +3 -2
  32. package/dist/transport/p2p/codec.d.ts +16 -0
  33. package/dist/transport/p2p/command-router.d.ts +27 -35
  34. package/dist/transport/p2p/fmp4.d.ts +12 -2
  35. package/dist/transport/p2p/live-trace.d.ts +11 -2
  36. package/dist/transport/p2p/p2p-session.d.ts +59 -12
  37. package/dist/transport/p2p/readable-egress.d.ts +4 -0
  38. package/dist/transport/p2p/recording-download.d.ts +4 -3
  39. package/dist/transport/p2p/shared-live-source.d.ts +27 -13
  40. package/dist/transport/p2p/talkback.d.ts +21 -10
  41. package/dist/transport/p2p/video.d.ts +2 -0
  42. package/dist/transport/rtc/call-live.d.ts +41 -0
  43. package/dist/transport/rtc/command-router.d.ts +141 -0
  44. package/dist/transport/rtc/live.d.ts +69 -0
  45. package/dist/transport/rtc/peer.d.ts +122 -0
  46. package/dist/transport/rtc/portal-keepalive.d.ts +11 -0
  47. package/dist/transport/rtc/portal-packet.d.ts +72 -0
  48. package/dist/transport/rtc/ptcs-framer.d.ts +86 -0
  49. package/dist/transport/rtc/scall-sdp.d.ts +50 -0
  50. package/dist/transport/rtc/session.d.ts +95 -0
  51. package/dist/transport/rtc/signaling.d.ts +139 -0
  52. package/dist/transport/token-echo.d.ts +8 -0
  53. package/dist/transport/tuya/account.d.ts +8 -5
  54. package/dist/transport/tuya/client.d.ts +19 -5
  55. package/dist/transport/tuya/command-router.d.ts +10 -14
  56. package/dist/transport/tuya/dp-codec.d.ts +2 -23
  57. package/dist/transport/tuya/et3.d.ts +22 -0
  58. package/dist/transport/tuya/request.d.ts +18 -8
  59. package/dist/transport/tuya/sign.d.ts +7 -0
  60. package/package.json +2 -1
@@ -60,6 +60,8 @@ export type ProductLine = "security" | "life" | "clean" | "print" | "display" |
60
60
  * `p2p-frame` shape of {@link InboundSignal}.
61
61
  */
62
62
  export interface CapabilityFrame {
63
+ /** Cloud device type resolved from the reporting station/channel. */
64
+ deviceType?: number;
63
65
  stationSn: string;
64
66
  commandId: number;
65
67
  channel: number;
@@ -247,8 +249,16 @@ export interface AvailabilityContext {
247
249
  homeBaseAttached?: boolean;
248
250
  /** Serial of the covering station, when the device record names one. */
249
251
  stationSerial?: string;
252
+ /**
253
+ * The vendor DeviceType of the station the device hangs off, when it is attached
254
+ * and the station's record states one. Answers which station family a command addressed to an
255
+ * attached device is carried through.
256
+ */
257
+ stationDeviceType?: number;
250
258
  }
251
259
  export interface CommandContext extends AvailabilityContext {
260
+ /** Vendor-formatted timezone reported by the station; never inferred from the host. */
261
+ timezone?: string;
252
262
  /** Device channel (0 for standalone, `device_channel` on a HomeBase). */
253
263
  channel: number;
254
264
  /** eufy DeviceType, when known. */
@@ -169,12 +169,11 @@ export declare const TUYA_CLEAN_TYPES: readonly ["Sweep", "SweepMop", "Mop"];
169
169
  /** @internal */
170
170
  export type TuyaCleanType = (typeof TUYA_CLEAN_TYPES)[number];
171
171
  /**
172
- * `ModeCtrlRequest.method` values for DP 152. Live-verified on T2351: START_AUTO_CLEAN → 0
173
- * (omitted from the wire when zero), START_GOHOME → 6, PAUSE_TASK → 13.
172
+ * `ModeCtrlRequest.method` values for DP 152. Live-verified on T2351: START_GOHOME → 6,
173
+ * PAUSE_TASK → 13. An auto clean is `START_AUTO_CLEAN` (0) with a payload — see
174
+ * {@link ModeCtrlParamMethod}.
174
175
  */
175
176
  export declare const ModeCtrlMethod: {
176
- /** Live-verified on a T2351. Zero, so it is omitted from the wire per the proto3 default rule. */
177
- readonly START_AUTO_CLEAN: 0;
178
177
  /** Live-verified on a T2351. */
179
178
  readonly START_GOHOME: 6;
180
179
  /** Live-verified on a T2351. */
@@ -202,9 +201,11 @@ export declare const ModeCtrlMethod: {
202
201
  * its argument in the signature: {@link VACUUM_CLEAN_MEMBERS.startScene},
203
202
  * {@link VACUUM_CLEAN_MEMBERS.cleanRooms} and {@link VACUUM_CLEAN_MEMBERS.cleanZones}.
204
203
  *
205
- * The outer frame these ride in is byte-verified on a live T2351, and `SCENE`, `SELECT_ROOMS` and
206
- * `SELECT_ZONES` have each since been RUN on a T2351 and did what they name — so their numbers rest on
207
- * observed behaviour rather than on the vendor's definition alone.
204
+ * The outer frame these ride in is byte-verified on a live T2351, and `AUTO`, `SCENE`, `SELECT_ROOMS`
205
+ * and `SELECT_ZONES` have each since been RUN on a T2351 and did what they name — so their numbers rest
206
+ * on observed behaviour rather than on the vendor's definition alone. `AUTO` is RUN with its payload:
207
+ * the vendor's definition calls `AutoClean.clean_times` valid only when non-zero, and the T2351 did
208
+ * not start on method 0 sent without it.
208
209
  *
209
210
  * That distinction is the whole point of checking, and this is the one place it is argued: an AIoT
210
211
  * data-point write is fire-and-forget, so a wrong number would be a different command arriving and
@@ -215,6 +216,11 @@ export declare const ModeCtrlMethod: {
215
216
  * scene id and a map id both arrive on DP 180.
216
217
  */
217
218
  export declare const ModeCtrlParamMethod: {
219
+ /** `START_AUTO_CLEAN` with an `AutoClean` payload — clean the whole floor. */
220
+ readonly AUTO: {
221
+ readonly method: 0;
222
+ readonly param: 3;
223
+ };
218
224
  /** `START_SELECT_ROOMS_CLEAN` — clean the named rooms of a named map. */
219
225
  readonly SELECT_ROOMS: {
220
226
  readonly method: 1;
@@ -274,17 +280,17 @@ export declare function encodeSelectZonesClean(mapId: number, zones: readonly Va
274
280
  * @internal
275
281
  */
276
282
  export declare function encodeSceneClean(sceneId: number): string;
283
+ /**
284
+ * Build a whole-floor auto clean carrying an `AutoClean` payload of one pass.
285
+ * @internal
286
+ */
287
+ export declare function encodeAutoClean(): string;
277
288
  /**
278
289
  * Encode a `ModeCtrlRequest` protobuf (DP 152) as a DP value: `varint(bodyLen) ++ {method:1, seq:2}`.
279
290
  *
280
291
  * Built on {@link RawDpWriter} rather than hand-rolled bytes. The frame is unchanged and the existing
281
292
  * byte-level test is what proves it — that test was written against a live T2351 capture, so it holds
282
293
  * the writer to the wire rather than to this function's own idea of the wire.
283
- *
284
- * Method 0 (START_AUTO_CLEAN) is omitted rather than written as an explicit zero, per the proto3
285
- * default-field rule and confirmed on that same capture. The writer deliberately does not apply that
286
- * rule itself: whether an explicit zero and an absent field mean the same thing is the
287
- * message's business, not the encoder's.
288
294
  * @internal
289
295
  */
290
296
  export declare function encodeModeCtrl(method: number, seq: number): string;
@@ -1955,7 +1961,7 @@ export declare const VACUUM_CLEAN_MEMBERS: {
1955
1961
  readonly provenance: "mega";
1956
1962
  readonly description: "Stop smart-follow mode (ModeCtrlRequest method 18 over DP 152). Method number not captured — unverified.";
1957
1963
  };
1958
- /** Start an auto-clean run via ModeCtrlRequest method 0 (DP 152). AIoT only — Tuya write unverified. */
1964
+ /** Start an auto-clean run via ModeCtrlRequest method 0 with one `AutoClean` pass (DP 152). AIoT only — Tuya write unverified. */
1959
1965
  readonly startCleaning: import("./members.js").MethodMember<() => Promise<void>> & {
1960
1966
  available: (ctx: import("./types.js").CommandContext) => boolean;
1961
1967
  };
@@ -56,8 +56,7 @@ export declare function codecForType(deviceType: number): Codec | undefined;
56
56
  * - `T74xx` → **lock** (SmartSafe 7400-series).
57
57
  * - `T80xx` / `T8001` / `T8002` / `T8010` / `T8030` / `T8023` / `T8025` → **station**
58
58
  * (HomeBase / HomeBase 2 / 3 / Mini), `T9000` (the app's own model registry files it as family
59
- * `STATION_9000`, the one station family outside the T8 band), and `T8N00` (NVR) / `T8E00` (PoE
60
- * NVR) → station.
59
+ * `STATION_9000`, the one station family outside the T8 band), and `T8N00` (NVR) → station.
61
60
  * - `T89xx` (entry/motion/water/siren sensors, e.g. T8900/T8910/T8920) → **sensor**.
62
61
  * - `T87xx` keypad (`T8960`) → **keypad**.
63
62
  * - any other `T8…` security T-code → **camera** (the default security family).
@@ -43,6 +43,12 @@ export declare const isOutdoorPanTilt: (ctx: FamilyContext) => boolean;
43
43
  export declare const isFloodLight: (ctx: FamilyContext) => boolean;
44
44
  /** HomeBase-family hub (has a speaker/alarm) — a station EXCLUDING the NVRs, per `HOMEBASE_TYPES`. */
45
45
  export declare const isHomeBase: (ctx: FamilyContext) => boolean;
46
+ /** HomeBase S1 Pro (`STATION_9000`), the station the app drives over its WebRTC control channel. */
47
+ export declare const isStation9000: (ctx: FamilyContext) => boolean;
48
+ /** T8N00 NVR, identified by its reported model. */
49
+ export declare const isNvrT8N00: (ctx: FamilyContext) => boolean;
50
+ /** Video Doorbell C31 (T8223), identified by its reported model. */
51
+ export declare const isDoorbellT8223: (ctx: FamilyContext) => boolean;
46
52
  /** Wired doorbell (DeviceType.DOORBELL). */
47
53
  export declare const isWiredDoorbell: (ctx: FamilyContext) => boolean;
48
54
  /**
@@ -72,3 +78,5 @@ export declare const isAiotVacuum: (ctx: FamilyContext) => boolean;
72
78
  * vacuums receive the same write actions as AIoT ones, routed by the facade's `routeCommand`.
73
79
  */
74
80
  export declare const isTuyaVacuum: (ctx: FamilyContext) => boolean;
81
+ /** Video Smart Lock S330, identified by its distinct cloud device type. */
82
+ export declare const isVideoSmartLock: (ctx: FamilyContext) => boolean;
@@ -9,6 +9,7 @@
9
9
  * `types.ts`.
10
10
  * - CLEAN_PARAMS — RoboVac Tuya DP space (ids 1 and above), names from the cloud
11
11
  * `get_product_data_point` data_point_list, hence provenance `mega`.
12
+ * - MOWER_PARAMS — robot-mower Tuya DP space, names from the mower's Tuya product schema.
12
13
  *
13
14
  * Which models reported an id, and the capture that named it, are in the commit that adds the entry.
14
15
  */
@@ -50,3 +51,8 @@ export declare const CLEAN_PARAMS: Record<number, ParamDef>;
50
51
  * display settings screen, one control at a time, params diffed after each.
51
52
  */
52
53
  export declare const DISPLAY_PARAMS: Record<number, ParamDef>;
54
+ /**
55
+ * Robot-mower Tuya DP space — names from the E15's Tuya product schema (`schemaInfo.schema`), each id
56
+ * also reported by a live E15. Only the scalar data points the `mower` capability reads are listed.
57
+ */
58
+ export declare const MOWER_PARAMS: Record<number, ParamDef>;
@@ -14,7 +14,7 @@
14
14
  import type { Codec } from "./types.js";
15
15
  import { type ParamDef } from "./param-dictionary.js";
16
16
  /** The param id spaces this SDK models. */
17
- export type ParamNamespace = "security" | "clean" | "life" | "print" | "display";
17
+ export type ParamNamespace = "security" | "clean" | "mower" | "life" | "print" | "display";
18
18
  /** Look up a param def in the given namespace. */
19
19
  export declare function paramDef(ns: ParamNamespace, paramType: number): ParamDef | undefined;
20
20
  /** The param namespace a device's ids live in, from its codec, via the module-local `NAMESPACE_BY_CODEC` table. */
@@ -48,7 +48,8 @@ export declare function resolveDevice(rec: CloudRecord): ResolvedDevice;
48
48
  * The property manifest for a device, from its capabilities and the record's facts. The single place
49
49
  * an {@link AvailabilityContext} is built — so a family-gate (`available`) and a per-model enum
50
50
  * (`enumValuesFor`) are decided from the same truthful, session-free view on every path (initial
51
- * resolve and {@link Device.reresolve}). Populated only from what a record carries, never transport
52
- * fields a live session hasn't produced.
51
+ * resolve and {@link Device.reresolve}). Populated only from what a record carries: its cloud params and
52
+ * the params its device reported over realtime, the same evidence a bound getter is gated on. Never
53
+ * transport fields a live session hasn't produced.
53
54
  */
54
55
  export declare function resolveProperties(rec: CloudRecord, codec: Codec, capabilities: Capability[]): PropertySpec[];
@@ -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_drop" | "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` / `megabytes` / `degrees` — a measured quantity
56
- * in the unit the device reports it in; each pairs with the matching `unit`. Values are
57
- * never converted on the way out — a converted reading is an invented one. `seconds` and `hours`
58
- * are separate kinds for exactly that reason: a robot reports a run in seconds and a consumable's
59
- * wear in hours, and normalising one into the other would publish a number the device never sent.
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`), or a
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
  /**
@@ -170,10 +177,11 @@ export interface PropertySpec {
170
177
  * consumable counters are `"number"` over one base64 string, and reading the value as a number is
171
178
  * exactly what must NOT happen at ingest.
172
179
  *
173
- * Storage already keeps such a value intact — a non-numeric string cannot be coerced to a number, so
174
- * it is passed through. What this flag changes is that the pass-through stops being reported as a
175
- * mistake: a robot reporting ten Raw DPs on every push logged ten warnings a time saying its
176
- * properties were misdeclared, which is how a real warning goes unread.
180
+ * Storage keeps such a value intact: a non-numeric string passes a `number` coercion unchanged, and
181
+ * on a `bool` property a string that is not a boolean wire form (`"0"`, `"1"`, `"true"`, `"false"`)
182
+ * is kept rather than read as `false`. The pass-through is not reported as a mistake: a robot
183
+ * reporting ten Raw DPs on every push logged ten warnings a time saying its properties were
184
+ * misdeclared, which is how a real warning goes unread.
177
185
  */
178
186
  raw?: true;
179
187
  /**
@@ -286,6 +294,12 @@ export interface CloudRecord {
286
294
  parentSn?: string;
287
295
  /** Reported param_type → raw value. Presence of a param is a capability signal. */
288
296
  params?: Record<number, string>;
297
+ /**
298
+ * Params the device reported over its realtime channel, keyed in the device's own namespace. A line
299
+ * whose state exists only on that channel carries none of it in {@link params}, so this is the
300
+ * evidence an availability gate on such a param is decided from.
301
+ */
302
+ dpParams?: Record<number, string>;
289
303
  }
290
304
  /** The fully-resolved device shape produced by the 3-tier resolver. */
291
305
  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. No credentials means nothing to try,
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, not the 0x80 deny code). */
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 — such a device reports as offline
140
- * (the throw below) until the backend grants the account's own credentials.
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
@@ -111,6 +111,22 @@ export declare function eciesUnwrap(envelope: Buffer, eccPrivateKeyHex: string,
111
111
  * Returns the 32-byte key, or undefined if the inputs don't parse.
112
112
  */
113
113
  export declare function deriveLevel2KeyFromGatewayInfo(gwPayload: Buffer, eccPrivateKeyHex: string): Buffer | undefined;
114
+ /**
115
+ * Derive the P2P **level-2** AES-128-ECB session key from a *decrypted* `CMD_GATEWAYINFO` (1100)
116
+ * payload and the RSA private key of a cipher whose `get_ciphers` answer carries no ECC key.
117
+ *
118
+ * Layout of the decrypted payload: `cipher_id(2 LE) ‖ 0000 ‖ RSA ciphertext ‖ …`, the ciphertext as
119
+ * long as the key's modulus. It is the 16-byte session key under PKCS#1 v1.5 encryption padding
120
+ * (`00 02 ‖ ≥8 non-zero bytes ‖ 00 ‖ key`). The padding is checked here over the raw RSA result so a wrong
121
+ * key fails closed: under OpenSSL's implicit rejection, `RSA_PKCS1_PADDING` answers random bytes for it
122
+ * instead of throwing, and those can be 16 bytes long.
123
+ *
124
+ * `rsaPrivateKey` is the `get_ciphers` `private_key`: base64 PKCS#8 DER.
125
+ *
126
+ * Returns the 16-byte key, or `undefined` when the key does not parse, the payload is short, or the
127
+ * padding or key length is wrong (fails closed, never throws).
128
+ */
129
+ export declare function deriveLevel2KeyFromRsaGatewayInfo(gwPayload: Buffer, rsaPrivateKey: string): Buffer | undefined;
114
130
  /** Local-lookup payload: two zero bytes. */
115
131
  export declare function buildLocalLookupPayload(): Buffer;
116
132
  /** Cloud-lookup payload (variant 2): `[p2pDid:20][dskKey][0x00000000]`. */
@@ -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";
@@ -119,7 +108,7 @@ export declare class P2PCommandRouter {
119
108
  * shared — see {@link P2PCommandRouter.openTalkback}.
120
109
  */
121
110
  private readonly talkbacks;
122
- /** cipher_id → ECC private key (one eufylife get_ciphers call per cipher), shared across (re)opens. */
111
+ /** cipher_id → its ECC and RSA private keys (one eufylife get_ciphers call per cipher), shared across (re)opens. */
123
112
  private readonly cipherKeyCache;
124
113
  /** The recording download in flight per station; a station serves one at a time. */
125
114
  private readonly recordingDownloads;
@@ -224,7 +213,7 @@ export declare class P2PCommandRouter {
224
213
  * Build + wire a {@link P2PSession} for a station (NOT yet connected — the caller awaits `connect()`).
225
214
  *
226
215
  * `resolveCipherKey` auto-negotiates the level-2 session key from `CMD_GATEWAYINFO` by resolving the
227
- * cipher's ECC private key via cloud `get_ciphers`, so signCode 2/8 frames decrypt live; results are
216
+ * cipher's private keys via cloud `get_ciphers`, so signCode 2/8 frames decrypt live; results are
228
217
  * cached on the router instance so a lazy re-open (after idle-detach) reuses the lookup. Only a
229
218
  * SUCCESSFUL lookup is cached — caching `undefined` after a transient failure would permanently
230
219
  * disable level-2 for the session's life.
@@ -240,11 +229,12 @@ export declare class P2PCommandRouter {
240
229
  */
241
230
  private makeSession;
242
231
  /**
243
- * The ECC private key of `cipherId`, from the router's cache or one `get_ciphers` call. When the cloud
244
- * answers with another cipher, that one is used and traced on `session`. Only a successful lookup is
245
- * cached.
232
+ * The ECC and RSA private keys of `cipherId` (`ecc_private_key`, `private_key`), from the router's cache
233
+ * or one `get_ciphers` call. When the cloud answers with another cipher that carries an ECC key and the
234
+ * asked one does not, that one is used and traced on `session`. Only a successful lookup — one that
235
+ * yields either key — is cached.
246
236
  */
247
- private cipherKeyFor;
237
+ private cipherKeysFor;
248
238
  /**
249
239
  * Open (or reuse) a station's P2P session and await its completed handshake. An optional abort only
250
240
  * stops this wait; session ownership remains with {@link SessionManager} and its normal teardown.
@@ -258,11 +248,14 @@ export declare class P2PCommandRouter {
258
248
  */
259
249
  dispatchCommand(sn: string, cmd: Command): Promise<void>;
260
250
  /**
261
- * Restart a HomeBase. `RESTART_HUB` (1034) is a station-scalar on the broadcast channel 255: a
262
- * level-2 frame whose body is `[u32 value][account_id padded]` — the same shape as the hub
263
- * alarm-volume control. ✅ Wire-confirmed byte-exact from a capture of the app's own Restart
264
- * (2026-08-03) and HW-tested: the captured frame carried value `0` and rebooted the hub. Replays
265
- * like every other level-2 control, so a single dropped datagram doesn't lose it.
251
+ * Restart the station that owns `sn`'s P2P session. `RESTART_HUB` (1034) is a station-scalar on the
252
+ * broadcast channel 255 whose body is `[u32 value][account_id padded]`, value `0`. ✅ Wire-confirmed
253
+ * byte-exact as a level-2 frame from a capture of the app's own HomeBase Restart (2026-08-03).
254
+ *
255
+ * The seal follows the session, as {@link sendBySessionLevel} defines it: a keyed session takes the
256
+ * level-2 frame, a keyless one — a standalone camera, which never negotiates a key — the same body
257
+ * sealed level-1. Both are replayed {@link DIRECT_CMD_SENDS}× at 200ms, as every other unacknowledged
258
+ * control here is.
266
259
  */
267
260
  rebootStation(sn: string): Promise<void>;
268
261
  /**
@@ -308,6 +301,11 @@ export declare class P2PCommandRouter {
308
301
  * stream costs nothing extra and a talkback on an otherwise idle camera opens the session it needs
309
302
  * instead of playing into silence.
310
303
  *
304
+ * **On a model in {@link ELD_TALKBACK_MODELS}, the speaker plays the codec the camera sends.** So the first
305
+ * audio frame of the media session this talkback holds decides its codec ({@link speakerCodec}), within
306
+ * {@link TALKBACK_CODEC_WAIT_MS} of the session's first frame, and the claim is re-checked after that wait as
307
+ * after the warm-up. Every other model opens `aac-lc` at once.
308
+ *
311
309
  * The level-2 key is waited for softly: only the HomeBase-attached path requires it, and
312
310
  * {@link Talkback.start} reports that failure precisely, so a hard wait here would reject an
313
311
  * own-session camera that legitimately never negotiates one.
@@ -453,15 +451,6 @@ export declare class P2PCommandRouter {
453
451
  * A still with no live sibling re-asserts, so a tile refreshing on a quiet station is
454
452
  * unaffected.
455
453
  */
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
454
  /**
466
455
  * Dispose one cached live source and forget it, so the next acquisition builds a fresh one. Its claim
467
456
  * on a session goes with it, and a media session opened for this source alone is closed: nothing else
@@ -521,11 +510,11 @@ export declare class P2PCommandRouter {
521
510
  */
522
511
  private onLiveStartFailed;
523
512
  /**
524
- * Replace the session under a warming source whose media start nothing acknowledged, and warm again on it.
513
+ * Replace the session under a warming source it is not serving, and warm again on it.
525
514
  *
526
515
  * Only a STANDALONE device's session is replaced, for the reason {@link onLiveStartFailed} gives: an attached
527
516
  * camera shares its HomeBase session with every other camera on it, and closing that to recover one would
528
- * drop the rest. Such a source keeps the re-issue it always had.
517
+ * drop the rest. Such a source keeps the re-issue it always had, which the answer (false) tells it.
529
518
  *
530
519
  * The source is left warming throughout, holding the deadline it started, so this either produces a stream
531
520
  * within that window or fails exactly as it would have. A replacement that cannot be opened leaves the
@@ -559,6 +548,7 @@ export declare class P2PCommandRouter {
559
548
  */
560
549
  p2pQuery(sn: string, subCmd: number, opts?: {
561
550
  timeoutMs?: number;
551
+ payload?: Record<string, unknown>;
562
552
  }): Promise<Record<string, unknown>>;
563
553
  /**
564
554
  * **Generic control-payload request/reply query.** Sends a `CONTROL_PAYLOAD` (1700) `{commandType,
@@ -687,7 +677,7 @@ export declare class P2PCommandRouter {
687
677
  * **`set-json-raw` intent** — bare JSON, no envelope: outer P2P cmd = `outerCmd` itself, plaintext
688
678
  * exactly `{account_id,...data}` (`session.sendRawLevel2` with no wrapper). Reversed from a live
689
679
  * 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.ALARM_DELAY_CONFIG`) reuses
680
+ * (`snoozeTime`); the alarm-delay config (1255, `arming.ts`'s `ARMING_CMD.SET_ALL_ACTION`) reuses
691
681
  * the same bare-JSON shape.
692
682
  */
693
683
  private sendJsonRaw;
@@ -702,7 +692,9 @@ export declare class P2PCommandRouter {
702
692
  * HomeBase-attached device takes the GCM signCode-8 form, a standalone one the level-1 form. A
703
693
  * standalone camera never negotiates a level-2 key, so pinning this to level 2 makes the envelope
704
694
  * unreachable on exactly the devices that serve their own RTSP stream. Verified live: a standalone
705
- * camera accepts the level-1 form. With no `form` (default) it stays level-2 only.
695
+ * camera accepts the level-1 form. With no `form` (default) it stays level-2 only and waits for the
696
+ * key every level-2 sender uses; `level2Sender: "set-payload"` also takes an RSA cipher's AES-128-ECB
697
+ * key (see {@link P2PSession.sendControlLevel2}). A `resolved` session keeps the wait its caller gave it.
706
698
  *
707
699
  * Both seals REPLAY the frame {@link DIRECT_CMD_SENDS}× at 200ms, as every other fire-and-forget
708
700
  * control on this router does: these are unacknowledged datagrams, and a level-1 device is the one
@@ -17,9 +17,12 @@ export declare class Fmp4Muxer {
17
17
  private baseDecodeTime;
18
18
  private fragTicks;
19
19
  private samples;
20
- private lastVideoTimestampMs?;
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
- /** Add one station-declared AAC access unit to the audio track. G.711 remains available via `live()`. */
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;
@@ -118,11 +118,19 @@ export type LiveTrace =
118
118
  phase: "level2-wait";
119
119
  waitMs: number;
120
120
  }
121
- /** The station's level-2 key was negotiated, under the cipher it selected. */
121
+ /** The station's AES-256-GCM level-2 key was negotiated, under the cipher it selected. */
122
122
  | {
123
123
  phase: "level2-ready";
124
124
  cipherId: number;
125
125
  }
126
+ /**
127
+ * The station's cipher negotiated only the AES-128-ECB level-2 key of an RSA cipher, under the cipher it
128
+ * selected. That key seals a level-2 set-payload and nothing else, so this is not `level2-ready`.
129
+ */
130
+ | {
131
+ phase: "level2-set-payload-only";
132
+ cipherId: number;
133
+ }
126
134
  /**
127
135
  * The station's key is not coming, why, and the cipher where a station named one.
128
136
  *
@@ -145,7 +153,8 @@ export type LiveTrace =
145
153
  * A station's cipher was answered with material for a DIFFERENT cipher, which was used in its place.
146
154
  *
147
155
  * The one lookup outcome no other phase accounts for: material for the cipher the station named is followed
148
- * by `level2-ready` or by `level2-unavailable` with `derivation-failed`, an answer holding none by
156
+ * by `level2-ready`, `level2-set-payload-only` or `level2-unavailable` with `derivation-failed`, an answer
157
+ * holding none by
149
158
  * `no-cipher-key`, and a lookup that threw is reported as an error. Substituted material derives to
150
159
  * nothing and otherwise reads as a station fault. `cipherId` is the cipher the station asked for,
151
160
  * `answeredCipherId` the one whose material was used.