Live events
Instead of polling, subscribe to a context’s real-time events. mero.events(contextIds)
opens a Server-Sent-Events stream and emits a ContextEvent for each node
emission — new messages, application-version flips, and other context activity —
so a screen can react as changes arrive.
Subscribe
Section titled “Subscribe”events(contextIds) returns a cold Flow<ContextEvent>. Collect it inside a
coroutine you can cancel:
val job = scope.launch { mero.events(contextIds = listOf(contextId)).collect { event -> when (event.kind) { "StateMutation" -> reloadMessages() // the context state moved "SyncStatus" -> updateSyncIndicator(event) // syncing / waitingForPeers / … else -> println("event on ${event.contextId}: ${event.kind}") } }}Each event is:
data class ContextEvent( val contextId: String, val kind: String, // "StateMutation" | "SyncStatus" | … val payload: JsonElement, // raw event JSON)For a contract emission (kind == "StateMutation") the encoded contract events
are nested under payload’s data.events[].data; each carries its own kind
(MessageSent above) and a byte array.
The token needs context:subscribe
Section titled “The token needs context:subscribe”Reconnection
Section titled “Reconnection”The stream reconnects automatically ~3 seconds after a drop and re-subscribes to your context ids (the node persists the session’s subscriptions). You don’t need to rebuild the flow on a transient network blip — the collector keeps delivering events across reconnects.
A 403 is the exception, and it ends the flow rather than restarting it. Two
things produce one: a token that never carried context:subscribe, and a refresh
family that was revoked (core answers that with x-auth-error and an empty body).
Neither is fixed by waiting, so retrying every three seconds is not resilience —
it is a silent outage, a live-looking subscription delivering nothing while no
exception ever reaches the collector. Instead:
- a revoked family throws
AuthRevokedException— route the user back to login; - any other
403throwsHttpException(status = 403)— the token is missingcontext:subscribe.
scope.launch { try { mero.events(listOf(contextId)).collect { reload() } } catch (e: AuthRevokedException) { forceReLogin() } catch (e: HttpException) { if (e.status == 403) reportMissingSubscribeGrant() }}Closing the stream
Section titled “Closing the stream”The stream lives for as long as its collecting coroutine — cancel that job (or the enclosing scope) to close the SSE connection:
job.cancel() // closes the connectionIn Compose
Section titled “In Compose”LaunchedEffect(contextId) ties collection to a composable’s lifetime — it starts
when the composable enters composition and is cancelled when contextId changes
or it leaves:
@Composablefun MessagesScreen(mero: Mero, contextId: String) { var messages by remember { mutableStateOf(emptyList<Message>()) }
LaunchedEffect(contextId) { mero.events(contextIds = listOf(contextId)).collect { messages = loadMessages() } }
MessageList(messages)}See also
Section titled “See also”- RPC client — trigger the state changes you’re observing.
- Events reference —
SseClientand the full API surface.