utd_channels 0.1.0
utd_channels: ^0.1.0 copied to clipboard
Standalone Pusher-style realtime SDK for UTD-Stream-Engine: channels (public/private/presence), event pub/sub, presence member lists, and typed connection lifecycle. Pure Dart — no Flutter, no UI, no [...]
example/utd_channels_example.dart
// A minimal two-user CLI chat over a UTD presence channel.
//
// Run it in two terminals against a dev project (one with
// `channels_insecure_dev` enabled):
//
// export UTD_APP_ID=app_x UTD_APP_KEY=key_x
// dart run example/utd_channels_example.dart alice
// dart run example/utd_channels_example.dart bob
//
// Type a line to send it. `/who` lists who is online, `/quit` exits.
import 'dart:convert';
import 'dart:io';
import 'package:utd_channels/utd_channels.dart';
Future<void> main(List<String> args) async {
final identity = args.isNotEmpty ? args.first : 'alice';
final env = Platform.environment;
final client = UTDChannelsClient(
appId: env['UTD_APP_ID'] ?? 'app_dev',
// Dev quickstart: publishable key, client-asserted identity. In
// production use UTDAuth.tokenProvider / UTDAuth.endpoint so YOUR backend
// verifies the user and mints the session (see the README).
auth: UTDAuth.insecureDevelopment(
appKey: env['UTD_APP_KEY'] ?? 'key_dev',
identity: identity,
name: identity,
),
// Dev only. Production: UTDChannelAuth.endpoint(<your auth endpoint>).
channelAuth: const UTDChannelAuth.insecureDevelopment(),
logger: (m) {}, // swap for stderr.writeln to see transport diagnostics
);
client.connectionStates.listen(
(c) => stdout.writeln('* connection: ${c.previous} -> ${c.current}'),
);
client.errors.listen((e) => stderr.writeln('! ${e.code}: ${e.message}'));
client.forceExit.listen((reason) {
// Terminal: signed in elsewhere (4003) or reconnect attempts exhausted.
stderr.writeln('! session ended: $reason');
exit(1);
});
// Synchronous — bind now, the subscribe itself is queued until connected
// and re-authorized automatically after every reconnect.
final room = client.subscribe('presence-example-room') as UTDPresenceChannel;
// `userId` is server-attested from the sender's subscription — a client
// can never spoof it in the payload.
room
.bind('client-chat')
.listen((e) => stdout.writeln('<${e.userId}> ${e.dataAsMap['text']}'));
room.memberAdded.listen((m) => stdout.writeln('* ${m.id} joined'));
room.memberRemoved.listen((m) => stdout.writeln('* ${m.id} left'));
try {
await client.connect();
await room.whenSubscribed;
} on UTDStreamException catch (e) {
stderr.writeln('connect failed (${e.code}): ${e.message}');
await client.dispose();
return;
}
stdout.writeln(
'* joined as ${room.me?.id} — ${room.members.length} online '
'(socket ${client.socketId})',
);
final lines = stdin.transform(utf8.decoder).transform(const LineSplitter());
await for (final line in lines) {
if (line.isEmpty) continue;
if (line == '/quit') break;
if (line == '/who') {
stdout.writeln('* online: ${room.members.keys.join(', ')}');
continue;
}
try {
// Ack-based: completes when the server accepted the event.
await room.trigger('client-chat', {'text': line});
} on UTDRateLimitedException catch (e) {
stderr.writeln('! rate limited — retry in ${e.retryAfter ?? "a moment"}');
} on UTDStreamException catch (e) {
stderr.writeln('! send failed (${e.code}): ${e.message}');
}
}
await client.unsubscribe(room.name);
await client.dispose();
}