Offline Analytics
Offline Analytics extends Coniglio's event-tracking pipeline to capture reading events when users have no internet connection. Events are queued locally on the host application and synchronized in batch when connectivity is restored.
Linear Project: Offline Analytics
Problem
Before this feature, Coniglio operated as an online-only ingestion system. If a user read downloaded content in Fenice (mobile or desktop), all reading events for that session were lost. This affected:
- Education tenants: students reading offline on transit or in areas without connectivity
- Mobile users: Fenice app users reading downloaded content
- Analytics accuracy: session and reading-time reports underrepresented actual usage
Architecture
The solution adds an offline queue layer in the mobile/desktop host (Fenice) between the reader (Volpe) and the analytics backend (Coniglio), using Delfino as the communication bridge. The web host (Farfalla) forwards events directly to Coniglio and does not implement an offline queue.
Component Responsibilities
| Component | Role |
|---|---|
| Volpe | Generates reading events, sends them to the host via Delfino RPC |
| Delfino | RPC bridge; analytics.track() handler routes events to the host |
| Farfalla | Host (web); forwards events to Coniglio directly (no offline queue) |
| Fenice | Host (mobile/desktop); queues events in local storage when offline, syncs on reconnect |
| Coniglio | Backend; receives individual or batch event payloads, deduplicates, stores |
Data Flow
┌─────────────────────────────────────────────────────────────┐
│ Volpe (iframe) │
│ │
│ reading event fires │
│ ↓ │
│ hostBridge.analytics.track(event) │
└────────── ──────────┬────────────────────────────────────────┘
│ Delfino RPC (MessageChannel)
▼
┌─────────────────────────────────────────────────────────────┐
│ Host │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Farfalla (web) │ │ Fenice (mobile/desk.)│ │
│ │ │ │ │ │
│ │ Always online; │ │ Online? ──NO──▶ │ │
│ │ forwards events │ │ ┌──────────────┐ │ │
│ │ immediately │ │ │ Local queue │ │ │
│ │ │ │ │ (configurable│ │ │
│ │ │ │ │ retention) │ │ │
│ │ │ │ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ │ │ ◄───────┘ on │ │
│ │ │ │ reconnect │ │
│ └────────┬─────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ POST /track/* POST /track/* (single) │
│ or /track/batch (queued) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Coniglio │
│ │
│ /track/batch (new endpoint) │
│ ↓ │
│ Validation + Deduplication (by event UUID) │
│ ↓ │
│ track_events → standard aggregation pipeline │
└──────────── ─────────────────────────────────────────────────┘
Key Design Decisions
-
Volpe never talks to Coniglio directly. All events go through Delfino to the host. This decouples the reader from the analytics backend and enables the host to manage connectivity.
-
Timestamps preserve the real event time. The
timestampfield in the tracking payload reflects when the event occurred, not when it was synced. Coniglio uses this original timestamp for session aggregation. -
Configurable local retention. Queued events have a configurable time-to-live to prevent unbounded storage growth. Retention is intended to be tenant-configurable from Farfalla (pending full implementation); Fenice applies a local fallback until the tenant-level setting is available.
-
Batch sync on reconnect. When connectivity is restored, the host sends accumulated events via
POST /track/batchinstead of individual requests, reducing HTTP overhead.
Events Tracked Offline
| Event | Description |
|---|---|
session-start | User opens the reader |
session-resume | User returns to reader within 30 minutes |
session-end | User closes the reader |
page-change | User navigates to a different page |
tab-hidden | Reader goes to background |
tab-visible | Reader returns to foreground |
Events NOT tracked offline:
heartbeat: sent only when the reader has an active connection; it is not queued locally.- Highlights, bookmarks, notes, and other user-generated content interactions: require immediate server sync for conflict resolution.
Delfino Analytics Module
Volpe sends events using the analytics domain in Delfino:
// Volpe side - sending a tracking event
await hostBridge.analytics.track({
eventName: 'session-start',
payload: {
sessionUuid: 'abc-123',
uuid: 'event-uuid-456',
timestamp: Date.now(),
index: 5,
schemaId: 1,
reader: 'volpe',
currentPage: 42,
lastPage: 200,
secondsReading: 120,
},
});
The host implements the analytics.track handler. On Farfalla (web) the handler forwards events directly to Coniglio. On Fenice (mobile/desktop) the handler checks connectivity and enqueues events locally when offline:
// Fenice host - receiving, routing, and queuing events
clientBridge.registerAnalyticsHandlers({
track: async ({ eventName, payload }): Promise<void> => {
if (isOnline()) {
await sendToConiglio(eventName, payload);
} else {
await offlineQueue.enqueue({ eventName, payload });
}
},
});
See the Delfino Architecture doc for the full TrackingPayload and TrackingEventName type definitions.
Coniglio Batch Endpoint
POST /api/v1/track/batch
Accepts an array of tracking events in a single request. Each event in the array follows the same schema as individual POST /track/* requests.
Deduplication: Events include a uuid field. Coniglio uses this to prevent double-counting when the same event is sent both individually (if connectivity was intermittent) and in a batch sync.
Processing: Batch events enter the same validation and storage pipeline as individual events (ValidateTrackRequest → TrackEventStorer → track_events table). The aggregation scheduler processes them in subsequent windows.
Host Queue Implementation
Only Fenice implements a local offline queue. Farfalla (web) forwards events directly to Coniglio and has no queuing layer.
Fenice (Mobile/Desktop)
- Storage: AsyncStorage (React Native) or SQLite
- Sync trigger: Network state change detection
- Precompute: Fenice performs heartbeat precomputation on-device to reduce the volume of events sent in bulk
- Session management: Offline sessions use locally generated
session_uuidvalues
Related Documentation
- Coniglio Overview - Core event tracking pipeline
- Coniglio Design and Architecture - Aggregation and session logic
- Delfino Overview - RPC bridge between Volpe and hosts
- Delfino Architecture - Analytics types and module structure
- Volpe Working Offline - Offline content loading in the reader
- Fenice Overview - Multi-platform reading apps