utd_signaling 0.3.0
utd_signaling: ^0.3.0 copied to clipboard
The UTD domain layer over utd_channels: presence, 1:1 call invitations, chat messaging + groups, typing, push-token registration and media-upload presign for Flutter, plus Pusher-style channels mounte [...]
utd_signaling #
The UTD domain layer for Flutter — calls, chat, uploads, and push tokens —
built on utd_channels, the generic Pusher-style
realtime core. It owns the REST + WebSocket plumbing the UTD kits
(utd_calls_kit / utd_chat_kit) share: per-user session mint, presence,
1:1 call invitations, chat messaging + groups, typing, push-token
registration, and media-upload presign. No media (LiveKit/WebRTC), no UI, no
local storage.
Most consumers use the high-level UTDSignaling facade; the lower-level
UTDApi / UTDSignalClient remain available for kits that need the full
surface. The domain vocabulary and generic channels share one WebSocket,
and the full utd_channels surface is re-exported through this package's
barrel.
Generic realtime (channels) #
For realtime features that aren't calls or chat — live cursors, dashboards,
your own event fan-out — use the Pusher-style channels core, mounted on the
same socket via the facade's .channels getter (authorize private-/
presence- subscriptions with the channelAuth constructor parameter):
final signaling = UTDSignaling(
appId: 'app_x',
appKey: 'key_x',
userId: 'alice',
channelAuth: UTDChannelAuth.endpoint(Uri.parse('https://api.my.app/utd/channel-auth')),
);
await signaling.connect();
final room = signaling.channels.subscribe('presence-room-7') as UTDPresenceChannel;
room.bind('client-poke').listen((e) => print('${e.userId}: ${e.dataAsMap}'));
await room.whenSubscribed;
await room.trigger('client-poke', {'x': 1});
Building something realtime without the UTD kits? Depend on
utd_channels directly — it is pure Dart, has no
Flutter dependency, and its README covers auth, presence, error handling, and
migrating from Pusher.
Capabilities & billing #
UTD-Stream exposes two independent, separately-billed capabilities:
| Capability | What it covers | Engine flag |
|---|---|---|
signaling |
Call invitation + presence + rooms/broadcast | signaling_enabled |
chat |
Persistent 1:1/group messaging + history + receipts | messaging_enabled |
Each is sold via a subscription plan (Free / Signaling / Pro / Enterprise) with
per-plan limits: history_days, max_group_members, max_friends,
max_chatrooms. Billing is MAU-based — there is no per-message charge.
Session capability surfacing #
The session mint (POST /api/v1/auth/session) may carry the capability flags +
plan limits. They are parsed defensively and are all nullable — a null
flag means the engine didn't surface it (older engine); treat it as "unknown,
let the server decide", distinct from an explicit false.
final signaling = UTDSignaling(appId: 'app_x', appKey: 'key_x', userId: 'alice');
await signaling.connect();
if (signaling.signalingEnabled == false) {
// Paid `signaling` capability is off for this plan — show an upsell.
}
final days = signaling.planLimits?.historyDays; // history retention, when known
UTDSignaling exposes signalingEnabled, chatEnabled, and planLimits
(a UTDPlanLimits with historyDays / maxGroupMembers / maxFriends /
maxChatrooms), captured on every (re)connect. UTDApi.mintSession() returns
the same flags + limits on its result record.
A paid-capability gate from the engine (a non-ban 403 saying a capability is
"not enabled") surfaces as UTDStreamException with
code == UTDErrorCode.featureDisabled; branch on isFeatureDisabled (or
isSignalingDisabled / isChatDisabled) to show an upsell instead of a raw
failure. isForbidden still returns true for it.