Files
BizGaze_Remote/server/config.js
T
SravanandClaude Opus 4.8 b60ce6cda3 Android native calling — Increment 1: full-screen incoming ring + answer
First slice of Android Phase C (full LiveKit parity), mirroring how iOS
started: native full-screen incoming call UI that wakes the app from
closed, with call MEDIA still in the WebView (foreground). Native LiveKit
media + a foreground service for true background audio are Increment 2.

Native (new Android side of the native-call plugin, Java to match the
proven official-plugin module layout):
- NativeCallPlugin: registered as window.Capacitor.Plugins.NativeCall
  (same jsName as iOS, so the shared web bridge drives both). getToken
  (FCM token), reportIncomingCall (ring), reportOutgoingCall, endCall,
  setMuted; video/screen/hole-punch/transcript methods are safe no-ops
  until later increments so the shared bridge never rejects.
- BzCallMessagingService: extends @capacitor/push-notifications'
  MessagingService — handles high-priority call-wake DATA messages
  (data.bzcall==1) to ring when closed, forwards all other push via super.
- IncomingCallActivity: full-screen call screen over the lock screen
  (showWhenLocked/turnScreenOn), Accept/Decline, looping ringtone.
- CallStore: incoming-call notification (channel, full-screen-intent,
  actions) + UUID dedup so the FCM and WebSocket paths don't double-ring.
- Plugin manifest: BzCallMessagingService + IncomingCallActivity + call
  perms (USE_FULL_SCREEN_INTENT/VIBRATE/WAKE_LOCK).
- android-patch.js: removes Capacitor's MessagingService in the APP
  manifest (highest merge priority) so ours is the sole MESSAGING_EVENT
  receiver; ensures the tools namespace. Idempotent.
- package.json: capacitor.android.src wired.

Server (dormant until ANDROID_CALLS=1):
- push.js sendFcmData: high-priority data-only FCM (rings when closed).
  sendCallNotification rings 'android' tokens via it; sendCallCancel too.
- config.js ANDROID_CALLS flag; /api/meetings/config exposes androidCall.

Web (home.html) — platform-aware, iOS/web behavior byte-for-byte unchanged:
- setupNativeCall: Android gated on cfg.androidCall, sets _callWebViewMedia
  (native ring, WebView media); iOS keeps CallKit native-media path.
- answerCall + both outgoing sites branch on _callWebViewMedia so Android
  joins the meeting normally while the callee still rings via the server.

Safe to deploy now: dormant until ANDROID_CALLS=1 AND an APK carrying the
plugin is installed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-25 11:44:52 +05:30

87 lines
4.9 KiB
JavaScript

