@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.0

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 (143) hide show
  1. package/README.md +31 -12
  2. package/dist/client/device-registry.d.ts +343 -0
  3. package/dist/client/eufy-mega.d.ts +872 -0
  4. package/dist/client/index.d.ts +1 -6
  5. package/dist/client/map-channels.d.ts +22 -0
  6. package/dist/client/types.d.ts +383 -0
  7. package/dist/core/contracts.d.ts +898 -0
  8. package/dist/core/crypto.d.ts +98 -0
  9. package/dist/core/index.d.ts +9 -7
  10. package/dist/core/logger.d.ts +53 -0
  11. package/dist/core/lz4-block.d.ts +35 -0
  12. package/dist/core/raw-dp-hex.d.ts +32 -0
  13. package/dist/core/raw-dp-writer.d.ts +83 -0
  14. package/dist/core/store.d.ts +43 -0
  15. package/dist/core/types.d.ts +169 -0
  16. package/dist/core/util.d.ts +78 -0
  17. package/dist/index.d.ts +4 -7
  18. package/dist/index.js +25526 -14
  19. package/dist/index.js.map +7 -1
  20. package/dist/model/capabilities/access.d.ts +127 -0
  21. package/dist/model/capabilities/arming.d.ts +201 -0
  22. package/dist/model/capabilities/audio.d.ts +154 -0
  23. package/dist/model/capabilities/battery.d.ts +363 -0
  24. package/dist/model/capabilities/camera.d.ts +564 -0
  25. package/dist/model/capabilities/co.d.ts +40 -0
  26. package/dist/model/capabilities/contact.d.ts +133 -0
  27. package/dist/model/capabilities/doorbell.d.ts +345 -0
  28. package/dist/model/capabilities/dp-catalog.d.ts +38 -0
  29. package/dist/model/capabilities/index.d.ts +561 -0
  30. package/dist/model/capabilities/info.d.ts +28 -0
  31. package/dist/model/capabilities/keypad.d.ts +61 -0
  32. package/dist/model/capabilities/leak.d.ts +43 -0
  33. package/dist/model/capabilities/light.d.ts +174 -0
  34. package/dist/model/capabilities/locate.d.ts +63 -0
  35. package/dist/model/capabilities/lock.d.ts +242 -0
  36. package/dist/model/capabilities/manifest.d.ts +107 -0
  37. package/dist/model/capabilities/members.d.ts +647 -0
  38. package/dist/model/capabilities/motion.d.ts +377 -0
  39. package/dist/model/capabilities/person-detection.d.ts +8 -0
  40. package/dist/model/capabilities/ptz.d.ts +289 -0
  41. package/dist/model/capabilities/rtsp.d.ts +221 -0
  42. package/dist/model/capabilities/siren.d.ts +218 -0
  43. package/dist/model/capabilities/smart-light.d.ts +172 -0
  44. package/dist/model/capabilities/smoke.d.ts +40 -0
  45. package/dist/model/capabilities/snapshot.d.ts +6 -0
  46. package/dist/model/capabilities/storage.d.ts +11 -0
  47. package/dist/model/capabilities/suction.d.ts +104 -0
  48. package/dist/model/capabilities/types.d.ts +484 -0
  49. package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
  50. package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
  51. package/dist/model/capabilities/video.d.ts +6 -0
  52. package/dist/model/classify.d.ts +78 -0
  53. package/dist/model/clean-record-detail.d.ts +65 -0
  54. package/dist/model/clean-records.d.ts +69 -0
  55. package/dist/model/device-family.d.ts +73 -0
  56. package/dist/model/device-types.d.ts +123 -0
  57. package/dist/model/device.d.ts +265 -0
  58. package/dist/model/index.d.ts +29 -4
  59. package/dist/model/infer.d.ts +23 -0
  60. package/dist/model/inspect.d.ts +61 -0
  61. package/dist/model/life-params.d.ts +21 -0
  62. package/dist/model/map-pixels.d.ts +70 -0
  63. package/dist/model/param-dictionary.d.ts +28 -0
  64. package/dist/model/param-namespace.d.ts +21 -0
  65. package/dist/model/proto-read.d.ts +53 -0
  66. package/dist/model/push-events.d.ts +147 -0
  67. package/dist/model/registry.d.ts +54 -0
  68. package/dist/model/types.d.ts +301 -0
  69. package/dist/model/vacuum-map-store.d.ts +92 -0
  70. package/dist/model/vacuum-map.d.ts +286 -0
  71. package/dist/model/vacuum-scenes.d.ts +76 -0
  72. package/dist/model/vacuum-schedules.d.ts +85 -0
  73. package/dist/transport/dp-preset.d.ts +102 -0
  74. package/dist/transport/ff09.d.ts +444 -0
  75. package/dist/transport/ffmpeg.d.ts +86 -0
  76. package/dist/transport/http/decodeImageV1.d.ts +20 -0
  77. package/dist/transport/http/decodeImageV2.d.ts +19 -0
  78. package/dist/transport/http/index.d.ts +5 -0
  79. package/dist/transport/http/light-catalog.d.ts +62 -0
  80. package/dist/transport/http/media-download.d.ts +14 -0
  81. package/dist/transport/http/mega-client.d.ts +514 -0
  82. package/dist/transport/http/phone-model.d.ts +21 -0
  83. package/dist/transport/index.d.ts +10 -7
  84. package/dist/transport/mqtt/app-client-id.d.ts +16 -0
  85. package/dist/transport/mqtt/availability.d.ts +14 -0
  86. package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
  87. package/dist/transport/mqtt/biz-stream.d.ts +98 -0
  88. package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
  89. package/dist/transport/mqtt/clean-codec.d.ts +14 -0
  90. package/dist/transport/mqtt/command-router.d.ts +285 -0
  91. package/dist/transport/mqtt/dp-codec.d.ts +58 -0
  92. package/dist/transport/mqtt/dp-color.d.ts +14 -0
  93. package/dist/transport/mqtt/engine.d.ts +16 -0
  94. package/dist/transport/mqtt/index.d.ts +5 -0
  95. package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
  96. package/dist/transport/mqtt/topics.d.ts +80 -0
  97. package/dist/transport/p2p/adts.d.ts +91 -0
  98. package/dist/transport/p2p/annexb.d.ts +124 -0
  99. package/dist/transport/p2p/codec.d.ts +166 -0
  100. package/dist/transport/p2p/command-router.d.ts +655 -0
  101. package/dist/transport/p2p/commands.d.ts +550 -0
  102. package/dist/transport/p2p/envelope.d.ts +46 -0
  103. package/dist/transport/p2p/fmp4.d.ts +89 -0
  104. package/dist/transport/p2p/fragment-recording.d.ts +33 -0
  105. package/dist/transport/p2p/index.d.ts +13 -0
  106. package/dist/transport/p2p/lan-ip.d.ts +25 -0
  107. package/dist/transport/p2p/live-stream.d.ts +223 -0
  108. package/dist/transport/p2p/live-trace.d.ts +123 -0
  109. package/dist/transport/p2p/media.d.ts +105 -0
  110. package/dist/transport/p2p/p2p-session.d.ts +620 -0
  111. package/dist/transport/p2p/readable-egress.d.ts +27 -0
  112. package/dist/transport/p2p/session-manager.d.ts +154 -0
  113. package/dist/transport/p2p/shared-live-source.d.ts +431 -0
  114. package/dist/transport/p2p/talkback.d.ts +187 -0
  115. package/dist/transport/p2p/video.d.ts +150 -0
  116. package/dist/transport/p2p/write-commands.d.ts +21 -0
  117. package/dist/transport/protobuf.d.ts +5 -0
  118. package/dist/transport/push/fcm.d.ts +23 -0
  119. package/dist/transport/push/index.d.ts +6 -0
  120. package/dist/transport/push/message-tags.d.ts +26 -0
  121. package/dist/transport/push/parser.d.ts +27 -0
  122. package/dist/transport/push/proto.d.ts +11 -0
  123. package/dist/transport/push/push-client.d.ts +64 -0
  124. package/dist/transport/push/store.d.ts +23 -0
  125. package/dist/transport/push/types.d.ts +180 -0
  126. package/dist/transport/raw-dp.d.ts +6 -0
  127. package/dist/transport/stored-image-cache.d.ts +23 -0
  128. package/dist/transport/tuya/account.d.ts +44 -0
  129. package/dist/transport/tuya/client.d.ts +89 -0
  130. package/dist/transport/tuya/command-router.d.ts +79 -0
  131. package/dist/transport/tuya/dp-codec.d.ts +56 -0
  132. package/dist/transport/tuya/index.d.ts +28 -0
  133. package/dist/transport/tuya/request.d.ts +157 -0
  134. package/dist/transport/tuya/sign.d.ts +64 -0
  135. package/package.json +12 -13
  136. package/dist/client/index.js +0 -2
  137. package/dist/client/index.js.map +0 -1
  138. package/dist/core/index.js +0 -2
  139. package/dist/core/index.js.map +0 -1
  140. package/dist/model/index.js +0 -2
  141. package/dist/model/index.js.map +0 -1
  142. package/dist/transport/index.js +0 -2
  143. package/dist/transport/index.js.map +0 -1
