Quick Start
Basic Setup
import { OfflineProtocol, MessagePriority } from '@offline-protocol/mesh-sdk';
const protocol = new OfflineProtocol({
appId: 'my-app',
userId: 'user123',
// Encryption is enabled by default with auto key exchange
});
protocol.on('message_received', (event) => {
console.log(`From ${event.sender}: ${event.content}`);
console.log(`Encrypted: ${event.encrypted}`);
});
// Listen for secure session establishment
protocol.on('secure_session_established', (event) => {
console.log(`Secure session with ${event.peer_id}`);
});
await protocol.start();
const messageId = await protocol.sendMessage({
recipient: 'user456',
content: 'Hello!',
priority: MessagePriority.High,
});
await protocol.stop();
await protocol.destroy();
Advanced Configuration
const protocol = new OfflineProtocol({
appId: 'my-app',
userId: 'user123',
transports: {
ble: { enabled: true },
internet: {
enabled: true,
serverAddress: 'wss://relay.example.com',
autoReconnect: true,
authToken: 'your-auth-token',
},
},
encryption: {
enabled: true, // E2E encryption (default: true)
autoKeyExchange: true, // Auto-exchange keys on peer discovery
storePending: true, // Queue messages until session is ready
requireEncryption: false, // Allow unencrypted fallback
pendingQueue: {
maxPendingPerPeer: 64,
maxPendingGlobal: 4096,
pendingTtlMs: 120000,
overflowPolicy: 'drop_oldest',
},
},
relay: {
allowRelay: true,
minBatteryForRelay: 30,
relayPriority: 'auto', // 'never' | 'auto' | 'always'
},
});
Protocol Lifecycle
Complete Flow Example
import {
OfflineProtocol,
MessagePriority,
ProtocolEvent,
MessageReceivedEvent,
MessageDeliveredEvent,
NeighborDiscoveredEvent,
SecureSessionEstablishedEvent,
ConnectionRequestReceivedEvent,
} 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}`);
});
// Track secure sessions
protocol.on('secure_session_established', (event: SecureSessionEstablishedEvent) => {
console.log(`[SECURE] Session established with ${event.peer_id} (group: ${event.group_id})`);
});
// Handle connection requests
protocol.on('connection_request_received', (event: ConnectionRequestReceivedEvent) => {
console.log(`[CONNECTION] Request from ${event.sender_name} (${event.sender})`);
// Accept or reject in your app UI
});
protocol.on('connection_accepted', (event) => {
console.log(`[CONNECTION] Accepted by ${event.accepted_by_name}`);
});
// Handle presence updates
protocol.on('presence_updated', (event) => {
console.log(`[PRESENCE] ${event.peer_id} is now ${event.status}`);
});
// Handle typing indicators
protocol.on('typing_indicator_received', (event) => {
console.log(`[TYPING] ${event.sender} is ${event.is_typing ? 'typing' : 'idle'}`);
});
// Handle group messages
protocol.on('group_message_received', (event) => {
console.log(`[GROUP] ${event.sender} in ${event.group_id}: ${event.content}`);
});
// 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()
- Protocol core starts in Rust
- 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)
- Process timer starts - polls for outgoing fragments every 100ms
On Peer Discovery
- BLE scan detects advertisement from another device
- 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
- If accepted: BLE connection established,
neighbor_discoveredfires - If at capacity: May evict a lower-scoring peer to make room
On protocol.sendMessage()
- Message created with unique ID, TTL, timestamp, priority
- message_sent event fires immediately
- Message queued for transmission
- DORS selects transport (BLE, WiFi Direct, or Internet)
- Message sent to connected peers
- ACK tracking begins (default 5s timeout)
- On ACK received:
message_deliveredevent fires - On timeout/max retries:
message_failedevent fires
On Incoming Message
- BLE fragment received from peer
- Deduplication check - skip if message ID already seen
- If addressed to this device:
message_receivedevent fires - ACK sent back to sender
- Hop count incremented for metrics
On protocol.stop()
- BLE Manager stops scanning and advertising
- All peer connections closed
- neighbor_lost events fire for each disconnected peer
- 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 configuration reference