ping(); return $_redis; } catch (\Exception $_) { $_redis = null; } } try { $redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_PASSWORD') ?: ''; $host = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: (file_exists('/.dockerenv') ? 'redis' : '127.0.0.1'); $r = new \Redis(); $r->connect($host, (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379), 1.5); if ($redisPass) $r->auth($redisPass); $r->setOption(\Redis::OPT_PREFIX, 'siro:'); $_redis = $r; return $r; } catch (\Exception $e) { socket_log('[REDIS_ERROR] Food Redis unavailable: ' . $e->getMessage()); return null; } } $INTERNAL_KEY = getInternalKey(); if (empty($INTERNAL_KEY)) { socket_log('[CRITICAL_ERROR] Internal key missing! Exiting.'); exit(1); } $PORT = 4040; $INTERNAL_PORT = 4041; $io = new SocketIO($PORT); $io->on('workerStart', function () use ($io, $INTERNAL_KEY, $INTERNAL_PORT) { $innerHttp = new Worker("http://0.0.0.0:$INTERNAL_PORT"); $innerHttp->onMessage = function ($connection, $request) use ($io, $INTERNAL_KEY) { $headers = $request->header(); $clientIp = $connection->getRemoteIp(); if (($headers['x-internal-key'] ?? '') !== $INTERNAL_KEY) { socket_log("[HTTP_ERROR] Unauthorized internal request from IP: $clientIp"); $connection->send('Unauthorized'); return; } $post = $request->post(); $action = trim($post['action'] ?? ''); $rawPayload = $post['payload'] ?? null; $payload = is_string($rawPayload) ? (json_decode($rawPayload, true) ?? []) : ($rawPayload ?? []); if ($action === 'order_status_update') { $orderId = $payload['order_id'] ?? null; $passengerId = $payload['passenger_id'] ?? null; $merchantId = $payload['merchant_id'] ?? null; $courierId = $payload['courier_id'] ?? null; if (!$orderId) { $connection->send('Error: Missing order_id'); return; } if ($passengerId) $io->to('customer_food_' . $passengerId)->emit('food_order_update', $payload); if ($merchantId) $io->to('merchant_food_' . $merchantId)->emit('food_order_update', $payload); if ($courierId) $io->to('courier_food_' . $courierId)->emit('food_order_update', $payload); socket_log("[HTTP_SUCCESS] order_status_update pushed for order #$orderId", $payload); $connection->send('OK'); } elseif ($action === 'courier_offer') { $courierId = $payload['courier_id'] ?? null; if (!$courierId) { $connection->send('Error: Missing courier_id'); return; } $io->to('courier_food_' . $courierId)->emit('food_delivery_offer', $payload); socket_log("[HTTP_SUCCESS] courier_offer pushed to courier #$courierId", $payload); $connection->send('OK'); } elseif ($action === 'courier_call') { // مكالمة مقنّعة من الزبون إلى السائق — نمرّر session_id فقط، // لا رقم هاتف ولا هوية حقيقية لأي طرف. $courierId = $payload['courier_id'] ?? null; $sessionId = $payload['session_id'] ?? null; if (!$courierId || !$sessionId) { $connection->send('Error: Missing courier_id/session_id'); return; } $io->to('courier_food_' . $courierId)->emit('food_incoming_call', $payload); socket_log("[HTTP_SUCCESS] courier_call pushed to courier #$courierId (order #" . ($payload['order_id'] ?? '?') . ')'); $connection->send('OK'); } else { socket_log("[HTTP_WARNING] Unknown action received: $action", $post); $connection->send('Unknown action: ' . $action); } }; $innerHttp->listen(); socket_log("[INFO] Internal HTTP started on port $INTERNAL_PORT"); }); $io->on('connection', function ($socket) { $query = $socket->handshake['query'] ?? []; $role = $query['role'] ?? ''; // passenger | driver | merchant $clientIp = $socket->conn->remoteAddress ?? 'Unknown'; if ($role === 'passenger' || $role === 'driver') { $userId = $query['id'] ?? null; $jwtToken = $query['jwt'] ?? ''; if (!$userId || !$jwtToken) { socket_log("[SOCKET_REJECTED] Missing id/jwt for role=$role from IP: $clientIp"); $socket->disconnect(); return; } try { $secretKey = getJwtSecret(); if (empty($secretKey)) { socket_log('[WARNING] JWT secret not configured!'); $socket->disconnect(); return; } $decoded = JWT::decode($jwtToken, new Key($secretKey, 'HS256')); $expectedRole = $role === 'passenger' ? 'passenger' : 'driver'; if ((string)($decoded->user_id ?? $decoded->sub ?? '') !== (string)$userId || ($decoded->role ?? '') !== $expectedRole) { socket_log("[SOCKET_REJECTED] Invalid JWT for $role #$userId from IP: $clientIp"); $socket->disconnect(); return; } } catch (\Exception $e) { socket_log('[SOCKET_REJECTED] JWT verification failed -> ' . $e->getMessage()); $socket->disconnect(); return; } $room = ($role === 'passenger' ? 'customer_food_' : 'courier_food_') . $userId; $socket->join($room); socket_log("[SOCKET_CONNECTED] $role #$userId joined $room (IP: $clientIp)"); } elseif ($role === 'merchant') { $merchantId = $query['id'] ?? null; $sessionToken = $query['token'] ?? ''; if (!$merchantId || !$sessionToken) { socket_log("[SOCKET_REJECTED] Missing id/token for role=merchant from IP: $clientIp"); $socket->disconnect(); return; } $redis = getFoodRedis(); if (!$redis) { $socket->disconnect(); return; } $hash = hash('sha256', $sessionToken); $val = $redis->get("food:merchant_session:{$hash}"); if (!$val) { socket_log("[SOCKET_REJECTED] Invalid/expired merchant session from IP: $clientIp"); $socket->disconnect(); return; } $data = json_decode($val, true); if (!$data || (string)($data['merchant_id'] ?? '') !== (string)$merchantId) { socket_log("[SOCKET_REJECTED] Merchant session/id mismatch from IP: $clientIp"); $socket->disconnect(); return; } $room = 'merchant_food_' . $merchantId; $socket->join($room); socket_log("[SOCKET_CONNECTED] merchant #$merchantId joined $room (IP: $clientIp)"); } else { socket_log("[SOCKET_REJECTED] Unknown role '$role' from IP: $clientIp"); $socket->disconnect(); return; } $socket->on('heartbeat', function () {}); }); Worker::runAll();