On an Android native call the WebView turned on hole-punch (transparent meeting grid, so native video can show BEHIND the WebView) whenever the plugin exposed setHolePunch — but on the 2a audio-only build there is no native video behind it, so the transparent grid revealed the white Android window (reported: white meeting background; tiles stayed visible as they're opaque web elements). iOS never showed this because CallKit covers the view. Gate hole-punch on native video actually existing: new ANDROID_NATIVE_VIDEO server flag (mirrors ANDROID_NATIVE_MEDIA, default off) exposed via /api/meetings/config. Web: bzNativeVideoOn() — iOS always, Android only when the flag is on (Increment 2b APK). While off, hole-punch stays off and the grid keeps its dark background; the native-tile poll doesn't run. iOS unchanged. Flip the flag on once the 2b native-video APK is installed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
6.3 KiB
JavaScript
105 lines
6.3 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';
|
|
|
|
// Native call VIDEO on Android (Increment 2b): the plugin captures/publishes the camera and renders remote video
|
|
// tiles natively via hole-punch (a transparent WebView over native VideoViews), mirroring iOS. INDEPENDENT of
|
|
// ANDROID_NATIVE_MEDIA (audio) so video can be enabled only once a 2b-capable APK is installed. OFF (default) →
|
|
// Android native calls are AUDIO-ONLY and the WebView keeps its normal (opaque, dark) meeting grid — hole-punch
|
|
// stays off, so no transparent grid reveals the page behind it. ON → native video + hole-punch. Requires an app
|
|
// build carrying the 2b native-video plugin. Set ANDROID_NATIVE_VIDEO=1.
|
|
const ANDROID_NATIVE_VIDEO = process.env.ANDROID_NATIVE_VIDEO === '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,
|
|
ANDROID_NATIVE_VIDEO,
|
|
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)
|
|
};
|