The Background
Traditional cloud POS systems fail the moment the router blinks. In a fast-paced retail or F&B environment, a 30-second hang while waiting for a server response means lost revenue and frustrated customers.
To solve this, I inverted the traditional client-server model. The PWA's local IndexedDB acts as the primary, authoritative database for the cashier. The central Supabase server acts as a background sync engine. Cashiers read, write, and complete transactions entirely on the edge, ensuring sub-second response times regardless of network conditions.
The Challenges
Challenge
When multiple offline terminals come back online simultaneously, state-based syncing (sending final numbers) leads to catastrophic ledger overwrites. If Terminal A and Terminal B both sell apples offline, whoever syncs last overwrites the other's math.
Solution
I mitigated this by implementing an Event Sourcing model paired with negative-inventory edge trust.
- Instead of syncing final inventory states, terminals sync immutable logs of actions (e.g., 'Terminal 1 subtracted 2 Apples').
- The central server processes these events sequentially upon connection, ensuring mathematically perfect ledgers.
- I utilize a 'Trust the Edge' physical resolution: If an offline sale pushes server inventory below zero, the transaction is accepted, but an anomaly alert is fired to the owner's real-time reporting dashboard.
Challenge
Storing sensitive transaction data locally exposes it to physical tampering.
Solution
Because web applications cannot access OS-level secure hardware enclaves, I built a robust cryptographic layer natively in the browser.
- Cashier PINs are mathematically stretched using the Web Crypto API (PBKDF2 ) and a unique device salt to derive an AES-256 encryption key.
- To prevent RAM scraping and unauthorized access, the derived key is kept strictly in memory and wiped immediately upon tab closure or power loss.
- Every transaction generates a cryptographic hash of the previous transaction, creating a tamper-evident chain.
- During the offline-to-online sync, an HMAC signature is generated. If a malicious actor alters a single byte of the local IndexedDB, the server rejects the batch.
Challenge
'Lie-Fi '—when a device is connected to a router with no active internet—is worse than being completely offline. The browser's native navigator.onLine property reports true, causing API calls to hang indefinitely.
Solution
I implemented a strict client-side Circuit Breaker pattern. It uses active background heartbeats and aggressive 3-second abort timeouts to detect latency. When the breaker trips open, the UI instantly routes all actions to the local queue without blocking the cashier.
typescript
class SyncCircuitBreaker {
state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
failureCount = 0;
threshold = 2;
async executeSync(payload: SyncPayload) {
if (this.state === 'OPEN') return this.queueLocally(payload);
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(payload),
signal: controller.signal
});
clearTimeout(timeoutId);
this.resetBreaker();
return response;
} catch (error) {
this.failureCount++;
if (this.failureCount >= this.threshold) this.tripBreaker();
return this.queueLocally(payload);
}
}
}Architectural Trade-Offs
trade-offThe largest operational risk in offline-first systems is deploying software updates. If the backend API requires a new field, but a tablet has been offline for a week running the old code, its eventual sync attempt will fail, stranding the data.
To mitigate this, backend schema changes are strictly additive (optional fields only). Furthermore, when the PWA service worker eventually pulls the new client code, RxDB executes a client-side database migration script to reshape the stuck local data before attempting to sync with the new API.