@mega-yfue/eufy-sdk 0.6.0-beta.2 → 0.6.0-beta.21
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 +6 -0
- package/dist/client/eufy-mega.d.ts +84 -11
- package/dist/client/types.d.ts +12 -9
- package/dist/core/contracts.d.ts +3 -1
- package/dist/core/store.d.ts +12 -1
- package/dist/core/types.d.ts +4 -2
- package/dist/index.js +2719 -2120
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/camera.d.ts +4 -1
- package/dist/model/capabilities/mower.d.ts +3 -2
- package/dist/model/capabilities/vacuum-clean.d.ts +23 -0
- package/dist/model/device-family.d.ts +1 -1
- package/dist/model/device-types.d.ts +1 -0
- package/dist/transport/http/mega-client.d.ts +3 -2
- package/dist/transport/mqtt/biz-stream.d.ts +22 -25
- package/dist/transport/p2p/audio-frame.d.ts +13 -0
- package/dist/transport/p2p/codec.d.ts +2 -2
- package/dist/transport/p2p/command-router.d.ts +28 -3
- package/dist/transport/p2p/database-wire.d.ts +30 -0
- package/dist/transport/p2p/level2-settle.d.ts +9 -0
- package/dist/transport/p2p/live-stream.d.ts +34 -7
- package/dist/transport/p2p/live-trace.d.ts +6 -4
- package/dist/transport/p2p/p2p-session.d.ts +99 -15
- package/dist/transport/p2p/readable-egress.d.ts +3 -1
- package/dist/transport/p2p/recording-download.d.ts +26 -4
- package/dist/transport/p2p/shared-live-source.d.ts +9 -3
- package/dist/transport/p2p/video.d.ts +4 -2
- package/dist/transport/push/store.d.ts +7 -6
- package/dist/transport/rtc/command-router.d.ts +63 -3
- package/dist/transport/rtc/portal-packet.d.ts +3 -13
- package/dist/transport/rtc/session.d.ts +4 -2
- package/dist/transport/stored-image-cache.d.ts +14 -0
- package/package.json +1 -1
|
@@ -475,6 +475,8 @@ export declare const CAMERA_MEMBERS: {
|
|
|
475
475
|
*
|
|
476
476
|
* `transaction` is part of the frame that was captured, so it is sent. The recording sibling's own
|
|
477
477
|
* confirmed frame carries no such field; neither is inferred from the other.
|
|
478
|
+
*
|
|
479
|
+
* Not offered where the device reports the doorbell's own live-view quality (1705).
|
|
478
480
|
*/
|
|
479
481
|
readonly streamingQuality: {
|
|
480
482
|
readonly accepts: StreamingQualityName;
|
|
@@ -483,7 +485,7 @@ export declare const CAMERA_MEMBERS: {
|
|
|
483
485
|
readonly kind: "enum";
|
|
484
486
|
readonly enumValues: Readonly<Record<number, string>>;
|
|
485
487
|
readonly provenance: "verified";
|
|
486
|
-
readonly available:
|
|
488
|
+
readonly available: (ctx: AvailabilityContext) => boolean;
|
|
487
489
|
readonly args: readonly [{
|
|
488
490
|
readonly name: "quality";
|
|
489
491
|
readonly kind: "enum";
|
|
@@ -596,6 +598,7 @@ export declare const CAMERA_MEMBERS: {
|
|
|
596
598
|
timeoutMs?: number;
|
|
597
599
|
skipKeyframes?: number;
|
|
598
600
|
} | undefined) => Promise<Buffer<ArrayBufferLike>>)>;
|
|
601
|
+
/** The T8530 serves its live feed as a Readable too; its other media pulls stay withheld. */
|
|
599
602
|
readonly openReadable: import("./members.js").ProvidedMember<"media", false | ((opts?: Parameters<NonNullable<MediaProvider["openReadable"]>>[0]) => Promise<import("node:stream").Readable>) | undefined>;
|
|
600
603
|
readonly recordFragments: import("./members.js").ProvidedMember<"media", false | ((opts?: Parameters<NonNullable<MediaProvider["recordFragments"]>>[0]) => import("../../core/contracts.js").FragmentRecordingHandle) | undefined>;
|
|
601
604
|
readonly downloadRecording: import("./members.js").ProvidedMember<"media", false | ((opts: Parameters<NonNullable<MediaProvider["downloadRecording"]>>[0]) => Promise<import("../../core/contracts.js").RecordingDownload>) | undefined>;
|
|
@@ -34,9 +34,9 @@ export declare const MOWER_DP: {
|
|
|
34
34
|
readonly SPARSE_LAWN_OPTIMIZATION: 141;
|
|
35
35
|
};
|
|
36
36
|
/** The `net_media_type` (DP 134) range, as the schema lists it. */
|
|
37
|
-
|
|
37
|
+
declare const MOWER_NETWORKS: readonly ["None", "Wifi", "Cellular"];
|
|
38
38
|
/** The mower's network link (DP 134). */
|
|
39
|
-
|
|
39
|
+
type MowerNetwork = (typeof MOWER_NETWORKS)[number];
|
|
40
40
|
/**
|
|
41
41
|
* Every `mower` read, declared once.
|
|
42
42
|
*
|
|
@@ -228,3 +228,4 @@ export declare const MOWER_MEMBERS: {
|
|
|
228
228
|
export type MowerActions = Surface<typeof MOWER_MEMBERS>;
|
|
229
229
|
/** `mower` — robot-mower state from its Tuya data points. */
|
|
230
230
|
export declare const MOWER: CapabilityModule;
|
|
231
|
+
export {};
|
|
@@ -280,6 +280,16 @@ export declare function encodeSelectZonesClean(mapId: number, zones: readonly Va
|
|
|
280
280
|
* @internal
|
|
281
281
|
*/
|
|
282
282
|
export declare function encodeSceneClean(sceneId: number): string;
|
|
283
|
+
/**
|
|
284
|
+
* Build one of the two DP 153 writes the vendor app sends when its map view opens: the time in
|
|
285
|
+
* microseconds, and on the second write field 2 set to `1`.
|
|
286
|
+
*
|
|
287
|
+
* Captured from the vendor app on the device's `cmd/…/req` leg, three times with the same layout. The
|
|
288
|
+
* product catalogue lists DP 153 as uplink-only (`WorkStatus`); the vendor app writes this message to it
|
|
289
|
+
* all the same. {@link VACUUM_CLEAN_MEMBERS.requestMap} dispatches both.
|
|
290
|
+
* @internal
|
|
291
|
+
*/
|
|
292
|
+
export declare function encodeMapWatch(nowMs: number, watching: boolean): string;
|
|
283
293
|
/**
|
|
284
294
|
* Build a whole-floor auto clean carrying an `AutoClean` payload of one pass.
|
|
285
295
|
* @internal
|
|
@@ -1986,6 +1996,19 @@ export declare const VACUUM_CLEAN_MEMBERS: {
|
|
|
1986
1996
|
readonly startScene: import("./members.js").MethodMember<(sceneId: number) => Promise<void>> & {
|
|
1987
1997
|
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
1988
1998
|
};
|
|
1999
|
+
/**
|
|
2000
|
+
* Ask the robot to push its whole map — plane, room outline, room list and restricted zones — on the
|
|
2001
|
+
* map stream, as the vendor app does when its map view opens: two DP 153 writes, the time and then the
|
|
2002
|
+
* time with field 2 set (see {@link encodeMapWatch}).
|
|
2003
|
+
*
|
|
2004
|
+
* A docked robot sends no map of its own accord, so this is how its room ids and names become
|
|
2005
|
+
* readable without a clean. The map arrives on the stream like any other, not as this call's result.
|
|
2006
|
+
*
|
|
2007
|
+
* WATCHED: sent to a docked robot, it pushed its whole map within a second.
|
|
2008
|
+
*/
|
|
2009
|
+
readonly requestMap: import("./members.js").MethodMember<() => Promise<void>> & {
|
|
2010
|
+
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
2011
|
+
};
|
|
1989
2012
|
/**
|
|
1990
2013
|
* State the cleaning settings a run uses — `CleanParamRequest.clean_param` over DP 154.
|
|
1991
2014
|
*
|
|
@@ -52,7 +52,7 @@ export declare const isHomeBase: (ctx: FamilyContext) => boolean;
|
|
|
52
52
|
export declare const isStation9000: (ctx: FamilyContext) => boolean;
|
|
53
53
|
/** T8N00 NVR, identified by its reported model. */
|
|
54
54
|
export declare const isNvrT8N00: (ctx: FamilyContext) => boolean;
|
|
55
|
-
/**
|
|
55
|
+
/** A camera attached to a T8N00 whose media the NVR's call serves. */
|
|
56
56
|
export declare const isNvrMediaCamera: (ctx: FamilyContext) => boolean;
|
|
57
57
|
/** PoE S4 camera with independently streamed fixed and movable optical views. */
|
|
58
58
|
export declare const isNvrDualLensCamera: (ctx: FamilyContext) => boolean;
|
|
@@ -112,6 +112,7 @@ export declare const DeviceType: {
|
|
|
112
112
|
readonly LOCK_85D0: 202;
|
|
113
113
|
readonly LOCK_85V0: 203;
|
|
114
114
|
readonly LOCK_85P0: 209;
|
|
115
|
+
readonly WALL_LIGHT_CAM_T86C1: 298;
|
|
115
116
|
readonly NVR_S4_MAX: 300;
|
|
116
117
|
readonly CAMERA_POE_S4: 301;
|
|
117
118
|
readonly WALL_LIGHT_CAM_81A0: 10005;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SessionEntry } from "../../core/crypto.js";
|
|
2
2
|
import { type SessionStore } from "../../core/store.js";
|
|
3
3
|
import { type Logger } from "../../core/logger.js";
|
|
4
|
+
import type { EufyDevice } from "../../core/types.js";
|
|
4
5
|
import type { SecureMqttCredentials } from "../mqtt/secure-mqtt.js";
|
|
5
6
|
export type RegionShard = "eu-pr" | "us-pr";
|
|
6
7
|
/**
|
|
@@ -354,11 +355,11 @@ export declare class MegaHttpClient {
|
|
|
354
355
|
/** Authed call to a mega service host: app-{service}-{region}.eufy.com. */
|
|
355
356
|
post<T = unknown>(service: string, path: string, body?: unknown, headerOverrides?: Record<string, string>): Promise<T>;
|
|
356
357
|
/**
|
|
357
|
-
* Fetch the per-station DSK key
|
|
358
|
+
* Fetch the per-station DSK key using each station's reported cloud category. Mirrors
|
|
358
359
|
* the legacy `get_dsk_keys` body shape on the mega devicerelation service.
|
|
359
360
|
* The P2P *local* lookup path does not need this; only the cloud path does.
|
|
360
361
|
*/
|
|
361
|
-
getDskKeys(
|
|
362
|
+
getDskKeys(stations: ReadonlyArray<Pick<EufyDevice, "sn" | "category">>, priorDsks?: Record<string, string>): Promise<Record<string, {
|
|
362
363
|
dskKey: string;
|
|
363
364
|
expiration: number;
|
|
364
365
|
}>>;
|
|
@@ -12,44 +12,41 @@ export declare const BIZ_PROTOCOL: {
|
|
|
12
12
|
readonly LIVE_PHOTO: 43;
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* The channels a map frame
|
|
15
|
+
* The channels a map frame arrives on, by id, as a robot's own frames identify them.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* *decodes* a channel's bytes must therefore prefer a device-declared mapping when one has been seen;
|
|
23
|
-
* this table is the default to fall back on, not an authority.
|
|
17
|
+
* Each id is paired with the `stream.proto` message whose field layout its frames carry, which is not
|
|
18
|
+
* the field-number order of `Metadata.ChanIds`: the cell plane (`Map`) arrives on 2, its room outline
|
|
19
|
+
* on 4, the room list on 5, the restricted zones on 6, the robot's pose (`DynamicData`) on 7, the map's
|
|
20
|
+
* `MapDescription` on 60 and a whole `MapBackup` on 68. A `Metadata` frame would declare a device's own numbering; none has been seen,
|
|
21
|
+
* so none is read, and this table is the numbering every frame is read by.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Only channels whose message is identified are listed. A channel whose frames carry too little to
|
|
24
|
+
* identify, or that carries a message nothing here reads, is absent.
|
|
25
|
+
*
|
|
26
|
+
* Channel 0 is not in `ChanIds` at all: the app hard-codes it, routing it to a scene-data event rather
|
|
27
|
+
* than a map one, and asks for the scene list on it by number.
|
|
28
28
|
*/
|
|
29
29
|
export declare const BIZ_CHANNEL: {
|
|
30
30
|
/** Scene (room-group) data. Hard-coded in the app, not part of `ChanIds`. */
|
|
31
31
|
readonly SCENES: 0;
|
|
32
|
-
readonly
|
|
33
|
-
readonly
|
|
34
|
-
readonly
|
|
35
|
-
readonly
|
|
36
|
-
readonly
|
|
37
|
-
readonly
|
|
38
|
-
readonly
|
|
39
|
-
readonly OBSTACLE_INFO: 8;
|
|
40
|
-
readonly MAP_DATA: 9;
|
|
41
|
-
readonly CRUISE_DATA: 10;
|
|
32
|
+
readonly MAP_DATA: 2;
|
|
33
|
+
readonly ROOM_OUTLINE: 4;
|
|
34
|
+
readonly ROOM_PARAMS: 5;
|
|
35
|
+
readonly RESTRICTED_ZONE: 6;
|
|
36
|
+
readonly DYNAMIC_DATA: 7;
|
|
37
|
+
readonly MAP_DESCRIPTION: 60;
|
|
38
|
+
readonly MAP_BACKUP: 68;
|
|
42
39
|
};
|
|
43
|
-
/** A channel's name under the
|
|
40
|
+
/** A channel's name under the {@link BIZ_CHANNEL} numbering. */
|
|
44
41
|
export type BizChannel = keyof typeof BIZ_CHANNEL;
|
|
45
42
|
/**
|
|
46
|
-
* Name a channel id under the
|
|
47
|
-
*
|
|
43
|
+
* Name a channel id under the {@link BIZ_CHANNEL} numbering, or `undefined` for an id it does not list.
|
|
44
|
+
* Not a substitute for a device-declared mapping where one exists.
|
|
48
45
|
*/
|
|
49
46
|
export declare function bizChannelName(id: number): BizChannel | undefined;
|
|
50
47
|
/** One frame off the map stream, unwrapped as far as its bytes but not decoded. */
|
|
51
48
|
export interface BizMapFrame {
|
|
52
|
-
/** Which channel the bytes belong to. Compare against {@link BIZ_CHANNEL}
|
|
49
|
+
/** Which channel the bytes belong to. Compare against {@link BIZ_CHANNEL}. */
|
|
53
50
|
readonly channelId: number;
|
|
54
51
|
/**
|
|
55
52
|
* The vendor's `clear_type`. Forwarded, not interpreted: the app passes it straight through to its
|
|
@@ -12,3 +12,16 @@ export declare const AUDIO_TYPE_OFFSET = 5;
|
|
|
12
12
|
* itself when a stream produces no audio at all, to feed its decoder silence.
|
|
13
13
|
*/
|
|
14
14
|
export declare const AUDIO_CODECS: Readonly<Record<number, AudioCodec>>;
|
|
15
|
+
/**
|
|
16
|
+
* Whether an audio frame is sealed: after its header, a 16-byte GCM tag and a 12-byte nonce precede the
|
|
17
|
+
* `u32@0` bytes its header declares, where a frame in the clear carries those bytes straight after the header.
|
|
18
|
+
* A stream whose keyframes wrap their media key in an ECIES envelope seals its audio this way, measured on
|
|
19
|
+
* HomeBase recordings and on HomeBase 3 live streams answering a P-256 start.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSealedAudio(frame: Buffer): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The payload of a sealed audio frame ({@link isSealedAudio}), opened with AES-256-GCM under `mediaKey`, the
|
|
24
|
+
* media key of the keyframe before it, with the video frames' AAD. Answers `undefined` for a frame that is not
|
|
25
|
+
* sealed or does not authenticate under that key.
|
|
26
|
+
*/
|
|
27
|
+
export declare function openSealedAudio(frame: Buffer, mediaKey: Buffer): Buffer | undefined;
|
|
@@ -224,8 +224,8 @@ export declare function buildRawCommandPayload(data: Buffer, channel?: number, s
|
|
|
224
224
|
export declare function buildAckPayload(dataTypeHeader: Buffer, seqNo: number): Buffer;
|
|
225
225
|
/** Parse the 16-byte data-frame header that follows the "XZYH" magic. */
|
|
226
226
|
export declare function parseDataFrameHeader(frame: Buffer): P2PDataFrameHeader;
|
|
227
|
-
/**
|
|
228
|
-
export declare function parseLookupAddr(msg: Buffer): Address;
|
|
227
|
+
/** A LOOKUP_ADDR message's leading address record, or `undefined` for a message too short to carry it. */
|
|
228
|
+
export declare function parseLookupAddr(msg: Buffer): Address | undefined;
|
|
229
229
|
/** The relay servers a LIST_REQ_ACK names, in the order listed, without the unspecified address; records past the message's end are dropped. */
|
|
230
230
|
export declare function parseRelayList(msg: Buffer): Address[];
|
|
231
231
|
/** A RLY_PORT_ACK's mark and relay port, or `undefined` for a message too short to carry them. */
|
|
@@ -62,6 +62,8 @@ export interface P2PRouterDeps {
|
|
|
62
62
|
onClose: (stationSn: string) => void;
|
|
63
63
|
onError: (err: Error) => void;
|
|
64
64
|
onLevel2Ready: (stationSn: string, cipherId: number) => void;
|
|
65
|
+
/** The station answered on its own session at `atMs` — a PONG or an ACK from the connected peer. */
|
|
66
|
+
onPeerAnswer?: (stationSn: string, atMs: number) => void;
|
|
65
67
|
/** A raw decoded frame — the client emits the low-level `p2p` event + runs the semantic decode. */
|
|
66
68
|
onFrame: (stationSn: string, frame: P2PFrame) => void;
|
|
67
69
|
/**
|
|
@@ -102,6 +104,18 @@ export declare class P2PCommandRouter {
|
|
|
102
104
|
* {@link liveSources} is a claim in flight.
|
|
103
105
|
*/
|
|
104
106
|
private readonly liveSessionKeys;
|
|
107
|
+
/**
|
|
108
|
+
* The live source build in flight per `${parentSn}:${channel}`, settled once its connection is opened
|
|
109
|
+
* or refused.
|
|
110
|
+
*
|
|
111
|
+
* A build awaits its media session before it can file the source in {@link liveSources}, so a second
|
|
112
|
+
* request for the same camera in that window finds nothing cached. Building a second source there
|
|
113
|
+
* overwrites the first, and the orphan's teardown then closes the connection the cached one streams
|
|
114
|
+
* on. A request that finds a build here waits for it, until its own signal aborts, then resolves its
|
|
115
|
+
* session again and reuses what the build filed, or builds its own when that build failed. The entry
|
|
116
|
+
* is removed when the build settles and its promise never rejects, so each wait ends on a later state.
|
|
117
|
+
*/
|
|
118
|
+
private readonly liveSourceBuilds;
|
|
105
119
|
/**
|
|
106
120
|
* The open talkback per `${parentSn}:${channel}`, if any. The device plays one audio stream at a
|
|
107
121
|
* time and the session carries one audio sequence, so this path is exclusive where a live pull is
|
|
@@ -220,6 +234,11 @@ export declare class P2PCommandRouter {
|
|
|
220
234
|
* so a start issued before the key arrives is dropped as `media-command-unsent`, and the stream then
|
|
221
235
|
* shows nothing until a later keepalive tick happens to find the key. A connected session that cannot
|
|
222
236
|
* carry the start is not a connected session, so this refuses rather than returning one.
|
|
237
|
+
*
|
|
238
|
+
* A session refused its key within the grace, or never connected, is closed and dropped from the manager at
|
|
239
|
+
* once. Nothing else releases it: no source holds it, a wired station's session never idles out, and the
|
|
240
|
+
* manager hands the same session to every later open of the camera, so the next open dials a fresh
|
|
241
|
+
* connection instead. A session another open has since replaced is left to its own close.
|
|
223
242
|
*/
|
|
224
243
|
private openMediaSession;
|
|
225
244
|
/**
|
|
@@ -394,8 +413,9 @@ export declare class P2PCommandRouter {
|
|
|
394
413
|
* {@link releaseLingeringSiblings}. A pull with consumers is never touched. The release runs before the
|
|
395
414
|
* reuse branch, so a reuse frees the station as a cold start does.
|
|
396
415
|
*
|
|
397
|
-
* `
|
|
398
|
-
* connection of its own for it
|
|
416
|
+
* `continuous` decides whether a camera that finds the station's own session claimed may open a
|
|
417
|
+
* connection of its own for it; on an {@link OWN_LIVE_SESSION_MODELS} station it always gets one, and an
|
|
418
|
+
* unwatched source left on the station's session is rebuilt there. Only the continuous-pull egresses pass it. A still must not: it wants one
|
|
399
419
|
* frame, and a socket plus a level-2 negotiation per thumbnail is a cost a tile refresh cannot justify —
|
|
400
420
|
* so a still asked for while a sibling is being watched contends as it always did, and the caller's
|
|
401
421
|
* retained image answers it. A live view still outranks a tile; what changed is that two live views no
|
|
@@ -404,7 +424,7 @@ export declare class P2PCommandRouter {
|
|
|
404
424
|
* The session goes into a {@link HeldSession} cell, so it can be replaced under a source that stays in
|
|
405
425
|
* place.
|
|
406
426
|
*/
|
|
407
|
-
sharedLiveSourceFor(sn: string, opts?: SharedLiveOpts,
|
|
427
|
+
sharedLiveSourceFor(sn: string, opts?: SharedLiveOpts, continuous?: boolean): Promise<SharedLiveSource>;
|
|
408
428
|
/**
|
|
409
429
|
* Tear down any pull on this station that is lingering for ANOTHER camera, before starting this one.
|
|
410
430
|
*
|
|
@@ -559,12 +579,17 @@ export declare class P2PCommandRouter {
|
|
|
559
579
|
* yield CMAF fragments (init segment first, then a `moof`+`mdat` per keyframe boundary) muxed by the
|
|
560
580
|
* dependency-free internal fMP4 muxer. The returned recording handle exposes battery-budget notices
|
|
561
581
|
* and detaches its consumer on `stop`, iterator return, or iterator throw. No ffmpeg.
|
|
582
|
+
*
|
|
583
|
+
* It is a continuous pull only on an {@link OWN_LIVE_SESSION_MODELS} station; elsewhere it never opens a
|
|
584
|
+
* connection of its own.
|
|
562
585
|
*/
|
|
563
586
|
recordFragments(sn: string, opts?: {
|
|
564
587
|
fragmentSeconds?: number;
|
|
565
588
|
eccPrivateKey?: Buffer;
|
|
566
589
|
keepAliveMs?: number;
|
|
567
590
|
} & SharedSourceHints): FragmentRecording;
|
|
591
|
+
/** Whether `stationSn` is an {@link OWN_LIVE_SESSION_MODELS} station. */
|
|
592
|
+
private ownsLiveSession;
|
|
568
593
|
/**
|
|
569
594
|
* **Generic P2P request/reply query.** Sends a `SET_PAYLOAD` (1350) wrapper carrying `subCmd` on the
|
|
570
595
|
* device channel, then resolves with the reply frame's `payload` — the `NOTIFY_PAYLOAD` (1351)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database wire — the ids and the whole-table query a station's on-station table and stored-image
|
|
3
|
+
* reads are built from.
|
|
4
|
+
*
|
|
5
|
+
* A HomeBase answers them over its P2P session and a T9000 over its portal control channel, with the
|
|
6
|
+
* same inner payloads, so both transports build their reads from this one module. The barrel does not
|
|
7
|
+
* re-export it: these are wire ids, not part of the package surface.
|
|
8
|
+
*
|
|
9
|
+
* @module p2p/database-wire
|
|
10
|
+
*/
|
|
11
|
+
/** CMD_DATABASE_IMAGE (1308) — fetch one stored image by its on-station path. */
|
|
12
|
+
export declare const CMD_DATABASE_IMAGE = 1308;
|
|
13
|
+
/** CMD_DATABASE (1306) — query one on-station table; reply carries `{cmd:10000,count,data:[…]}`. */
|
|
14
|
+
export declare const CMD_DATABASE = 1306;
|
|
15
|
+
/**
|
|
16
|
+
* Inner query cmds carried in a {@link CMD_DATABASE} payload. `FULL_TABLE` reads one table whole;
|
|
17
|
+
* `COMBINATION_WITH_AI` is the AI event-history read that bundles the face roster.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DB_QUERY: {
|
|
20
|
+
readonly FULL_TABLE: 10000;
|
|
21
|
+
readonly COMBINATION_WITH_AI: 10011;
|
|
22
|
+
readonly QUERY_LOCAL: 10017;
|
|
23
|
+
};
|
|
24
|
+
/** How long a station's database reply has to arrive whole, when the caller states no bound. */
|
|
25
|
+
export declare const DB_TABLE_TIMEOUT_MS = 15000;
|
|
26
|
+
/**
|
|
27
|
+
* The query params the vendor app sends with a whole-table `CMD_DATABASE` read (inner `cmd 10000`).
|
|
28
|
+
* `count` bounds the row count.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fullTableQuery(count?: number): Record<string, unknown>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bound a caller waits for the level-2 negotiation to SETTLE, measured from connect.
|
|
3
|
+
*
|
|
4
|
+
* For a caller that picks its seal once from `P2PSession.hasLevel2Key` and has no second chance. A media
|
|
5
|
+
* start reads the key on every send and is re-issued by the warm-up, so it needs no wait; a property write
|
|
6
|
+
* framed level-1 to a family that only accepts level-2 is ignored, and nothing re-frames it. Session-scoped,
|
|
7
|
+
* so a station that offers no key does not charge this to every later command.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LEVEL2_SETTLE_MS = 8000;
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* - **Plaintext** (`signCode 0`, e.g. HomeBase-attached cameras): a fixed 22-byte frame header
|
|
8
8
|
* (`parseVideoFrameHeader`) followed directly by Annex-B NAL units — verified live (960×540,
|
|
9
9
|
* keyframe = header flag bit0). We strip the 22-byte header.
|
|
10
|
-
* - **Encrypted
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* - **Encrypted keyframes**: either the AES-256-GCM body under a media key wrapped in an ECIES envelope
|
|
11
|
+
* for an offered P-256 key ({@link VideoFrameDecoder}), or an AES-ECB head under a media key wrapped for
|
|
12
|
+
* the session's RSA key ({@link P2PSession.decodeVideoFrame}). An attached camera's start on a station
|
|
13
|
+
* with a {@link P2PSession.mediaEccPrivateKey} offers that P-256 key first, so its stream decodes with
|
|
14
|
+
* that pair unless `eccPrivateKey` names another.
|
|
13
15
|
*
|
|
14
16
|
* Emits: `video` ({@link LiveVideoFrame} — Annex-B), `audio` ({@link LiveAudioFrame}), `start`, `stop`,
|
|
15
17
|
* `error`.
|
|
@@ -54,7 +56,10 @@ export interface LiveStreamOptions {
|
|
|
54
56
|
* however long the station keeps serving that sibling instead of this one.
|
|
55
57
|
*/
|
|
56
58
|
channel?: number;
|
|
57
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* P-256 private key (32B) whose public point the camera wraps each keyframe's media key for. Defaults, on an
|
|
61
|
+
* attached camera, to the session's `P2PSession.mediaEccPrivateKey`, the pair its start offers.
|
|
62
|
+
*/
|
|
58
63
|
eccPrivateKey?: Buffer;
|
|
59
64
|
/** Admin account id — required for the level-2 (`signCode 8`) media-start payload selecting a camera. */
|
|
60
65
|
accountId?: string;
|
|
@@ -112,6 +117,8 @@ export declare class LiveStream extends EventEmitter {
|
|
|
112
117
|
private tracedFirstVideoUnit;
|
|
113
118
|
private tracedFirstKeyframe;
|
|
114
119
|
private tracedDecodeFailures;
|
|
120
|
+
/** How the frame that opened the latest access unit was read; see {@link settleStartKey}. */
|
|
121
|
+
private unitOpenedBy;
|
|
115
122
|
private tracedFirstForeignFrame;
|
|
116
123
|
private stallTimer?;
|
|
117
124
|
/**
|
|
@@ -173,6 +180,7 @@ export declare class LiveStream extends EventEmitter {
|
|
|
173
180
|
stop(): void;
|
|
174
181
|
/** Extract the Annex-B payload from a plaintext 1300 frame (fixed 22-byte header), or undefined. */
|
|
175
182
|
private plaintextAnnexB;
|
|
183
|
+
/** Emit one accepted media frame; an audio frame with no bytes past its header emits nothing. */
|
|
176
184
|
private onFrame;
|
|
177
185
|
/**
|
|
178
186
|
* Whether this stream may take `frame` — the demultiplexer for a station that serves several cameras.
|
|
@@ -198,11 +206,30 @@ export declare class LiveStream extends EventEmitter {
|
|
|
198
206
|
*/
|
|
199
207
|
private acceptsMedia;
|
|
200
208
|
/**
|
|
201
|
-
* The Annex-B payload of a frame that starts an access unit. The
|
|
202
|
-
*
|
|
203
|
-
*
|
|
209
|
+
* The Annex-B payload of a frame that starts an access unit. The authenticated ECIES/GCM
|
|
210
|
+
* {@link VideoFrameDecoder} goes first: its HMAC and GCM checks fail closed on a frame not sealed for its
|
|
211
|
+
* key. The session's RSA path cannot: on Node ≥ 24.5, PKCS#1 v1.5 decryption of a key not wrapped for it
|
|
212
|
+
* returns pseudo-random bytes instead of throwing (implicit rejection), which would decode an ECC
|
|
213
|
+
* keyframe into noise. The session's decode, which also passes plaintext frames, and the header strip
|
|
214
|
+
* follow. A payload that opens an access unit records how it was read: by the ECIES path, or otherwise by
|
|
215
|
+
* its sign code, sealed (`"rsa"`) or in the clear (`"plain"`).
|
|
204
216
|
*/
|
|
205
217
|
private annexbOf;
|
|
218
|
+
/**
|
|
219
|
+
* Tell the session how this keyframe was opened, which answers the attached starts on this channel
|
|
220
|
+
* ({@link P2PSession.settleAttachedKey}), and re-issue the start when the session asks for one: a plaintext
|
|
221
|
+
* keyframe answering the session's P-256 offer, from a camera that did not use the point, which the next
|
|
222
|
+
* start asks with RSA. Only a delivered keyframe answers: a frame no key opens, or a continuation without the
|
|
223
|
+
* frame that opened its unit, delivers nothing.
|
|
224
|
+
*/
|
|
225
|
+
private settleStartKey;
|
|
226
|
+
/**
|
|
227
|
+
* The payload of an audio frame. On a stream with an ECIES decoder, a sealed frame ({@link isSealedAudio}) is
|
|
228
|
+
* opened under the media key of the latest keyframe, and answers `undefined` when it does not open or arrives
|
|
229
|
+
* before any keyframe gave a media key, since its bytes are ciphertext. Every other frame is the bytes after
|
|
230
|
+
* its header, empty for a frame that carries only its header.
|
|
231
|
+
*/
|
|
232
|
+
private audioPayload;
|
|
206
233
|
/**
|
|
207
234
|
* Report an access unit the transport could not complete.
|
|
208
235
|
*
|
|
@@ -23,6 +23,8 @@ export type LiveTrace =
|
|
|
23
23
|
topology: "attached" | "own";
|
|
24
24
|
action: "start" | "keepalive";
|
|
25
25
|
level2: boolean;
|
|
26
|
+
/** The media-key type an attached start offered. */
|
|
27
|
+
key?: "ecc" | "rsa";
|
|
26
28
|
}
|
|
27
29
|
/** The device acknowledged a retained start, or that start was repeated / abandoned unacknowledged. */
|
|
28
30
|
| {
|
|
@@ -137,10 +139,10 @@ export type LiveTrace =
|
|
|
137
139
|
*
|
|
138
140
|
* Every ending of a level-2 wait carries one of these reasons, so a start refused for want of a key is
|
|
139
141
|
* accounted for however it ended. `grace-elapsed` is a wait that ran out and states how long was waited;
|
|
140
|
-
* the rest are answered without waiting, because
|
|
141
|
-
*
|
|
142
|
-
* material, `not-negotiating` and `session-closed` about the station or its connection —
|
|
143
|
-
* reason reached under a negotiation has a cipher to name.
|
|
142
|
+
* the rest are answered without waiting, because a concluded negotiation stays concluded until a
|
|
143
|
+
* re-prompt for `CMD_GATEWAYINFO` re-opens it. `no-cipher-key` and `derivation-failed` are about this
|
|
144
|
+
* account's cipher material, `not-negotiating` and `session-closed` about the station or its connection —
|
|
145
|
+
* and only a reason reached under a negotiation has a cipher to name.
|
|
144
146
|
*
|
|
145
147
|
* A `grace-elapsed` start proceeds at level 1 where it has such a form, and not at all where it does not.
|
|
146
148
|
*/
|