Codemagic build failed at :app:checkDebugAarMetadata — "Could not find
com.github.davidliu:audioswitch". LiveKit Android 2.29.2 pulls in that
JitPack-only fork (commit-hash version), and Gradle resolves the whole
:app transitive graph against the APP module's (+ allprojects) repos, not
the native-call plugin's. The plugin-level jitpack repo was therefore
ignored. android-patch.js now injects `maven { url 'https://jitpack.io' }`
into the generated root build.gradle's allprojects{} (and settings.gradle
dependencyResolutionManagement{} if a newer template uses it). Idempotent,
build-tolerant. TECHSTACK rev 3 note updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
Tech Stack — BizGaze Connect
Living document. Last updated: 2026-10-30 (rev 3). Update this file whenever a dependency, platform, service, or tool is added/removed/version-bumped. See Maintaining this file and the Changelog at the bottom.
What this project is
BizGaze Connect is a self-hosted team chat + meetings + calling product (evolved from a remote screen-sharing/"remote-access" tool, whose agent console still lives in the repo). One backend serves a web app and is wrapped by native mobile (Capacitor) and desktop (Electron) shells. The UI loads live from the server, so web changes deploy without rebuilding the native apps.
Architecture style: thin, dependency-light Node backend (custom modules, no web framework) + a single-page vanilla-JS web UI + native shells that wrap the web and add native capabilities (push, calling, screen capture, file access). Real-time via WebSockets; meeting media via WebRTC (P2P mesh, or LiveKit SFU when enabled).
Backend (server)
- Runtime: Node.js —
enginesrequires >= 22.5; the Docker image isnode:24-alpine. - HTTP/HTTPS: Node built-in
http/httpsmodules. No Express/Koa/Fastify — a small custom router (routes.js) maps"METHOD /path"to handlers; static files + authenticated downloads viastatic.js. - WebSockets:
ws^8.18(WebSocketServeron/ws) — chat, presence, meeting signaling (SDP/ICE relay + consent). - Structure (server/):
server.js(wiring),routes.js(HTTP JSON API),repos.js(all SQL / data access),signaling.js(WS),calls.js,chat.js,presence.js,session.js/auth.js,push.js,livekit.js,media.js,mailer.js,webhooks.js,bizgaze.js(BizGaze identity provider),directory.js,config.js. - Media tooling: ffmpeg (in the image) for server-side video poster thumbnails.
Data layer
- Database: PostgreSQL 16 (
pg^8.13). This is the only backend — SQLite was retired 2026-08-12; one schema source of truth atserver/db/schema.pg.sql. Selected viaDB_BACKEND=pg/DATABASE_URL. - DB access: hand-written SQL through an async adapter (
server/dbx.js,server/db/), centralized inrepos.js. No ORM. - Cache / pub-sub: Redis 7 (
redis^4.7) for cross-instance real-time fan-out (chat/presence) — optional, only whenPUBSUB_BACKEND=redis; default is in-memory single-instance (pubsub.js).
Identity & auth
No external auth library — Node's built-in crypto only (server/auth.js): scrypt password hashing (64-byte
hash, per-user salt, timing-safe compare), opaque random session tokens, SHA-256-hashed token storage, and
TOTP MFA (RFC 6238, SHA-1, 30s, 6 digits) with otpauth:// enrollment URLs.
- Sessions (
sessions_authtable, resolved inserver/session.js): one opaque bearer token accepted over three transports — asidHttpOnly cookie (web),Authorization: Bearer <token>(native desktop/mobile- WS upgrade), or a
?access_token=/?token=query param (browser WS fallback). 90-day TTL, sliding — any/api/mehit re-stamps the cookie and pushes the expiry out, so active users stay signed in indefinitely and only lapse after 90 days of no use (or logout).
- WS upgrade), or a
- Native refresh tokens (
refresh_tokens): 90-day, one-time-use with rotation, stored only as a hash;POST /api/auth/refreshswaps a refresh token for a fresh access token so mobile/desktop never re-prompt for a password. - MFA: optional TOTP second factor. A
requireMfagate incurrentUserholds a session back untilmfa_passed; flow is/api/mfa/setup(secret/QR) →/api/mfa/enable(confirm) →/api/login/mfa(step-2 code). - Identity providers:
- Local password accounts — dev/test and self-contained runs.
- BizGaze as IdP (
server/bizgaze.js, active whenBIZGAZE_LOGIN_URLis set): credentials are verified against BizGaze'sValidateAndLoginand BizGaze becomes the sole authority (local passwords rejected unlessALLOW_LOCAL_LOGIN=1). A local user row is provisioned/synced (name, avatar, role, tenant) on every login, keyed on the BizGaze person-id (not the typed email/phone) so signing in with either identifier resolves to one account — legacy duplicates are auto-merged. - BizGaze SSO (
GET /sso): an HMAC-SHA256-signed, time-boxed token (SSO_SECRET) signs an agent straight in (auto-provisioning the user) and redirects to/connect.
- Roles & tenancy: every user belongs to a team (tenant); roles are
admin/technician/viewer(defaulttechnician). Admins manage members (/api/users,/api/users/manage: add / reset-password / rename / activate / deactivate / delete) and see tenant-wide views. Role is synced from BizGazeisAdmineach login;ADMIN_EMAILSis a lockout safety-net. - Registration (
POST /api/register): first run creates the team + admin user; closed afterward unlessALLOW_REGISTRATION=1. - Third-party API keys (
api_keys,apiKeyFromReq):bzc_-prefixed, stored hashed, comma-scoped (*= all), presented viaX-API-KeyorAuthorization: Bearer bzc_…— for the roadmap's 3rd-party integration. - Directory search (
server/directory.js,/api/directory/search): server-side BizGaze cross-tenant user lookup; the directory auth token stays server-side and is never exposed to the browser. - Audit log (
audit_log): logins, SSO/BizGaze provisioning, account merges, and every admin action on a member are recorded.
Real-time & media
- WebRTC for calls/meetings: built-in P2P mesh by default; LiveKit SFU when configured.
- LiveKit SFU server:
livekit/livekit-server:**v1.9**(Docker,sfucompose profile, dormant unlessLIVEKIT_*env is set). Server mints per-user JWT join tokens (server/livekit.js). - LiveKit clients:
- Web: self-hosted
livekit-client~2.20 UMD (server/public/vendor/livekit-client.umd.min.js). - iOS: LiveKit Swift SDK
2.15.3(SPM,client-sdk-swift, productLiveKit). - Android: LiveKit Android SDK
io.livekit:livekit-android2.29.2 (+com.twilio:audioswitchfor output routing, Kotlin coroutines) — native call audio + Speaker/Bluetooth/Earpiece (Increment 2a); native video is Increment 2b. Needs the JitPack repo; raises the app to minSdk 24.
- Web: self-hosted
Web client (server/public/)
- Vanilla JavaScript SPA —
home.htmlis the app (large single file with inline JS). No framework, no bundler, no build step. Supporting files:icons.js(shared Lucide icon set viaic()),connect.html(embed),sw.js(service worker),bizconnect-toast.js. - PWA: installable; service worker for Web Push + offline shell.
- Icons: Lucide (shared set, rendered through
ic()); no emoji as control icons.
Mobile app (mobile/) — Capacitor
- Framework: Capacitor 8 (
@capacitor/core^8) — a native shell (iOS WKWebView / Android WebView) loading the remote web UI fromhttps://remote.bizgaze.com. Not Flutter/React Native. - Official plugins (
^8): App, Camera, Filesystem, Keyboard, Share, Push Notifications, Splash Screen, Status Bar; community@capacitor-community/safe-area^8;@capacitor/assets^3(icons/splash). - Local/custom plugins (
mobile/plugins/):native-call— native calling. iOS: Swift, CallKit + PushKit (VoIP) + LiveKit Swift SDK (native media, video tiles, screen-share via a ReplayKit broadcast extension), on-device live transcription (SFSpeechRecognizer). Android: Java, full-screen incoming-call UI over the lock screen + aFirebaseMessagingServicewoken by high-priority FCM data messages. Increment 2a adds native LiveKit audio (KotlinCallMedia+ amicrophoneforeground service for background audio +AudioSwitchHandlerrouting), gated by the server flagANDROID_NATIVE_MEDIA; native video = Increment 2b.audio-route(iOS audio output routing),media-library(save to Photos/gallery),share-inbox(inbound share sheet),file-opener(native Quick Look / open-with).
- iOS build: Capacitor 8 + SPM (CocoaPods retired); Xcode 26; a share extension + a broadcast extension.
- Android build: JDK 21, AGP 8.7 / Gradle 8.11, minSdk 24 (raised for LiveKit), target SDK 36,
edge-to-edge; the native-call plugin module also applies kotlin-android for the LiveKit media layer.
mobile/android/is generated fresh per build, so permissions, the minSdk bump, and the JitPack repository (allprojects/dependencyResolutionManagement) that LiveKit Android's transitivecom.github.davidliu:audioswitchfork needs are all applied in CI bymobile/scripts/android-patch.js.
Desktop app (desktop/) — Electron
- Electron 31 + electron-builder 24 (NSIS/Windows, DMG/mac, AppImage/Linux) + electron-updater 6
(auto-update from
https://remote.bizgaze.com/downloads/). Wraps the web UI and adds native screen capture. Optional@nut-tree-fork/nut-jsfor input automation.
Native host agent (agent/) — Electron
- Legacy remote-access agent (
remote-access-agent): Electron 31 host for screen capture, WebRTC, consent, and OS input injection (@nut-tree-fork/nut-js), signaling overws.
Push notifications
- Web Push via VAPID (
web-push^3.6) for browsers/PWA. - Android: Firebase Cloud Messaging (FCM) — Firebase project
bizgaze-connect; clientgoogle-services.jsonships in the APK; server uses a service-account key (base64 in.env). High-priority data messages wake the native-call plugin for incoming calls. - iOS: Apple Push Notification service (APNs) over HTTP/2 with a token (
.p8) auth key, including VoIP pushes (PushKit → CallKit) for incoming calls.
Infrastructure & deployment
- Containers: Docker + Docker Compose (
Dockerfile,docker-compose.yml). Services:app(Node),bizgazepg(Postgres 16),bizgazeredis(Redis 7,scaleprofile),livekit(SFU,sfuprofile). - Reverse proxy / TLS: Nginx Proxy Manager (external, shared
nginx_proxy_manager_defaultnetwork) — terminates HTTPS and proxiesremote.bizgaze.com→app:8090andwss://livekit.bizgaze.com→ LiveKit. - Persistence: named Docker volumes — app data (
/data: uploads, recordings, transcripts, downloads) and Postgres data. - Deploy flow: push to
master(Gitea remotecode.bizgaze.com) →redeploy.sh(plink/SSH to the host) → server-sidedeploy.sh(git pull + docker rebuild + recreate). Secrets live in a gitignored.envon the server (/opt/bizgaze-support/.env); see.env.example. - CI (mobile builds): Codemagic (
codemagic.yaml) — iOS (mac_mini_m2, SPM, publishes to TestFlight) and Android (mac_mini_m2, Node 22, JDK 21, produces an installable debug APK).
External integrations
- BizGaze SSO / identity provider + outbound webhook (
bizgaze.js,webhooks.js). - SMTP email via Nodemailer
^6.9(optional) — meeting invites to external participants. - GIPHY GIF search (server-proxied, optional, key-gated).
- TURN/STUN for WebRTC NAT traversal (credentials via
.env).
Dev & test tooling
- Local DB for tests: PostgreSQL 16 (local).
- Browser automation:
puppeteer-coredriving local Chrome (scratch testing). - Android debugging:
adb(platform-tools). - Shell/OS: developed on Windows; Git Bash + PowerShell; deploy via plink (PuTTY).
Repo layout (key directories)
| Path | What |
|---|---|
server/ |
Node backend (API, WS, data access, push, media) |
server/public/ |
Vanilla-JS web UI (home.html, icons.js, sw.js, vendor/) |
server/db/ |
PostgreSQL schema + DB adapter |
mobile/ |
Capacitor app (iOS/Android) |
mobile/plugins/ |
Custom Capacitor plugins (native-call, audio-route, media-library, share-inbox, file-opener) |
desktop/ |
Electron technician desktop client |
agent/ |
Legacy Electron remote-access host agent |
Dockerfile, docker-compose.yml, livekit.yaml |
Deploy stack |
codemagic.yaml |
Mobile CI (iOS + Android) |
redeploy.sh, deploy.sh |
Deployment |
ARCHITECTURE.md, DEPLOY.md, CLIENTS.md, README.md |
Docs |
Maintaining this file
Update this document in the same commit as any change that alters the stack, specifically when you:
- add/remove a dependency, or bump a major version (server
package.json,mobile/package.json,desktop/package.json,agent/package.json, plugin manifests); - change a platform/toolchain version (Node, Postgres, Redis, LiveKit, Capacitor, Electron, JDK, Xcode);
- add/remove a service in
docker-compose.ymlor a build workflow incodemagic.yaml; - add a new custom Capacitor plugin or a new external integration.
Bump the Last updated date at the top and add a line to the Changelog below.
Changelog
- 2026-10-30 (rev 3) — Android native call media (Increment 2a): added
io.livekit:livekit-android2.29.2 +com.twilio:audioswitch+ Kotlin to the native-call plugin, amicrophoneforeground service, theANDROID_NATIVE_MEDIAserver flag, and raised the Android app to minSdk 24. Build fix:android-patch.jsnow injects the JitPack repo (LiveKit'scom.github.davidliu:audioswitchfork resolves nowhere else). - 2026-10-27 (rev 2) — Added the Identity & auth section (sessions/cookies + bearer, refresh tokens, TOTP MFA, BizGaze IdP/SSO, roles & tenancy, API keys, directory, audit).
- 2026-10-27 — Initial tech-stack document created.