Skip to content

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 later

Best Practices

  1. Always show connection status - Users should know they're offline
  2. Persist queue - Enable persistQueue: true to survive reload
  3. Handle conflicts - Implement custom onConflict for important data
  4. Warn before close - Alert if pending changes would be lost
  5. Test offline mode - Use Chrome DevTools → Network → Offline

What's Next?