@@ -1,6 +1 @@
1
- /**
2
- * The facade: login, session lifecycle, the device registry, and the realtime event surface.
3
- *
4
- * This is what a caller imports; the layers below it are implementation detail.
5
- */
6
- export {};
1
+ export * from "./eufy-mega.js";
@@ -0,0 +1,22 @@
1
+ import type { BizChannel, BizMapFrame } from "../transport/mqtt/biz-stream.js";
2
+ import type { RawDpCodec } from "../core/contracts.js";
3
+ import type { VacuumMapPiece } from "../model/index.js";
4
+ /**
5
+ * Which channels are read, named as {@link BIZ_CHANNEL} names them — the key set of
6
+ * {@link READER_BY_CHANNEL}. The `satisfies` on that table constrains every key to a `BizChannel`.
7
+ */
8
+ export declare const DECODED_MAP_CHANNELS: readonly BizChannel[];
9
+ /**
10
+ * Decode one frame off the map stream into the map piece it carries, or `undefined`.
11
+ *
12
+ * `undefined` is the ordinary answer for a channel nothing reads yet, and for a frame that is not a
13
+ * whole message.
14
+ *
15
+ * **A frame with a non-zero `offset` is skipped without being decoded.** A large map is split across
16
+ * frames, and `offset` locates this one within its channel — so whatever it counts, a non-zero value
17
+ * means this is not the start of a message. Handing a fragment to a protobuf reader is how a decoder
18
+ * produces a confident wrong answer. The reader would in fact reject it anyway, because the frame's
19
+ * own length prefix disagrees with a partial body, but relying on that would make correctness an
20
+ * accident of the framing rather than a decision.
21
+ */
22
+ export declare function decodeMapFrame(frame: BizMapFrame, codec: RawDpCodec): VacuumMapPiece | undefined;
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Public type surface of the {@link EufyMega} facade — options + the typed event map.
3
+ *
4
+ * Kept separate from the class so the event contract reads on its own. The declaration-merged
5
+ * `interface EufyMega` (the typed on/once/off/emit overloads) stays in `eufy-mega.ts` next to the
6
+ * class — TS declaration merging requires both in the same module.
7
+ */
8
+ import type { MegaClientConfig } from "../transport/http/mega-client.js";
9
+ import type { FcmStore } from "../transport/push/store.js";
10
+ import type { FfmpegLevel } from "../transport/ffmpeg.js";
11
+ import type { DeviceEventMap } from "../model/capabilities/index.js";
12
+ import type { Capability } from "../model/index.js";
13
+ import type { P2PFrame } from "../transport/p2p/p2p-session.js";
14
+ import type { PowerTier } from "../transport/p2p/session-manager.js";
15
+ import type { BizMapFrame } from "../transport/mqtt/biz-stream.js";
16
+ import type { VacuumMapSnapshot } from "../model/index.js";
17
+ import type { PushEvent, RawPushMessage } from "../transport/push/types.js";
18
+ import type { AvailabilityObservation, EufyDevice, RealtimeMessage } from "../core/types.js";
19
+ /** Count-only startup status for one auto-managed realtime transport plane. */
20
+ export interface RealtimePlaneReadiness {
21
+ /** Number of transport starts selected for the plane. */
22
+ readonly required: number;
23
+ /** Number of selected starts that completed successfully. */
24
+ readonly ready: number;
25
+ /** Number of selected starts that failed. */
26
+ readonly failed: number;
27
+ /** Number of selected starts that have not settled. */
28
+ readonly pending: number;
29
+ }
30
+ /**
31
+ * Count-only status of the current auto-managed realtime generation.
32
+ *
33
+ * The summary intentionally carries no credentials, identifiers, or underlying errors. Transport
34
+ * failures continue to surface through the `error` event.
35
+ */
36
+ export interface RealtimeReadiness {
37
+ /** Outcome of the generation or of this caller's bounded wait. */
38
+ readonly state: "ready" | "partial" | "disabled" | "superseded" | "timed-out";
39
+ /** Account-wide FCM push startup status. */
40
+ readonly push: RealtimePlaneReadiness;
41
+ /** Secure-MQTT credential-scope startup status. */
42
+ readonly mqtt: RealtimePlaneReadiness;
43
+ /** Persistent station-control P2P startup status for wired stations. */
44
+ readonly wiredP2p: RealtimePlaneReadiness;
45
+ }
46
+ /** Options for {@link EufyMega.waitForRealtime}. */
47
+ export interface WaitForRealtimeOptions {
48
+ /**
49
+ * Maximum time in milliseconds for this caller to wait. Expiry does not cancel background startup;
50
+ * a later call can observe the generation's final result.
51
+ */
52
+ timeoutMs?: number;
53
+ }
54
+ export interface EufyMegaOptions extends MegaClientConfig {
55
+ /** Persist FCM push credentials + seen ids across runs (default: in-memory). */
56
+ pushStore?: FcmStore;
57
+ /** Eagerly retain validated push thumbnails in memory for `camera.snapshotStored()` (default `true`). */
58
+ storedSnapshotCache?: boolean;
59
+ /**
60
+ * LAN address overrides for direct P2P, keyed by **parent-station serial** → `host` or `host:port`.
61
+ * The SDK normally derives a station's LAN address from its device record; an entry here overrides it
62
+ * where the record's IP is wrong/blocked (AP isolation, a stale `ip_addr`).
63
+ */
64
+ localAddresses?: Record<string, string>;
65
+ /**
66
+ * Suppress the `255.255.255.255` local-lookup broadcast (default `false` — broadcast is sent).
67
+ *
68
+ * An unconnected P2P session broadcasts a local lookup **once a second for the whole connect
69
+ * timeout**, which is how a station on the same LAN is found without knowing its address. That is
70
+ * cheap when it works and not free when it does not: the datagram goes to every host on the segment,
71
+ * every associated client of a WLAN has to receive it, and a host that sets `SO_BROADCAST` on many
72
+ * sockets in quick succession is doing something unusual to its own network stack.
73
+ *
74
+ * Turning it off costs the LAN-discovery path only. A station whose record carries a usable
75
+ * `ip_addr` is still found directly, and the PPCS cloud lookup — which is what actually connects a
76
+ * station in most topologies — is unaffected. Set it when the caller knows its stations' addresses,
77
+ * or when the broadcast is suspected of disturbing the host's own networking.
78
+ */
79
+ noBroadcast?: boolean;
80
+ /**
81
+ * Auto-manage connectivity (default `true`). When on, a successful {@link EufyMega.login} brings up
82
+ * the always-on event channels itself — FCM push + secure MQTT (if the account has appliances) — and
83
+ * eagerly warms P2P only for **wired** stations (HomeBases / mains cameras). Battery cameras stay
84
+ * detached until a command / stream — or a pre-warm the caller opted into via {@link
85
+ * EufyMegaOptions.prewarmEvents} — needs them, and idle-detach afterwards. The host calls no
86
+ * `connect*` — connectivity is transport-agnostic. Set `false` to manage nothing automatically
87
+ * (advanced/testing).
88
+ */
89
+ autoRealtime?: boolean;
90
+ /**
91
+ * Read-through cache freshness window in ms (default 15000). A `getProperty`/`getProperties` read of
92
+ * a value older than this schedules ONE coalesced background refresh and returns the last-known value
93
+ * immediately; realtime (push/P2P) updates keep values fresh so a live device rarely refetches.
94
+ */
95
+ cacheTtlMs?: number;
96
+ /**
97
+ * How long {@link EufyMega.getDevice} waits (ms, default `4000`) for a device whose state exists ONLY
98
+ * on its realtime wire to make its first report, before resolving it.
99
+ *
100
+ * Such a device has no pollable cloud state, and the typed read getters are gated on what it has
101
+ * actually reported — so one resolved before its first report has no readable state, and no later
102
+ * report can add the getters to it. A short wait buys a populated read surface. `0` disables the wait
103
+ * and accepts that reads appear only on a `Device` fetched after the first report. Devices with a
104
+ * cloud record never wait.
105
+ */
106
+ stateSnapshotMs?: number;
107
+ /**
108
+ * Idle window in ms before an on-demand P2P session to a **battery** station is closed so the device
109
+ * can sleep (default 300000 = 5 min). Wired stations stay persistent.
110
+ */
111
+ p2pIdleMs?: number;
112
+ /**
113
+ * How long a speculative pre-warm holds the session it opened, in ms (default 28000). Applies only to
114
+ * the events {@link EufyMegaOptions.prewarmEvents} opts into; pre-warm is off until then.
115
+ *
116
+ * When the window expires with nothing attached, the session does not close — the hold is released and
117
+ * the station's own idle window takes over, which for a battery station is {@link
118
+ * EufyMegaOptions.p2pIdleMs} (5 min by default). Budget an unattended pre-warm at the sum of the two.
119
+ */
120
+ prewarmMs?: number;
121
+ /**
122
+ * How often to re-read the cloud device list and emit a semantic event for each param that changed
123
+ * (default 600000 = 10 min). Set `0` to disable polling entirely.
124
+ *
125
+ * The default is paced to the data rather than to a host's refresh appetite — see
126
+ * the device's `params` for how slowly the cloud actually refreshes them. Polling faster costs
127
+ * requests without seeing anything sooner.
128
+ *
129
+ * This channel carries the slow-moving state that has no push of its own (a battery level; a sensor
130
+ * that only reports to the cloud). Fast state — motion, doorbell, contact, lock — arrives over
131
+ * push/P2P/MQTT and is unaffected by this setting.
132
+ */
133
+ pollMs?: number;
134
+ /**
135
+ * Which semantic events speculatively pre-warm a camera's P2P session — **opt-in, default `[]`**, an
136
+ * empty list being what disables it. Naming an event buys a stream or talkback opened right after it
137
+ * starting warm rather than paying a cold open, and costs what the three paragraphs below describe.
138
+ *
139
+ * Any name in {@link DeviceEventMap} is accepted, so the list autocompletes and a typo won't compile.
140
+ * A pre-warm rides the push channel, so only an event push carries can trigger one — a poll-carried
141
+ * event is inert however it is listed, and each capability module declares which source carries its own
142
+ * events. An event from a device that is not a camera pre-warms the station behind it, which for an
143
+ * attached sensor is its HomeBase.
144
+ *
145
+ * **One camera pays for it.** Wired stations are warmed at login and never idle-detach, and an attached
146
+ * camera's session lives on its wired base — so the only station a pre-warm genuinely opens is a
147
+ * standalone battery camera, the device class the on-demand session lifecycle exists to let sleep.
148
+ * {@link EufyMegaOptions.prewarmTiers} is how that class is spared while keeping the opt-in.
149
+ *
150
+ * **An unwatched pre-warm costs more than its window**, per {@link EufyMegaOptions.prewarmMs}: the hold
151
+ * expiring arms the station's idle window instead of closing the session, and a second qualifying event
152
+ * inside that tail restarts it.
153
+ *
154
+ * **Frequency is a property of the installation, not of the event name.** A camera set to report human
155
+ * detection only fires `personDetected` as often as a busier one fires raw `motion`, so the rate is the
156
+ * fleet's and not the event's.
157
+ */
158
+ prewarmEvents?: (keyof DeviceEventMap)[];
159
+ /**
160
+ * Which station power tiers {@link EufyMegaOptions.prewarmEvents} may pre-warm (default: both). The
161
+ * tier is the one of the **station whose session would open** — a camera attached to a HomeBase is
162
+ * pre-warmed as `wired`, because that base's session is the one being held.
163
+ *
164
+ * `["wired"]` keeps the opt-in and spends no battery: it is close to a no-op, since wired stations are
165
+ * already warmed at login and never idle-detach, so it only bites after a session drops.
166
+ */
167
+ prewarmTiers?: PowerTier[];
168
+ /**
169
+ * ffmpeg's own `-loglevel` for the media paths that shell out to it (live snapshot / record).
170
+ * Default `"error"` (quiet). A raised level (e.g. `"trace"`) reports a failing decode/mux;
171
+ * ffmpeg's stderr is then forwarded to the {@link EufyMegaOptions.logger} as `[ffmpeg]` debug lines
172
+ * — visible only where that logger shows `debug`. Independent of the SDK's own log level.
173
+ */
174
+ ffmpegLogLevel?: FfmpegLevel;
175
+ /**
176
+ * Opt into unverified Tuya DP writes for `eufy_home_tuya` clean-line devices (G-series / X8).
177
+ *
178
+ * By default `TuyaCommandRouter` refuses to send `dp.publish` because the request shape
179
+ * has been reversed but not yet confirmed from a live on-device capture — a wrong shape comes back
180
+ * as a generic Tuya error indistinguishable from an actual device rejection. `true` sends it anyway,
181
+ * which is sound only where the full round-trip has been confirmed on a real device, or that
182
+ * ambiguity is accepted.
183
+ */
184
+ tuyaAllowUnverified?: boolean;
185
+ /**
186
+ * The `ffmpeg` executable the media paths that shell out should run (live snapshot / record).
187
+ * Default: the bare name `"ffmpeg"`, looked up on `PATH`.
188
+ *
189
+ * Set it when the host ships or manages its own build — an absolute path is resolved without any
190
+ * `PATH` lookup, so those paths work on a host that has no system ffmpeg at all. The SDK never
191
+ * edits `process.env.PATH`; naming the binary here is the supported way to point it at one. The
192
+ * path is not probed, so a wrong one surfaces as the media call's own "not runnable" rejection.
193
+ */
194
+ ffmpegPath?: string;
195
+ }
196
+ /**
197
+ * What the SDK can honestly say about a device's liveness at one instant — the facts, never a verdict.
198
+ *
199
+ * There is deliberately **no `online: boolean`**. "Unreachable" is a threshold decision, and the right
200
+ * threshold differs per device: a mains camera reports constantly, while a battery contact sensor can
201
+ * be silent for days by design and is perfectly healthy. Baking one timeout into the SDK would force
202
+ * that choice on every host. The SDK reports when the device last spoke; the caller decides what that
203
+ * means — the same split as the snapshot cache TTL and the live power budget.
204
+ *
205
+ * P2P session state is **not** a liveness signal and is not carried here: sessions are opened only
206
+ * when something needs one and closed when idle, so "no session" is the resting state of a healthy
207
+ * device. Transport visibility lives on `getP2pSessions()` and the `p2pConnect`/`p2pClose` events,
208
+ * station-scoped like the session itself.
209
+ *
210
+ * The cloud record carries no connectivity field either: it has no `status` / `device_online`, and the
211
+ * connection-related fields it does carry are opaque routing strings, not booleans.
212
+ */
213
+ export interface DeviceState {
214
+ sn: string;
215
+ /** The parent station whose P2P session covers this device (itself, when standalone). */
216
+ stationSn: string;
217
+ /**
218
+ * When the device last reported to the cloud, in ms. Bounded by
219
+ * the cloud's own slow refresh — minutes, not seconds — so it answers "is this device alive at all",
220
+ * not "what is it doing right now".
221
+ */
222
+ lastSeenMs?: number;
223
+ }
224
+ /**
225
+ * A single semantic event tagged with its name — the payload of the catch-all `"event"` listener.
226
+ * A discriminated union over {@link DeviceEventMap}, so switching on `e.eventName` narrows `e` to that
227
+ * event's payload.
228
+ *
229
+ * The tag is `eventName`, not `name`: an event payload may legitimately carry its own `name` field, and
230
+ * overwriting it to tag the event would destroy data. Matches the `eventName` carried on a push event.
231
+ */
232
+ export type AnyDeviceEvent = {
233
+ [K in keyof DeviceEventMap]: DeviceEventMap[K] & {
234
+ eventName: K;
235
+ };
236
+ }[keyof DeviceEventMap];
237
+ /**
238
+ * The complete typed event surface of {@link EufyMega} — event name → listener-argument tuple.
239
+ *
240
+ * Two groups:
241
+ * - **Semantic events** (motion, doorbellPress, lockState, ptzNotify, …) — projected from the
242
+ * capability modules via {@link DeviceEventMap}, so adding a capability event adds a typed event
243
+ * here automatically (one line in that map).
244
+ * - **Low-level / lifecycle events** — the raw escape hatches and transport lifecycle.
245
+ */
246
+ export type EufyMegaEventMap = {
247
+ [K in keyof DeviceEventMap]: [DeviceEventMap[K]];
248
+ } & {
249
+ /** Catch-all: fires for EVERY semantic event, payload tagged with its `eventName`. */
250
+ event: [AnyDeviceEvent];
251
+ /**
252
+ * A device appeared on the account since the previous poll — a pairing, or a device that became
253
+ * visible again. Account topology, so it lives here rather than on the per-device capability map.
254
+ *
255
+ * Fires only for a device the SDK has seen the account WITHOUT; the first enumeration after login is
256
+ * not a stream of additions. Suppressed when the baseline it would be measured against only partly
257
+ * resolved, so a recovering outage doesn't read as a burst of pairings.
258
+ */
259
+ deviceAdded: [device: EufyDevice];
260
+ /**
261
+ * A device is gone from the account — unpaired, or moved away.
262
+ *
263
+ * Deliberately conservative: suppressed when a poll only partially resolved (a failed house query
264
+ * returns a subset), because an absence caused by an outage is not a removal.
265
+ */
266
+ deviceRemoved: [device: EufyDevice];
267
+ /**
268
+ * A device a caller is holding gained capabilities, because it reported evidence it hadn't before.
269
+ * `gained` is what is newly available; `capabilities` is the full set after widening.
270
+ *
271
+ * A `Device` resolves its capabilities from the evidence available at the time, so one resolved
272
+ * before the device had reported a param lacks the capability that param proves. When a later poll
273
+ * supplies it, the object is re-resolved and re-bound in place — the new accessor is live on the
274
+ * instance the host already has. Capabilities are never retracted, so this only ever widens.
275
+ */
276
+ deviceCapabilities: [info: {
277
+ deviceSn: string;
278
+ gained: Capability[];
279
+ capabilities: Capability[];
280
+ }];
281
+ connect: [];
282
+ disconnect: [reason?: unknown];
283
+ message: [msg: RealtimeMessage];
284
+ /**
285
+ * One frame off a clean-line device's map stream — the `biz/…/res` leg, which carries pixel planes,
286
+ * room outlines and names, virtual walls and the live pose.
287
+ *
288
+ * The frame is unwrapped as far as its bytes and no further: `frame.payload` is a Raw-DP frame in
289
+ * the base64 a codec reads, and `frame.channelId` says which `stream.proto` message it holds. That
290
+ * split is deliberate while the decoders are being built — the meaning of a channel is settled in one
291
+ * place rather than in this event's shape.
292
+ */
293
+ mapFrame: [info: {
294
+ deviceSn: string;
295
+ frame: BizMapFrame;
296
+ }];
297
+ /**
298
+ * A device's map changed — a new cell plane, a renamed room, a zone the user drew.
299
+ *
300
+ * Carries the whole snapshot rather than the piece that changed, because the pieces are only useful
301
+ * together: a room outline without the room list names nothing. Emitted only when something actually
302
+ * changed; the robot republishes its map throughout a clean and a repeat of what is already held is
303
+ * dropped rather than woken on.
304
+ */
305
+ map: [info: {
306
+ deviceSn: string;
307
+ map: VacuumMapSnapshot;
308
+ }];
309
+ /**
310
+ * A device reported to the cloud since the last poll — its {@link DeviceState.lastSeenMs} advanced.
311
+ * Carries {@link DeviceState}; the host applies its own staleness threshold.
312
+ *
313
+ * Transport/session lifecycle is NOT this event: that's `p2pConnect`/`p2pClose`, station-scoped where
314
+ * a session actually lives.
315
+ */
316
+ deviceState: [state: DeviceState];
317
+ /**
318
+ * A verified vendor-wire availability observation. Duplicate states are coalesced; silence,
319
+ * `lastSeenMs`, operation failure and transport lifecycle never emit or clear this event.
320
+ */
321
+ availability: [observation: AvailabilityObservation];
322
+ p2pConnect: [stationSn: string];
323
+ p2pClose: [stationSn: string];
324
+ p2pLevel2Ready: [info: {
325
+ stationSn: string;
326
+ cipherId: number;
327
+ }];
328
+ p2p: [frame: P2PFrame];
329
+ pushConnect: [];
330
+ pushDisconnect: [];
331
+ pushRaw: [raw: RawPushMessage];
332
+ push: [event: PushEvent];
333
+ /**
334
+ * A transport-level command got an acknowledgement (or didn't) — emitted by the MQTT command router.
335
+ * For `ff09-actuate` the reply is a "device received it"
336
+ * signal, not a physical-actuation-complete one (see that handler's doc); for `ff09-autolock` the GET
337
+ * step already threw on no reply by the time this fires — `getAcked` is always `true` here, `acked`
338
+ * reports the SET step's fire-and-forget ack. `dispatch()`/`lock()`/`unlock()`/`setAutoLock()` stay
339
+ * `Promise<void>` and never throw on a missing SET ack (fire-and-forget, same as every other write) —
340
+ * this event is the optional channel for delivery visibility, without the dispatch
341
+ * contract itself changing shape. Secure-MQTT DP writes use the persistent account connection and
342
+ * report broker publication (`acked: true`) without an `instanceIp`; that is not device convergence.
343
+ */
344
+ commandAck: [info: {
345
+ sn: string;
346
+ kind: string;
347
+ acked: boolean;
348
+ instanceIp?: string;
349
+ getAcked?: boolean;
350
+ }];
351
+ /**
352
+ * A write was acknowledged and its declared observation then never converged, so the device never
353
+ * reported the state the write asked for.
354
+ *
355
+ * This is the answer to the question `dispatch` deliberately does not wait for. A command resolves once the
356
+ * transport has carried it, and the observation a member declares decides separately whether the device
357
+ * applied it; where that observation times out, the wire accepted the write and the device ignored it — seen
358
+ * on a battery camera whose power write is acknowledged and never acted on. It is reported here rather than
359
+ * on `error` because it is an outcome and not a fault, for the same reason `commandAck` has its own
360
+ * channel. `observed` is what the param read when the deadline passed, absent where the
361
+ * device reported none at all.
362
+ */
363
+ commandUnconfirmed: [
364
+ info: {
365
+ sn: string;
366
+ property: string;
367
+ param: number;
368
+ expected?: boolean | number | string;
369
+ observed?: boolean | number | string;
370
+ timeoutMs: number;
371
+ }
372
+ ];
373
+ /**
374
+ * The cloud session was kicked or invalidated — another client logged into the same account, or the
375
+ * token expired. The SDK has already cleared the persisted session, so recovery is a fresh `login()`
376
+ * (which usually needs 2FA). Distinct from `error`: a session error is emitted ONLY here, not also
377
+ * on `error`.
378
+ */
379
+ sessionExpired: [err: Error];
380
+ error: [err: Error];
381
+ };
382
+ /** Event names {@link EufyMega} can emit. */
383
+ export type EufyMegaEvent = keyof EufyMegaEventMap;