value('value'); $port = (int) (Setting::where('key', 'voip_socket_port')->value('value') ?: 0); $token = Setting::where('key', 'voip_socket_token')->value('value'); $timeout = (float) (Setting::where('key', 'voip_socket_timeout')->value('value') ?: 5); if (! $host || ! $port) { return [ 'success' => false, 'provider_call_id' => null, 'message' => 'میزبان یا پورت سوکت تنظیم نشده است', 'status' => 'configuration_error', ]; } $connection = @stream_socket_client("tcp://{$host}:{$port}", $errorCode, $errorMessage, $timeout); if (! $connection) { return [ 'success' => false, 'provider_call_id' => null, 'message' => $errorMessage ?: 'اتصال سوکت برقرار نشد', 'status' => 'connection_failed', 'error_code' => $errorCode, ]; } stream_set_timeout($connection, (int) ceil($timeout)); $providerCallId = 'socket_'.uniqid(); $payload = [ 'type' => 'call.initiate', 'provider_call_id' => $providerCallId, 'phone' => $phone, 'caller_id' => $callerId, 'token' => $token, ]; fwrite($connection, json_encode($payload, JSON_UNESCAPED_UNICODE)."\n"); $line = fgets($connection); fclose($connection); $response = $line ? json_decode($line, true) : []; return [ 'success' => ($response['success'] ?? false) === true, 'provider_call_id' => $response['provider_call_id'] ?? $providerCallId, 'message' => $response['message'] ?? 'درخواست تماس از راه سوکت ارسال شد', 'status' => $response['status'] ?? 'initiated', 'raw' => $response, ]; } public function getCallStatus(string $providerCallId): array { return [ 'status' => 'pending', 'provider_call_id' => $providerCallId, ]; } public function getRecordingUrl(string $providerCallId): ?string { $template = Setting::where('key', 'voip_socket_recording_url')->value('value'); return $template ? str_replace('{id}', $providerCallId, $template) : null; } public function testConnection(): array { $missing = []; foreach (['voip_socket_host', 'voip_socket_port'] as $key) { if (! Setting::where('key', $key)->value('value')) { $missing[] = $key; } } if ($missing !== []) { return ['ok' => false, 'message' => 'تنظیمات اتصال سوکت کامل نیست.', 'missing' => $missing]; } $host = Setting::where('key', 'voip_socket_host')->value('value'); $port = (int) Setting::where('key', 'voip_socket_port')->value('value'); $token = Setting::where('key', 'voip_socket_token')->value('value'); $timeout = (float) (Setting::where('key', 'voip_socket_timeout')->value('value') ?: 5); $started = microtime(true); $connection = @stream_socket_client("tcp://{$host}:{$port}", $errorCode, $errorMessage, $timeout); if (! $connection) { return ['ok' => false, 'message' => $errorMessage ?: "اتصال سوکت برقرار نشد ({$errorCode})", 'missing' => []]; } stream_set_timeout($connection, (int) ceil($timeout)); fwrite($connection, json_encode(['type' => 'health.check', 'token' => $token], JSON_UNESCAPED_UNICODE)."\n"); $line = fgets($connection); $meta = stream_get_meta_data($connection); fclose($connection); $response = $line ? json_decode($line, true) : null; $ok = is_array($response) && (($response['success'] ?? false) === true || ($response['status'] ?? '') === 'ok'); $latency = (int) round((microtime(true) - $started) * 1000); return [ 'ok' => $ok, 'message' => $ok ? "اتصال و handshake سوکت موفق بود ({$latency} میلی‌ثانیه)." : ($meta['timed_out'] ? 'پاسخ health-check سوکت timeout شد.' : 'سوکت باز شد اما پاسخ health-check معتبر نبود.'), 'missing' => [], 'latency_ms' => $latency, ]; } }