Skip to main content

Presence & Notifications with the Dart SDK

Presence queries

The client.presence facade wraps the presence service with typed responses.

Room presence

final presence = await client.presence.room(roomId);
// presence.roomId
// presence.participantCount
// presence.participants -> List<PresenceParticipant>
// (userId, displayName, joinedAt, speaking)

User presence

final user = await client.presence.user(userId);
// user.online -> bool
// user.currentRoomId -> String? (null when not in a room)
// user.lastSeen -> ISO-8601 timestamp

Tenant summary

final summary = await client.presence.tenantSummary();
// summary.onlineUsers, summary.activeRooms, summary.totalParticipants

The tenant is derived from your Bearer token — no tenant ID parameter is needed.

Live presence: prefer events over polling

Inside a room, do not poll the presence API. RoomHandle.events pushes RosterSnapshot messages (including per-participant speaking state) and incremental ParticipantJoined/ParticipantLeft events in real time (see Signaling). Use the REST presence facade for lobby screens, dashboards, and "is this user online?" checks outside a room.

Notifications

Webhook subscriptions (server-side event delivery)

The notification service delivers platform events to your backend via webhooks. Webhook management is typically an admin/backend concern — most apps create subscriptions once during setup rather than from the Flutter client. There is no dedicated facade; if you do need to manage subscriptions from Dart, use the authenticated raw client:

final sub = await client.httpClient.post('/webhooks', body: {
'url': 'https://api.myapp.example.com/hooks/ethioconnect',
'secret': 'a-random-32-char-signing-secret!',
'event_types': ['room.created', 'room.closed'],
});

final subs = await client.httpClient.get('/webhooks');

Mobile push (FCM/APNs)

No device-token registration endpoint yet

The current notification contract covers webhook subscriptions only — there is no platform endpoint for registering FCM/APNs device tokens, and consequently no SDK support for it (ADR 0022 carries this follow-up forward). Mobile push is therefore composed in your own backend: subscribe your backend to platform webhooks, and fan out pushes to devices yourself (e.g. with firebase_messaging in Flutter and FCM on the server).

The recommended pattern:

  1. Flutter app obtains an FCM token with firebase_messaging and registers it with your backend, tied to the platform user_id you mint tokens for.
  2. Your backend receives webhook deliveries (e.g. room.created), verifies the signature with your subscription secret, and looks up the affected users' device tokens.
  3. Your backend sends the push via FCM/APNs; the app deep-links into the room and calls client.joinRoom(roomId).
import 'package:firebase_messaging/firebase_messaging.dart';

final fcmToken = await FirebaseMessaging.instance.getToken();
// Register with YOUR backend — not with the platform API.
await myBackend.registerDevice(userId: userId, fcmToken: fcmToken!);

Under the hood

The facade maps onto the presence and notification contracts (all Bearer-authenticated):

SDK callEndpointResponse shape
presence.room(roomId)GET /v1/presence/rooms/{room_id}{ "room_id", "participant_count", "participants": [ { "user_id", "display_name", "joined_at", "speaking" } ] }
presence.user(userId)GET /v1/presence/users/{user_id}{ "user_id", "online", "current_room_id" | null, "last_seen" }
presence.tenantSummary()GET /v1/presence/tenant/summary{ "tenant_id", "online_users", "active_rooms", "total_participants" }
(raw client)POST /v1/webhooksBody requires url (HTTPS), secret (min 16 chars, signs deliveries), event_types (e.g. room.created, room.closed). Returns a Subscription (id, tenant_id, url, event_types, active, created_at).
(raw client)GET /v1/webhooksSubscriptions for the authenticated tenant.