Skip to main content

Quick Start

Basic Setup

import { OfflineProtocol, MessagePriority } from '@offline-protocol/mesh-sdk';

const protocol = new OfflineProtocol({
  appId: 'my-app',
  userId: 'user123',
});

protocol.on('message_received', (event) => {
  console.log(`From ${event.sender}: ${event.content}`);
});

await protocol.start();

const messageId = await protocol.sendMessage({
  recipient: 'user456',
  content: 'Hello!',
  priority: MessagePriority.High,
});

await protocol.stop();
await protocol.destroy();

Protocol Lifecycle

Complete Flow Example

import { 
  OfflineProtocol, 
  MessagePriority,
  ProtocolEvent,
  MessageReceivedEvent,
  MessageDeliveredEvent,
  NeighborDiscoveredEvent,
} from '@offline-protocol/mesh-sdk';

// 1. CREATE PROTOCOL INSTANCE
const protocol = new OfflineProtocol({
  appId: 'my-chat-app',
  userId: 'alice-device-001',
});

// 2. REGISTER EVENT LISTENERS (before starting)
// Track discovered peers
const discoveredPeers = new Map<string, number>(); // peerId -> rssi

protocol.on('neighbor_discovered', (event: NeighborDiscoveredEvent) => {
  console.log(`[PEER FOUND] ${event.peer_id} via ${event.transport}, RSSI: ${event.rssi}`);
  discoveredPeers.set(event.peer_id, event.rssi ?? -100);
});

protocol.on('neighbor_lost', (event) => {
  console.log(`[PEER LOST] ${event.peer_id}`);
  discoveredPeers.delete(event.peer_id);
});

// Track outgoing messages
const pendingMessages = new Map<string, { recipient: string; content: string }>();

protocol.on('message_sent', (event) => {
  console.log(`[SENT] Message ${event.message_id} to ${event.recipient}`);
  pendingMessages.set(event.message_id, {
    recipient: event.recipient,
    content: event.content,
  });
});

protocol.on('message_delivered', (event: MessageDeliveredEvent) => {
  console.log(`[DELIVERED] Message ${event.message_id} in ${event.latency_ms}ms, ${event.hop_count} hops`);
  pendingMessages.delete(event.message_id);
});

protocol.on('message_failed', (event) => {
  console.log(`[FAILED] Message ${event.message_id}: ${event.reason} (${event.retry_count} retries)`);
  pendingMessages.delete(event.message_id);
});

// Handle incoming messages
protocol.on('message_received', (event: MessageReceivedEvent) => {
  console.log(`[RECEIVED] From ${event.sender}: ${event.content}`);
  console.log(`  - Message ID: ${event.message_id}`);
  console.log(`  - Hop count: ${event.hop_count}`);
  console.log(`  - Transport: ${event.transport}`);
  
  // Process the message in your app
  handleIncomingMessage(event);
});

// Monitor transport changes
protocol.on('transport_switched', (event) => {
  console.log(`[TRANSPORT] Switched from ${event.from} to ${event.to}: ${event.reason}`);
});

// 3. START THE PROTOCOL
await protocol.start();

// At this point:
// - BLE scanning begins (discovers nearby devices)
// - BLE advertising begins (makes this device discoverable)
// - neighbor_discovered events will start firing as peers are found

// 4. WAIT FOR PEERS (optional helper)
async function waitForPeer(peerId: string, timeoutMs = 30000): Promise<boolean> {
  if (discoveredPeers.has(peerId)) return true;
  
  return new Promise((resolve) => {
    const timeout = setTimeout(() => resolve(false), timeoutMs);
    
    const handler = (event: NeighborDiscoveredEvent) => {
      if (event.peer_id === peerId) {
        clearTimeout(timeout);
        protocol.off('neighbor_discovered', handler);
        resolve(true);
      }
    };
    
    protocol.on('neighbor_discovered', handler);
  });
}

// 5. SEND A MESSAGE
async function sendChatMessage(recipientId: string, text: string) {
  try {
    const messageId = await protocol.sendMessage({
      recipient: recipientId,
      content: text,
      priority: MessagePriority.High,
    });
    console.log(`Message queued with ID: ${messageId}`);
    return messageId;
  } catch (error) {
    console.error('Failed to send message:', error);
    throw error;
  }
}

// 6. CLEANUP ON APP EXIT
async function cleanup() {
  await protocol.stop();
  await protocol.destroy();
}

Event Sequence Timeline

