@mega-yfue/eufy-sdk 0.5.0-beta.20 → 0.5.0-beta.22

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.
@@ -79,7 +79,8 @@ export declare const ARMING_CMD: {
79
79
  */
80
80
  readonly SET_ARMING: 1224;
81
81
  /**
82
- * The per-mode alarm/arm-delay configuration write. ✅ WIRE CAPTURED live on a T8030 (
82
+ * One guard mode's per-device action table (app `CMD_SET_ALL_ACTION`), which carries the alarm delays
83
+ * and each device's actions in that mode. ✅ WIRE CAPTURED live on a T8030 (
83
84
  * 2026-07-23, both directions): a **bare
84
85
  * JSON frame, no `1350`/`1700` envelope** — outer P2P cmd IS `1255` itself, station channel 255,
85
86
  * plaintext `{account_id, count_down_alarm:{channel_list,delay_time},
@@ -96,15 +97,23 @@ export declare const ARMING_CMD: {
96
97
  * corresponding GET command (`1310`/inner cmd `40003`, sent by the app right before editing) always
97
98
  * replied `{count:0,data:null}` in every capture — genuinely empty, not a decrypt failure (confirmed
98
99
  * via the same bidirectional decrypt this finding used) — so the app does NOT read the current
99
- * config this way; how it does is still unknown. Without a working GET, safely PATCHING just one
100
- * channel in or out of an existing list isn't possible without risking clobbering the rest — so
100
+ * config this way; a T8010 reports it on its own params instead, see below. Without a working GET,
101
+ * safely PATCHING just one channel in or out of an existing list isn't possible without risking
102
+ * clobbering the rest — so
101
103
  * this ships as a caller-supplies-everything write instead of guessing a merge.
102
104
  * `devices`/`siren_sensor_action` are even less understood (raw per-device action codes, meaning
103
105
  * unconfirmed) and MUST come from a value independently read/captured for the target mode —
104
106
  * see `AlarmDelayConfig`'s field docs.
107
+ *
108
+ * ✅ On a T8010 the app writes the same command for the Device Control screen: header channel 0, the
109
+ * whole table of the mode `{mode_id, devices, count_down_alarm, count_down_arm}`, acked by an all-zero
110
+ * 1255 reply. The T8010 reports each table on its own params, Home on 1157 and Away on 1158, and the
111
+ * reported table follows a write within 15 s. See {@link DEVICE_NOTIFY_FLAG}.
105
112
  */
106
- readonly ALARM_DELAY_CONFIG: 1255;
113
+ readonly SET_ALL_ACTION: 1255;
107
114
  };
115
+ /** The guard modes whose device notification is read and written: both replayed on a T8010. */
116
+ export type DeviceNotificationMode = Extract<AlarmDelayMode, "home" | "away">;
108
117
  /**
109
118
  * Alarm-delay durations the app's OWN picker UI offers — `AlarmDelaySeconds` is both the const
110
119
  * value-object (`AlarmDelaySeconds.sec45`) and the union type of its values, so callers pass the
@@ -225,6 +234,24 @@ export declare const ARMING_MEMBERS: {
225
234
  * would be the same unverified guess `setMode` refuses.
226
235
  */
227
236
  readonly setAlarmDelayConfig: import("./members.js").MethodMember<(mode: AlarmDelayMode, config: AlarmDelayConfig) => Promise<void>>;
237
+ /**
238
+ * Whether the device on `channel` sends a push notification in `mode`: its {@link DEVICE_NOTIFY_FLAG}
239
+ * in the action table the station reports for that mode. `undefined` where the station reports no table
240
+ * or the table does not list `channel`. A device's channel is `EufyDevice.channel`.
241
+ */
242
+ readonly deviceNotification: import("./members.js").MethodMember<(mode: DeviceNotificationMode, channel: number) => boolean | undefined> & {
243
+ available: (ctx: CommandContext) => boolean;
244
+ };
245
+ /**
246
+ * Turn the push notification of the device on `channel` on or off in `mode` (cmd
247
+ * {@link ARMING_CMD.SET_ALL_ACTION}): the station's reported table written back with that one flag
248
+ * changed. Rejects, sending nothing, where the table does not list `channel`. A write builds on the last
249
+ * one sent for that mode until the station reports a table observed 15 s after it, so a second write does
250
+ * not revert it; a write whose dispatch rejects is not built on.
251
+ */
252
+ readonly setDeviceNotification: import("./members.js").MethodMember<(mode: DeviceNotificationMode, channel: number, on: boolean) => Promise<void>> & {
253
+ available: (ctx: CommandContext) => boolean;
254
+ };
228
255
  };
