Skip to content

Troubleshooting & debugging

When something misbehaves, work in this order: turn on logging → listen for error → match the symptom below.

1. Turn on logging

The client is silent by default — it emits diagnostics only through a logger you pass. Attach the built-in ConsoleLogger to send verbose logs to the console across every channel (login, secure MQTT, P2P, push, live media), each line prefixed by subsystem ([mega] / [smqtt] / [p2p] / [push] / [fcm] / [live]):

ts
import { EufyMega, ConsoleLogger } from "@mega-yfue/eufy-sdk";

const eufy = new EufyMega({ email, password, region: "eu", logger: new ConsoleLogger() });
// or gate by severity — warnings and errors only:
const quiet = new EufyMega({ email, password, region: "eu", logger: new ConsoleLogger("warn") });

Any logger with debug / info / warn / error methods works too (tslog and winston fit directly; pino via a one-line adapter) — see Getting started.

2. Listen for error

The realtime channels are independent — one channel's failure surfaces on the facade's catch-all error event without aborting the others. Always attach a handler; an unhandled error on an EventEmitter throws.

ts
eufy.on("error", (err) => console.error("[eufy]", err));

Media streams have their own error (including a start stall — see below) and a stop:

ts
const stream = await cam?.live();
stream?.on("error", (err) => console.error("stream", err));
stream?.on("stop", () => console.log("source ended — re-attach to rebuild"));

Auth loss is a separate signal. A kicked/expired cloud session (another client logged into the account, or the token lapsed) does not come through error — it fires sessionExpired. The SDK has already cleared the session, so handle it by re-driving login() (usually a fresh 2FA):

ts
eufy.on("sessionExpired", async () => {
  await eufy.login(); // then submit the 2FA code
});

3. Common symptoms

Symptom / errorLikely causeWhat to do
login() firsta method was called before authentication finishedawait eufy.login() and step through captcha/2FA until ok, then continue
no pending captcha — call login() firstsolveCaptcha() called without an active challengedrive the login() result: only call solveCaptcha when status === "captcha"
Captcha keeps appearing / logins briefly blockedrepeated failed-password logins on one device fingerprint trip an abuse cooldowngive the client its own distinct phoneModel / openudid, then back off before retrying (see Getting started)
2FA prompt on every runthe device fingerprint changed, so it looks like a new devicekeep openudid stable and use a store so the session persists
dev.camera is not a function (or any other capability)a device carries only the accessors for capabilities it has, so the accessor is absent — not a function returning undefined. A capability the device hadn't yet reported evidence for is added when it does (deviceCapabilities)call it optionally: dev.camera?.()?.on(). Branch on dev.has("camera") or read dev.capabilities to decide what a device can do; the presence of an accessor is not a check you should write yourself
A write method is missing from dev.<cap>() (e.g. no lock().setOneTouchLock())that write isn't confirmed on a mega device yet, so it's absent rather than present-and-throwing (currently a few lock settings)expected — a method being present means its wire is verified; the read getter still works. (The untyped setProperty escape hatch still throws "wire unverified" for such a write.)
A command fails only on a standalone camerasome controls need a HomeBase-attached device and aren't available standaloneexpected — drive that control on a HomeBase-attached device, or use the property that adapts automatically
live stream failed to start (no frames within warm-up window)the source never produced a frame in timehandle the stream error, then call cam.live() again to rebuild
timeout waiting for a clean keyframea snapshot/record couldn't get a keyframe in the windowretry, or widen the timeout via the call's options
the P2P session closed … into the clip / the stream failed during the cliprecord()'s own pull went away before the clip's window elapsedretry once the session is back; a clip shorter than requested is returned rather than failed when the camera simply goes quiet
ffmpeg not runnable / snapshot or record failsno runnable ffmpeg (needed for JPEG snapshot / mp4 record)point the SDK at the binary you ship with ffmpegPath, install ffmpeg, or use the ffmpeg-free paths (openReadable(), recordFragments()); see §5
p2p down / smqtt reconnecting on errora transient transport dropthe channels reconnect on their own; re-attach live streams when a consumer gets stop

4. Streams that hang or stop

  • No silent hang. live() keeps nudging the start until the first frame arrives; if none comes in the warm-up window it emits error rather than hanging. Handle error.

  • Reconnect. On a dropped session the source stops and consumers get stop / error. Call cam.live() again to rebuild the pull. See Consuming a live stream for the full lifecycle (one pull / many consumers, keyframe priming, power budgets).

  • Trace the lifecycle. With a logger attached (§1), [live …] lines trace a stream warming, going live, a warm-up timeout, an upstream drop, and the linger-before-teardown — the detail you want when a live view won't start or drops unexpectedly.

  • Match startup traces on the published vocabulary, not on strings. Every startup trace is logged under LIVE_TRACE_MESSAGE with a LiveTrace payload, and both are exported from the package root:

    ts
    import { LIVE_TRACE_MESSAGE, type LiveTrace } from "@mega-yfue/eufy-sdk";

    A host that bounds or redacts what it retains should key its phase allowlist off the union, so a phase added here fails to compile rather than being discarded:

    ts
    } satisfies Record<LiveTrace["phase"], true>;

    Copying the message literal or the phase names by hand is the one thing that cannot survive a new phase being added — the SDK widens that union without needing any coordination from you.

5. Media (snapshot / record / ffmpeg)

The JPEG snapshot (snapshotLive) and one-shot record paths shell out to ffmpeg.

No ffmpeg on PATH? That is ordinary on a managed host, and it does not mean these paths are unavailable — name the binary you ship instead of editing the process PATH:

ts
const eufy = new EufyMega({ email, password, ffmpegPath: "/opt/your-host/bin/ffmpeg" });

An absolute path is spawned directly, with no PATH lookup. It is not probed at construction, so a wrong path surfaces as the media call's own ffmpeg not runnable rejection; check it up front with ffmpegAvailable(path), which resolves the same executable the media paths will run.

When one of them fails, surface ffmpeg's own diagnostics through your logger: raise ffmpegLogLevel on the client and the SDK forwards ffmpeg's stderr as [ffmpeg]-prefixed debug lines.

ts
const eufy = new EufyMega({ email, password, ffmpegLogLevel: "trace", logger: new ConsoleLogger("debug") });

Accepted values (quiet → loud): quiet panic fatal error (default) warning info verbosedebug trace. An unset/invalid value stays at error.

This is ffmpeg's own verbosity — orthogonal to the Logger's min-level, which still gates whether the [ffmpeg] lines are shown. So to see them you need both: a level above error and a logger that shows debug (e.g. new ConsoleLogger("debug")).

Note the ffmpeg-free egress paths don't need any of this: openReadable() (raw Annex-B bytes) and recordFragments() (fMP4 / CMAF) mux without ffmpeg on PATH.

6. Login & session issues

Captcha and 2FA are expected flow, not errorslogin() returns a status to switch on, it does not throw. Rapid or repeated failed logins can trigger a captcha or a short cooldown even for valid credentials. Full flow and persistence rules are in Getting started.

Still stuck?

Re-run with new ConsoleLogger("debug"), capture the error output, and check the behavior against the API reference for the method you're calling — signatures and the events each one emits are documented there.

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.