clotpulse_flutter
Official Flutter SDK for ClotPulse — self-hosted realtime messaging platform.
Works on Android, iOS, and Flutter Web.
Installation
# pubspec.yaml
dependencies:
clotpulse_flutter:
path: ../sdks/flutter # local — until published to pub.dev
flutter pub get
Android
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
iOS
No extra config needed for HTTPS. For local dev over HTTP, add to ios/Runner/Info.plist:
<key>NSAppTransportSecurity</key>
<dict><key>NSAllowsArbitraryLoads</key><true/></dict>
Quick Start
import 'package:clotpulse_flutter/clotpulse_flutter.dart';
final cp = ClotPulse(
host: 'https://api.clotvpn.app', // your ClotPulse server
appId: 'app_xxxxxxxxxxxx',
key: 'pk_live_xxxxxxxxxxxx', // Public Key — safe in client apps
);
await cp.connect();
// Get a channel and subscribe
final ch = cp.channels.get('chat:1');
// All events on this channel:
ch.subscribe().listen((msg) => print('${msg.name}: ${msg.data}'));
// Specific event only:
ch.subscribe('message').listen((msg) => print(msg.data));
API Reference
ClotPulse — main client
final cp = ClotPulse(
host: 'https://api.clotvpn.app', // required
appId: 'app_xxxxxxxxxxxx', // required
key: 'pk_live_xxxxxxxxxxxx', // required — Public Key
autoReconnect: true, // default: true
reconnectDelay: 2000, // ms, default: 2000
);
await cp.connect(); // connect (throws ClotPulseConnectionException on failure)
cp.close(); // disconnect + dispose all resources
cp.channels — channel registry
// Get or create a channel
final ch = cp.channels.get('chat:1');
// Unsubscribe and release
cp.channels.release('chat:1');
ClotPulseChannel — subscribe to events
// Subscribe to ALL events on the channel
ch.subscribe().listen((ClotPulseMessage msg) {
print('event=${msg.name} data=${msg.data}');
});
// Subscribe to a specific event
ch.subscribe('message').listen((msg) => print(msg.data));
ch.subscribe('order.placed').listen((msg) => handleOrder(msg.data));
ClotPulseMessage
| Field | Type | Description |
|---|---|---|
name |
String |
Event name (e.g. 'message') |
channel |
String |
Channel name (e.g. 'chat:1') |
data |
dynamic |
Event payload (usually Map<String, dynamic>) |
eventId |
String? |
Server-assigned unique event ID |
cp.connection — connection state
// Listen for state changes
cp.connection.on.listen((ConnectionStateChange change) {
print('${change.previous} → ${change.current}');
if (change.current == ConnectionState.connected) {
print('Online!');
}
});
// Current state
print(cp.connection.state); // ConnectionState.connected
print(cp.connection.isConnected); // true / false
States: initialized, connecting, connected, disconnected, failed
ClotPulsePublisher — server-side publish
⚠️ Only use in admin/trusted apps. Never ship the Secret Key in a public app.
final publisher = ClotPulsePublisher(
appId: 'app_xxxxxxxxxxxx',
secretKey: 'sk_live_xxxxxxxxxxxx', // Secret Key
);
// Single event
await publisher.publish('chat:1', 'message', {'text': 'Hello!'});
// Batch
await publisher.publishBatch([
{'channel': 'chat:1', 'event': 'message', 'data': {'text': 'Hi'}},
{'channel': 'alerts', 'event': 'order.placed', 'data': {'id': 42}},
]);
Flutter Widget Example
class ChatWidget extends StatefulWidget {
final String channelName;
const ChatWidget({super.key, required this.channelName});
@override
State<ChatWidget> createState() => _ChatWidgetState();
}
class _ChatWidgetState extends State<ChatWidget> {
late final ClotPulse _cp;
final _messages = <ClotPulseMessage>[];
@override
void initState() {
super.initState();
_connect();
}
Future<void> _connect() async {
_cp = ClotPulse(
host: 'https://api.clotvpn.app',
appId: const String.fromEnvironment('CP_APP_ID'),
key: const String.fromEnvironment('CP_API_KEY'),
);
await _cp.connect();
_cp.channels.get(widget.channelName)
.subscribe('message')
.listen((msg) {
if (mounted) setState(() => _messages.add(msg));
});
}
@override
void dispose() {
_cp.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _messages.length,
itemBuilder: (_, i) => ListTile(
title: Text(_messages[i].data['text'] ?? ''),
),
);
}
}
Secure Token Pattern
For public apps, never hardcode keys. Fetch from your server:
Future<ClotPulse> createClient() async {
final res = await http.post(
Uri.parse('https://yourapp.com/api/realtime-token'),
headers: {'Authorization': 'Bearer $userJwt'},
);
final data = jsonDecode(res.body);
return ClotPulse(
host: 'https://${data['host']}',
appId: data['appId'],
key: data['apiKey'], // field name is apiKey, not key
);
}
Environment Config (no hardcoded keys)
flutter run \
--dart-define=CP_APP_ID=app_xxxxxxxxxxxx \
--dart-define=CP_API_KEY=pk_live_xxxxxxxxxxxx
# In code:
const appId = String.fromEnvironment('CP_APP_ID');
const apiKey = String.fromEnvironment('CP_API_KEY');
Common Mistakes
| ❌ Wrong | ✅ Correct |
|---|---|
host: 'api.clotvpn.app' |
host: 'https://api.clotvpn.app' |
Using secretKey in a public app |
Use key (Public Key) client-side |
key: data['key'] (null) |
key: data['apiKey'] |
Not calling cp.close() in dispose() |
Always call cp.close() to prevent leaks |
Forgetting await cp.connect() |
Await connect before subscribing |
License
MIT — ClotPulse
Libraries
- clotpulse_flutter
- ClotPulse Flutter SDK