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-nativeEventSource wrapper in:
example_app/src/lib/event-stream.ts
Complete recipe
Usage
Python
Complete recipe
Usage
Typed event variants
For stricter type safety, replace thedict payload with Pydantic models per event:
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 connectwaitForDisconnected()for the lifetime of a connectionwaitForTransferComplete()around each SD-card download
- This keeps the state machine explicit and avoids flooding the UI with
propertyChangedtraffic. - Most importantly on Apple platforms, this recipe uses
URLSessionDataDelegatebecauseURLSession.bytes(...).lineswas not reliable enough for sparse SSE traffic.
Common pitfalls
- Don’t forget the keepalive skip. The server sends
: keepalive\n\nevery 30 seconds. If your parser treats these as events, you’ll get garbage data. - The first
connectedevent 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
EventSourceauto-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.
