Skip to main content

ricochet 0.3.1 copy "ricochet: ^0.3.1" to clipboard
ricochet: ^0.3.1 copied to clipboard

Dart client library for the Ricochet P2P store-and-forward messaging network

Ricochet 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 #

dependencies:
  ricochet: ^0.2.1

Usage #

import 'package:ricochet/ricochet.dart';

Prerequisites #

The application must provide:

  • A dart_libp2p Host — already started, with transports configured and the S&F server addresses in its peerstore.
  • A PubSub instance (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 #

// The application creates and owns the libp2p host and pubsub
final host = await buildLibp2pHost(...);  // your setup
final 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 client
final client = SFClient(
  host: host,        // required — application-managed libp2p Host
  config: config,
  pubsub: pubsub,   // optional — enables presence tracking
);

await client.start();

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 #

// Own messages
final messages = await client.retrieveMessages();

// From another peer's public mailbox
final publicMessages = await client.retrieveMessages(
  targetPeerId: otherPeerId,
  folderPath: 'announcements',
);

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 #

// Mark as read
await client.markDelivered(messageIds: ['msg-1', 'msg-2']);

// Update flags
await client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.flagged);

// Soft-delete then expunge
await client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.deleted);
await client.expunge();

// Hard-delete
await client.deleteMessages(messageIds: ['msg-1']);

Mailbox management #

final mailboxes = client.mailboxes;

// Create a shared mailbox
await mailboxes.createMailbox(
  folderPath: 'team-updates',
  type: MailboxType.shared,
);

// Grant access
await mailboxes.grantAccess(
  folderPath: 'team-updates',
  type: MailboxType.shared,
  targetPeerId: colleaguePeerId,
  accessMode: AccessMode.readWrite,
);

// List ACLs
final acl = await mailboxes.listACL(
  folderPath: 'team-updates',
  type: MailboxType.shared,
);

Document store #

Per-user key-value document storage with ETag versioning.

// Store a document
final putResult = await client.putDocument(
  path: 'profile',
  content: utf8.encode(jsonEncode({'name': 'Alice'})),
  contentType: 'application/json',
);

// Retrieve a document
final doc = await client.getDocument(path: 'profile');

// Conditional GET (returns 304 if unchanged)
final doc2 = await client.getDocument(
  path: 'profile',
  ifNoneMatch: putResult.etag,
);

// JSON Merge Patch
await client.patchDocument(
  path: 'profile',
  patch: {'bio': 'Updated bio'},
);

// List all documents
final listing = await client.listDocuments();

Peer directory #

Opt-in server directory for peer discovery.

// Join the directory
await client.joinDirectory(displayName: 'Alice', bio: 'Hello!');

// Browse the directory
final results = await client.browseDirectory(limit: 20);

// Leave the directory
await client.leaveDirectory();

Presence tracking #

Requires PubSub (GossipSub) to be passed when constructing SFClient.

final tracker = client.presenceTracker;

// Track a contact's online/offline status
tracker.trackContact(contactPeerId, serverPeerId);

// Listen for changes
tracker.contactPresenceChanges.listen((change) {
  print('${change.peerId} is now ${change.state.name}');
});

// Check status
if (tracker.isOnline(contactPeerId)) {
  print('Contact is online');
}

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/profile

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/direct

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 #

This client works with any server implementing the above protocols:

  • ricochet — Reference S&F server implementation
  • go-ricochet — Go S&F server implementation
0
likes
130
points
749
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Dart client library for the Ricochet P2P store-and-forward messaging network

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

args, cbor, dart_libp2p, dart_libp2p_kad_dht, dart_libp2p_pubsub, dart_multihash, dcid, http, logging, path, pinenacl, uuid, yaml

More

Packages that depend on ricochet