إصلاح سوكيت الراكب وتوحيد مسارات الإشعارات
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول): - تطبيق الراكب كان يرسل id فقط بلا jwt، و passenger_socket.php يرفض أي اتصال بلا jwt ⇒ الراكب لا ينضم لغرفته أبداً ولا يستلم ride_status_change ولا driver_location_update. تظهر حالة القبول عبر الـ polling فقط بينما driver_info يصل بالسوكيت وحده. (سوكيت السائق يعتبر الـ jwt اختيارياً، ومن هنا جاء التباين بين التطبيقين.) - cancelled_by_driver كان يسقط من switch حالات الراكب فيبقى معلّقاً بعد إلغاء السائق. - حماية socket (late) من القراءة قبل التهيئة عند الانسحاب بلا jwt. الإشعارات والرسائل (سبب "مرات توصل ومرات لا"): - جدول tokens يخزّن توكن الراكب مشفّراً، و getRideWaiting.php كان يرجعه بلا فك تشفير ⇒ من يقبل من قائمة السوق يحمل blob مشفّراً يستخدمه كـ FCM target فيرفضه FCM بـ 400: لا إشعار قبول ولا رسائل. ومن يقبل من الـ dispatch/FCM يحمل نصاً صريحاً فتعمل. الفرق كان في طريقة القبول. - acceptRide.php يحلّ التوكن من القاعدة دائماً ولا يثق بالعميل (أصحّ أمنياً). - market_new_ride كان لا يحمل passengerId ولا الإحداثيات فتصل "null"؛ أُضيفت بلا أي PII لأن الحمولة تُبَثّ لكل سائق قريب لا للفائز فقط. - send_fcm.php: مهلة على OAuth (كان يعلّق حتى مهلة PHP فتُسقط الرسالة بصمت)، توحيد ding→default لأندرويد، وحقن title/body/tone في data مطابقةً لـ FcmService. - تطبيق السائق يقرأ title/body من data أولاً مثل الراكب، ولا يعرض فقاعة فارغة للرسائل الصامتة. السوكيت والإعدادات: - forwardLocationToPassengerSocket كان يقرأ lat/lng والحمولة فيها latitude/longitude ⇒ المسافة تخرج ضخمة والـ throttle معطّل تماماً فيُعاد التوجيه مع كل نبضة GPS. - notifyPassengerOnRideServer كان يرجع null بصمت مطلق عند حجب العنوان. - العنوان الافتراضي لسيرفر الموقع كان nginx/loction_server/driver_socket.php وهو ديمون Workerman لا يُخدَم عبر nginx ⇒ صار socket_driver:2021. (LOCATION_API_URL بقي على nginx لأن api_get_nearby.php سكربت عادي.) - ride_server/passenger_socket.php (النسخة التي يشغّلها Docker) كانت ناقصة كل كود مواصلاتي الموجود في passenger_server/ ⇒ نُقل مع REDIS_HOST. - .env.example: ALLOWED_SOCKET_URLS يغطّي أسماء حاويات Docker، وإضافة PASSENGER_SOCKET_INTERNAL_URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e269cb9b70
commit
143146c1b4
+11
-5
@@ -59,7 +59,7 @@ function isAllowedSocketUrl(string $url): bool {
|
||||
}
|
||||
|
||||
function sendToLocationServer($action, $data) {
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
|
||||
if (!isAllowedSocketUrl($url)) {
|
||||
error_log("[SSRF_BLOCKED] Attempted connection to: $url");
|
||||
return;
|
||||
@@ -258,7 +258,7 @@ function getDistanceBetweenPoints($lat1, $lon1, $lat2, $lon2) {
|
||||
}
|
||||
// --- دالة مساعدة لمخاطبة سيرفر السائقين (Location Socket) ---
|
||||
function notifyDriversRideTaken($rideId, $winnerDriverId) {
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
|
||||
if (!isAllowedSocketUrl($url)) return;
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
|
||||
@@ -280,7 +280,7 @@ function notifyDriversRideTaken($rideId, $winnerDriverId) {
|
||||
curl_close($ch);
|
||||
}
|
||||
function notifyDriversOnLocationServer($drivers_ids_array, $payload, $rideId = null) {
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
|
||||
$url = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
|
||||
if (!isAllowedSocketUrl($url)) return null;
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
|
||||
@@ -317,7 +317,13 @@ function notifyDriversOnLocationServer($drivers_ids_array, $payload, $rideId = n
|
||||
*/
|
||||
function notifyPassengerOnRideServer($passenger_id, $payload) {
|
||||
$url = getenv('PASSENGER_SOCKET_INTERNAL_URL') ?: (getenv('RIDE_SOCKET_URL') ?: 'http://socket_passenger:3031');
|
||||
if (!isAllowedSocketUrl($url)) return null;
|
||||
if (!isAllowedSocketUrl($url)) {
|
||||
// كان يرجع null بصمت تام — عكس sendToLocationServer — فإذا كان
|
||||
// ALLOWED_SOCKET_URLS لا يغطّي عنوان السوكيت تسقط كل أحداث الراكب
|
||||
// بلا أي أثر في اللوج.
|
||||
error_log("[SOCKET_BLOCKED] Passenger socket URL not in ALLOWED_SOCKET_URLS: $url");
|
||||
return null;
|
||||
}
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
|
||||
if (empty($INTERNAL_KEY)) {
|
||||
@@ -363,7 +369,7 @@ function dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startNa
|
||||
$countDrivers = count($driversData);
|
||||
error_log("🚀 [DISPATCH_START] RideID: $rideId | Drivers Count: $countDrivers");
|
||||
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
|
||||
if (!isAllowedSocketUrl($socketUrl)) return;
|
||||
$internalKey = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
|
||||
|
||||
@@ -92,9 +92,13 @@ function getAccessToken($credentialsPath) {
|
||||
'assertion' => $jwt
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// بلا مهلة كان الطلب يقدر يعلّق حتى مهلة PHP نفسها، فتُسقط الرسالة بصمت
|
||||
// وهذا أحد أسباب "الإشعار مرات يوصل ومرات لا".
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
$res = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
return json_decode($res, true)['access_token'] ?? null;
|
||||
}
|
||||
|
||||
@@ -114,6 +118,11 @@ $fcmUrl = "https://fcm.googleapis.com/v1/projects/$projectId/messages:send";
|
||||
// ============================================================================
|
||||
// بناء هيكل الرسالة
|
||||
// ============================================================================
|
||||
// 🔧 توحيد نغمة أندرويد مع FcmService::send — هناك 'ding' تُترجم إلى 'default'،
|
||||
// وهنا كانت تُمرَّر كما هي فيبحث أندرويد عن ملف صوت اسمه "ding" وقد لا يوجد
|
||||
// فيصل الإشعار بلا صوت (أو لا يُلفت النظر إطلاقاً).
|
||||
$androidSound = ($tone === 'ding' || $tone === 'default') ? 'default' : $tone;
|
||||
|
||||
$messagePayload = [
|
||||
'message' => [
|
||||
'notification' => [
|
||||
@@ -123,7 +132,7 @@ $messagePayload = [
|
||||
'android' => [
|
||||
'priority' => 'HIGH',
|
||||
'notification' => [
|
||||
'sound' => $tone,
|
||||
'sound' => $androidSound,
|
||||
'channel_id' => 'high_importance_channel' // تأكد من تطابقه مع Android
|
||||
]
|
||||
],
|
||||
@@ -149,6 +158,15 @@ if ($isTopic) {
|
||||
// ============================================================================
|
||||
// 🔥 معالجة Data Payload (يجب أن تكون String: String فقط)
|
||||
// ============================================================================
|
||||
// FcmService::send يحقن title/body/tone/category/type داخل data، وتطبيق الراكب
|
||||
// يقرأ message.data['title'] أولاً. هذا المسار كان لا يحقنها، فأي رسالة تمرّ من
|
||||
// هنا تظهر بعنوان فارغ عند العميل الذي يعتمد على data. نوحّد السلوك.
|
||||
$customData = array_merge(is_array($customData) ? $customData : [], [
|
||||
'title' => (string)$title,
|
||||
'body' => (string)$body,
|
||||
'tone' => (string)$tone,
|
||||
]);
|
||||
|
||||
if (!empty($customData)) {
|
||||
$processedData = [];
|
||||
foreach ($customData as $key => $val) {
|
||||
|
||||
@@ -131,6 +131,21 @@ try {
|
||||
$ride['first_name'] = $encryptionHelper->decryptData($ride['first_name'] ?? '');
|
||||
$ride['email'] = $encryptionHelper->decryptData($ride['email'] ?? '');
|
||||
|
||||
// جدول tokens يخزّن التوكن مشفّراً (ride/firebase/addToken.php). كان
|
||||
// يُرجَع هنا كما هو، فيصل تطبيق السائق blob مشفّر يستخدمه كـ FCM target
|
||||
// في كل رسالة للراكب ⇒ رفض 400 من FCM ولا يصل الراكب شيء.
|
||||
if (!empty($ride['passengerToken'])) {
|
||||
$decodedPassengerToken = false;
|
||||
try {
|
||||
$decodedPassengerToken = $encryptionHelper->decryptData($ride['passengerToken']);
|
||||
} catch (\Throwable $eTok) {
|
||||
$decodedPassengerToken = false;
|
||||
}
|
||||
if ($decodedPassengerToken !== false && $decodedPassengerToken !== null && $decodedPassengerToken !== '') {
|
||||
$ride['passengerToken'] = trim($decodedPassengerToken);
|
||||
}
|
||||
}
|
||||
|
||||
$ride['start_location'] = $ride['start_lat'] . ',' . $ride['start_lng'];
|
||||
$ride['end_location'] = (!empty($ride['end_lat']))
|
||||
? $ride['end_lat'] . ',' . $ride['end_lng']
|
||||
|
||||
@@ -198,6 +198,38 @@ try {
|
||||
$passengerIdValue = $passengerId->fetchColumn();
|
||||
}
|
||||
|
||||
// 🆕 نحلّ توكن الراكب من القاعدة دائماً ولا نعتمد على ما يرسله التطبيق:
|
||||
// • مسار الـ dispatch/FCM يرسل التوكن سليماً (نص صريح من add_ride.php).
|
||||
// • مسار "سوق الرحلات" يرسله كما خرج من getRideWaiting.php أي **مشفّراً**
|
||||
// (جدول tokens يخزّنه مشفّراً — انظر ride/firebase/addToken.php)، فكان
|
||||
// FCM يرفضه بـ 400 ولا يصل الراكب أي إشعار قبول.
|
||||
// القيمة القادمة من العميل تبقى fallback أخيراً فقط.
|
||||
$passengerTokenFromClient = $passengerToken;
|
||||
$passengerToken = '';
|
||||
if ($passengerIdValue) {
|
||||
try {
|
||||
$stmtTok = $con->prepare("SELECT token FROM tokens WHERE passengerID = ? LIMIT 1");
|
||||
$stmtTok->execute([$passengerIdValue]);
|
||||
$rawPassengerToken = $stmtTok->fetchColumn();
|
||||
if (!empty($rawPassengerToken)) {
|
||||
$decryptedToken = false;
|
||||
try {
|
||||
$decryptedToken = $encryptionHelper->decryptData($rawPassengerToken);
|
||||
} catch (\Throwable $eTok) {
|
||||
$decryptedToken = false;
|
||||
}
|
||||
$passengerToken = ($decryptedToken !== false && $decryptedToken !== null && $decryptedToken !== '')
|
||||
? trim($decryptedToken)
|
||||
: $rawPassengerToken;
|
||||
}
|
||||
} catch (PDOException $eTok) {
|
||||
error_log("[accept_ride] passenger token lookup WARNING: " . $eTok->getMessage());
|
||||
}
|
||||
}
|
||||
if (empty($passengerToken)) {
|
||||
$passengerToken = $passengerTokenFromClient;
|
||||
}
|
||||
|
||||
if ($passengerIdValue) {
|
||||
// Socket — real-time update على خريطة الراكب
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
|
||||
@@ -24,19 +24,29 @@ function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketDa
|
||||
}
|
||||
}
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
// ⚠️ هذه الحمولة تُبَثّ لكل سائق قريب (وليس للفائز فقط) — لا نضع فيها أي
|
||||
// بيانات راكب حسّاسة (هاتف/إيميل/FCM token). ما ينقص السائق من تفاصيل
|
||||
// الراكب يجلبه بعد نجاح القبول، والسيرفر يحلّ التوكن بنفسه في acceptRide.php.
|
||||
//
|
||||
// أسماء الحقول تطابق getRideWaiting.php لأن available_rides_page.dart يحشر
|
||||
// رحلات الـ socket في نفس القائمة ويقرأ منها بنفس المفاتيح — أي حقل ناقص
|
||||
// يصل للسيرفر لاحقاً كنص "null".
|
||||
$marketPayload = array_merge([
|
||||
'id' => (string)$rideId,
|
||||
'start_lat' => $lat,
|
||||
'start_lng' => $lng,
|
||||
'end_lat' => $payloadData[3],
|
||||
'end_lng' => $payloadData[4],
|
||||
'price' => $payloadData[2],
|
||||
'carType' => $payloadData[31],
|
||||
'startName' => $payloadData[29],
|
||||
'endName' => $payloadData[30],
|
||||
'distance' => $payloadData[11],
|
||||
'duration' => $payloadData[15],
|
||||
'passengerRate' => $payloadData[33],
|
||||
'id' => (string)$rideId,
|
||||
'start_lat' => $lat,
|
||||
'start_lng' => $lng,
|
||||
'end_lat' => $payloadData[3],
|
||||
'end_lng' => $payloadData[4],
|
||||
'start_location' => $lat . ',' . $lng,
|
||||
'end_location' => $payloadData[3] . ',' . $payloadData[4],
|
||||
'price' => $payloadData[2],
|
||||
'carType' => $payloadData[31],
|
||||
'startName' => $payloadData[29],
|
||||
'endName' => $payloadData[30],
|
||||
'distance' => $payloadData[11],
|
||||
'duration' => $payloadData[15],
|
||||
'passengerRate' => $payloadData[33],
|
||||
'passengerId' => $payloadData[7],
|
||||
], $extraMarketData);
|
||||
|
||||
$postData = [
|
||||
|
||||
@@ -82,7 +82,7 @@ try {
|
||||
// 🔥 يُرسل دائماً بغض النظر عن driver_id — إذا كانت الرحلة لم تُقبل بعد
|
||||
// (driver_id = 0)، لوكيشن سيرفر يستخدم ride:offered_drivers:{rideId}
|
||||
// من Redis لإشعار كل السائقين الذين وصلهم عرض هذه الرحلة أصلاً.
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
|
||||
$internalKeyPath = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '';
|
||||
$internalKey = ($internalKeyPath && file_exists($internalKeyPath)) ? trim(file_get_contents($internalKeyPath)) : (getenv('INTERNAL_SOCKET_KEY') ?: '');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user