Dart client
Dart client library for the Ricochet P2P store-and-forward messaging network.
This is a protocol-level client library — it provides a high-level API for sending/receiving messages, managing mailboxes, storing documents, and tracking presence over the Ricochet S&F protocol. It is designed to work with any Ricochet-compatible server implementation.
This library does not manage networking. It expects a fully configured
dart_libp2p Host (and optionally PubSub) to be injected by the application.
The application is responsible for creating the libp2p host, configuring
transports, discovery, and connection management. This library only operates
on top of that stack by opening libp2p streams to S&F servers using the
Ricochet protocol IDs.
Installation
Section titled “Installation”dependencies: ricochet: path: ../ricochet-dart-clientimport 'package:ricochet/ricochet.dart';Prerequisites
Section titled “Prerequisites”The application must provide:
- A
dart_libp2pHost — already started, with transports configured and the S&F server addresses in its peerstore. - A
PubSubinstance (optional) — only needed for presence tracking. The same GossipSub instance used by the rest of the application.
This library never creates or manages these — it only uses them.
Create and start the client
Section titled “Create and start the client”// The application creates and owns the libp2p host and pubsubfinal host = await buildLibp2pHost(...); // your setupfinal pubsub = PubSub(host); // your setup
// Configure with your preferred S&F servers (MX-style priority)final config = SFClientConfig.withServers([ SFServerPreference(serverId: serverPeerId, priority: 10),]);
// Inject the host and pubsub into the clientfinal client = SFClient( host: host, // required — application-managed libp2p Host config: config, pubsub: pubsub, // optional — enables presence tracking);
await client.start();Send a message
Section titled “Send a message”Messages are automatically routed via S&F servers for offline delivery.
final result = await client.sendMessage( recipient: recipientPeerId, payload: utf8.encode('Hello!'), priority: MessagePriority.normal, folderPath: 'inbox', // target folder (default: inbox) persistent: false, // remove after reading);
if (result.success) { print('Stored at server: ${result.storedAtServer}');}Retrieve messages
Section titled “Retrieve messages”// Own messagesfinal messages = await client.retrieveMessages();
// From another peer's public mailboxfinal publicMessages = await client.retrieveMessages( targetPeerId: otherPeerId, folderPath: 'announcements',);Encrypt the payload
Section titled “Encrypt the payload”Off by default. Build the client with a PayloadEncryptor from the identity’s
Ed25519 seed and pass encrypt: true; the payload is sealed with NaCl box
(X25519 from the two identity keys, XSalsa20-Poly1305) in the same format the
Go client uses, so either client can read what the other sent. The ciphertext
is bound to the recipient, folder and message id, and retrieveMessages
refuses one the server moved or relabelled. Retrieval opens sealed messages
automatically; without an encryptor an encrypted message fails retrieval with
a PayloadDecryptException that names this layer, so an application running
its own end-to-end layer above can tell the two apart.
final client = SFClient( host: host, config: config, encryptor: PayloadEncryptor.fromEd25519Seed(identitySeed), // 32 bytes);
await client.sendMessage( recipient: recipientPeerId, payload: utf8.encode('Hello!'), encrypt: true,);This layer has no forward secrecy (one static key pair per identity; a ratchet belongs above it) and does not detect a replay of the same ciphertext into the same folder.
IMAP-style flag operations
Section titled “IMAP-style flag operations”// Mark as readawait client.markDelivered(messageIds: ['msg-1', 'msg-2']);
// Update flagsawait client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.flagged);
// Soft-delete then expungeawait client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.deleted);await client.expunge();
// Hard-deleteawait client.deleteMessages(messageIds: ['msg-1']);Mailbox management
Section titled “Mailbox management”final mailboxes = client.mailboxes;
// Create a shared mailboxawait mailboxes.createMailbox( folderPath: 'team-updates', type: MailboxType.shared,);
// Grant accessawait mailboxes.grantAccess( folderPath: 'team-updates', type: MailboxType.shared, targetPeerId: colleaguePeerId, accessMode: AccessMode.readWrite,);
// List ACLsfinal acl = await mailboxes.listACL( folderPath: 'team-updates', type: MailboxType.shared,);Document store
Section titled “Document store”Per-user key-value document storage with ETag versioning.
// Store a documentfinal putResult = await client.putDocument( path: 'profile', content: utf8.encode(jsonEncode({'name': 'Alice'})), contentType: 'application/json',);
// Retrieve a documentfinal doc = await client.getDocument(path: 'profile');
// Conditional GET (returns 304 if unchanged)final doc2 = await client.getDocument( path: 'profile', ifNoneMatch: putResult.etag,);
// JSON Merge Patchawait client.patchDocument( path: 'profile', patch: {'bio': 'Updated bio'},);
// List all documentsfinal listing = await client.listDocuments();Peer directory
Section titled “Peer directory”Opt-in server directory for peer discovery.
// Join the directoryawait client.joinDirectory(displayName: 'Alice', bio: 'Hello!');
// Browse the directoryfinal results = await client.browseDirectory(limit: 20);
// Leave the directoryawait client.leaveDirectory();Presence tracking
Section titled “Presence tracking”Requires PubSub (GossipSub) to be passed when constructing SFClient.
final tracker = client.presenceTracker;
// Track a contact's online/offline statustracker.trackContact(contactPeerId, serverPeerId);
// Listen for changestracker.contactPresenceChanges.listen((change) { print('${change.peerId} is now ${change.state.name}');});
// Check statusif (tracker.isOnline(contactPeerId)) { print('Contact is online');}Ricochet URIs
Section titled “Ricochet URIs”Stable, owner-centric resource addressing.
// Format: ricochet://<ownerPeerId>/<resourceType>/<path>[?servers=...]final uri = RicochetUri( ownerPeerId: peerId, resourceType: RicochetResourceType.doc, path: 'profile',);
print(uri.toString());// ricochet://12D3KooW.../doc/profileArchitecture
Section titled “Architecture”lib/ ricochet.dart # Barrel export file client/ sf_client.dart # High-level client API (main entry point) sf_client_config.dart # Client configuration mailbox_manager.dart # Mailbox CRUD and ACL management message_sender.dart # Message routing with retries & failover server_selector.dart # MX-style server selection & health checking presence_tracker.dart # GossipSub presence subscription core/ sf_message.dart # Core message type + request/response types mailbox_address.dart # Mailbox addressing (owner/folder/type) mailbox_types.dart # MailboxType, AccessMode enums message_types.dart # MessagePriority enum message_flags.dart # IMAP-style flags (seen, flagged, deleted, draft) protocol/ maa/ # Mail Access Agent (read path) access_handler.dart # Client-side retrieve, flags, expunge, delete access_frame.dart # Wire encoding/decoding msa/ # Mail Submission Agent (write path) submission_handler.dart # Client-side message submission submission_frame.dart # Wire encoding/decoding mma/ # Mailbox Management Agent (admin) admin_protocol.dart # Request/response/error types admin_frame.dart # Wire encoding/decoding sda/ # Store Document Access document_handler.dart # Client-side GET/PUT/PATCH/HEAD/DELETE/LIST document_frame.dart # Wire encoding/decoding document_crdt.dart # CRDT merge support mailbox_notify_protocol.dart # Push notification protocol sf_frame.dart # Legacy frame utilities stream_utils.dart # Stream read helpers presence/ presence_cache.dart # TTL-based presence cache presence_event.dart # Presence event/heartbeat types registry/ peer_preferences.dart # MX-style server preference records uri/ ricochet_uri.dart # Owner-centric URI scheme ricochet_uri_resolver.dart # URI resolution via DHT/directProtocol IDs
Section titled “Protocol IDs”| Protocol | ID | Purpose |
|---|---|---|
| MSA | /sf-network/submit/1.0.0 |
Message submission |
| MAA | /sf-network/access/1.0.0 |
Message retrieval & flags |
| MMA | /sf-network/admin/1.0.0 |
Mailbox management |
| SDA | /ricochet/store/doc/1.0.0 |
Document storage |
| Notify | /ricochet/mailbox-notify/1.0.0 |
Push notifications |
Server compatibility
Section titled “Server compatibility”This client works with any server implementing the above protocols:
- ricochet — Reference S&F server implementation
- go-ricochet — Go S&F server implementation