229
256
  /**
230
257
  * `arming` — guard/arming mode. `armingMode` (see {@link ARMING_CMD.SET_ARMING}) has a verified read/write
@@ -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).
@@ -45,6 +45,8 @@ export declare const isFloodLight: (ctx: FamilyContext) => boolean;
45
45
  export declare const isHomeBase: (ctx: FamilyContext) => boolean;
46
46
  /** HomeBase S1 Pro (`STATION_9000`), the station the app drives over its WebRTC control channel. */
47
47
  export declare const isStation9000: (ctx: FamilyContext) => boolean;
48
+ /** T8N00 NVR, identified by its reported model. */
49
+ export declare const isNvrT8N00: (ctx: FamilyContext) => boolean;
48
50
  /** Wired doorbell (DeviceType.DOORBELL). */
49
51
  export declare const isWiredDoorbell: (ctx: FamilyContext) => boolean;
50
52
  /**
@@ -695,7 +695,7 @@ export declare class P2PCommandRouter {
695
695
  * **`set-json-raw` intent** — bare JSON, no envelope: outer P2P cmd = `outerCmd` itself, plaintext
696
696
  * exactly `{account_id,...data}` (`session.sendRawLevel2` with no wrapper). Reversed from a live
697
697
  * capture of the app's own SET_SNOOZE_TIME (1271) frame — see `param-dictionary.ts`'s `1271` entry
698
- * (`snoozeTime`); the alarm-delay config (1255, `arming.ts`'s `ARMING_CMD.ALARM_DELAY_CONFIG`) reuses
698
+ * (`snoozeTime`); the alarm-delay config (1255, `arming.ts`'s `ARMING_CMD.SET_ALL_ACTION`) reuses
699
699
  * the same bare-JSON shape.
700
700
  */
701
701
  private sendJsonRaw;
@@ -611,6 +611,18 @@ export declare class P2PSession extends EventEmitter {
611
611
  accountId?: string;
612
612
  channel?: number;
613
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[]>;
614
626
  /**
615
627
  * Query one on-station table and answer its rows, once the reply is whole.
616
628
  *
@@ -618,13 +630,15 @@ export declare class P2PSession extends EventEmitter {
618
630
  * several frames whose decrypted text is a fragment of one document, so the fragments are
619
631
  * accumulated here and scanned after each one. Answers that document's `data` rows. Rejects when
620
632
  * `signal` aborts, and when `timeoutMs` (default {@link DB_TABLE_TIMEOUT_MS}) elapses with no
621
- * complete reply.
633
+ * complete reply, and when the station answers a non-zero `mIntRet`.
622
634
  *
623
635
  * One read at a time per session: while one is accumulating, every {@link queryDatabase} on the
624
636
  * session throws, so a second reply cannot land in this buffer.
625
637
  */
626
638
  readDatabase(table: string, opts?: {
627
639
  accountId?: string;
640
+ innerCmd?: number;
641
+ query?: Record<string, unknown>;
628
642
  timeoutMs?: number;
629
643
  signal?: AbortSignal;
630
644
  }): Promise<unknown[]>;
@@ -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. Keyframes go through one
50
- * {@link VideoFrameDecoder}, whose media key then opens the audio; a keyframe that does not open under
51
- * `eccPrivateKeyHex`, and every audio frame before the first media key, are dropped. Rejects with
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;
@@ -1,6 +1,5 @@
1
1
  /**
2
- * RTC command router: drives a **HomeBase S1 Pro (T9000)** over the portal's WebRTC data channel instead
3
- * of P2P.
2
+ * RTC command router: drives a station over the portal's WebRTC data channel instead of P2P.
4
3
  *
5
4
  * The T9000 answers no P2P lookup, but it accepts the portal's control channel: sign → WS join → scall →
6
5
  * SDP answer → ICE over the relay the hub grants → DTLS → SCTP data channels, with commands riding
@@ -26,6 +25,10 @@ export interface RtcIdentity {
26
25
  }
27
26
  /** Where a command goes: the station's session, and whether it addresses the station or a device on it. */
28
27
  export interface RtcRoute {
28
+ /** The station's signaling exchange; defaults to compact SDP. */
29
+ signalingMode?: "call" | "scall";
30
+ /** Candidate policy used by the station's exchange; defaults to relay-only. */
31
+ iceTransportPolicy?: "relay" | "all";
29
32
  stationSn: string;
30
33
  /**
31
34
  * The station's `member.admin_user_id`, the account the session and the payload name; the logged-in
@@ -7,7 +7,7 @@
7
7
  * then audio, idr, video, notify, download). Every one carries PTCS-framed portal packets; which logical
8
8
  * channel a reassembled frame belongs to is the PTCS header's channel, not the data channel it rode on.
9
9
  *
10
- * ICE is relay-only. The hub grants a TURN allocation in its `scall 100` reply and completes DTLS only
10
+ * ICE defaults to relay-only. The T9000 grants a TURN allocation in its `scall 100` reply and completes DTLS only
11
11
  * over the relay pair; on a host pair ICE connects and the DTLS handshake never completes. The peer
12
12
  * plays the DTLS client (`active`) and opens its channels on the client's even SCTP stream ids, which
13
13
  * is what the hub pairs with.
@@ -28,6 +28,8 @@ export interface TurnConfig {
28
28
  export type NativePeerFactory = (name: string, config: RtcConfig) => PeerConnection;
29
29
  export interface RtcPeerOptions {
30
30
  createPeer?: NativePeerFactory;
31
+ /** Candidate policy for the station's exchange; the default is relay-only. */
32
+ iceTransportPolicy?: "relay" | "all";
31
33
  logger?: Logger;
32
34
  }
33
35
  export interface RtcPeerEvents {
@@ -51,6 +53,7 @@ export declare class RtcPeer extends EventEmitter<RtcPeerEvents> {
51
53
  private readonly wireTally;
52
54
  private commandOpen;
53
55
  private remoteSet;
56
+ private remoteMid;
54
57
  private handlingOffer;
55
58
  private channelsCreated;
56
59
  private gatheringDone;
@@ -50,7 +50,7 @@ export declare class RtcSession extends EventEmitter<RtcSessionEvents> {
50
50
  constructor(opts: RtcSessionOptions);
51
51
  get isConnected(): boolean;
52
52
  /**
53
- * Start the sequence; resolves once `scall` is sent. `connected` fires when the channel opens. A session
53
+ * Start the sequence; resolves once the call is sent. `connected` fires when the channel opens. A session
54
54
  * closed by the time the sign fetch or the auth completes rejects instead of opening the socket or
55
55
  * placing the call.
56
56
  */
@@ -68,6 +68,8 @@ export interface SignalingSocketInit {
68
68
  }
69
69
  export type SignalingSocketFactory = (url: string, init: SignalingSocketInit) => SignalingSocket;
70
70
  export interface RtcSignalingOptions {
71
+ /** Full SDP (`call`) or compact SDP (`scall`, the default). */
72
+ signalingMode?: "call" | "scall";
71
73
  /** The mega session's auth token. */
72
74
  authToken: string;
73
75
  /** The `gtoken` header value the mega session's authed HTTP calls carry. */
@@ -124,8 +126,8 @@ export declare class RtcSignalingClient extends EventEmitter<RtcSignalingEvents>
124
126
  sendSession(dataType: string, payload?: Record<string, unknown>): void;
125
127
  sendCall(): void;
126
128
  sendAck(): void;
127
- /** The SDP answer, as scall JSON text, in an `info`. */
128
- sendInfoSdp(scallJson: string): void;
129
+ /** The SDP answer, encoded for the negotiated call format, in an `info`. */
130
+ sendInfoSdp(sdp: string): void;
129
131
  /** Trickle a candidate on the session's channel, like its SDP answer. An empty candidate ends them. */
130
132
  sendInfoCandidate(candidate: string): void;
131
133
  sendHangup(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.5.0-beta.20",
3
+ "version": "0.5.0-beta.22",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",