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>
87 lines
4.9 KiB
JavaScript
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)
|
|
};
|