Skip to main content
Subscribe to real-time camera events: photo taken, property changed, autofocus status, transfer progress, etc. The server exposes GET /api/events (all cameras) and GET /api/cameras/{id}/events (single camera) as Server-Sent Events streams.

When to use

  • You need to react when a photo is captured (downloadComplete) instead of polling
  • You want progress feedback during SD card downloads (transferProgress)
  • You’re building a dashboard that reflects property changes immediately
  • You need to know when a camera is unplugged mid-session (disconnected)

When NOT to use

  • One-shot scripts that take a photo and exit — just call the endpoint, disconnect, done
  • Polling a single property rarely — a periodic GET is simpler

Event catalog

Keepalive comments (: keepalive) fire every 30s — ignore them. The dual connected event quirk: connected fires immediately when the SSE channel opens (subscription handshake) AND when a real camera’s OnConnected SDK callback fires. Discriminate by payload shape or ignore the first one after opening.

TypeScript

The real Next.js example app uses a small browser-native EventSource wrapper in:
  • example_app/src/lib/event-stream.ts

Complete recipe

Usage

This is the pattern the real browser app uses. It keeps SSE logic small and lets the lifecycle layer decide which events matter.

Python

Complete recipe

Usage

Typed event variants

For stricter type safety, replace the dict payload with Pydantic models per event:
This gives you isinstance checks + autocomplete in consumers. Optional — the plain dict works too.

Swift

Complete recipe

Usage

Why this differs from a generic AsyncSequence SSE client

  • The real app does not keep a single catch-all event stream on the main actor.
  • It uses purpose-built waits:
    • waitForConnected() during connect
    • waitForDisconnected() for the lifetime of a connection
    • waitForTransferComplete() around each SD-card download
  • This keeps the state machine explicit and avoids flooding the UI with propertyChanged traffic.
  • Most importantly on Apple platforms, this recipe uses URLSessionDataDelegate because URLSession.bytes(...).lines was not reliable enough for sparse SSE traffic.

Common pitfalls

  • Don’t forget the keepalive skip. The server sends : keepalive\n\n every 30 seconds. If your parser treats these as events, you’ll get garbage data.
  • The first connected event is the handshake, not a camera connecting. All three recipes skip the first occurrence.
  • JSON parse failures shouldn’t kill the stream. Log and continue.
  • Reconnect without cap = infinite loop on bad config. All three recipes cap backoff at 10 seconds.
  • Browser EventSource vs Node fetch: native EventSource auto-reconnects; if you use the fetch variant in Node, the manual reconnect loop in this recipe handles it.

Verified against

test-app/sdk-comparison/debug-autotransfer.ts uses this exact fetch-streaming pattern to verify downloadComplete events fire during shooting. See that file for a working in-repo example.