@mega-yfue/eufy-sdk 0.6.0-beta.4 → 0.6.0-beta.40
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 +103 -14
- package/dist/client/push-repeats.d.ts +10 -0
- package/dist/client/types.d.ts +12 -9
- package/dist/core/contracts.d.ts +28 -14
- package/dist/core/store.d.ts +12 -1
- package/dist/core/types.d.ts +4 -2
- package/dist/index.js +3332 -2431
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/access.d.ts +3 -2
- package/dist/model/capabilities/arming.d.ts +2 -1
- package/dist/model/capabilities/battery.d.ts +8 -7
- package/dist/model/capabilities/camera.d.ts +13 -2
- package/dist/model/capabilities/doorbell.d.ts +15 -3
- package/dist/model/capabilities/index.d.ts +1 -1
- package/dist/model/capabilities/members.d.ts +3 -1
- package/dist/model/capabilities/mower.d.ts +3 -2
- package/dist/model/capabilities/ptz.d.ts +11 -5
- package/dist/model/capabilities/vacuum-clean.d.ts +23 -0
- package/dist/model/device-family.d.ts +3 -1
- package/dist/model/device-types.d.ts +1 -0
- package/dist/transport/http/mega-client.d.ts +18 -5
- 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 +8 -2
- package/dist/transport/p2p/command-router.d.ts +48 -13
- 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 +7 -5
- package/dist/transport/p2p/p2p-session.d.ts +112 -16
- package/dist/transport/p2p/readable-egress.d.ts +3 -1
- package/dist/transport/p2p/recording-download.d.ts +109 -13
- package/dist/transport/p2p/shared-live-source.d.ts +9 -3
- package/dist/transport/p2p/video.d.ts +12 -6
- package/dist/transport/push/store.d.ts +7 -6
- package/dist/transport/rtc/command-router.d.ts +86 -9
- package/dist/transport/rtc/live.d.ts +4 -1
- package/dist/transport/rtc/portal-packet.d.ts +3 -13
- package/dist/transport/rtc/session.d.ts +19 -2
- package/dist/transport/stored-image-cache.d.ts +14 -0
- package/package.json +1 -1
|
@@ -248,6 +248,12 @@ export declare class DeviceRegistry {
|
|
|
248
248
|
* for an expired token is the same lie {@link getDevices} stopped telling, one level down.
|
|
249
249
|
*/
|
|
250
250
|
refreshedList(sn: string): Promise<EufyDevice | undefined>;
|
|
251
|
+
/**
|
|
252
|
+
* Record that `sn` was heard on a realtime wire at `atMs`, advancing its {@link EufyDevice.lastSeenMs}.
|
|
253
|
+
*
|
|
254
|
+
* Only ever moves the time forward, and survives a {@link getDevices} rebuild.
|
|
255
|
+
*/
|
|
256
|
+
markSeen(sn: string, atMs: number): void;
|
|
251
257
|
/**
|
|
252
258
|
* Record state a device reported over its realtime wire, merging into whatever it last reported.
|
|
253
259
|
*
|
|
@@ -20,8 +20,8 @@ import { type P2PSession } from "../transport/p2p/p2p-session.js";
|
|
|
20
20
|
import { type CleanRecordPage } from "../model/clean-records.js";
|
|
21
21
|
import { type AvailabilityObservation, type EufyDevice } from "../core/types.js";
|
|
22
22
|
import { Device, type DeviceInspection } from "../model/index.js";
|
|
23
|
-
import type { EufyMegaOptions, EufyMegaEvent, EufyMegaEventMap, DeviceState, RealtimeReadiness, StationFace,
|
|
24
|
-
export type { EufyMegaOptions, EufyMegaEvent, EufyMegaEventMap, AnyDeviceEvent, DeviceState, RealtimePlaneReadiness, RealtimeReadiness, StationFace,
|
|
23
|
+
import type { EufyMegaOptions, EufyMegaEvent, EufyMegaEventMap, DeviceState, RealtimeReadiness, StationFace, StationReadOptions, WaitForRealtimeOptions } from "./types.js";
|
|
24
|
+
export type { EufyMegaOptions, EufyMegaEvent, EufyMegaEventMap, AnyDeviceEvent, DeviceState, RealtimePlaneReadiness, RealtimeReadiness, StationFace, StationReadOptions, WaitForRealtimeOptions, } from "./types.js";
|
|
25
25
|
export interface EufyMega {
|
|
26
26
|
on<E extends EufyMegaEvent>(event: E, listener: (...args: EufyMegaEventMap[E]) => void): this;
|
|
27
27
|
once<E extends EufyMegaEvent>(event: E, listener: (...args: EufyMegaEventMap[E]) => void): this;
|
|
@@ -54,9 +54,20 @@ export declare class EufyMega extends EventEmitter {
|
|
|
54
54
|
/** Shared in-flight/settled `startMqtt()` promise PER SCOPE — so realtime bring-up and an on-demand
|
|
55
55
|
* publish await the SAME fully-connected transport instead of racing (and never open two). */
|
|
56
56
|
private readonly mqttReady;
|
|
57
|
+
/**
|
|
58
|
+
* Serials {@link subscribeMqttDevices} has subscribed or is subscribing, per transport, so a later pass
|
|
59
|
+
* subscribes only devices that transport has not carried yet. Keyed by transport, so a new connection
|
|
60
|
+
* starts empty.
|
|
61
|
+
*/
|
|
62
|
+
private readonly mqttSubscribed;
|
|
57
63
|
/** Device list/record/capability resolution + the frame→caps cache. */
|
|
58
64
|
private readonly registry;
|
|
59
65
|
private pushClient?;
|
|
66
|
+
/**
|
|
67
|
+
* Second FCM deliveries of one detection. Only pulse events (no {@link STATE_EVENT_FIELDS} entry) are
|
|
68
|
+
* held back on a repeat; state-carrying events are already edge-triggered by {@link noteState}.
|
|
69
|
+
*/
|
|
70
|
+
private readonly pushRepeats;
|
|
60
71
|
/** Push-fed passive image store; absent when the caller disables acquisition. */
|
|
61
72
|
private readonly storedImages?;
|
|
62
73
|
/** Account whose retained images are currently held. */
|
|
@@ -123,6 +134,20 @@ export declare class EufyMega extends EventEmitter {
|
|
|
123
134
|
private readonly pollTimer;
|
|
124
135
|
/** Armed while a failed push bring-up waits to be retried; cancelled by {@link disconnect}. */
|
|
125
136
|
private readonly pushRetryTimer;
|
|
137
|
+
/**
|
|
138
|
+
* Per wired station, the pending reopen of its session and the pause before the next one; see
|
|
139
|
+
* {@link reopenWiredStation}. Dropped for a station when its session connects, and wholly by
|
|
140
|
+
* {@link disconnect}.
|
|
141
|
+
*/
|
|
142
|
+
private readonly stationReopens;
|
|
143
|
+
/**
|
|
144
|
+
* Refuse the one misnamed option that misroutes silently. `country` is not read — the account country
|
|
145
|
+
* is {@link EufyMegaOptions.countryCode}, which defaults to `"US"` — so a caller that passes `country`
|
|
146
|
+
* has it ignored. We throw only for the case that changes routing: a non-`"US"` `country` with no
|
|
147
|
+
* `countryCode`, which would run on the US shard (login succeeds, but the house list comes back an empty
|
|
148
|
+
* placeholder). `country: "US"` already matches the default route, so it is left to pass through.
|
|
149
|
+
*/
|
|
150
|
+
private static assertCountryOption;
|
|
126
151
|
constructor(opts: EufyMegaOptions);
|
|
127
152
|
/**
|
|
128
153
|
* Builds the inbound listener that converts ThingClips DP maps to realtime capability state.
|
|
@@ -307,7 +332,17 @@ export declare class EufyMega extends EventEmitter {
|
|
|
307
332
|
* manager's own log, since by then nothing is waiting to receive it.
|
|
308
333
|
*/
|
|
309
334
|
private recycleStandaloneSession;
|
|
310
|
-
/**
|
|
335
|
+
/**
|
|
336
|
+
* Reopen a wired station's session while the generation that warmed it is current: after
|
|
337
|
+
* {@link recycleStandaloneSession} closed it, after it closed on its own, or after its warm-up failed.
|
|
338
|
+
*
|
|
339
|
+
* A wired station's session is the persistent one realtime start opened, so a station left without one
|
|
340
|
+
* receives nothing until a command dials it again. The first reopen runs at once; one that fails is
|
|
341
|
+
* retried after {@link STATION_REOPEN_MIN_MS}, doubling up to {@link STATION_REOPEN_MAX_MS}, until the
|
|
342
|
+
* session connects or the generation ends. One reopen is pending per station at a time, and each failure
|
|
343
|
+
* is reported once, by {@link P2PCommandRouter.ensureStation}. A HomeBase S1 Pro (T9000) answers no P2P
|
|
344
|
+
* lookup, so it is never reopened.
|
|
345
|
+
*/
|
|
311
346
|
private reopenWiredStation;
|
|
312
347
|
/**
|
|
313
348
|
* Whether the DECODED property now reads what the write asked for.
|
|
@@ -333,6 +368,8 @@ export declare class EufyMega extends EventEmitter {
|
|
|
333
368
|
* fans same-kind sensors out by channel, so the resolved serial is folded in; a payload's own
|
|
334
369
|
* fields still win on conflict.
|
|
335
370
|
*
|
|
371
|
+
* A station's camera-info table lands per channel, each channel's params on the device attached there.
|
|
372
|
+
*
|
|
336
373
|
* Params the station volunteered land as device state first. They arrive in the cloud record's own
|
|
337
374
|
* `param_type → value` shape, so no capability has to claim an id it does not own, and the reads
|
|
338
375
|
* they back stop waiting for the next cloud poll. A capability decode follows for the frames that
|
|
@@ -385,7 +422,9 @@ export declare class EufyMega extends EventEmitter {
|
|
|
385
422
|
/**
|
|
386
423
|
* On a successful login, kick off auto-realtime (unless `autoRealtime:false`). Fire-and-forget so
|
|
387
424
|
* `login()` returns as soon as the session is ready — realtime channels come up in the background and
|
|
388
|
-
* surface failures via `error`. Idempotent through the retained generation promise.
|
|
425
|
+
* surface failures via `error`. Idempotent through the retained generation promise. A login for a
|
|
426
|
+
* different account drops every stored image; one for the same account drops only the lifecycle
|
|
427
|
+
* failures recorded against the session it replaces.
|
|
389
428
|
*/
|
|
390
429
|
private afterLogin;
|
|
391
430
|
/**
|
|
@@ -458,7 +497,8 @@ export declare class EufyMega extends EventEmitter {
|
|
|
458
497
|
private rtcRouteFor;
|
|
459
498
|
/**
|
|
460
499
|
* The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
|
|
461
|
-
* `device_channel` resolves back to it on the station; `undefined` otherwise
|
|
500
|
+
* `device_channel` resolves back to it on the station; `undefined` otherwise. A camera attached to a T8N00 gets the
|
|
501
|
+
* NVR's dedicated media call when {@link isNvrMediaCamera} admits it.
|
|
462
502
|
*/
|
|
463
503
|
private rtcMediaFor;
|
|
464
504
|
/**
|
|
@@ -534,6 +574,8 @@ export declare class EufyMega extends EventEmitter {
|
|
|
534
574
|
* first announces it and the others stay silent. Its timing says only when a caller happened to read; the
|
|
535
575
|
* value is the news. It applies what the device volunteered over realtime on top of the cloud half, which
|
|
536
576
|
* the registry keeps apart, so it can neither revert nor announce a revert of a report already landed.
|
|
577
|
+
* Nothing awaits that refresh, so its failure is reported like the other background paths' — a dead
|
|
578
|
+
* token as `sessionExpired`, anything else as `error` — and the read keeps its last-known value.
|
|
537
579
|
*
|
|
538
580
|
* The `Device` returned is held WEAKLY: it is what the inbound paths announce against, so a caller that
|
|
539
581
|
* wants property changes for a serial keeps its own reference. Dropping it stops the announcements, not
|
|
@@ -624,6 +666,12 @@ export declare class EufyMega extends EventEmitter {
|
|
|
624
666
|
* device absent from the previous pass is skipped — first sight is discovery, not a transition;
|
|
625
667
|
* {@link deviceState} answers an initial reading.
|
|
626
668
|
*
|
|
669
|
+
* A device with changed params is re-resolved by {@link widenCapabilities} once per pass, however many
|
|
670
|
+
* of its params moved, since each re-resolution is a cloud param-list read.
|
|
671
|
+
*
|
|
672
|
+
* Then brings the secure-MQTT plane up to the roster ({@link syncMqttRoster}): the startup bring-up
|
|
673
|
+
* sized it from the roster at login, which is empty when the cloud was unreachable then.
|
|
674
|
+
*
|
|
627
675
|
* Then re-reads every Tuya clean-line robot from the Tuya cloud ({@link readTuyaDevices}): its state
|
|
628
676
|
* is not in the cloud record this pass diffs and no live channel carries it, so the poll is where it
|
|
629
677
|
* stays fresh. That read runs after the `try`, not inside it, so a cloud error on the list diff does
|
|
@@ -737,7 +785,9 @@ export declare class EufyMega extends EventEmitter {
|
|
|
737
785
|
*/
|
|
738
786
|
keepStationWarm(stationSn: string): () => void;
|
|
739
787
|
/** Eagerly open P2P sessions for WIRED stations only (persistent — they don't drain). Battery
|
|
740
|
-
* stations stay closed until an on-demand open
|
|
788
|
+
* stations stay closed until an on-demand open, and a HomeBase S1 Pro (T9000), which answers no P2P
|
|
789
|
+
* lookup and is driven over RTC, is never opened here. Best-effort per station: one that fails is
|
|
790
|
+
* handed to {@link reopenWiredStation}. */
|
|
741
791
|
private warmWiredP2P;
|
|
742
792
|
/**
|
|
743
793
|
* A station's power tier for the P2P lifecycle: a HomeBase/station is `"wired"` (persistent); a
|
|
@@ -753,10 +803,11 @@ export declare class EufyMega extends EventEmitter {
|
|
|
753
803
|
* semantic event in — so a stream or talkback opened right after a doorbell ring or a detection starts
|
|
754
804
|
* warm instead of paying a cold open.
|
|
755
805
|
*
|
|
756
|
-
*
|
|
806
|
+
* Five gates. `autoRealtime: false` means the SDK opens nothing on its own initiative at all;
|
|
757
807
|
* {@link EufyMegaOptions.prewarmEvents} must name the event, and it names none by default, which is
|
|
758
|
-
* what makes pre-warm opt-in; the station must be one the account actually reports;
|
|
759
|
-
*
|
|
808
|
+
* what makes pre-warm opt-in; the station must be one the account actually reports; it must not be a
|
|
809
|
+
* HomeBase S1 Pro (T9000), which answers no P2P lookup and is driven over RTC; and its power tier must
|
|
810
|
+
* be one {@link EufyMegaOptions.prewarmTiers} allows.
|
|
760
811
|
*
|
|
761
812
|
* The tier is resolved for the STATION whose session would open, which is why an attached camera is
|
|
762
813
|
* judged by its base — {@link P2PCommandRouter.stationKeyOf} is the single source of that mapping, and
|
|
@@ -810,11 +861,20 @@ export declare class EufyMega extends EventEmitter {
|
|
|
810
861
|
/**
|
|
811
862
|
* Subscribe the devices on one credential scope; a failing subscribe is reported, not fatal (one
|
|
812
863
|
* unreachable device must not stop the rest of the roster from coming up).
|
|
864
|
+
* A device is recorded in that transport's {@link mqttSubscribed} entry before its subscribe is awaited,
|
|
865
|
+
* whatever the outcome, so a pass running alongside or after skips it: each device is attempted once
|
|
866
|
+
* per transport.
|
|
813
867
|
*
|
|
814
868
|
* A Tuya clean-line device (`eufy_home_tuya`) is not subscribed: its state lives on the Tuya cloud,
|
|
815
869
|
* and the broker refuses its `eufy_home` topics on every credential.
|
|
816
870
|
*/
|
|
817
871
|
private subscribeMqttDevices;
|
|
872
|
+
/**
|
|
873
|
+
* Start every credential scope the roster now needs and has no transport for, and subscribe the
|
|
874
|
+
* devices that joined a scope already up — each scope's failure reported, not thrown. A no-op once
|
|
875
|
+
* the realtime generation is superseded or was never started.
|
|
876
|
+
*/
|
|
877
|
+
private syncMqttRoster;
|
|
818
878
|
/**
|
|
819
879
|
* Send whatever a device's capabilities want sent once its realtime channel is up — for a line whose
|
|
820
880
|
* state is pushed on change with no heartbeat, the request that makes its state readable before the
|
|
@@ -835,19 +895,45 @@ export declare class EufyMega extends EventEmitter {
|
|
|
835
895
|
*/
|
|
836
896
|
getP2pSessions(): Map<string, P2PSession>;
|
|
837
897
|
/**
|
|
838
|
-
* The people enrolled on a station, read from its own `person_basic_info` table
|
|
898
|
+
* The people enrolled on a station, read from its own `person_basic_info` table through
|
|
899
|
+
* {@link readStationTable}.
|
|
839
900
|
*
|
|
840
901
|
* The cloud roster (`api.getFaces()`) answers empty for an account that keeps its faces on the
|
|
841
902
|
* station, because they were never uploaded — the station holds them.
|
|
842
903
|
*
|
|
843
|
-
* Resolves with the rows the station returned
|
|
844
|
-
*
|
|
845
|
-
* reply is `P2PSession.readDatabase`'s; this resolves which station and which account id.
|
|
904
|
+
* Resolves with the rows the station returned that are objects. Rejects as {@link readStationTable}
|
|
905
|
+
* does.
|
|
846
906
|
*
|
|
847
907
|
* `stranger<n>` names are the station's own placeholders for a face nobody has named, and are
|
|
848
908
|
* returned as they arrive; a caller that wants only named people filters them.
|
|
849
909
|
*/
|
|
850
|
-
getStationFaces(stationSn: string, opts?:
|
|
910
|
+
getStationFaces(stationSn: string, opts?: StationReadOptions): Promise<StationFace[]>;
|
|
911
|
+
/**
|
|
912
|
+
* The rows of one of a station's own tables (`history_record_info`, `person_basic_info`, …), read over
|
|
913
|
+
* the transport the station speaks: the control channel of a HomeBase S1 Pro (T9000), P2P for any
|
|
914
|
+
* other station. The query is the table's whole content; the rows are the station's, shaped by it.
|
|
915
|
+
*
|
|
916
|
+
* Resolves with the rows the station returned. Rejects on a serial not in the device list, on a record
|
|
917
|
+
* stating no `admin_user_id`, on a station that cannot be reached, on a query the station refuses, and
|
|
918
|
+
* on the reply not arriving in time. Assembling the reply is the transport's; this resolves which
|
|
919
|
+
* station, which transport and which account id.
|
|
920
|
+
*/
|
|
921
|
+
readStationTable(stationSn: string, table: string, opts?: StationReadOptions): Promise<unknown[]>;
|
|
922
|
+
/**
|
|
923
|
+
* One image stored on a HomeBase S1 Pro (T9000), by the on-station path a table row names
|
|
924
|
+
* (`thumb_path`, …), read over the station's control channel.
|
|
925
|
+
*
|
|
926
|
+
* Resolves with the image bytes as the station holds them. Rejects on a serial not in the device list,
|
|
927
|
+
* on a record stating no `admin_user_id`, on a station other than a T9000, on a station that cannot be
|
|
928
|
+
* reached, and on the reply not arriving in time. Fetching the image is the transport's; this resolves
|
|
929
|
+
* which station and which account id.
|
|
930
|
+
*/
|
|
931
|
+
readStationImage(stationSn: string, filePath: string, opts?: StationReadOptions): Promise<Buffer>;
|
|
932
|
+
/**
|
|
933
|
+
* The station a database read goes to, its `admin_user_id` and, for a T9000, its RTC route. Throws,
|
|
934
|
+
* prefixed with `op`, on a serial the device list does not carry and on a record stating no admin id.
|
|
935
|
+
*/
|
|
936
|
+
private stationReadTarget;
|
|
851
937
|
/** The station's own `admin_user_id`, which its database refuses the query without. */
|
|
852
938
|
private adminUserIdOf;
|
|
853
939
|
/**
|
|
@@ -959,6 +1045,9 @@ export declare class EufyMega extends EventEmitter {
|
|
|
959
1045
|
* Returns the connected client rather than installing it. Registration can outlive a `disconnect()`,
|
|
960
1046
|
* and {@link ensureRealtime} owns the decision of whether a finished bring-up is still the current
|
|
961
1047
|
* one — so this never overwrites a channel a later login already brought up.
|
|
1048
|
+
*
|
|
1049
|
+
* Persisting the seen ids after each push reports a failed `save` through `error` rather than throwing:
|
|
1050
|
+
* the push handler runs inside the MCS socket's `data` callback, where a throw is an uncaught exception.
|
|
962
1051
|
*/
|
|
963
1052
|
private startPush;
|
|
964
1053
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PushEvent } from "../transport/push/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A push repeats an earlier one with the same device, `event_type` and `unique_id` within the window,
|
|
4
|
+
* and a push missing any of the three never does.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PushRepeats {
|
|
7
|
+
private readonly seen;
|
|
8
|
+
/** Record this push, answering `true` when it repeats one already recorded. */
|
|
9
|
+
repeat(event: PushEvent): boolean;
|
|
10
|
+
}
|
package/dist/client/types.d.ts
CHANGED
|
@@ -202,9 +202,11 @@ export interface EufyMegaOptions extends MegaClientConfig {
|
|
|
202
202
|
* that choice on every host. The SDK reports when the device last spoke; the caller decides what that
|
|
203
203
|
* means — the same split as the snapshot cache TTL and the live power budget.
|
|
204
204
|
*
|
|
205
|
-
* P2P session
|
|
206
|
-
* when something needs one and closed when idle, so "no session" is the resting state of a healthy
|
|
207
|
-
* device.
|
|
205
|
+
* The absence of a P2P session is **not** a liveness signal and is not carried here: sessions are opened
|
|
206
|
+
* only when something needs one and closed when idle, so "no session" is the resting state of a healthy
|
|
207
|
+
* device. A station answering an open session is the opposite fact, positive evidence, and does advance
|
|
208
|
+
* the station's own {@link DeviceState.lastSeenMs} — never that of a device it covers, whose liveness its answer does
|
|
209
|
+
* not show. Transport visibility lives on `getP2pSessions()` and the `p2pConnect`/`p2pClose` events,
|
|
208
210
|
* station-scoped like the session itself.
|
|
209
211
|
*
|
|
210
212
|
* The cloud record carries no connectivity field either: it has no `status` / `device_online`, and the
|
|
@@ -215,8 +217,9 @@ export interface DeviceState {
|
|
|
215
217
|
/** The parent station whose P2P session covers this device (itself, when standalone). */
|
|
216
218
|
stationSn: string;
|
|
217
219
|
/**
|
|
218
|
-
* When the device last
|
|
219
|
-
*
|
|
220
|
+
* When the device was last heard from, in ms: its last report to the cloud, or a later one over a
|
|
221
|
+
* realtime wire, a station's heartbeat answer on its open P2P session included. The cloud half is bounded
|
|
222
|
+
* by the cloud's own slow refresh — minutes, not seconds — so it answers "is this device alive at all",
|
|
220
223
|
* not "what is it doing right now".
|
|
221
224
|
*/
|
|
222
225
|
lastSeenMs?: number;
|
|
@@ -240,9 +243,9 @@ export interface StationFace {
|
|
|
240
243
|
group_id?: number | string;
|
|
241
244
|
[column: string]: unknown;
|
|
242
245
|
}
|
|
243
|
-
/** How long to wait for a station's
|
|
244
|
-
export interface
|
|
245
|
-
/** Default 15s. A
|
|
246
|
+
/** How long to wait for a station's reply to a read, and how to give up early. */
|
|
247
|
+
export interface StationReadOptions {
|
|
248
|
+
/** Default 15s. A station that will not answer says nothing. */
|
|
246
249
|
timeoutMs?: number;
|
|
247
250
|
signal?: AbortSignal;
|
|
248
251
|
}
|
|
@@ -332,7 +335,7 @@ export type EufyMegaEventMap = {
|
|
|
332
335
|
map: VacuumMapSnapshot;
|
|
333
336
|
}];
|
|
334
337
|
/**
|
|
335
|
-
* A device
|
|
338
|
+
* A device was heard from since the last poll — its {@link DeviceState.lastSeenMs} advanced.
|
|
336
339
|
* Carries {@link DeviceState}; the host applies its own staleness threshold.
|
|
337
340
|
*
|
|
338
341
|
* Transport/session lifecycle is NOT this event: that's `p2pConnect`/`p2pClose`, station-scoped where
|
package/dist/core/contracts.d.ts
CHANGED
|
@@ -153,9 +153,10 @@ export declare class DeviceChannelUnresolvedError extends Error {
|
|
|
153
153
|
/**
|
|
154
154
|
* Why {@link MediaProvider.downloadRecording} returned no recording.
|
|
155
155
|
*
|
|
156
|
-
* - `invalid-recording` — the recording name is not one the station could hold.
|
|
157
|
-
* - `key-unavailable` — the
|
|
158
|
-
*
|
|
156
|
+
* - `invalid-recording` — the recording name is not one the camera's station could hold.
|
|
157
|
+
* - `key-unavailable` — the station stores the recording sealed and the key it was sealed under could not be
|
|
158
|
+
* obtained.
|
|
159
|
+
* - `no-data` — the station sent no recording: it answered that it holds none, or sent nothing within the wait.
|
|
159
160
|
* - `incomplete` — the transfer ended before the station's finish frame.
|
|
160
161
|
* - `undecodable` — data arrived but no video frame could be decoded from it.
|
|
161
162
|
*/
|
|
@@ -170,14 +171,16 @@ export declare class RecordingDownloadError extends Error {
|
|
|
170
171
|
/**
|
|
171
172
|
* A recording downloaded from the station, as elementary streams.
|
|
172
173
|
*
|
|
173
|
-
* `video` is Annex-B H.264 in decode order, `audio` AAC-LC 16 kHz mono in ADTS framing (absent
|
|
174
|
-
* recording carried none that could be decoded). `
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
174
|
+
* `video` is Annex-B H.264 or H.265 in decode order, `audio` AAC-LC 16 kHz mono in ADTS framing (absent
|
|
175
|
+
* when the recording carried none that could be decoded). `codec` is the codec of `video`, read off the parameter
|
|
176
|
+
* sets of its first keyframe that carries them, and is absent when no keyframe does. `frames` counts the pictures
|
|
177
|
+
* emitted, numbered by the camera's frame numbers counted on across a change of frame size. `missingFrames` counts
|
|
178
|
+
* gaps in that numbering and numbered frames that yielded no usable picture, including at either end. `durationMs`
|
|
179
|
+
* spans the first to the last emitted picture by the camera's own timestamps; `fps` is derived from those stamps.
|
|
178
180
|
*/
|
|
179
181
|
export interface RecordingDownload {
|
|
180
182
|
video: Buffer;
|
|
183
|
+
codec?: VideoCodec;
|
|
181
184
|
audio?: Buffer;
|
|
182
185
|
frames: number;
|
|
183
186
|
missingFrames: number;
|
|
@@ -344,6 +347,8 @@ export type Command = {
|
|
|
344
347
|
cmd: number;
|
|
345
348
|
value: number;
|
|
346
349
|
channel: number;
|
|
350
|
+
/** `"auto"`: the seal follows the session, level-2 with a key and level-1 without. Absent, level-2 only. */
|
|
351
|
+
form?: Extract<ScalarForm, "auto">;
|
|
347
352
|
}
|
|
348
353
|
/** P2P int-plus-string frame; the transport injects the authenticated account id string. */
|
|
349
354
|
| {
|
|
@@ -490,7 +495,7 @@ export interface TuyaDpInbound {
|
|
|
490
495
|
*/
|
|
491
496
|
onDps(sn: string, dps: Record<number, boolean | number | string>): void;
|
|
492
497
|
}
|
|
493
|
-
/** Elementary-stream video codec
|
|
498
|
+
/** Elementary-stream video codec — eufy cameras stream H.264 or H.265. */
|
|
494
499
|
export type VideoCodec = "h264" | "h265" | "av1";
|
|
495
500
|
/**
|
|
496
501
|
* One decoded video access unit, as Annex-B (H.264 or H.265).
|
|
@@ -742,9 +747,12 @@ export interface LiveStreamConsumer extends LiveStreamHandle {
|
|
|
742
747
|
export interface AbortableCall {
|
|
743
748
|
signal?: AbortSignal;
|
|
744
749
|
}
|
|
745
|
-
/**
|
|
750
|
+
/** NVR output size requested when a live pull starts. */
|
|
751
|
+
export type MediaOutputResolution = "1080p";
|
|
752
|
+
/** Select an optical view and output size; absence selects view 0 at the route's default output. */
|
|
746
753
|
export interface MediaSourceSelection {
|
|
747
754
|
view?: number;
|
|
755
|
+
outputResolution?: MediaOutputResolution;
|
|
748
756
|
}
|
|
749
757
|
export interface SharedSourceHints {
|
|
750
758
|
/**
|
|
@@ -771,6 +779,8 @@ export interface SharedSourceHints {
|
|
|
771
779
|
* because an unbound model has no live client (hence `?.` at the call site).
|
|
772
780
|
*/
|
|
773
781
|
export interface MediaProvider {
|
|
782
|
+
/** Output sizes supported by this bound media route. Absence means no size selection. */
|
|
783
|
+
outputResolutions?: readonly MediaOutputResolution[];
|
|
774
784
|
/** Optical view identifiers supported by this bound media route. */
|
|
775
785
|
views?: readonly number[];
|
|
776
786
|
/**
|
|
@@ -852,7 +862,9 @@ export interface MediaProvider {
|
|
|
852
862
|
* {@link ReceivedVideoFrame} envelopes whose original source-arrival time survives retained replay.
|
|
853
863
|
* Raw mode ignores arrivalTime. Audio is available separately through {@link live} or muxed through
|
|
854
864
|
* {@link recordFragments}; it is never interleaved into raw video. The caller owns the Readable's
|
|
855
|
-
* lifetime, and destroying it releases the shared pull.
|
|
865
|
+
* lifetime, and destroying it releases the shared pull. On a battery-budgeted source the Readable
|
|
866
|
+
* emits a `budget` event with a {@link StreamBudgetNotice}; unless `extend()` is called within its
|
|
867
|
+
* grace, the Readable ends.
|
|
856
868
|
*/
|
|
857
869
|
openReadable?(opts?: {
|
|
858
870
|
objectMode?: boolean;
|
|
@@ -880,9 +892,11 @@ export interface MediaProvider {
|
|
|
880
892
|
encoder?: AacEncoder;
|
|
881
893
|
} & SharedSourceHints): Promise<TalkbackHandle>;
|
|
882
894
|
/**
|
|
883
|
-
* Download one recording the camera's station stored
|
|
884
|
-
* the
|
|
885
|
-
*
|
|
895
|
+
* Download one recording the camera's station stored. `recording` names it: the file name the station stores
|
|
896
|
+
* it under, without the folder or the extension. `cipherId` is the cipher the station names for it, and is not
|
|
897
|
+
* read on a station that stores its recordings in the clear. One download runs at a time per station. Rejects with
|
|
898
|
+
* {@link RecordingDownloadError}. Optional, and absent on a camera whose station's recording download is
|
|
899
|
+
* unverified.
|
|
886
900
|
*/
|
|
887
901
|
downloadRecording?(opts: {
|
|
888
902
|
recording: string;
|
package/dist/core/store.d.ts
CHANGED
|
@@ -48,7 +48,18 @@ export declare class MemorySessionStore<T = PersistedSession> implements Session
|
|
|
48
48
|
save(s: T): void;
|
|
49
49
|
clear(): void;
|
|
50
50
|
}
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* JSON-file store, e.g. new FileSessionStore("./.eufy-session.json").
|
|
53
|
+
*
|
|
54
|
+
* `save` resolves a symlinked path to its real file (the file a dangling symlink names, created with its
|
|
55
|
+
* directory), writes a fresh owner-only (`0o600`) temp file under a random name beside that file and
|
|
56
|
+
* renames it over it, so where the directory allows it the file on disk is always a complete record — the
|
|
57
|
+
* old one until the rename, the new one after — and the symlink itself stays in place. When the directory
|
|
58
|
+
* refuses the temp file or the rename (`EACCES`, `EPERM`, `EBUSY`, `EXDEV`, `EROFS`: a read-only directory
|
|
59
|
+
* around a writable file, a single-file bind mount), `save` rewrites the file in place instead, which is
|
|
60
|
+
* not atomic and keeps an existing file's mode. The atomic path always leaves `0o600`, whatever mode an
|
|
61
|
+
* existing file had. Any other failure removes the temp file and throws.
|
|
62
|
+
*/
|
|
52
63
|
export declare class FileSessionStore<T = PersistedSession> implements SessionStore<T> {
|
|
53
64
|
private readonly path;
|
|
54
65
|
constructor(path: string);
|
package/dist/core/types.d.ts
CHANGED
|
@@ -80,8 +80,10 @@ export interface EufyDevice {
|
|
|
80
80
|
*/
|
|
81
81
|
paramUpdatedAt?: Record<number, number>;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* When the device was last heard from, in **milliseconds** (comparable to `Date.now()` directly): the
|
|
84
|
+
* most recent {@link paramUpdatedAt} across all of its params, or a later report over a realtime wire,
|
|
85
|
+
* including a station answering its own P2P session's heartbeat. Never moves backwards. `undefined` when
|
|
86
|
+
* neither has been seen.
|
|
85
87
|
*
|
|
86
88
|
* This is an observation — "the device last reported at T" — deliberately **not** a reachability
|
|
87
89
|
* verdict. How long a silence means "unreachable" depends on the device (a mains camera heartbeats
|