Skip to content

@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

ts
new EufyMega(opts): EufyMega;

Parameters

opts

EufyMegaOptions

Returns

EufyMega

Accessors

api

Get Signature

ts
get api(): MegaHttpClient;

Raw mega HTTP client, for endpoints not yet wrapped.

Returns

MegaHttpClient


loggedIn

Get Signature

ts
get loggedIn(): boolean;

True if a usable session (restored from store or freshly logged in) is held.

Returns

boolean


pollIntervalMs

Get Signature

ts
get pollIntervalMs(): number;

The effective cloud poll interval in ms — the configured EufyMegaOptions.pollMs or the default.

Returns

number

Methods

clearSession()

ts
clearSession(): void;

Forget the persisted session (forces a fresh login + 2FA next time) and clear account-owned media.

Returns

void


connectStation()

ts
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()

ts
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()

ts
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

DeviceState


disconnect()

ts
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()

ts
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()

ts
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()

ts
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

ts
const dev = await eufy.getDevice(sn);
if (dev.has("camera")) await dev.camera()?.snapshotStored();
console.log(dev.getProperty("battery"));

getDevices()

ts
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()

ts
getMqttDevices(): EufyDevice[];

Devices that this client drives over MQTT (transport ≠ p2p).

Returns

EufyDevice[]


getP2pSessions()

ts
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()

ts
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()

ts
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()

ts
login(opts?): Promise<LoginResult>;

Begin (or resume) login. Returns a LoginResult — switch on status:

  • ok → authenticated (result.session).
  • captcha → show result.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

ts
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()

ts
logout(): Promise<void>;

Tear down realtime, clear passive media, and forget the persisted login session.

Returns

Promise<void>


off()

ts
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()

ts
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()

ts
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()

ts
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()

ts
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()

ts
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()

ts
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

ts
await eufy.setProperty(sn, "brightness", 50);
await eufy.setProperty(sn, "light", true);

solveCaptcha()

ts
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()

ts
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()

ts
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?

WaitForRealtimeOptions = {}

Returns

Promise<RealtimeReadiness>

Independent and unofficial. Not affiliated with, endorsed by, or sponsored by Anker Innovations, Anker eufy, or eufy. "Anker eufy", "eufy" and "Anker" are trademarks of their respective owners. Use responsibly — rapid or failed logins can trigger captcha or temporary cooldowns.