art_adk 1.0.2
art_adk: ^1.0.2 copied to clipboard
Flutter SDK for ART realtime messaging — WebSocket channels, presence tracking, end-to-end encrypted channels, and CRDT-backed shared objects.
Flutter SDK for ART — A Realtime Tech communication,, a realtime messaging platform providing WebSocket-based channels, presence tracking, end-to-end encrypted messaging, and CRDT-backed shared objects.
Features #
- WebSocket connection management — connect, pause, resume, and auto-reconnect with exponential backoff
- Channel subscriptions — default, targeted, group, secure (encrypted), and shared-object (CRDT) channels
- Push messages — send structured payloads with optional per-user targeting
- Event listening — receive every message with
listen()or bind to named events viaemitter.on() - Presence tracking — observe users online on a channel in real time
- End-to-end encryption — crypto applied transparently on secure channels
- Interceptors — hook into the message pipeline to resolve or reject payloads
- Shared objects (CRDT) — collaborative state with automatic conflict resolution
Installation #
Add art_adk to your pubspec.yaml:
dependencies:
art_adk: ^1.0.2
Fetch packages:
flutter pub get
Import the SDK where you need it:
import 'package:art_adk/art_adk.dart';
Configuration #
1. Create assets/adk-services.json #
Generate client credentials from the ART Live Dashboard and save them under assets/adk-services.json:
{
"Client-ID": "xxxxxxxx",
"Client-Secret": "xxxxxxxxxxxxxxxx",
"Org-Title": "YOUR_ORG",
"ProjectKey": "YOUR_PROJECT_KEY",
"Environment": "YOUR_ENV"
}
Register the asset in your app's pubspec.yaml:
flutter:
assets:
- assets/adk-services.json
See Workspace and Project for how to obtain
ProjectKeyandEnvironment.
2. Load the credentials #
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
import 'package:art_adk/art_adk.dart';
Future<CredentialStore> loadCredentials() async {
final raw = await rootBundle.loadString('assets/adk-services.json');
final json = jsonDecode(raw) as Map<String, dynamic>;
return CredentialStore(
environment: json['Environment'] as String,
projectKey: json['ProjectKey'] as String,
orgTitle: json['Org-Title'] as String,
clientID: json['Client-ID'] as String,
clientSecret: json['Client-Secret'] as String,
);
}
3. Get a user passcode #
The art_adk package authenticates with ART using a short-lived passcode issued for a single user:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> fetchPasscode(CredentialStore creds) async {
final response = await http.post(
Uri.parse('https://dev.arealtimetech.com/ws/v1/connect/passcode'),
headers: <String, String>{
'Client-Id': creds.clientID,
'Client-Secret': creds.clientSecret,
'X-Org': creds.orgTitle,
'Environment': creds.environment,
'ProjectKey': creds.projectKey,
'Content-Type': 'application/json',
},
body: jsonEncode(<String, dynamic>{
'username': 'jane_doe',
'first_name': 'Jane',
'last_name': 'Doe',
}),
);
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
final data = decoded['data'] as Map<String, dynamic>;
return data['passcode'] as String;
}
Quick start #
import 'package:art_adk/art_adk.dart';
Future<void> main() async {
final creds = await loadCredentials(); // from asset JSON
final passcode = await fetchPasscode(creds); // from your auth service
final updated = creds.copyWith(accessToken: passcode);
final adk = Adk(
adkConfig: AdkConfig(
uri: 'ws.arealtimetech.com',
authToken: passcode,
getCredentials: () => updated,
),
);
adk.on('connection', (dynamic data) {
if (data is ConnectionDetail) {
debugPrint('connected → ${data.connectionId}');
}
});
adk.on('close', (dynamic reason) => debugPrint('closed: $reason'));
await adk.connect();
final sub = await adk.subscribe(channel: 'room-42');
sub.emitter.on('message', (dynamic data) {
debugPrint('received: $data');
});
await sub.push(
event: 'message',
data: <String, dynamic>{'text': 'Hello ART!'},
);
}
Connecting #
final adk = Adk(
adkConfig: AdkConfig(
uri: 'ws.arealtimetech.com', // scheme optional; wss:// is added automatically
authToken: passcode,
getCredentials: () => updatedCreds,
),
);
await adk.connect();
For short-lived workflows, wrap connect / disconnect in try / finally:
try {
await adk.connect();
// your logic here
} finally {
await adk.disconnect();
}
Read the current state at any time:
adk.getState(); // 'connected' | 'retrying' | 'paused' | 'stopped'
Subscribing to a channel #
adk.subscribe returns a BaseSubscription. For default / secure / targeted / group channels it is a Subscription; for shared-object channels it is a LiveObjSubscription.
final BaseSubscription sub = await adk.subscribe(channel: 'room-42');
if (sub is LiveObjSubscription) {
// Shared-object (CRDT) channel — use sub.state(), sub.query(), sub.flush().
}
Unsubscribe when you no longer need the channel:
await sub.unsubscribe();
Pushing messages #
await sub.push(
event: 'message',
data: <String, dynamic>{'text': 'Hello'},
);
Target a specific user (required for secure and targeted channels — exactly one recipient):
await sub.push(
event: 'message',
data: <String, dynamic>{'text': 'Hi Bob'},
options: const PushConfig(to: <String>['bob']),
);
Attempting to target zero or more than one recipient on a secure / targeted channel throws
ARTError.serverError.
Receiving messages #
Bind to a specific event on the subscription's emitter:
sub.emitter.on('message', (dynamic data) {
debugPrint('got: $data');
});
Or, on default channels, cast to Subscription and stream every event:
if (sub is Subscription) {
sub.listen((Map<String, dynamic> data) {
debugPrint("event=${data['event']} content=${data['content']}");
});
}
Unbind a specific listener:
if (sub is Subscription) {
sub.remove(event: 'message');
}
Presence #
Track who is online on a channel:
final cancelPresence = await sub.fetchPresence(
callback: (List<String> users) {
debugPrint('online: $users');
},
);
// later:
await cancelPresence();
Presence tracking must be enabled on the channel in the ART dashboard.
Encrypted channels #
Secure channels apply end-to-end NaCl-box encryption per message. After connecting, generate (or set) a key pair — the SDK publishes the public half automatically:
await adk.generateKeyPair(); // creates + uploads a fresh KeyPair
// or
await adk.setKeyPair(existingPair); // adopt an externally-managed KeyPair
Then push as usual. Encryption and decryption happen transparently:
final secure = await adk.subscribe(channel: 'YOUR_SECURE_CHANNEL');
await secure.push(
event: 'message',
data: <String, dynamic>{'text': 'This is private'},
options: const PushConfig(to: <String>['bob']), // secure requires exactly one recipient
);
secure.emitter.on('message', (dynamic data) {
debugPrint('decrypted: $data');
});
Interceptors never see encrypted channel traffic — ART preserves message privacy end-to-end.
Shared object channels (CRDT) #
Shared-object channels expose a JSON-like document that every subscriber edits concurrently; conflicts are resolved automatically via the SDK's CRDT engine.
final sub = await adk.subscribe(channel: 'YOUR_CRDT_CHANNEL');
if (sub is LiveObjSubscription) {
// 1. Write
sub.state()['document']['title'].set('My Doc');
await sub.flush();
// 2. Read once
final snapshot = await sub.query(path: 'document').execute();
debugPrint('doc: $snapshot');
// 3. Observe
final dispose = await sub.query(path: 'document').listen((dynamic data) {
debugPrint('document updated: $data');
});
// later:
dispose();
}
Array operations #
final items = sub.state()['items']; // CRDTProxy
items.push('alpha'); // append
items.unshift('zero'); // prepend
items.pop(); // remove last
items.removeAt(2); // remove by index
items.splice(start: 1, deleteCount: 2, insert: <dynamic>['x', 'y']);
await sub.flush();
debugPrint('length: ${items.length}');
Pending local writes are auto-batched and flushed every 50 ms; call flush() to force an immediate merge.
Interceptors #
Interceptors let you plug custom logic into the server-side message pipeline. Each intercepted message must be resolved (forwarded) or rejected.
await adk.intercept(
interceptor: 'profanity-filter',
fn: (
Map<String, dynamic> payload,
void Function(dynamic data) resolve,
void Function(String error) reject,
) {
final content = payload['data'];
if (content is Map && '${content['text']}'.contains('anyword')) {
reject('blocked by profanity filter');
return;
}
resolve(payload);
},
);
Encrypted channels are never routed through interceptors.
Connection lifecycle #
// Life-cycle listeners
adk.on('connection', (dynamic data) {
if (data is ConnectionDetail) {
debugPrint('connectionId=${data.connectionId}');
}
});
adk.on('close', (dynamic reason) => debugPrint('closed: $reason'));
// Manual controls
adk.pause(); // tear the socket down, keep subscriptions
await adk.resume(); // bring it back
await adk.disconnect(); // full teardown
Reconnection is automatic — up to 5 attempts with a 3–5 s backoff, after which the state becomes stopped and you must call connect() manually.
Common codes: notConnected, authenticationFailed, serverError, encryptionError, decryptionError, timeout, channelNotFound, forbidden.
API reference #
| Class / type | Purpose |
|---|---|
Adk |
Top-level facade — connect, subscribe, intercept, key management |
AdkConfig |
Connection configuration (URI, credentials, auth token) |
CredentialStore |
Immutable holder for org / project / client credentials |
BaseSubscription |
Base type for all channel subscriptions (push, fetchPresence, unsubscribe) |
Subscription |
Default / secure / targeted / group channel — listen(), bind(), emitter.on() |
LiveObjSubscription |
CRDT-backed shared-object channel — state(), query(), flush() |
CRDTProxy |
Chainable accessor for reading / mutating CRDT state |
PushConfig |
Push options — to: List<String> for targeted delivery |
KeyPairType |
KeyPairType for secure channels |
ConnectionDetail |
Emitted on 'connection' — contains connectionId, instanceId, tenant info |
Documentation #
Full documentation is available at docs.arealtimetech.com/docs/adk.
| Topic | Link |
|---|---|
| Overview | ADK Overview |
| Installation | Flutter Installation |
| Publish & Subscribe | Pub/Sub Docs |
| Connection Management | Connection Docs |
| User Presence | Presence Docs |
| Encrypted Channels | Encryption Docs |
| Shared Object Channels | Shared Object Docs |
| Interceptors | Interceptor Docs |
License #
Released under the MIT License.