How offline works
localStorage outbox
nu_pos_react/src/realtime/sync/outbox.ts
The outbox is a queue of pending HTTP actions stored in localStorage under the key pos_sync_outbox. It survives browser refreshes and crashes.
Entry structure
Queuing
pos_reference: if an entry with the same reference already exists, it’s replaced with the newer payload. This prevents duplicate orders.
In hub mode, when connection.send() fails, the snapshot is queued as an outbox entry with an _hubSnapshot field.
Flushing
flushOutbound() processes the queue:
- Reads all entries from localStorage
- Filters entries where
nextAttemptAt <= now(respects backoff) - For each entry, sends
POST /pos-react/api/orders/{action}to Odoo - On success: removes entry from queue
- On failure: increments
attempts, computes next retry time, stores error
Retry backoff
After
maxRetry attempts (default 8), the entry stays in the queue but won’t be retried until app restart.
Outbox depth monitoring
The transport exposesgetOutboxDepth() which returns the current number of pending entries. This is reflected in the sync state machine via OUTBOX_CHANGED events and displayed by the SyncStatusIndicator badge.
Hub-side event log
The hub maintains its own event log for upstream forwarding to Odoo. This is a separate mechanism from the terminal outbox.Forwarder
nu_pos_hub/src/upstream/forwarder.ts drains the event log to Odoo:
- Runs every 5 seconds (
FORWARD_INTERVAL_MS) - Queries up to 20 pending events (
FORWARD_BATCH_SIZE), oldest first - Non-order events are marked as forwarded immediately (no Odoo action needed)
- Order snapshot events are forwarded via the Odoo adapter
- On success: marks events as
forwarded=1 - On first failure: logs error, stops processing batch (preserves ordering)
Idempotency
All order operations are idempotent viapos_reference:
- Odoo’s create endpoint returns the existing order if the reference already exists
- The hub’s
orderStore.upsert()usesINSERT OR REPLACEkeyed bypos_reference - The outbox deduplicates by
pos_referencebefore queuing