+=======================================================================+
|                        PROTOCOL LIFECYCLE                             |
+=======================================================================+
|                                                                       |
|  1. new OfflineProtocol(config)                                       |
|     |                                                                 |
|     v                                                                 |
|  2. protocol.on('...', handler)  <- Register all event listeners      |
|     |                                                                 |
|     v                                                                 |
|  3. await protocol.start()                                            |
|     |                                                                 |
|     +--> BLE advertising starts (device becomes discoverable)         |
|     +--> BLE scanning starts (looking for other devices)              |
|     |                                                                 |
|     v                                                                 |
|  +---------------------------------------------------------------+    |
|  |  PEER DISCOVERY PHASE                                         |    |
|  |                                                               |    |
|  |  * neighbor_discovered { peer_id, transport, rssi }           |    |
|  |  * neighbor_discovered { peer_id, transport, rssi }           |    |
|  |  * ...                                                        |    |
|  |                                                               |    |
|  |  MeshController evaluates peers, establishes connections:     |    |
|  |    - MEMBER for same cluster                                  |    |
|  |    - BRIDGE for different clusters                            |    |
|  +---------------------------------------------------------------+    |
|     |                                                                 |
|     v                                                                 |
|  +---------------------------------------------------------------+    |
|  |  MESSAGING PHASE                                               |   |
|  |                                                                |   |
|  |  protocol.sendMessage({ recipient, content, priority })        |   |
|  |     |                                                          |   |
|  |     v                                                          |   |
|  |  message_sent { message_id, recipient, content, ... }          |   |
|  |     |                                                          |   |
|  |     +--> [SUCCESS] message_delivered { message_id, ... }       |   |
|  |     |                                                          |   |
|  |     +--> [FAILURE] message_failed { message_id, reason }       |   |
|  |                                                                |   |
|  |  - - - - - - - - - - - - - - - - - - - - - - - - - - - - -     |   |
|  |                                                                |   |
|  |  INCOMING: message_received { sender, content, ... }           |   |
|  +---------------------------------------------------------------+    |
|     |                                                                 |
|     |  (peers may come and go)                                        |
|     |                                                                 |
|     v                                                                 |
|  * neighbor_lost { peer_id }                                          |
|  * neighbor_discovered { peer_id, ... }  <- new peer appears          |
|     |                                                                 |
|     v                                                                 |
|  4. await protocol.stop()                                             |
|     |                                                                 |
|     +--> BLE scanning stops                                           |
|     +--> BLE advertising stops                                        |
|     +--> All connections closed                                       |
|     |                                                                 |
|     v                                                                 |
|  5. await protocol.destroy()  <- Clean up resources                   |
|                                                                       |
+=======================================================================+

What Happens Under the Hood

On protocol.start()

  1. Protocol core starts in Rust
  2. BLE Manager initializes:
    • Starts scanning for devices advertising the Offline Protocol service UUID
    • Starts advertising this device with mesh metadata (degree, free slots, battery, uptime)
  3. Process timer starts - polls for outgoing fragments every 100ms

On Peer Discovery

  1. BLE scan detects advertisement from another device
  2. MeshController.shouldInitiateOutbound() evaluates the candidate:
    • Checks connection budget (default max: 4)
    • Calculates peer score (RSSI, availability, battery, uptime, stability, load)
    • Determines if this is a cluster bridge opportunity
  3. If accepted: BLE connection established, neighbor_discovered fires
  4. If at capacity: May evict a lower-scoring peer to make room

On protocol.sendMessage()

  1. Message created with unique ID, TTL, timestamp, priority
  2. message_sent event fires immediately
  3. Message queued for transmission
  4. DORS selects transport (BLE, WiFi Direct, or Internet)
  5. Message sent to connected peers
  6. ACK tracking begins (default 5s timeout)
  7. On ACK received: message_delivered event fires
  8. On timeout/max retries: message_failed event fires

On Incoming Message

  1. BLE fragment received from peer
  2. Deduplication check - skip if message ID already seen
  3. If addressed to this device: message_received event fires
  4. ACK sent back to sender
  5. Hop count incremented for metrics

On protocol.stop()

  1. BLE Manager stops scanning and advertising
  2. All peer connections closed
  3. neighbor_lost events fire for each disconnected peer
  4. Protocol core stops

Diagnostic Events

The SDK emits diagnostic events for debugging:
protocol.on('diagnostic', (event) => {
  console.log(`[${event.level.toUpperCase()}] ${event.message}`, event.context);
});

Next Steps

Explore the API reference