Files
SravanandClaude Opus 4.8 1e01130c6f android(call): Increment 2c — screen-share + received-screen pinch-zoom
Screen-share (send): startScreenShare requests the MediaProjection grant via
startActivityForResult, then CallMedia.setScreenShareEnabled(true,
ScreenCaptureParams(data)) publishes it over the native LiveKit room.
stopScreenShare unpublishes. Both emit screenShareState so the web button
reflects reality (incl. a declined grant). LiveKit's SDK manifest already
provides the mediaProjection foreground service + FOREGROUND_SERVICE_MEDIA_
PROJECTION (merged into the app), so no manifest change is needed.

Received shared screen: implemented setTileZoom (was a no-op) — the web
captures pinch/pan under hole-punch and forwards {scale,tx,ty}; CallVideo
applies it as a renderer transform (scale about centre + translate ×density),
scale<=1 clears it. Mirrors the iOS applyZoom contract. Screen tiles already
render aspect-fit.

APK-only: the web bridge already calls these methods and received-screen
rendering is gated on ANDROID_NATIVE_VIDEO (already on) — no server/web change.
All LiveKit 2.29.2 APIs (setScreenShareEnabled, ScreenCaptureParams) verified
against source. Clarity-when-zoomed (adaptive-stream) is a follow-up if needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-11-03 22:14:42 +05:30

15 KiB

Tech Stack — BizGaze Connect

Living document. Last updated: 2026-11-03 (rev 6). 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 — engines requires >= 22.5; the Docker image is node:24-alpine.
  • HTTP/HTTPS: Node built-in http/https modules. No Express/Koa/Fastify — a small custom router (routes.js) maps "METHOD /path" to handlers; static files + authenticated downloads via static.js.
  • WebSockets: ws ^8.18 (WebSocketServer on /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 at server/db/schema.pg.sql. Selected via DB_BACKEND=pg / DATABASE_URL.
  • DB access: hand-written SQL through an async adapter (server/dbx.js, server/db/), centralized in repos.js. No ORM.
  • Cache / pub-sub: Redis 7 (redis ^4.7) for cross-instance real-time fan-out (chat/presence) — optional, only when PUBSUB_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_auth table, resolved in server/session.js): one opaque bearer token accepted over three transports — a sid HttpOnly 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/me hit 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).
  • Native refresh tokens (refresh_tokens): 90-day, one-time-use with rotation, stored only as a hash; POST /api/auth/refresh swaps a refresh token for a fresh access token so mobile/desktop never re-prompt for a password.
  • MFA: optional TOTP second factor. A requireMfa gate in currentUser holds a session back until mfa_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 when BIZGAZE_LOGIN_URL is set): credentials are verified against BizGaze's ValidateAndLogin and BizGaze becomes the sole authority (local passwords rejected unless ALLOW_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 (default technician). Admins manage members (/api/users, /api/users/manage: add / reset-password / rename / activate / deactivate / delete) and see tenant-wide views. Role is synced from BizGaze isAdmin each login; ADMIN_EMAILS is a lockout safety-net.
  • Registration (POST /api/register): first run creates the team + admin user; closed afterward unless ALLOW_REGISTRATION=1.
  • Third-party API keys (api_keys, apiKeyFromReq): bzc_-prefixed, stored hashed, comma-scoped (* = all), presented via X-API-Key or Authorization: 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, sfu compose profile, dormant unless LIVEKIT_* 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, product LiveKit).
    • Android: LiveKit Android SDK io.livekit:livekit-android 2.29.2 (+ com.twilio:audioswitch for 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 client (server/public/)

  • Vanilla JavaScript SPA — home.html is the app (large single file with inline JS). No framework, no bundler, no build step. Supporting files: icons.js (shared Lucide icon set via ic()), 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 from https://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 + a FirebaseMessagingService woken by high-priority FCM data messages. Increment 2a adds native LiveKit audio (Kotlin CallMedia + a microphone foreground service for background audio + AudioSwitchHandler routing), gated by the server flag ANDROID_NATIVE_MEDIA. Increment 2b adds native video (Kotlin CallVideo: camera publish/flip + remote/local TextureViewRenderer tiles hole-punched behind a transparent WebView), gated by ANDROID_NATIVE_VIDEO. Increment 2c adds screen-share (MediaProjection grant → LiveKit setScreenShareEnabled; LiveKit's own mediaProjection foreground service) + pinch-zoom of a received shared screen (web forwards the gesture under hole-punch → renderer transform).
    • 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 transitive com.github.davidliu:audioswitch fork needs are all applied in CI by mobile/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-js for 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 over ws.

Push notifications

  • Web Push via VAPID (web-push ^3.6) for browsers/PWA.
  • Android: Firebase Cloud Messaging (FCM) — Firebase project bizgaze-connect; client google-services.json ships 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, scale profile), livekit (SFU, sfu profile).
  • Reverse proxy / TLS: Nginx Proxy Manager (external, shared nginx_proxy_manager_default network) — terminates HTTPS and proxies remote.bizgaze.com → app:8090 and wss://livekit.bizgaze.com → LiveKit.
  • Persistence: named Docker volumes — app data (/data: uploads, recordings, transcripts, downloads) and Postgres data.
  • Deploy flow: push to master (Gitea remote code.bizgaze.com) → redeploy.sh (plink/SSH to the host) → server-side deploy.sh (git pull + docker rebuild + recreate). Secrets live in a gitignored .env on 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-core driving 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.yml or a build workflow in codemagic.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-11-03 (rev 6) — Android native call screen-share (Increment 2c): CallMedia.setScreenShareEnabled (MediaProjection → LiveKit; SDK-provided mediaProjection foreground service) + plugin startScreenShare/ stopScreenShare (permission via startActivityForResult), and pinch-zoom for a received shared screen (CallVideo.setTileZoom). APK-only — no server/web change.
  • 2026-11-03 (rev 5) — Reliability: Postgres pool safety timeouts (connectionTimeoutMillis, statement_timeout, idle_in_transaction_session_timeout, max 10→20) so a saturated pool fails fast instead of hanging forever; and a pre-deploy HTML syntax guard (server/tools/check-syntax.js) run in the Docker build and redeploy.sh — a JS syntax error in an inline <script> now fails the build instead of reaching production (it had caused an app-wide login hang).
  • 2026-11-03 (rev 4) — Android native call video (Increment 2b): CallVideo renders camera/screen tiles with LiveKit TextureViewRenderer hole-punched behind a transparent WebView; CallMedia gains camera publish/flip + track lookup; new ANDROID_NATIVE_VIDEO server flag. Also fixed the white meeting background (hole-punch no longer turns on without native video).
  • 2026-10-30 (rev 3) — Android native call media (Increment 2a): added io.livekit:livekit-android 2.29.2 + com.twilio:audioswitch + Kotlin to the native-call plugin, a microphone foreground service, the ANDROID_NATIVE_MEDIA server flag, and raised the Android app to minSdk 24. Build fix: android-patch.js now injects the JitPack repo (LiveKit's com.github.davidliu:audioswitch fork 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.