Przejdź do treści

Etap 4.5 — Offline mode guarantees

The kiosk is designed to keep authenticating users and releasing parcels even when its uplink to api.smartbox.ergoflow.app is down. Backend remains the source of truth — anything the kiosk does offline is a local approximation that gets reconciled on the next sync tick.

What the kiosk caches

SyncService pulls a delta from GET /machines/me/sync?since=<isoCheckpoint> every 2 minutes (and once at boot) and writes into sqflite:

Table Source Purpose
cached_users active User rows updated since checkpoint recipient identity for pickup screen
cached_credentials active Credential rows (id / userId / type / bcrypt valueHash only) offline PIN auth (OfflineRepo.authenticatePin)
cached_lockers this machine's Locker rows future deposit-offline support; menu currently uses availableFunctions from /machines/me
cached_shipments this machine's DEPOSITED shipments offline pickup list (OfflineRepo.shipmentsForUser)
sync_cursor one row, holds latest checkpoint ISO next pull is a delta, not a full snapshot

Plaintext PINs / RFID values never leave the backend. We ship the bcrypt hash and BCrypt.checkpw locally — exactly the same primitive CredentialsService.authenticate uses on the server side.

ErgoFlow employees (etap 6) flow through this same pipeline. The backend's ErgoflowService JIT-creates a SmartBox User (email = <userNumber>@ergoflow.local) plus an RFID Credential (valueHash = bcrypt(userNumber)) for every active upstream employee. The next /machines/me/sync tick fans those rows out to cached_users + cached_credentials, so a kiosk card swipe from an ErgoFlow employee authenticates offline with no extra setup. When ErgoFlow flips allowLogin=0 for an employee or drops them from the active list, the backend marks the SmartBox User inactive — the next sync tick removes the row from cached_users and offline auth stops accepting that card.

What the kiosk queues for replay

outbox table — one row per recorded mutation that the kiosk wants the backend to apply once the link is back. Drained in insert order by SyncService after every successful pull.

kind payload Server handler Notes
pickup.completed { shipmentId } MachineSyncService.replayPickupCompleted flips DEPOSITED → PICKED_UP, locker → FREE; honours kiosk's occurredAt for pickedUpAt
deposit.completed { shipmentId } replayDepositCompleted flips ASSIGNED → DEPOSITED, locker → OCCUPIED. Sender flow is online-only today; format reserved for etap 7

Each event in the replay POST gets one of three outcomes:

  • OK — server applied the mutation. Kiosk deletes the outbox row.
  • CONFLICT — server already moved on (panel cancelled the shipment, another kiosk pickup, expiry worker fired etc.). Kiosk deletes the outbox row anyway — retrying never resolves a conflict — and the conflict is captured in AuditLog (actorType=SYSTEM, action=system.notification_*).
  • FAILED — unexpected server error or unknown event kind. Kiosk increments retryCount and retries on the next tick. Manual ops intervention is needed if these stack up.

Operations matrix

Operation Online behaviour Offline behaviour Reconciliation
PIN at login POST /deposit/verify-sender bcrypt-check against cached_credentials n/a — read-only
List my parcels (menu → Pickup) GET /pickup/by-user?userId= (creates OPEN_LOCKER tasks) OfflineRepo.shipmentsForUser (no task creation — kiosk drives LockerHardware.open() locally) next sync rebuilds cached_shipments
Open a locker LockerHardware.open() (Mock today, Modbus etap 5) identical hardware is local
Confirm pickup POST /pickup/:id/complete queue pickup.completed in outbox + local cached_shipments.status='PICKED_UP' next replay flips server-side; CONFLICT possible if panel cancelled meanwhile
Send a parcel (deposit) full online flow via POST /deposit/begin rejected — kiosk shows network error. Couldn't allocate a tracking ID without server n/a
OTA install runs through PS agent + state file n/a (OTA needs internet) n/a

Header indicator

The SyncStatusBanner chip in the kiosk header has three visual states:

  • Hidden — online + outbox empty. Healthy steady state.
  • Cyan SYNC (n) — online + outbox has n rows pending replay. Transient (next tick clears it on success).
  • Amber OFFLINE (n) — last sync attempt failed; n rows queued. Stays up until the link returns.

Manual recovery

If the operator suspects the kiosk has a stuck queue:

```powershell

Windows

Get-Content C:\smartbox-kiosk\agent.log -Tail 50 # shows last sync attempt + reason

Optional: open the cache directly with a SQLite browser

C:\smartbox-kiosk\kiosk-cache.sqlite — examine outbox, sync_cursor

```

```bash

Linux

tail -50 ~/.smartbox-kiosk/agent.log sqlite3 ~/.smartbox-kiosk/kiosk-cache.sqlite "SELECT * FROM outbox; SELECT * FROM sync_cursor;" ```

A drastic reset (clears outbox + cache; kiosk re-pulls from scratch on next tick): delete kiosk-cache.sqlite and restart the UI. Any in-flight pickup completions in the outbox are lost — only do this if you've already manually verified the panel state matches.