@capgo/capacitor-updater (MPL-2.0) in manual mode: autoUpdate off,
stats/update/channel URLs empty (nothing contacts Capgo), appReadyTimeout
15s (auto rollback), resetWhenUpdate (APK update → built-in bundle, venue
bundle re-fetched), autoDeletePrevious off (we keep one per venue).
src/native/bundles.js syncVenueBundle():
- manifest fetched over the pinned TLS link; zip downloaded natively over
the LAN HTTP port with the MANDATORY sha256 check - a tampered zip never runs
- applied at once only when nobody is mid-service (after pairing / venue
switch, or no one logged in); otherwise staged with next() → applied at the
next background/restart; never a mid-service reload
- APK too old for the bundle (min_shell_build) → skipped + update banner
linking to the venue's /downloads/xenia-waiter.apk
- a rolled-back version is blocklisted (attempt tracking + grace window) -
without it the app re-applied the still-advertised broken bundle in a loop
- server without bundles (404) → built-in UI; bundles no venue needs deleted
BundleSync: notifyAppReady on start; sync at start, on resume, every 30 min.
saveVenue merges fields (bundleVersion); switchVenue flags immediate apply.
E2E (emulator vs two isolated venue stacks): fresh pairing applies the venue
bundle and it stays healthy; logged-in update staged, applied after
background, session kept; tampered zip refused; shell-too-old banner;
broken bundle rolled back automatically and not retried (80s + resume);
two venues each run their own bundle, round trips served from cache with 0
downloads, stale bundles cleaned; no *.capgo.app traffic. Step 5/7-era
rediscovery + cold-start suites and web modes still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Android: MainActivity installs a WebViewClient whose onReceivedSslError
proceeds only when the server's SPKI SHA-256 is in TrustStore (pins of all
paired venues, SharedPreferences); XeniaTlsPlugin lets JS set that list.
Spike showed this one callback covers fetch/XHR, images AND WebSockets.
Pins are per key, not per address: IP changes, renewals and expiry never
need action; a different key is always refused.
- Pairing: QR key (k=) must equal the server's advertised pin, else refused;
typed addresses trust the advertised key on first use. Then the venue moves
to https://<ip>:<tls.port> (stays on HTTP if that port isn't reachable yet).
- upgradeToTls(): on every start, a plain-HTTP venue moves onto TLS once the
server offers it (never accepting a key different from the stored one).
- main.jsx pushes the venues' pins to native before the first request.
- Rediscovery made proactive: checks the saved address at start and every
minute while the live connection is down, instead of waiting for requests
to an unanswered IP to time out (minutes). HTTPS probes get 5s: the first
TLS connection in a fresh process takes ~2s (measured).
E2E on the emulator vs an isolated stack: wrong-key QR refused; pairing on
TLS; tables + wss live; impostor server with another key refused natively;
HTTP venue upgraded on start; server cert renewed (same key) - app keeps
working without re-pairing; rediscovery over TLS (11s). Step 5 HTTP
rediscovery (now 2.7s/4.8s) and cold-start login tests, and web modes, pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- capacitor.config.json: appId gr.bonamin.xenia, webDir dist-native,
androidScheme http (origin http://localhost - no mixed-content block when
calling venue servers over plain HTTP on the LAN)
- vite: `--mode native` builds to dist-native with the PWA plugin disabled
(no service worker inside the app)
- Android: minSdk 24 / target 36; CAMERA permission for QR pairing;
cleartext allowed via network_security_config (LAN IPs can't be listed
per-domain); allowBackup=false so backups never carry login tokens
- Release signing reads ~/.xenia/keystore.properties (override with
XENIA_KEYSTORE_PROPS) - the key never enters the repo; versionName 1.0.0 /
versionCode 1 with a bump-both rule for sideloaded updates
- npm scripts build:native, apk:debug, apk:release (scripts/build-apk.mjs);
APKs land in releases/ (gitignored); release APK is also copied to
public/downloads/ so venue servers serve it at /downloads/xenia-waiter.apk
- waiter nginx: /downloads/ served as application/vnd.android.package-archive,
real 404 when missing (never falls through to index.html)
- launcher icons + splash generated from the app icon (assets/ is the source)
- .gitattributes: gradlew LF, *.bat CRLF
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>