Replaces WebView call media on Android with a native LiveKit connection (iOS parity), so call audio survives backgrounding and the output can be switched to Speaker/Bluetooth/Earpiece — the #3 (missing speaker control) and #4 (background audio) items. Gated behind a new, default-off server flag ANDROID_NATIVE_MEDIA (independent of ANDROID_CALLS, which only governs the native ring). Flag off = today's WebView media, video included; flag on = native audio (video is Increment 2b, so enable it for VOICE testing first). No behavior change until flipped. Native (mobile/plugins/native-call/android): - build.gradle: add kotlin-android + io.livekit:livekit-android 2.29.2 (+ twilio audioswitch, coroutines) + JitPack repo; minSdk 24 (LiveKit floor). - CallMedia.kt: LiveKit Room wrapper — connect/mic/disconnect + AudioSwitchHandler routing (auto + selectDevice) and route-change events. - CallForegroundService.java: microphone-type foreground service for background audio (no-ops without RECORD_AUDIO so it never crashes the call). - NativeCallPlugin.java: real bodies for reportOutgoingCall/reconnectRoom/setMuted /endCall + new setAudioRoute/getAudioRoute; fires callConnected/callError/audioRoute. - AndroidManifest: FG service + FOREGROUND_SERVICE(_MICROPHONE)/RECORD_AUDIO/ MODIFY_AUDIO_SETTINGS/BLUETOOTH_CONNECT. - android-patch.js: bump the generated app minSdk to 24. Server: - config.js/routes.js: ANDROID_NATIVE_MEDIA → /api/meetings/config.androidNativeMedia. Web (home.html): - Android flips to the native-media path when androidNativeMedia is set; the existing outgoing/answer/mic/end code then drives the plugin unchanged. - Speaker button wired for Android native: cycles speaker/earpiece/bluetooth via NC.setAudioRoute and reflects the real output from the audioRoute event. Video stays no-op on the native path (Increment 2b). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
5.6 KiB
JavaScript
96 lines
5.6 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';
|
|
|
|
// Native call MEDIA on Android (Increment 2): LiveKit audio in the plugin + a foreground service, instead of the
|
|
// WebView carrying the media. INDEPENDENT of ANDROID_CALLS (which only governs the native ring) so media can be
|
|
// flipped on for testing after the ring is solid. OFF (default) → Android keeps WebView call media (works today,
|
|
// video included). ON → the Android app drives call audio natively (background audio + Speaker/Bluetooth), but
|
|
// video is not yet native (Increment 2b) — so enable this for VOICE testing first. Requires an app build carrying
|
|
// the LiveKit-Android plugin. Set ANDROID_NATIVE_MEDIA=1.
|
|
const ANDROID_NATIVE_MEDIA = process.env.ANDROID_NATIVE_MEDIA === '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,
|
|
ANDROID_NATIVE_MEDIA,
|
|
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)
|
|
};
|