send_fcm.php هي نقطة كل الرسائل والمكالمات بين الراكب والسائق (التطبيقان
يستدعيانها مباشرة)، ولم تكن تحتوي أي error_log إطلاقاً — بخلاف FcmService
التي تطبع [FCM_DEBUG] وتخدم دورة حياة الرحلة فقط.
النتيجة أن فشل الرسائل كان غير قابل للتشخيص: لا سبب، ولا رد Google، ولا
حتى معرفة إن كان الطلب وصل. وقد وعدت المستخدم بقراءة [FCM_DEBUG] لهذا
المسار وهو وعد خاطئ — لا وجود له هنا.
أُضيفت ثلاث نقاط:
- نتيجة الإرسال: category + طرف من التوكن + http + طول التوكن + رد Google
عند الفشل. طول التوكن مقصود: توكن FCM الحقيقي ~163 محرفاً، والمشفّر في
جدول tokens 216 — فيُكشف أي blob مشفّر من سطر واحد.
- رفض 403: التطبيقان لا يرسلان x-api-key، فلحظة ضبط FCM_INTERNAL_API_KEY
تموت كل الرسائل بينهما بصمت. الآن يُسجَّل السبب صريحاً.
- رفض 400 على target فارغ: يعني أن المُرسِل لا يملك توكن الطرف الآخر
(tokenPassenger أو driverToken لم يصله في حمولة القبول).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول):
- تطبيق الراكب كان يرسل 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>