إصلاح سوكيت الراكب وتوحيد مسارات الإشعارات
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول): - تطبيق الراكب كان يرسل 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
@@ -63,6 +63,34 @@ function getInternalSocketKey(): string {
|
||||
|
||||
$INTERNAL_KEY = getInternalSocketKey();
|
||||
|
||||
// ── Redis سيرفر الموقع (للتحقق من عضوية الراكب في خط مواصلاتي) ──
|
||||
// نفس الـ Redis الذي يكتب عليه driver_socket و transit/functions.php.
|
||||
// الهوست من REDIS_HOST حتى يعمل داخل Docker وخارجه على حدّ سواء.
|
||||
$_locationRedis = null;
|
||||
|
||||
function getLocationRedis(): ?\Redis {
|
||||
global $_locationRedis;
|
||||
if ($_locationRedis !== null) {
|
||||
try { $_locationRedis->ping(); return $_locationRedis; }
|
||||
catch (\Exception $_) { $_locationRedis = null; }
|
||||
}
|
||||
try {
|
||||
$redisPass = '';
|
||||
foreach ([getenv('REDIS_PASS_KEY_PATH') ?: '', '/keys/.reds_pass_key', '/home/location/.reds_pass_key'] as $p) {
|
||||
if (!empty($p) && file_exists($p)) { $redisPass = trim((string)@file_get_contents($p)); break; }
|
||||
}
|
||||
$host = getenv('REDIS_HOST') ?: (file_exists('/.dockerenv') ? 'redis' : '127.0.0.1');
|
||||
$r = new \Redis();
|
||||
$r->connect($host, (int)(getenv('REDIS_PORT') ?: 6379), 1.5);
|
||||
if ($redisPass) $r->auth($redisPass);
|
||||
$_locationRedis = $r;
|
||||
return $r;
|
||||
} catch (\Exception $e) {
|
||||
socket_log("[REDIS_ERROR] Location Redis unavailable: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getJwtSecret(): string {
|
||||
$keyPath = getenv('JWT_SECRET_KEY_PATH');
|
||||
if ($keyPath && file_exists($keyPath)) {
|
||||
@@ -142,6 +170,28 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY, $INTERNAL_PORT) {
|
||||
|
||||
$connection->send('OK');
|
||||
|
||||
} elseif ($action === 'broadcast_bus_location') {
|
||||
|
||||
// 🚌 بثّ موقع الباص لكل ركاب الخط المشتركين (مواصلاتي)
|
||||
// يصل من driver_socket بعد أن يبعث سائق الباص update_bus_location
|
||||
$routeId = $post['route_id'] ?? null;
|
||||
$rawPayload = $post['payload'] ?? null;
|
||||
|
||||
if (!$routeId || !$rawPayload) {
|
||||
socket_log("[HTTP_ERROR] Missing route_id or payload for action: broadcast_bus_location", $post);
|
||||
$connection->send('Error: Missing route_id or payload');
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = is_string($rawPayload)
|
||||
? (json_decode($rawPayload, true) ?? $rawPayload)
|
||||
: $rawPayload;
|
||||
|
||||
socket_log("[HTTP_SUCCESS] Emitting 'bus_location_update' to route #$routeId", $payload);
|
||||
$io->to('transit_route_' . $routeId)->emit('bus_location_update', $payload);
|
||||
|
||||
$connection->send('OK');
|
||||
|
||||
} else {
|
||||
socket_log("[HTTP_WARNING] Unknown action received: $action", $post);
|
||||
$connection->send('Unknown action: ' . $action);
|
||||
@@ -191,6 +241,46 @@ $io->on('connection', function ($socket) {
|
||||
// socket_log("[SOCKET_HEARTBEAT] Received from Passenger #$passengerId");
|
||||
});
|
||||
|
||||
// 🚌 اشتراك الراكب في بثّ موقع باص خط (مواصلاتي)
|
||||
// يُرفض إن لم يكن الراكب عضواً نشطاً في المؤسسة المالكة للخط
|
||||
$socket->on('subscribe_transit_route', function ($data) use ($socket, $passengerId) {
|
||||
$data = (array) $data;
|
||||
$routeId = (int)($data['route_id'] ?? 0);
|
||||
if ($routeId <= 0) return;
|
||||
|
||||
// transit:route_org:{routeId} → org_id (كُتب في route/approve.php)
|
||||
// transit:org_members:{orgId} → SET من passenger_ids (transitCacheEnrollment)
|
||||
$redis = getLocationRedis();
|
||||
if ($redis) {
|
||||
$orgId = $redis->get("transit:route_org:{$routeId}");
|
||||
if ($orgId === false || $orgId === null) {
|
||||
socket_log("[TRANSIT_BLOCKED] Route #{$routeId} not in Redis — passenger #{$passengerId}");
|
||||
$socket->emit('transit_error', ['code' => 'ROUTE_NOT_FOUND', 'route_id' => $routeId]);
|
||||
return;
|
||||
}
|
||||
if (!$redis->sIsMember("transit:org_members:{$orgId}", (string)$passengerId)) {
|
||||
socket_log("[TRANSIT_BLOCKED] Passenger #{$passengerId} not enrolled in org #{$orgId} (route #{$routeId})");
|
||||
$socket->emit('transit_error', ['code' => 'NOT_ENROLLED', 'route_id' => $routeId]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
socket_log("[TRANSIT_WARN] Redis unavailable — enrollment check skipped (passenger #{$passengerId} route #{$routeId})");
|
||||
}
|
||||
|
||||
$socket->join('transit_route_' . $routeId);
|
||||
socket_log("[TRANSIT] Passenger #$passengerId subscribed to route #$routeId");
|
||||
});
|
||||
|
||||
// 🚌 إلغاء الاشتراك عند إغلاق الراكب للخط
|
||||
$socket->on('unsubscribe_transit_route', function ($data) use ($socket, $passengerId) {
|
||||
$data = (array) $data;
|
||||
$routeId = (int)($data['route_id'] ?? 0);
|
||||
if ($routeId <= 0) return;
|
||||
|
||||
$socket->leave('transit_route_' . $routeId);
|
||||
socket_log("[TRANSIT] Passenger #$passengerId unsubscribed from route #$routeId");
|
||||
});
|
||||
|
||||
$socket->on('disconnect', function () use ($passengerId, $clientIp) {
|
||||
socket_log("[SOCKET_DISCONNECTED] Passenger Disconnected: #$passengerId (IP: $clientIp)");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user