Offline Architecture
Overview
Formbird's offline mode enables continuous data access in environments with unreliable connectivity. The system caches documents to the browser's IndexedDB database and static assets via a Service Worker, allowing the application to function fully offline.
This document describes the technical architecture for developers implementing or maintaining offline functionality.
High-Level Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │ Main Thread │ │ Service Worker │ │ Shared/Web Worker │ │
│ │ │ │ │ │ (Offline Poller) │ │
│ │ ┌───────────┐ │ │ - Workbox │ │ │ │
│ │ │DataService│◄─┼──┼──┤ caching │ │ - Polls IndexedDB │ │
│ │ └─────┬─────┘ │ │ - Static files │ │ - Sends to server │ │
│ │ │ │ │ - Vendor libs │ │ - Updates sync status │ │
│ │ ┌─────▼─────┐ │ └────────┬────────┘ └────────────┬────────────┘ │
│ │ │IndexedDB │ │ │ │ │
│ │ │Service │ │ │ │ │
│ │ └─────┬─────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ ┌─────▼─────────────────────────────────────────────▼───────────┐ │
│ │ │ IndexedDB (Dexie) │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ │ │ documents │ │ queue │ │ keyValueStorage │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │
│ └─────────────────┘ │
└────────────────────────────────────────────────────────────────────────┬┘
│
Network │
│
┌────────────────────────────────────────────────────────────────────────▼┐
│ Server Infrastructure │
│ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Formbird Web │ │ Rust Syncher │ │
│ │ Services │ │ (offline-server) │ │
│ │ - REST API │◄─┤ │ │
│ │ - WebSocket │ │ - BSON protocol │ │
│ │ │ │ - Redis session │ │
│ └────────┬─────────┘ └─────────┬─────────┘ │
│ │ │ │
│ ┌────────▼──────────────────────▼──────────────────────────────────┐ │
│ │ MongoDB + ElasticSearch │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
Core Components
1. IndexedDB with Dexie
IndexedDB is the browser-based database used for offline data storage. Formbird uses Dexie.js as a wrapper library because it provides:
- Promise-based API
- Query translation from ElasticSearch syntax
- Schema versioning and upgrades
- Multi-key index support
Key Collections:
| Collection | Purpose |
|------------|---------|
| documents | Cached documents, templates, rulesets |
| queue | Documents waiting to be synced to server |
| keyValueStorage | User config, session data, offline state |
Index Management:
- Indexes are defined in a single offlineIndex document (systemType: "offlineIndex")
- Schema upgrades occur automatically when indexes are added/removed
- The following default indexes are always present:
DOCUMENT_INDEX_PENDING_OPERATION: 'systemHeader.offline.status',
DOCUMENT_INDEX_CURRENT_VERSION_OPERATION: 'systemHeader.offline.currentVersionClient',
DOCUMENT_INDEX_VERSION_ID: 'systemHeader.versionId',
DOCUMENT_INDEX_DOCUMENT_ID: 'documentId',
DOCUMENT_INDEX_SYSTEM_TYPE: 'systemHeader.systemType',
DOCUMENT_INDEX_SUMMARY_NAME: 'systemHeader.summaryName',
DOCUMENT_INDEX_SUMMARY_DESCRIPTION: 'systemHeader.summaryDescription',
DOCUMENT_INDEX_NAME: 'name',
2. Service Worker
The Service Worker handles caching of static assets (HTML, CSS, JS, images) using Workbox. It:
- Caches the application shell for offline loading
- Handles vendor libraries configured via
vendorLibrariesRel - Survives page refresh and continues running when tabs are closed
- Built with Webpack (see
webpack.offline.config.js)
Configuration:
// Service worker injects manifest during build
injectManifest({
"swDest": swDest,
"globDirectory": "server/public",
"globPatterns": ["**/*.{gif,eot,svg,ttf,woff,woff2,ico,png,jpg,js,css,html}"],
"maximumFileSizeToCacheInBytes": 10 * 1024 * 1024 // 10MB max
})
3. Rust Syncher (offline-server)
The Rust-based offline syncher handles efficient synchronization of large datasets. It:
- Uses BSON binary protocol for efficient data transfer
- Reads session data from Redis
- Handles document pagination during initial sync
- Runs as a separate service, proxied via fb-proxy
Starting the syncher:
FB_CONFIG_FILE=server/config/production.json cargo run --package offline-server
4. Offline Poller
The Offline Poller runs in a Shared Worker (or Web Worker on Android Chrome) to:
- Monitor the IndexedDB queue for unsynced documents
- Send documents to the server in order
- Post status updates to the main thread
- Continue running across page refreshes
Note: Shared Workers on Android Chrome are currently being trialled but are not a standard Chrome feature yet. By default, the system uses Web Workers on Android devices. This is configured via the offline.android.enableSharedWorkers setting (see 012-Offline-Setup.md).
Data Flow
Online Mode (Caching Enabled)
- Read Operations:
- Documents are fetched from the server
- If the network fails mid-request, the system falls back to the local cache
-
The user receives data from whichever source succeeds
-
Write Operations:
- Documents are saved locally first to maintain order
- The document is added to the sync queue
- The Offline Poller sends queued items to the server in the background
- Queue status is updated on success or error
Offline Mode
- Read Operations:
- Documents are retrieved directly from the local IndexedDB cache
- No network requests are attempted
-
Returns cached data or an error if the document is not cached
-
Write Operations:
- Documents are saved to the local cache
- Documents are added to the sync queue
- Changes are visible immediately to the user
- Queued documents sync to the server when connectivity is restored
Data Service Switching
The system automatically switches between online and offline data sources based on the following conditions:
- Caching not enabled: All operations use the online server only
- Caching enabled, no connection: All operations use the local offline cache
- Caching enabled, connected: Operations attempt to use the server first. If a network error occurs, the system automatically falls back to the local cache
Initial Caching Process
When a user enables caching:
- Navigate to caching template (if configured via
cachingEnableTemplate) - Load IndexedDB indexes from
offlineIndexdocument - Initialize DataService with offline mode
- Sync documents via Rust syncher:
- Fetch documents in pages (configurable via
syncPageSize) - Insert into IndexedDB
documentscollection - Track progress in
OfflineStatusService - Cache static assets via Service Worker
- Cache map tiles (if configured)
- Mark caching complete
WebSocket Integration
Documents are pushed to cached clients via WebSocket:
- Server saves document to MongoDB
- If document has offline keys, push via WebSocket
WebSocketServicereceives push notification- If document matches user's offline keys, save to IndexedDB
- UI updates automatically with new data
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| Browser Database | IndexedDB + Dexie | Offline document storage |
| Dexie Addon | dexie-addon | Extends Dexie with additional offline capabilities |
| Static Caching | Service Worker + Workbox | App shell, assets, vendor libs |
| Data Sync | Rust (offline-server) | Efficient binary sync protocol |
| Query Translation | dexie-elasticsearch-addon | Formbird-developed translator that converts ElasticSearch queries to IndexedDB queries |
| Session Management | Redis | Share sessions between services |
Related Documentation
- 100-Formbird-Offline-Mode.md - Setup guide
- 102-IndexedDB-and-Dexie.md - Database details
- 103-Service-Workers.md - Service Worker configuration
- 104-Offline-Syncing.md - Rust syncher details