function trimTrailingSlashes(value: string): string { return value.replace(/\/+$/, ''); } export type AppVariant = 'development' | 'staging' | 'production'; function resolveAppVariant(): AppVariant { const raw = process.env.EXPO_PUBLIC_APP_VARIANT; if (raw === 'development' || raw === 'staging' || raw === 'production') { return raw; } if (__DEV__) { return 'development'; } // Release 包若 env 未写入 APP_VARIANT(例如仅复制了 env/staging 到 .env 但变量缺失), // 仍按 API 是否为 http 推断预发,避免关于页误隐藏后端地址。 const apiUrl = process.env.EXPO_PUBLIC_API_URL ?? ''; if (apiUrl.startsWith('http://')) { return 'staging'; } return 'production'; } /** Shown on About screen for dev/staging builds only (never production Release). */ export function shouldShowAboutBackendUrl(variant: AppVariant = appVariant): boolean { // Metro / 调试包:始终显示,避免 .env 误用 production variant 时看不到实际 API if (__DEV__) { return true; } return variant === 'development' || variant === 'staging'; } export const appVariant = resolveAppVariant(); export const config = { apiBaseUrl: trimTrailingSlashes( process.env.EXPO_PUBLIC_API_URL ?? 'http://192.168.10.151:8000', ), wsBaseUrl: trimTrailingSlashes( process.env.EXPO_PUBLIC_WS_URL ?? 'ws://192.168.10.151:8000', ), isDebugMode: __DEV__, appVariant, showAboutBackendUrl: shouldShowAboutBackendUrl(), api: { timeoutMs: 30_000, refreshPath: '/api/auth/refresh', }, ws: { reconnectMaxRetries: 10, reconnectBaseDelayMs: 1_000, reconnectMaxDelayMs: 30_000, heartbeatIntervalMs: 30_000, /** * 仅当 App 处于 `background` 连续超过该毫秒数才释放当前会话 WebSocket。 * 短暂切到其它应用再返回时保持连接,避免反复重连。 */ backgroundDisconnectAfterMs: 300_000, }, } as const;