A purpose-built two-sided communication system — a locked-down Android kiosk for seniors, paired with a native app and PWA for family. One tap to connect. No tech skills required.
System flow
The kiosk tablet lives with the senior. The family app lives on everyone else's phone. Each is purpose-built for its user.
The hard problems in building for seniors aren't in the happy path — they're in the edge cases.
Provisioned as Android Device Owner via ADB or QR setup. Lock Task Mode enforced by Device Policy Manager — camera and microphone permissions granted automatically, no user prompts.
Family APK stores all credentials (device token, API key) in EncryptedSharedPreferences via SecurityUtils. No plaintext secrets on-device. VAPID keys are server-side only.
Google OAuth2 and FCM v1 signaling implemented manually using Node's native crypto library — minimal container footprint, no heavy SDK dependency chains.
Better-SQLite3 with WAL mode and a single-thread sync model for zero-latency kiosk heartbeat processing. No async database races, deterministic state.
KioskJsInterface exposes Android hardware to the React WebView — volume, brightness, wake locks, Bluetooth SCO. The Kotlin layer intercepts WebView requests to serve locally cached R2 photos offline.
Both APKs self-update via a lifecycle manager that polls the backend, downloads from R2 (5-min OkHttp timeout), and installs silently through Device Policy Manager — no Play Store required.
Device tokens verified using constant-time cryptographic comparison to prevent timing side-channel attacks. Admin routes require API key; tablet routes use x-device-id scoping.
Full lifecycle coverage — patient and contact creation, photo ordering, call flow, auth enforcement (401 on bad key), and CORS verification. Test data is deterministically isolated from production.
Zero-downtime deployments on a self-hosted private cloud. No open inbound firewall ports — all traffic flows through a Cloudflare Zero Trust Tunnel.
Hypervisor fabric across three PVE nodes — instant VM failover, split-brain mitigation, and live migration without downtime.
Backend API runs as a replicated Swarm service. Deploy triggers a rolling update — new container starts before old one stops.
Traefik handles internal routing and TLS termination. Cloudflare Tunnel forwards public traffic — no exposed ports on the host network.
APK binaries and slideshow photos staged in R2. Kiosk tablets cache photos locally via transparent WebView request interception — offline-first.
No ORM. No connection pools. Synchronous better-sqlite3 on a single worker — predictable, fast, and simple to reason about.
Admin routes, tablet routes, and family device routes each have distinct auth mechanisms and access patterns.
Protected by shared x-api-key header. Manages patients, contacts, photos, APK releases.
Auth via x-device-id. Tablet registers itself on first boot, heartbeats every minute, receives call signals.
Paired via QR/deep-link, assigned a device_token. Used for call initiation, status polling, callback requests.
Purpose-picked tools — no framework churn, no unnecessary abstractions.
Kiosk APK + Family APK
Kiosk UI + Family PWA
Node.js API framework
better-sqlite3, sync model
WebRTC video / audio
Push notifications, both platforms
Asset CDN + zero-port ingress
Multi-replica HA deployment
Reverse proxy + TLS
3-node HA hypervisor cluster
Self-hosted secrets backup
214 tests, full lifecycle coverage
The backend deploys as a Docker stack. The kiosk APK provisions itself as Device Owner. Family members pair with a QR code.
Configure stack/family-kiosk.yml with your domain and Docker secrets. Run deploy-backend.ps1 to build and push to your Swarm cluster.
Factory reset an Android tablet, tap the welcome screen 6× to enter QR provisioning mode, scan the generated code. The APK installs and sets itself as Device Owner automatically.
Admin generates a pairing QR code or deep link. Family member scans it in the Family App or PWA — device token is issued, push subscriptions registered, done.