@mega-yfue/eufy-sdk / EufyMega
Class: EufyMega
The package entry point — one client per Anker eufy account. Handles the login state machine (captcha/2FA/persistence), resolves the account's devices (getDevices/getDevice → capability-driven Devices), auto-manages the realtime channels (FCM push + secure MQTT start on login; P2P opens on demand per station and idle-detaches battery cameras), and fans every transport's traffic into one typed semantic event stream (eufy.on("motion", …)). Construct it with an EufyMegaOptions (email/password + optional session/push stores), then drive login() to completion — no connect* call needed (set autoRealtime:false to opt out). Call disconnect to tear the realtime channels down.
Extends
EventEmitter
Constructors
Constructor
new EufyMega(opts): EufyMega;Parameters
opts
Returns
EufyMega
Accessors
api
Get Signature
get api(): MegaHttpClient;Raw mega HTTP client, for endpoints not yet wrapped.
Returns
MegaHttpClient
loggedIn
Get Signature
get loggedIn(): boolean;True if a usable session (restored from store or freshly logged in) is held.
Returns
boolean
pollIntervalMs
Get Signature
get pollIntervalMs(): number;The effective cloud poll interval in ms — the configured EufyMegaOptions.pollMs or the default.
Returns
number
Methods
clearSession()
clearSession(): void;Forget the persisted session (forces a fresh login + 2FA next time) and clear account-owned media.
Returns
void
connectStation()
connectStation(deviceSn, signal?): Promise<void>;Open the P2P session for ONE device's station, and nothing else.
Auto-realtime warms every wired station on the account, which is what a host driving a fleet wants. A caller that needs exactly one station does not: an unreachable station broadcasts a local lookup to 255.255.255.255 once a second for the full connect timeout and sends a PPCS lookup to every cloud address in the same tick, so warming a fleet to talk to one camera is a burst of broadcast and NAT churn on the user's network for stations nobody asked about. Pair this with autoRealtime: false to open only what is being used.
Resolves when the station is connected; rejects on its connect timeout. Best-effort and idempotent — an already-open session resolves immediately.
Parameters
deviceSn
string
signal?
AbortSignal
Returns
Promise<void>
deviceAvailability()
deviceAvailability(sn):
| AvailabilityObservation
| undefined;Return the latest explicit availability observation for sn, or undefined when no verified vendor signal has been observed. This never derives a state from DeviceState.lastSeenMs, connection silence, P2P lifecycle, operation failures or caller-selected timeouts.
Parameters
sn
string
Returns
| AvailabilityObservation | undefined
deviceState()
deviceState(sn): DeviceState;The liveness facts for one device — see DeviceState. Facts, not an online verdict: "how long is too long" is a threshold that belongs to the caller, and it differs per device class.
The deviceState event announces when a device reports in.
Parameters
sn
string
Returns
disconnect()
disconnect(): Promise<void>;Tear down every realtime channel: close the secure-MQTT transport, all P2P sessions, and the FCM push socket. Idempotent — safe to call when nothing is connected. Leaves the login session intact (call login again to reconnect without re-authenticating).
Returns
Promise<void>
emit()
emit<E>(event, ...args): boolean;Type Parameters
E
E extends | "error" | "p2p" | "connect" | "disconnect" | "message" | "push" | "map" | "event" | keyof DeviceEventMap | "deviceAdded" | "deviceRemoved" | "deviceCapabilities" | "mapFrame" | "deviceState" | "availability" | "p2pConnect" | "p2pClose" | "p2pLevel2Ready" | "pushConnect" | "pushDisconnect" | "pushRaw" | "commandAck" | "commandUnconfirmed" | "sessionExpired"
Parameters
event
E
args
...EufyMegaEventMap[E]
Returns
boolean
getCleanRecords()
getCleanRecords(
deviceSn,
pageSize?,
page?
): Promise<CleanRecordPage>;One page of a robot vacuum's cleaning history, in whatever order the cloud returns it — newest first in practice, but that is the gateway's contract and the SDK does not re-sort.
pageSize is how many records to return and page is 1-based; page through until the returned total is reached. Answers an empty page rather than throwing when the account has no history for the device or the response cannot be read.
Each record carries a downloadUrl for the run's binary detail blob (map and per-run statistics). The SDK hands that URL over rather than fetching it — the host is unconfirmed and the blob's format is not evidenced yet.
Parameters
deviceSn
string
pageSize?
number = 20
page?
number = 1
Returns
Promise<CleanRecordPage>
getDevice()
getDevice(sn): Promise<Device>;Build a live Device model object for one serial: the resolved codec/capabilities with its current param values applied (named via the param dictionary; unknown ids kept as unknown_<pt>). This is the device primitive — dev.getProperties(), dev.has(cap), etc. Prefers fresh get_device_param_list, falls back to the device-list params. Under auto-realtime the returned Device is wired with a read-through freshness cache (see Device.setFreshnessPolicy), so repeat reads are served from cache instead of re-fetching, and realtime updates keep values fresh.
That refresh ANNOUNCES what it lands, like the other two inbound paths. Under frequent reads it fires every cacheTtlMs where the poll fires every ten minutes, so it is where most fresh cloud values arrive — and each announcing path is edge-triggered on the same live state, so whichever sees a change first announces it and the others stay silent. Its timing says only when a caller happened to read; the value is the news. It applies what the device volunteered over realtime on top of the cloud half, which the registry keeps apart, so it can neither revert nor announce a revert of a report already landed.
The Device returned is held WEAKLY: it is what the inbound paths announce against, so a caller that wants property changes for a serial keeps its own reference. Dropping it stops the announcements, not the device.
Parameters
sn
string
Returns
Promise<Device>
Example
const dev = await eufy.getDevice(sn);
if (dev.has("camera")) await dev.camera()?.snapshotStored();
console.log(dev.getProperty("battery"));getDevices()
getDevices(): Promise<EufyDevice[]>;List + classify devices across all houses (mega API). Each device is tagged with its API backend
- realtime transport. Camera/HomeBase records still appear here for inventory; driving them is P2P. Delegates to
DeviceRegistry(the house-scoped merge/dedupe lives there).
Side-effect: registers eufy_home_tuya devices with the Tuya command router so the command dispatcher can resolve a eufy SN → Tuya devId without a separate lookup. The Tuya id is extracted from the device's raw cloud record (tuya_uuid, tuya_virtual_id, tuya_device_id, or virtualId fields — whichever is non-empty).
A partial cloud outage still resolves, with the devices that answered plus the ones already known — but a session the cloud has rejected REJECTS, with SessionExpiredError. An empty list would be indistinguishable from an account with no devices.
Returns
Promise<EufyDevice[]>
getMqttDevices()
getMqttDevices(): EufyDevice[];Devices that this client drives over MQTT (transport ≠ p2p).
Returns
getP2pSessions()
getP2pSessions(): Map<string, P2PSession>;Stations with a live P2P session. P2P is auto-managed: wired stations are warmed at login, battery stations open on demand (command / stream, or an opted-in event pre-warm) and idle-detach — so this map grows and shrinks over time. p2pConnect(stationSn) / p2pClose(stationSn) events track the changes.
Returns
Map<string, P2PSession>
getUserMqttInfo()
getUserMqttInfo(appName?): Promise<SecureMqttCredentials>;Fetch the per-user secure-MQTT credentials for realtime appliance control. Pass appName to request a specific capability scope on the current session without re-logging in — security devices (locks/garage) need the eufy_security scope, which the default scope can't reach.
Parameters
appName?
string
Returns
Promise<SecureMqttCredentials>
inspectDevice()
inspectDevice(sn): Promise<DeviceInspection>;Inspect one device by serial: resolve its codec/capabilities, cross-reference every reported param_type against the param dictionary, and emit a paste-ready registry.ts row plus dictionary snippets for anything unknown. Loads the device list if needed; prefers the live get_device_param_list for freshest params, falling back to the device-list params.
Parameters
sn
string
Returns
Promise<DeviceInspection>
login()
login(opts?): Promise<LoginResult>;Begin (or resume) login. Returns a LoginResult — switch on status:
ok→ authenticated (result.session).captcha→ showresult.image, then solveCaptcha(answer).2fa→ a code was sent; submitVerifyCode(code).
A restored session resolves straight to ok. No exceptions for the expected captcha/2FA flow.
Parameters
opts?
messageType?
number
Returns
Promise<LoginResult>
Example
const res = await eufy.login();
if (res.status === "captcha") await eufy.solveCaptcha(await ask(res.image));
else if (res.status === "2fa") await eufy.submitVerifyCode(await ask());logout()
logout(): Promise<void>;Tear down realtime, clear passive media, and forget the persisted login session.
Returns
Promise<void>
off()
off<E>(event, listener): this;Type Parameters
E
E extends | "error" | "p2p" | "connect" | "disconnect" | "message" | "push" | "map" | "event" | keyof DeviceEventMap | "deviceAdded" | "deviceRemoved" | "deviceCapabilities" | "mapFrame" | "deviceState" | "availability" | "p2pConnect" | "p2pClose" | "p2pLevel2Ready" | "pushConnect" | "pushDisconnect" | "pushRaw" | "commandAck" | "commandUnconfirmed" | "sessionExpired"
Parameters
event
E
listener
(...args) => void
Returns
this
on()
on<E>(event, listener): this;Type Parameters
E
E extends | "error" | "p2p" | "connect" | "disconnect" | "message" | "push" | "map" | "event" | keyof DeviceEventMap | "deviceAdded" | "deviceRemoved" | "deviceCapabilities" | "mapFrame" | "deviceState" | "availability" | "p2pConnect" | "p2pClose" | "p2pLevel2Ready" | "pushConnect" | "pushDisconnect" | "pushRaw" | "commandAck" | "commandUnconfirmed" | "sessionExpired"
Parameters
event
E
listener
(...args) => void
Returns
this
once()
once<E>(event, listener): this;Type Parameters
E
E extends | "error" | "p2p" | "connect" | "disconnect" | "message" | "push" | "map" | "event" | keyof DeviceEventMap | "deviceAdded" | "deviceRemoved" | "deviceCapabilities" | "mapFrame" | "deviceState" | "availability" | "p2pConnect" | "p2pClose" | "p2pLevel2Ready" | "pushConnect" | "pushDisconnect" | "pushRaw" | "commandAck" | "commandUnconfirmed" | "sessionExpired"
Parameters
event
E
listener
(...args) => void
Returns
this
reboot()
reboot(sn): Promise<void>;Restart a HomeBase.
HomeBases only — restart is a hub operation, so a non-HomeBase serial (a camera, an NVR) throws rather than doing nothing. The hub drops its connection and returns after a minute or two, so everything behind it is briefly offline. Verified on real hardware.
Parameters
sn
string
Returns
Promise<void>
reportedRtspUrl()
reportedRtspUrl(sn): Promise<string | undefined>;The device's LIVE, authoritative rtsp:// URL — host, path, and the credentials it is enforcing right now — or undefined when none is pushed within the read window.
A thin public door onto the P2P transport (which stays internal otherwise): opens the station's session on demand, so a viewer adopting a tile can call this directly without one already existing. It writes only the publish switch and the test-stream provoke, never the credentials, so a stream a NAS/NVR already consumes keeps its own pair.
This is the CANONICAL way to fetch the URL: it provokes and returns it. The rtsp capability's url member surfaces the SAME value as inbound state for code that already holds a dev.rtsp() and reacts to propertyChanged — not a second way to fetch it.
Every failure — no route, no account id, level-2 not ready, no push before the deadline — collapses to undefined. The distinction the caller might want (terminal "no RTSP" vs a transient "session not warm yet") is not drawn here yet; a caller that retries on undefined recovers from the transient case. The read window is a fixed 12 s — long enough for a cold HomeBase to wake and answer, and about the ceiling a UI adopting a tile will wait — deliberately not caller-tunable.
Parameters
sn
string
Returns
Promise<string | undefined>
setPollInterval()
setPollInterval(ms): void;Change the cloud poll interval at runtime; ms is the gap between polls, 0 disables polling.
Takes effect immediately: the pending tick is cancelled and the loop re-armed at the new interval (or left cancelled for 0). Unlike the constructor EufyMegaOptions.pollMs, this can be changed after login.
Parameters
ms
number
Returns
void
setProperty()
setProperty(
sn,
name,
value
): Promise<void>;Write a device property. Asks the capability modules to build the command for this (name, value) — the module owns how THIS device applies it. No (name, value) recipe → the device doesn't support the property, so we throw CapabilityNotSupportedError rather than a silent no-op. On success the device echoes the new state back as a param update — read it with getDevice to confirm.
Parameters
sn
string
device serial.
name
string
property name (e.g. "light", "brightness", "enabled").
value
string | number | boolean
desired value.
Returns
Promise<void>
Example
await eufy.setProperty(sn, "brightness", 50);
await eufy.setProperty(sn, "light", true);solveCaptcha()
solveCaptcha(answer, opts?): Promise<LoginResult>;Continue a {status:"captcha"} login with the solved answer. See login.
Parameters
answer
string
opts?
messageType?
number
Returns
Promise<LoginResult>
submitVerifyCode()
submitVerifyCode(code): Promise<LoginResult>;Continue a {status:"2fa"} login with the verify code that was sent. See login.
Parameters
code
string
Returns
Promise<LoginResult>
waitForRealtime()
waitForRealtime(options?): Promise<RealtimeReadiness>;Wait for the auto-managed realtime startup begun by the current successful login.
A caller-specific timeout does not cancel startup. Calls made before successful login reject with login() first; clients configured with autoRealtime:false resolve as disabled without opening a transport.
Parameters
options?
Returns
Promise<RealtimeReadiness>