Offline & Persistence
Tracked events and survey responses are persisted locally before being sent, and marked
synced once the network request succeeds — so a page reload or a dropped connection doesn’t
lose data mid-flight. flush() (web only — see Platform Parity) forces
a send of whatever’s currently pending, ahead of the periodic timer.
Web’s storage fallback
Web persists to IndexedDB. If IndexedDB is unavailable (private browsing in some browsers,
storage quota exceeded, etc.), it falls back to localStorage transparently — isReady()
returns true either way, and isFallbackMode() tells you which one you’re in if you need to
know. Callers don’t need to branch on this; both modes support the same add/get/mark-synced
operations.
Locally-stored session and cached data are obfuscated with a simple XOR cipher keyed on the
API key before being written to localStorage. This is not encryption — the API key
itself is public (it ships in your client-side bundle), so this only deters casual
inspection via browser DevTools, not a real confidentiality guarantee. Don’t rely on it to
protect sensitive data.
Event history retention
user.eventHistory (used by EVENT_FREQUENCY/EVENT_RECENCY/EVENT_ABSENCE trigger rules —
see How Triggering Works) is capped: 90-day retention, 100
timestamps per event name, 50 distinct event names tracked. Once a given event’s timestamp
list exceeds its cap, older entries are dropped and the entry is marked truncated — a windowed
count against a truncated history becomes a lower bound, which is sound for
“greater than N” checks and unsound for “less than N” or “exactly N” ones. This is a known,
accepted limit of bounded local storage, not a bug to work around.