// Runtime config + filesystem paths. Reads process.env once at startup.
const fs = require('fs');
const path = require('path');
const PUBLIC_DIR = path.join(__dirname, 'public');
// Uploaded chat files, recordings and transcripts MUST live on the persistent volume (like the DB
// and downloads). With the old in-image path, every deploy.sh rebuild wiped them — old images/files
// then 404 ("broken image") while their DB rows survive. Overridable so prod points them at /data.
const REC_DIR = process.env.REC_DIR || path.join(__dirname, 'recordings');
const TRANS_DIR = process.env.TRANS_DIR || path.join(__dirname, 'transcripts');
const UPLOADS_DIR = process.env.UPLOADS_DIR || path.join(__dirname, 'uploads');
// Desktop installers + auto-update feed (latest.yml). Override with DOWNLOADS_DIR to point at a
// mounted volume in production; IT drops the electron-builder dist/ output here.
const DOWNLOADS_DIR = process.env.DOWNLOADS_DIR || path.join(__dirname, 'downloads');
try { fs.mkdirSync(REC_DIR, { recursive: true }); } catch (e) {}
try { fs.mkdirSync(TRANS_DIR, { recursive: true }); } catch (e) {}
try { fs.mkdirSync(UPLOADS_DIR, { recursive: true }); } catch (e) {}
try { fs.mkdirSync(DOWNLOADS_DIR, { recursive: true }); } catch (e) {}
// LiveKit SFU (scales meetings past the ~5-peer mesh ceiling). Entirely optional and config-gated:
// when LIVEKIT_URL/API_KEY/API_SECRET are all set the client uses LiveKit for meeting media; when
// they're unset the app falls back to the built-in P2P mesh, unchanged. The API secret is used
// ONLY server-side to mint per-user join tokens — it never reaches the browser.
const LIVEKIT_URL = process.env.LIVEKIT_URL || ''; // wss://livekit.bizgaze.com
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || '';
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || '';
const LIVEKIT_ENABLED = !!(LIVEKIT_URL && LIVEKIT_API_KEY && LIVEKIT_API_SECRET);
// SMTP for outbound email (meeting invites to external participants, #4). Entirely optional and
// config-gated: email is only sent when SMTP_HOST/USER/PASS are set. Credentials stay server-side.
// PUBLIC_BASE_URL is the origin used to build guest meeting links in emails (e.g. https://remote.bizgaze.com).
const SMTP_HOST = process.env.SMTP_HOST || '';
const SMTP_PORT = Number(process.env.SMTP_PORT || 587);
const SMTP_SECURE = String(process.env.SMTP_SECURE || '').toLowerCase() === 'true' || SMTP_PORT === 465; // TLS on connect (465) vs STARTTLS
const SMTP_USER = process.env.SMTP_USER || '';
const SMTP_PASS = process.env.SMTP_PASS || '';
const SMTP_FROM = process.env.SMTP_FROM || (SMTP_USER ? ('Biz Connect <' + SMTP_USER + '>') : '');
const SMTP_ENABLED = !!(SMTP_HOST && SMTP_USER && SMTP_PASS);
const PUBLIC_BASE_URL = (process.env.PUBLIC_BASE_URL || 'https://remote.bizgaze.com').replace(/\/+$/, '');
// GIPHY GIF search (#5). Key is read from the server env only and never sent to the browser — the client
// calls our /api/gifs proxy. GIF picker is hidden when this isn't configured.
const GIPHY_API_KEY = process.env.GIPHY_API_KEY || '';
// Native CallKit / PushKit VoIP calling (iOS). Config-gated so it can be flipped WITHOUT an app rebuild:
// OFF (default) → calls use the WebView flow (works today); ON → iOS rings via CallKit + a VoIP push.
// Only turn ON once native LiveKit media carries the call audio — a CallKit call reserves the mic, so the
// WebView's WebRTC can't capture it (mic dead). Set CALLKIT_ENABLED=1 in the server .env to enable.
const CALLKIT_ENABLED = process.env.CALLKIT_ENABLED === '1';
// Native calling on ANDROID (ConnectionService-style full-screen ring via a high-priority FCM data message +
// the native-call plugin). Same config-gated pattern as CALLKIT_ENABLED, but INDEPENDENT so Android can be
// switched on without touching iOS. OFF (default) → Android calls use the WebView flow (works today, foreground
// only); ON → incoming calls ring natively (full-screen, over the lock screen, when the app is closed). Requires
// FCM configured (FCM_SERVICE_ACCOUNT) + an app build carrying the native-call Android plugin. Set ANDROID_CALLS=1.
const ANDROID_CALLS = process.env.ANDROID_CALLS === '1';
module.exports = {
PORT: process.env.PORT || 8090,
HTTPS_PORT: process.env.HTTPS_PORT || 8443,
LIVEKIT_URL,
LIVEKIT_API_KEY,
LIVEKIT_API_SECRET,
LIVEKIT_ENABLED,
SMTP_HOST,
SMTP_PORT,
SMTP_SECURE,
SMTP_USER,
SMTP_PASS,
SMTP_FROM,
SMTP_ENABLED,
PUBLIC_BASE_URL,
GIPHY_API_KEY,
CALLKIT_ENABLED,
ANDROID_CALLS,
PUBLIC_DIR,
REC_DIR,
TRANS_DIR,
UPLOADS_DIR,
DOWNLOADS_DIR,
// Access-token / web-cookie lifetime. Long by design + SLID FORWARD on every /api/me (app load / focus /
// heartbeat), so an actively-used session never lapses — you only get logged out by choosing to log out.
// (Was 24h, which logged people out overnight.)
SESSION_TTL: 1000 * 60 * 60 * 24 * 90, // 90d
REFRESH_TTL: 1000 * 60 * 60 * 24 * 90, // 90d refresh-token lifetime (native clients)
};