enforce(RateLimiter::identifier(), 'api'); $jwtService = new JwtService($redis); $decoded = $jwtService->authenticate(); $role = (string)($decoded->role ?? ''); $userId = (string)($decoded->user_id ?? ''); if (!in_array($role, ['driver', 'passenger'], true) || $userId === '') { jsonError('Forbidden — driver or passenger token required', 403); } // السر نفسه المُمرَّر إلى coturn — من /keys أولاً كبقية أسرار المشروع $secret = ''; $secretPath = getenv('TURN_SECRET_PATH') ?: '/keys/turn_secret'; if (is_readable($secretPath)) { $secret = trim((string)file_get_contents($secretPath)); } if ($secret === '') { $secret = (string)(getenv('TURN_STATIC_AUTH_SECRET') ?: ''); } $turnHost = getenv('TURN_HOST') ?: ''; // بلا إعداد TURN لا نُفشل المكالمة: نُعيد STUN فقط ويبقى السلوك كما كان // قبل إضافة TURN (اتصال مباشر ينجح في أغلب الشبكات المنزلية والـ WiFi). if ($secret === '' || $turnHost === '') { appLog('[TURN] not configured (missing secret or TURN_HOST) — returning STUN only', 'WARNING'); jsonSuccess([ 'ice_servers' => turnDefaultStunServers(), 'ttl' => 0, 'turn_enabled' => false, ]); } $ttl = (int)(getenv('TURN_CREDENTIAL_TTL') ?: 43200); // 12 ساعة $expiry = time() + $ttl; $username = $expiry . ':' . $role . '_' . $userId; $password = base64_encode(hash_hmac('sha1', $username, $secret, true)); $turnPort = (int)(getenv('TURN_PORT') ?: 3478); $turnTlsPort = (int)(getenv('TURN_TLS_PORT') ?: 5349); // الترتيب مقصود: STUN أولاً (اتصال مباشر أرخص وأقل تأخيراً)، ثم UDP TURN، // ثم TCP/TLS على 5349 للشبكات التي تحجب UDP كلياً (بعض شبكات الشركات). $iceServers = turnDefaultStunServers(); $iceServers[] = [ 'urls' => "turn:{$turnHost}:{$turnPort}?transport=udp", 'username' => $username, 'credential' => $password, ]; $iceServers[] = [ 'urls' => "turn:{$turnHost}:{$turnPort}?transport=tcp", 'username' => $username, 'credential' => $password, ]; // turns: يُعلَن فقط إذا كانت شهادة TLS مركّبة فعلاً على coturn — وإلا صار // مساراً ميتاً في قائمة ICE يُبطئ التفاوض بلا أن يعمل أبداً. if (getenv('TURN_TLS_ENABLED') === 'true') { $iceServers[] = [ 'urls' => "turns:{$turnHost}:{$turnTlsPort}?transport=tcp", 'username' => $username, 'credential' => $password, ]; } jsonSuccess([ 'ice_servers' => $iceServers, 'ttl' => $ttl, 'expires_at' => $expiry, 'turn_enabled' => true, ]); function turnDefaultStunServers(): array { $host = getenv('TURN_HOST') ?: ''; $port = (int)(getenv('TURN_PORT') ?: 3478); // خادمنا يعمل STUN أيضاً — نضعه أولاً، ونُبقي خوادم Google احتياطاً // إن سقط خادمنا (اكتشاف العنوان العام فقط، لا يمرّ عبره أي صوت). $servers = []; if ($host !== '') { $servers[] = ['urls' => "stun:{$host}:{$port}"]; } $servers[] = ['urls' => 'stun:stun.l.google.com:19302']; $servers[] = ['urls' => 'stun:stun1.l.google.com:19302']; return $servers; }