# How BitChat's MessageRouter Prioritizes Bluetooth Mesh Over Nostr

> Discover how BitChat's MessageRouter prioritizes Bluetooth mesh over Nostr using integer transport priorities to ensure the first reachable connection. Learn more about this efficient routing strategy.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-08

---

**BitChat's MessageRouter prioritizes Bluetooth mesh over Nostr by assigning transports integer priority values—where `BluetoothMeshTransport` returns 0 and `NostrTransport` returns 1—then selecting the first reachable transport from the ascending-sorted list.**

The `MessageRouter` in the [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat) repository implements a deterministic transport selection algorithm that favors low-latency local networks over internet-wide relays. When sending outbound messages, the router evaluates the `priority` and `isReachable` properties of each configured transport to determine the optimal delivery path. Understanding how BitChat's MessageRouter prioritizes Bluetooth mesh over Nostr reveals the optimization strategy behind its decentralized messaging architecture.

## Transport Priority Architecture

BitChat abstracts message delivery through transport objects that expose a common interface including `priority: Int`, `isReachable: Bool`, and `isConnected: Bool`. The router interprets lower integer values as higher precedence during route selection.

- **BluetoothMeshTransport**: Returns `priority = 0` (highest precedence)
- **NostrTransport**: Returns `priority = 1` (fallback precedence)

This numeric ranking system allows `MessageRouter` to make transport decisions without hard-coded conditional logic, adhering to the open/closed principle for adding future transports.

## The Selection Algorithm

The core routing logic resides in `MessageRouter.selectTransport(for:)` inside [`bitchat/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/MessageRouter.swift). When routing an `OutgoingMessage`, the algorithm executes two sequential operations:

1. Sorts the transports array by priority in ascending order
2. Returns the first transport where `isReachable` evaluates to `true`

```swift
private func selectTransport(for message: OutgoingMessage) -> Transport? {
    // 1️⃣ Sort by priority (ascending)
    let sorted = transports.sorted { $0.priority < $1.priority }
    // 2️⃣ Pick the first reachable transport
    return sorted.first { $0.isReachable }
}

```

Because `BluetoothMeshTransport` maintains `isReachable = true` whenever peer devices are within proximity, it preempts `NostrTransport` even if the latter maintains an active WebSocket connection. This guarantees that local mesh delivery is attempted before any internet-bound transmission.

## Why Bluetooth Mesh Takes Precedence

The priority assignment reflects concrete performance characteristics rather than arbitrary preference.

### Latency and Locality

Bluetooth mesh messages travel directly between peer devices via multi-hop radio links, eliminating round-trips to external servers. This architecture reduces delivery latency to sub-second mesh hops compared to the multi-second propagation delays inherent in Nostr relay networks.

### Cost and Privacy

Mesh communication remains confined to the user's immediate physical environment. By routing through `BluetoothMeshTransport` first, BitChat minimizes bandwidth consumption on metered connections and reduces metadata exposure to public relays, keeping conversations within the local trust domain until internet fallback becomes necessary.

## Implementation Details

The production initialization in [`bitchat/ViewModels/ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModelBootstrapper.swift) demonstrates the explicit transport ordering:

```swift
let router = MessageRouter(
    transports: [
        BluetoothMeshTransport.shared,   // priority 0
        NostrTransport.shared           // priority 1
    ]
)

```

Key source files defining this behavior:

- **[`bitchat/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/MessageRouter.swift)**: Implements `selectTransport(for:)` and the priority-based sorting logic
- **[`bitchat/Transports/BluetoothMeshTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Transports/BluetoothMeshTransport.swift)**: Defines `priority = 0` and calculates `isReachable` based on nearby peer detection
- **[`bitchat/Transports/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Transports/NostrTransport.swift)**: Defines `priority = 1` and reports `isReachable` when WebSocket connections are active
- **[`bitchatTests/Services/MessageRouterTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/MessageRouterTests.swift)**: Contains unit tests verifying mesh selection precedes Nostr when both transports are available

## Practical Usage Examples

*Creating a router with explicit priority ordering:*

```swift
import BitChat

let mesh = BluetoothMeshTransport.shared      // priority 0
let nostr = NostrTransport.shared            // priority 1

let router = MessageRouter(transports: [mesh, nostr])

// Sending automatically prefers mesh if peers are reachable
router.sendPrivate(
    "Hey there!",
    to: peerID,
    recipientNickname: "Alice",
    messageID: UUID().uuidString
)

```

*Inspecting transport selection for debugging:*

```swift
router.onTransportChosen = { transport, messageID in
    print("Picked transport: \(type(of: transport)) for message \(messageID)")
}

```

*Forcing Nostr-only fallback:*

```swift
let router = MessageRouter(transports: [NostrTransport.shared])   // mesh omitted

```

## Summary

- **BitChat's MessageRouter prioritizes Bluetooth mesh over Nostr** by sorting transports using ascending integer priority values.
- `BluetoothMeshTransport` reports priority `0`, while `NostrTransport` reports priority `1`, establishing a strict precedence hierarchy.
- The `selectTransport(for:)` method in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift) selects the first reachable transport from the sorted array, guaranteeing mesh is chosen first when peers are nearby.
- If the mesh transport becomes unreachable (e.g., devices move out of range), the router automatically falls back to Nostr without requiring manual intervention.
- This design minimizes latency, preserves bandwidth, and keeps local communications within the physical device mesh before attempting internet-wide relay.

## Frequently Asked Questions

### How does MessageRouter handle transport failures?

If `selectTransport(for:)` returns a transport that subsequently fails during transmission, the router does not automatically cascade to the next priority transport for that specific message instance. Instead, the failure propagates to the caller, which must initiate a new send attempt. Upon retry, the router re-evaluates `isReachable` status and may select `NostrTransport` if `BluetoothMeshTransport` has become unreachable, ensuring eventual delivery without complex retry logic inside the router itself.

### Can I force BitChat to use Nostr instead of Bluetooth mesh?

Yes. When initializing `MessageRouter`, simply exclude `BluetoothMeshTransport.shared` from the transports array and pass only `NostrTransport.shared`. This configuration forces all messages through internet relays regardless of local mesh availability. You can also dynamically filter the transports array before router instantiation to honor user preferences for internet-only mode.

### What happens if Bluetooth mesh becomes unreachable mid-conversation?

The router evaluates transport reachability on every call to `selectTransport(for:)`. If peer devices move out of Bluetooth range and `BluetoothMeshTransport.isReachable` transitions to `false`, subsequent messages automatically route through `NostrTransport`. This state transition requires no manual network switching and provides seamless conversation continuity across changing network topologies.

### Where is the MessageRouter instantiated in the production app?

The router is wired into the view-model architecture in [`bitchat/ViewModels/ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModelBootstrapper.swift). This bootstrapper constructs the ordered transport array—placing `BluetoothMeshTransport` before `NostrTransport`—and injects the configured `MessageRouter` instance into the chat view models, where it manages all outbound message routing for the application lifecycle.