@mega-yfue/eufy-sdk 0.4.0-beta.14 → 0.4.0-beta.16
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/core/contracts.d.ts +43 -0
- package/dist/index.js +403 -106
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/camera.d.ts +1 -0
- package/dist/transport/p2p/adts.d.ts +6 -0
- package/dist/transport/p2p/codec.d.ts +13 -0
- package/dist/transport/p2p/command-router.d.ts +29 -0
- package/dist/transport/p2p/p2p-session.d.ts +5 -0
- package/dist/transport/p2p/recording-download.d.ts +54 -0
- package/dist/transport/p2p/video.d.ts +2 -0
- package/package.json +1 -1
|
@@ -552,6 +552,7 @@ export declare const CAMERA_MEMBERS: {
|
|
|
552
552
|
} | undefined) => Promise<Buffer<ArrayBufferLike>>>;
|
|
553
553
|
readonly openReadable: import("./members.js").ProvidedMember<"media", ((opts?: Parameters<NonNullable<MediaProvider["openReadable"]>>[0]) => Promise<import("node:stream").Readable>) | undefined>;
|
|
554
554
|
readonly recordFragments: import("./members.js").ProvidedMember<"media", ((opts?: Parameters<NonNullable<MediaProvider["recordFragments"]>>[0]) => import("../../core/contracts.js").FragmentRecordingHandle) | undefined>;
|
|
555
|
+
readonly downloadRecording: import("./members.js").ProvidedMember<"media", ((opts: Parameters<NonNullable<MediaProvider["downloadRecording"]>>[0]) => Promise<import("../../core/contracts.js").RecordingDownload>) | undefined>;
|
|
555
556
|
/**
|
|
556
557
|
* Push audio from the host to this camera's speaker. Gated on the **speaker** param specifically —
|
|
557
558
|
* not the `audio` capability, which resolves on a microphone alone and would advertise a speaker the
|
|
@@ -59,6 +59,12 @@ export interface AdtsHeader {
|
|
|
59
59
|
* nothing about whether the frame's payload has arrived yet; that is the scanner's job.
|
|
60
60
|
*/
|
|
61
61
|
export declare function parseAdtsHeader(buf: Buffer, offset?: number): AdtsHeader | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The 7-byte ADTS header (no CRC, buffer fullness "variable") that frames one raw AAC-LC, 16 kHz, mono
|
|
64
|
+
* access unit of `payloadLength` bytes, so that header and payload together read back through
|
|
65
|
+
* {@link parseAdtsHeader} as a supported frame.
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildAdtsHeader(payloadLength: number): Buffer;
|
|
62
68
|
/**
|
|
63
69
|
* Whether a header describes the audio parameters the device's path is fixed at — AAC-LC, 16 kHz,
|
|
64
70
|
* mono. A stream at any other rate or channel count is rejected rather than resampled: the device has
|
|
@@ -22,6 +22,11 @@ export declare const ResponseMessageType: {
|
|
|
22
22
|
readonly LOOKUP_ADDR: Buffer<ArrayBuffer>;
|
|
23
23
|
readonly LOOKUP_ADDR2: Buffer<ArrayBuffer>;
|
|
24
24
|
readonly CAM_ID: Buffer<ArrayBuffer>;
|
|
25
|
+
/**
|
|
26
|
+
* The device's own address record, sent in answer to CHECK_CAM ahead of CAM_ID: its 20-byte device id, one
|
|
27
|
+
* record in `encodeSelfAddress`'s shape naming the address it answers from, then 8 zero bytes (44-byte payload).
|
|
28
|
+
*/
|
|
29
|
+
readonly CAM_ADDR: Buffer<ArrayBuffer>;
|
|
25
30
|
readonly TURN_SERVER_CAM_ID: Buffer<ArrayBuffer>;
|
|
26
31
|
readonly PING: Buffer<ArrayBuffer>;
|
|
27
32
|
readonly PONG: Buffer<ArrayBuffer>;
|
|
@@ -149,6 +154,14 @@ export declare function buildStringCommandPayload(value: string, channel?: numbe
|
|
|
149
154
|
* length prefix ({@link stringWithLength}).
|
|
150
155
|
*/
|
|
151
156
|
export declare function buildIntStringCommandPayload(value: number, valueSub: number, strValue: string, channel?: number, key?: Buffer, encType?: number): Buffer;
|
|
157
|
+
/**
|
|
158
|
+
* Build a **string-pair** command body: five zero bytes, then `strValue` and `strValueSub`, each in the
|
|
159
|
+
* 128-byte-chunk length form ({@link stringWithLength}), AES-128-ECB encrypted (level-1) like
|
|
160
|
+
* {@link buildStringCommandPayload} when `key` is given. `CMD_DOWNLOAD_VIDEO` (1024) takes this shape on a
|
|
161
|
+
* HomeBase 2: `strValue` = the recording's path on the station, `strValueSub` = the station admin
|
|
162
|
+
* `account_id`, on the camera's channel.
|
|
163
|
+
*/
|
|
164
|
+
export declare function buildStringPairCommandPayload(strValue: string, strValueSub: string, channel?: number, key?: Buffer, encType?: number): Buffer;
|
|
152
165
|
/**
|
|
153
166
|
* Build a command body around an ALREADY-encrypted (or plaintext) `data` buffer with an explicit
|
|
154
167
|
* `signCode` — used for level-2 (`signCode 8`, AES-256-GCM) commands like the media-start 1350 the
|
|
@@ -121,6 +121,8 @@ export declare class P2PCommandRouter {
|
|
|
121
121
|
private readonly talkbacks;
|
|
122
122
|
/** cipher_id → ECC private key (one eufylife get_ciphers call per cipher), shared across (re)opens. */
|
|
123
123
|
private readonly cipherKeyCache;
|
|
124
|
+
/** The recording download in flight per station; a station serves one at a time. */
|
|
125
|
+
private readonly recordingDownloads;
|
|
124
126
|
constructor(deps: P2PRouterDeps);
|
|
125
127
|
/** Forward one P2P failure once even when both the session listener and startup waiter observe it. */
|
|
126
128
|
private reportError;
|
|
@@ -237,6 +239,12 @@ export declare class P2PCommandRouter {
|
|
|
237
239
|
* while its own session is still serving.
|
|
238
240
|
*/
|
|
239
241
|
private makeSession;
|
|
242
|
+
/**
|
|
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.
|
|
246
|
+
*/
|
|
247
|
+
private cipherKeyFor;
|
|
240
248
|
/**
|
|
241
249
|
* Open (or reuse) a station's P2P session and await its completed handshake. An optional abort only
|
|
242
250
|
* stops this wait; session ownership remains with {@link SessionManager} and its normal teardown.
|
|
@@ -269,6 +277,27 @@ export declare class P2PCommandRouter {
|
|
|
269
277
|
* start has no level-1 form for.
|
|
270
278
|
*/
|
|
271
279
|
mediaProviderFor(sn: string): MediaProvider;
|
|
280
|
+
/**
|
|
281
|
+
* Whether `sn` is a camera attached to a HomeBase 2 (T8010), the one station whose recording path layout
|
|
282
|
+
* and frame formats are confirmed.
|
|
283
|
+
*/
|
|
284
|
+
private downloadsRecordings;
|
|
285
|
+
/**
|
|
286
|
+
* Download one recording a HomeBase 2 holds for an attached camera, then decode it
|
|
287
|
+
* ({@link decodeRecording}). Downloads queue per station: the station streams one recording at a time
|
|
288
|
+
* on the camera's channel, so a download starts only once the previous one has drained
|
|
289
|
+
* ({@link RecordingTransfer.drained}), even when that one's caller already has its answer. An abort
|
|
290
|
+
* while waiting rejects without sending anything; an abort mid-transfer rejects at once and the station's
|
|
291
|
+
* turn is held until the transfer drains.
|
|
292
|
+
*/
|
|
293
|
+
private downloadRecording;
|
|
294
|
+
/** Resolve once `previous` settles, or reject as soon as `signal` aborts. */
|
|
295
|
+
private waitTurn;
|
|
296
|
+
/**
|
|
297
|
+
* Start one recording download, once the station's previous download has drained: the decoded recording,
|
|
298
|
+
* and when the station is done sending it.
|
|
299
|
+
*/
|
|
300
|
+
private startRecordingDownload;
|
|
272
301
|
/**
|
|
273
302
|
* Open a {@link Talkback} on a device's camera channel.
|
|
274
303
|
*
|
|
@@ -326,6 +326,9 @@ export declare class P2PSession extends EventEmitter {
|
|
|
326
326
|
* straddling a level-2 wait on a camera whose failure to deliver video had a separate cause, and did not
|
|
327
327
|
* recur across later probes of it. Correlate an unmodelled type against a WORKING session before reading it
|
|
328
328
|
* as a cause.
|
|
329
|
+
*
|
|
330
|
+
* CAM_ADDR is recognised and dropped: it names the address the device is already answering from, and the
|
|
331
|
+
* CAM_ID that follows it is what completes the connect.
|
|
329
332
|
*/
|
|
330
333
|
private onMessage;
|
|
331
334
|
private beginCheckCam;
|
|
@@ -410,6 +413,8 @@ export declare class P2PSession extends EventEmitter {
|
|
|
410
413
|
* `strValue` (admin `account_id`). See {@link buildIntStringCommandPayload}. Fire-and-forget.
|
|
411
414
|
*/
|
|
412
415
|
sendIntStringCommand(commandType: number, value: number, valueSub: number, strValue: string, channel?: number): void;
|
|
416
|
+
/** Send a level-1 {@link buildStringPairCommandPayload} command on `channel`. */
|
|
417
|
+
sendStringPairCommand(commandType: number, strValue: string, strValueSub: string, channel: number): void;
|
|
413
418
|
/**
|
|
414
419
|
* Send a **level-2 (AES-256-GCM, signCode 8) control payload** to a HomeBase-attached device. The
|
|
415
420
|
* target camera is selected by `channel` (= device_channel) + the `mChannel` envelope — the same
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type RecordingDownload } from "../../core/contracts.js";
|
|
2
|
+
import type { P2PSession } from "./p2p-session.js";
|
|
3
|
+
/** Data type a station sends a recording's frames on: the binary channel (`0xd1 0x03`), not live video's. */
|
|
4
|
+
export declare const RECORDING_DATA_TYPE = 3;
|
|
5
|
+
/**
|
|
6
|
+
* Most frame bytes a transfer holds. The station sends faster than real time, so the time bounds alone do
|
|
7
|
+
* not bound memory; an event recording is far below this.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_TRANSFER_BYTES: number;
|
|
10
|
+
/** One recording frame as received, in arrival order. */
|
|
11
|
+
export interface RecordingFrame {
|
|
12
|
+
commandId: number;
|
|
13
|
+
signCode: number;
|
|
14
|
+
raw: Buffer;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The path a HomeBase 2 stores a camera's recording under: the camera's channel, two digits, and the
|
|
18
|
+
* recording name the event push carries. Answers `undefined` for a name that is not the station's
|
|
19
|
+
* fourteen-digit `yyyyMMddHHmmss` form. The name comes from a push, so the test stays anchored and
|
|
20
|
+
* digits-only: no `..`, slash or other character that could reach another path on the station gets through.
|
|
21
|
+
*/
|
|
22
|
+
export declare function homeBase2RecordingPath(channel: number, recording: string): string | undefined;
|
|
23
|
+
/** One recording transfer on a station session. */
|
|
24
|
+
export interface RecordingTransfer {
|
|
25
|
+
/** The recording's frames, in arrival order. Rejects as {@link receiveRecording} describes. */
|
|
26
|
+
frames: Promise<RecordingFrame[]>;
|
|
27
|
+
/** Resolves once the station has stopped sending this recording, which can be after `frames` rejected. */
|
|
28
|
+
drained: Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Request one recording and collect its frames until `CMD_DOWNLOAD_FINISH`. Only frames on
|
|
32
|
+
* {@link RECORDING_DATA_TYPE} tagged with the camera's channel belong to it, so a live stream open on the same
|
|
33
|
+
* station is never mixed in.
|
|
34
|
+
*
|
|
35
|
+
* `frames` rejects with {@link RecordingDownloadError}: `no-data` when nothing arrives within
|
|
36
|
+
* {@link FIRST_FRAME_WAIT_MS}, and `incomplete` when the transfer ends without its finish frame, on a
|
|
37
|
+
* {@link IDLE_END_MS} silence, on `timeoutMs` or on {@link MAX_TRANSFER_BYTES}. An abort rejects it at once
|
|
38
|
+
* with the signal's reason. After a time bound, the byte ceiling or an abort, the transfer stops collecting
|
|
39
|
+
* but keeps listening until the station finishes or goes quiet, and only then resolves `drained`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function receiveRecording(session: P2PSession, request: {
|
|
42
|
+
path: string;
|
|
43
|
+
accountId: string;
|
|
44
|
+
channel: number;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}): RecordingTransfer;
|
|
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
|
|
52
|
+
* {@link RecordingDownloadError} `undecodable` when no video frame decodes.
|
|
53
|
+
*/
|
|
54
|
+
export declare function decodeRecording(frames: readonly RecordingFrame[], eccPrivateKeyHex: string): RecordingDownload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mega-yfue/eufy-sdk",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.16",
|
|
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",
|