Offline & resilience
Buffer writes when the device is offline and replay them in order on reconnect, apply optimistic updates with automatic rollback, and keep push delivery alive across token rotation.
Real apps go offline — a subway tunnel, an elevator, a flaky cafe network. The SDK has three opt-in capabilities so a lost connection never means lost data or a broken UI:
- Automatic offline queue — a write that fails because the device is offline is buffered to a durable on-device outbox and replayed in order the moment connectivity returns. Replays are idempotent, so a write that reached the server before the connection dropped is never applied twice.
- Optimistic updates — update your local view immediately, fire the real write, and roll the local change back automatically if the write fails.
- Push-token auto-rotation — re-register the device push token whenever the OS rotates it, so notifications keep arriving without the app noticing.
All three are opt-in and additive — existing code keeps its exact behavior until you turn them on.
Automatic offline queue
Call Amba.offline.enable() once after configure(). From then on, a
collection write (insert / update / delete) or a track() that fails with
a transport error is queued instead of throwing. On reconnect the queue
drains in FIFO order.
Idempotent replay
Each queued collection write carries a stable idempotency key, sent on both the live attempt and every replay. If a write actually reached the server before the connection dropped, the replay is recognized as a duplicate and ignored — you never get a double-insert. This is automatic; you don't manage the keys.
Buffered events (track) carry an idempotency key too, and Amba records
each key once, so an event replayed any number of times is recorded once. On
React Native the live attempt of an engagement event sends the same key, so an
event whose request reached Amba but timed out is recorded once when it
replays. On Web and Node the live attempt carries no key, so such an event can
be recorded twice. Replays keep the event's original time, so an event keeps
its real occurred_at instead of being stamped at flush. A replay that finds
no valid session stays in the queue and lands once the session is back.
Request deadlines
Every request runs under a deadline that covers sending it and reading the
whole response. A request still unfinished at its deadline is aborted and
fails with the retryable NETWORK_ERROR, so the outbox queues it (when it is a
write) and keeps it for the next flush. A request the network never answers
can't hold up the queue, a sign-in or a later call.
| Option | Default | Applies to |
|---|---|---|
requestTimeoutMs | 15000 (15 s) | Every API request. |
longRequestTimeoutMs | 300000 (5 min) | AI generation (for a stream, the time until it starts), functions.invoke(), media uploads and downloads. |
Pass either to configure(). functions.invoke(name, body, { timeoutMs })
sets the deadline for one invocation. A request that moves more than 64 KiB
(an upload, a download, a large event batch) gets one more second for every
further 8 KiB, so it can finish on a slow link. A realtime subscription must
start streaming within requestTimeoutMs; a connection that hasn't is retried
with backoff, and once it streams it stays open for as long as you subscribe.
A request that timed out may still have reached Amba. Collection inserts and
queued events carry idempotency keys, so their replays are safe. Before you
retry another write yourself after a NETWORK_ERROR (a purchase, a currency
spend), read back its result.
Each write belongs to the user who made it
Every queued write records the signed-in user (or, before any sign-in, the
install's anonymous id). A flush replays only the current user's writes, and
pending() / pendingCount() list only theirs. When someone else signs in on
the same device without a sign-out, the earlier user's writes wait and replay
once that user signs back in. Amba.auth.signOut() empties the queue, so
nothing a signed-out user queued can land in the next account; a write whose
request was still in flight at sign-out is refused with
OFFLINE_QUEUE_PURGED. A write queued before sign-in belongs to the install's
anonymous id, so once that id changes (signOut(true), or every page load with
web sessionPersistence: 'memory') it can't replay and is dropped. Writes
queued by an SDK version without owners are dropped too. Dropped writes are
counted in dropped.
The queue API
| Method | Description |
|---|---|
Amba.offline.enable() | Turn the outbox on. Returns a disable function. Idempotent. |
Amba.offline.isEnabled() | Whether the outbox is currently on. |
Amba.offline.pendingCount() | Number of the current user's writes awaiting replay. |
Amba.offline.pending() | Snapshot the current user's pending writes (a copy). |
Amba.offline.flush() | Force a replay attempt now → { flushed, dropped, remaining }. |
Amba.offline.clear() | Drop every queued write without replaying. |
A queued write that the server later rejects deterministically (e.g. a
VALIDATION_ERROR it would fail every time) is dropped on replay rather
than retried forever — it's reported in the dropped count (and a one-time
warning) so one poisoned write can't wedge everything behind it. Transient
connectivity failures, by contrast, keep the queue intact for the next attempt.
This is distinct from Amba.sync (manual change replay for explicit offline sync flows).
Amba.offline is the automatic, fire-and-forget safety net for ordinary writes — turn it on and
forget about it.
Optimistic updates
Show the change instantly, reconcile in the background, and roll back cleanly if the write fails. The collection surface has thin convenience helpers for the common single-write case:
On success the helper resolves with the server's authoritative row. On failure
it runs your rollback and rethrows the original error, so you can surface it.
Optimistic writes always hit the network directly — they are not diverted to the offline
outbox even when Amba.offline is enabled. Optimistic UI needs an unambiguous outcome, so the
write resolves only on a confirmed server write and rejects (running rollback) on any failure,
including being offline. To buffer-and-replay instead, use the plain insert / update /
delete with the outbox on.
For multi-step flows, the framework-agnostic withOptimistic helper takes any
async mutate — apply and rollback operate on whatever local view you hand
them (a React state setter, a store, an in-memory cache). Give it a mutate
that fails on error (a transaction, a function call, your own request) so a
failure rejects and triggers rollback:
Push-token auto-rotation
The OS can rotate a device's push token at any time (reinstall, restore, key roll). When it does, the old token stops delivering. Auto-rotation re-registers the fresh token through the idempotent registration path so notifications keep arriving.
Re-registration is idempotent, so re-registering the same device is a no-op server-side — it's always safe to leave auto-rotation on.