feat(waiter): configurable server/venue layer for native app and plain-HTTP browser mode
New waiter_pwa/src/config/server.js is the single place that knows where the backend is. Served by the venue's server (https domain or http://<LAN IP>) nothing changes: same-origin URLs and the original storage keys, so existing installs keep their token and unsynced offline queue. With an active venue (native app, or dev builds with VITE_SERVER_URL) URLs become absolute and all venue data is namespaced by siteId: token/savedUsername keys, the Dexie DB (pos_snapshot__<siteId>, which also covers the WS cursor), favorites and table-view prefs. Switching venue reloads the app. - api client baseURL, WebSocket and SSE URLs routed through the layer - product images / waiter avatars rendered via assetUrl() - service-worker update prompt skipped in native builds - InstallAppBanner: shown only in plain-HTTP browser mode and only when VITE_APP_DOWNLOAD_URL is set at build time (dismiss for 7 days) - VITE_SERVER_URL override is DEV-only (a URL-controlled server in prod would let a crafted link capture PINs) - pack README: rule CS-8 on never assuming same-origin Verified with Playwright/Edge against a local backend: prod build same-origin, prod build via LAN IP over plain HTTP (insecure context, no SW, banner shown), and dev build pointed at the backend by URL - all three log in, reach /tables and receive the WebSocket 'ready' frame; storage keys and IndexedDB names are as expected. Lint: no new problems (103 before/after). Build passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -25,7 +25,7 @@ phone ──https──▶ proxy:443 ──http──▶ waiter_pwa nginx:80 ─
|
||||
└─ /static/ → backend:8000/static/
|
||||
```
|
||||
Every hop must forward WebSocket upgrades: `proxy_http_version 1.1`, plus `Upgrade` and `Connection` headers, plus a long `proxy_read_timeout`.
|
||||
The frontends call the API **same-origin, with relative paths** (`baseURL: ''`). Don't hardcode hosts.
|
||||
The manager calls the API **same-origin, with relative paths**. The waiter app resolves every backend URL through `waiter_pwa/src/config/server.js`: same-origin when served by the venue's server, absolute when running in the native app (see CS-8). Don't hardcode hosts.
|
||||
|
||||
## Key files
|
||||
|
||||
@@ -35,6 +35,7 @@ The frontends call the API **same-origin, with relative paths** (`baseURL: ''`).
|
||||
| `local_backend/services/cloud_sync.py` | Every call to the cloud (see the parent's `docs/reference/cloud-contract.md`) |
|
||||
| `local_backend/roles.py`, `routers/deps.py` | Roles, permission checks, auth dependencies |
|
||||
| `local_backend/routers/ws.py` | Real-time event stream (seq + cursor replay) |
|
||||
| `waiter_pwa/src/config/server.js` | **Where the backend is**: active venue, `apiBase()`, `wsUrl()`, `assetUrl()`, `storageKey()`, `dbName()`, `deliveryMode()` |
|
||||
| `waiter_pwa/src/api/client.js` | axios instance: auth header, 401 → logout, network error → offline |
|
||||
| `waiter_pwa/src/context/SSEContext.jsx` | Real-time lifecycle, event → store/cache updates, visibility refresh |
|
||||
| `waiter_pwa/src/db/posdb`, `src/services/offlineOrders` | Offline cache and queued orders |
|
||||
@@ -69,6 +70,13 @@ Edit `nginx-proxy/nginx.conf` **and** the heredoc in `install.sh` together (glob
|
||||
**CS-6. Permissions are checked on the backend.**
|
||||
Hiding a button in the UI is not access control. Every new endpoint declares its role or `perm_*` requirement through `routers/deps.py`.
|
||||
|
||||
**CS-8. The waiter app never assumes it's served by the backend.**
|
||||
The same waiter build runs same-origin (https domain, or plain `http://<LAN IP>`) **and** inside the native app talking to a remote venue server. So in `waiter_pwa`:
|
||||
- REST goes through `api/client.js` (its `baseURL` is `apiBase()`). Any other fetch, `EventSource` or `WebSocket` must build its URL with `apiBase()` or `wsUrl()`.
|
||||
- Backend-served files (`/static/...` images, avatars) are rendered through `assetUrl()`.
|
||||
- Persistent **venue-specific** data (token, IDs of products/tables/zones) uses `storageKey()` for localStorage keys and zustand `persist` names. The IndexedDB name comes from `dbName()`. Device preferences (theme, payment safety…) stay global.
|
||||
- Features that need a secure context (service worker, camera, notifications, clipboard) must degrade gracefully when `deliveryMode()` is `'plain-web'`.
|
||||
|
||||
**CS-7. Greek-market realities.**
|
||||
Staff UIs are used by Greek staff and must render Greek correctly, including on thermal printers (codepage). Money uses €.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user