board.offline & board.connection - Network Resilience
Handle network disconnections and offline operations gracefully.
Overview
The SDK provides robust offline support through two APIs:
- board.offline - Offline queue and retry logic
- board.connection - Connection status monitoring
Connection Status
isOnline - Check Connection State
typescript
const isOnline = board.connection.isOnline;
if (!isOnline) {
showOfflineIndicator();
}status - Get Detailed Status
typescript
const status = board.connection.status;
// 'connected' | 'connecting' | 'offline'
switch (status) {
case 'connected':
console.log('All good');
break;
case 'connecting':
console.log('Reconnecting...');
break;
case 'offline':
console.log('No connection');
break;
}on('statusChange') - Watch Connection
typescript
board.connection.on('statusChange', (status, previousStatus) => {
console.log(`Connection: ${previousStatus} → ${status}`);
const indicator = document.querySelector('#status');
if (status === 'offline') {
indicator.textContent = 'Offline';
indicator.className = 'offline';
} else if (status === 'connecting') {
indicator.textContent = 'Reconnecting...';
indicator.className = 'connecting';
} else {
indicator.textContent = 'Online';
indicator.className = 'online';
}
});Offline Queue
Automatic Buffering
Operations are automatically queued when offline:
typescript
// Works even offline!
await board.storage.set('count', 5);
// SDK saves to IndexedDB:
// { op: 'set', key: 'count', value: 5, timestamp: ... }When connection is restored, the queue is automatically processed.
getPendingOperations() - View Queue
typescript
const pending = board.offline.getPendingOperations();
console.log(pending);
/*
[
{ type: 'set', key: 'count', value: 5, timestamp: 1732530000000 },
{ type: 'update', key: 'votes', value: [...], timestamp: 1732530005000 }
]
*/retryPending() - Manual Retry
typescript
// Force retry of pending operations
await board.offline.retryPending();clearPending() - Clear Queue
typescript
// WARNING: This will lose unsynced data!
board.offline.clearPending();Configuration
Enable/Disable Offline Support
typescript
const board = new BoardAPI({
offline: {
enabled: true, // Enable offline support
maxQueueSize: 100, // Max operations in queue
retryInterval: 5000, // Retry every 5 seconds
maxRetries: 10, // Max retry attempts
persistQueue: true // Save to IndexedDB (survives reload)
}
});Examples
Connection Indicator
typescript
import { BoardAPI } from '@boardapi/sdk';
const board = new BoardAPI();
function updateIndicator() {
const indicator = document.querySelector('#connection-indicator');
const status = board.connection.status;
const isOnline = board.connection.isOnline;
indicator.className = status;
if (isOnline) {
indicator.innerHTML = '● Online';
} else {
const pending = board.offline.getPendingOperations().length;
indicator.innerHTML = `● Offline (${pending} pending)`;
}
}
board.connection.on('statusChange', updateIndicator);
board.on('ready', updateIndicator);Offline Notice
typescript
const board = new BoardAPI();
board.connection.on('statusChange', (status) => {
const notice = document.querySelector('#offline-notice');
if (status === 'offline') {
notice.style.display = 'block';
notice.textContent = 'You are offline. Changes will sync when reconnected.';
} else if (status === 'connecting') {
notice.style.display = 'block';
notice.textContent = 'Reconnecting...';
} else {
notice.style.display = 'none';
}
});Sync Status
typescript
const board = new BoardAPI();
function showSyncStatus() {
const pending = board.offline.getPendingOperations().length;
const status = board.connection.status;
const badge = document.querySelector('#sync-badge');
if (pending > 0) {
badge.textContent = `${pending} pending`;
badge.style.display = 'block';
} else if (status === 'connecting') {
badge.textContent = 'Syncing...';
badge.style.display = 'block';
} else {
badge.textContent = 'Synced';
badge.style.display = 'none';
}
}
// Update every second
setInterval(showSyncStatus, 1000);
board.on('reconnect', () => {
board.ui.showToast('Changes synced', { type: 'success' });
showSyncStatus();
});Manual Retry Button
typescript
const board = new BoardAPI();
document.querySelector('#retry-btn').addEventListener('click', async () => {
const pending = board.offline.getPendingOperations();
if (pending.length === 0) {
board.ui.showToast('Nothing to sync', { type: 'info' });
return;
}
board.ui.showToast('Syncing...', { type: 'info' });
try {
await board.offline.retryPending();
board.ui.showToast('Synced successfully', { type: 'success' });
} catch (err) {
board.ui.showToast('Sync failed', { type: 'error' });
}
});Offline Form Submission
typescript
const board = new BoardAPI();
document.querySelector('#form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = collectFormData();
try {
await board.storage.set('submission', formData);
if (board.connection.isOnline) {
board.ui.showToast('Submitted', { type: 'success' });
} else {
board.ui.showToast('Saved offline - will submit when online', {
type: 'warning',
duration: 5000
});
}
clearForm();
} catch (err) {
board.ui.showToast('Failed to save', { type: 'error' });
}
});
// Show success when synced
board.on('reconnect', () => {
const pending = board.offline.getPendingOperations();
if (pending.length === 0) {
board.ui.showToast('Form submitted', { type: 'success' });
}
});Prevent Data Loss
typescript
const board = new BoardAPI();
// Warn before closing with pending changes
window.addEventListener('beforeunload', (e) => {
const pending = board.offline.getPendingOperations();
if (pending.length > 0 && !board.connection.isOnline) {
e.preventDefault();
e.returnValue = 'You have unsaved changes that will be lost.';
return e.returnValue;
}
});Conflict Resolution
typescript
const board = new BoardAPI({
offline: {
enabled: true,
onConflict: async (localValue, serverValue, key) => {
// Custom merge logic
if (key === 'count') {
// For counters, use max value
return Math.max(localValue, serverValue);
} else if (key === 'votes') {
// For arrays, merge unique items
return [...new Set([...localValue, ...serverValue])];
} else {
// Default: last-write-wins (server wins)
return serverValue;
}
}
}
});Offline Queue Flow
User Action (offline)
↓
board.storage.set('count', 5)
↓
SDK saves to IndexedDB
↓
Operation queued
↓
Connection restored
↓
SDK auto-retries queue
↓
Server processes operations
↓
Success → remove from queue
Conflict → call onConflict()
Error → retry laterBest Practices
- Always show connection status - Users should know they're offline
- Persist queue - Enable
persistQueue: trueto survive reload - Handle conflicts - Implement custom
onConflictfor important data - Warn before close - Alert if pending changes would be lost
- Test offline mode - Use Chrome DevTools → Network → Offline
What's Next?
- Lifecycle Events - Handle reconnect event
- UI Controls - Show toast notifications
- Storage API - Understand what gets queued