What Is the MessageRouter in BitChat's Architecture?

The MessageRouter acts as the central nervous system of BitChat's messaging layer, bridging high-level UI components with low-level transport protocols while managing outbox persistence, secure session routing, and store-and-forward fallback mechanisms.

In the permissionlesstech/bitchat repository, the MessageRouter class located in bitchat/Services/MessageRouter.swift implements the critical coordination logic required for decentralized messaging. It abstracts the complexity of transport selection and message reliability, ensuring that ChatViewModel and other consumers can invoke sendPrivate() without managing network intermittency or peer availability directly.

Transport Selection and Routing Logic

The router evaluates transport availability through methods like reachableTransport and connectedTransport to determine the optimal delivery path for a given peer. According to the source implementation, this selection logic evaluates the current network topology and connection state before committing to a transmission strategy.

The core transport selection occurs in [MessageRouter.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift#L82-L88), where the router iterates through available transports (Mesh, Nostr, BLE) to identify viable pathways. When a direct transport is available, the router immediately dispatches the message; otherwise, it triggers fallback mechanisms.

Outbox Management and Persistence

To prevent message loss during network partitions, the router maintains an in-memory outbox with disk persistence through MessageOutboxStore. The enqueue logic implements FIFO eviction and per-peer limits to prevent unbounded memory growth.

The enqueue function retains copies of outgoing private messages until acknowledgements are received, respecting a 24-hour TTL. For durability, the router persists the outbox state via MessageOutboxStore, restoring queued messages on application launch and merging recovered state into the active routing table.

Secure Session Handling

When a transport supports encrypted delivery, the router manages secure session establishment before transmission. The secure transmission path verifies that authentication has completed before marking messages for secure dispatch.

If authentication completes after messages are queued, the router invokes retrySecurePrivateMessagesAfterAuthentication to re-evaluate the outbox and transmit eligible messages through the newly authenticated secure channel.

Courier Deposit and Store-and-Forward

When no direct transport is available, the router seals payloads using the recipient's static Noise key and deposits them with intermediary peers (couriers) or internet bridges. This store-and-forward mechanism ensures delivery even when sender and recipient are never simultaneously online.

The attemptCourierDeposit function handles the cryptographic sealing and handoff to available couriers. The router monitors carrier status through the courierBecameAvailable callback, immediately offering queued messages to new carriers that enter proximity.

Retry Logic and Bridge Coordination

For messages deposited with internet bridges, the router implements periodic retry logic to handle transient connectivity failures. The retryBridgeCourierDeposits function re-attempts submission for messages that remain undelivered, using exponential backoff to avoid overwhelming the bridge infrastructure.

When flushOutbox is triggered—typically when a peer becomes reachable—the router iterates through pending messages and attempts immediate delivery, bypassing the courier layer if a direct transport is now available.

Delivery Acknowledgements and Cleanup

The router processes delivery acknowledgements through markDelivered, removing retained copies from the outbox and invoking UI callbacks like onMessageCarried. For messages that exceed the 24-hour TTL or surpass per-peer caps, the cleanupExpiredMessages routine purges stale entries and notifies the UI via onMessageDropped.

Implementation Patterns from the Source Code

Applications interact with the router through a streamlined API that abstracts the underlying complexity. The ChatViewModel initializes the router with available transports and delegates message dispatch to this central coordinator.

Sending a private message requires only the content and recipient metadata:

let router = MessageRouter(transports: availableTransports)
router.sendPrivate(
    "Hey, did you see the new update?",
    to: peerID,
    recipientNickname: "Alice",
    messageID: UUID().uuidString
)

The router exposes callback closures for UI updates:

router.onMessageCarried = { messageID, peerID in
    // Update UI to show "carried by a peer" indicator
    chatUI.showCarrierNotice(messageID: messageID, for: peerID)
}

router.onMessageDropped = { messageID in
    // Handle TTL expiration or delivery failure
    chatUI.showDeliveryFailed(messageID: messageID)
}

When network conditions change, explicit flushing ensures immediate delivery attempts:

// Triggered when a peer's connection status changes
router.flushOutbox(for: peerID)

For secure session recovery, the router provides:

router.retrySecurePrivateMessagesAfterAuthentication(
    for: [peerIDAlias1, peerIDAlias2]
)

Key Files in the BitChat Routing System

Summary

  • The MessageRouter serves as the central abstraction between BitChat's UI layer and its decentralized transport implementations.
  • It implements transport selection logic to choose between Mesh, Nostr, BLE, or courier-based delivery based on real-time peer availability.
  • The outbox system provides FIFO-ordered, persistent queuing with TTL enforcement and per-peer limits to ensure message durability without unbounded growth.
  • Store-and-forward capabilities leverage Noise-encrypted courier deposits and internet bridge fallbacks when direct transport is unavailable.
  • Automatic retry mechanisms handle bridge deposits and secure session re-authentication, flushing the outbox when connectivity is restored.
  • Delivery lifecycle management includes acknowledgement processing, expiration cleanup, and UI callbacks for carried or dropped messages.

Frequently Asked Questions

What triggers the MessageRouter to select a specific transport?

The router evaluates transport availability through reachableTransport and connectedTransport checks at L82-L88. It selects the first viable transport that reports connectivity to the target peer, prioritizing direct channels over courier-based store-and-forward.

How does the MessageRouter handle message persistence across app restarts?

The router serializes its in-memory outbox to disk via MessageOutboxStore before termination and restores state on launch at L149-L158. This merge process recovers undelivered messages and reconstructs the routing queue without requiring re-transmission from the UI layer.

What happens when a message exceeds the 24-hour TTL?

The cleanupExpiredMessages routine periodically scans the outbox for entries exceeding the TTL threshold. Expired messages are purged from both memory and persistent storage, and the onMessageDropped callback notifies the UI to update message status indicators.

How does the MessageRouter differ from the Transport layer in BitChat?

While Transport implementations (defined in [Transport.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift)) handle protocol-specific wire transmission, the MessageRouter manages cross-transport decision logic, message queuing, and delivery guarantees. The router consumes transport interfaces but adds store-and-forward semantics, retry logic, and outbox persistence that individual transports do not implement.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →