From f7df080b271974eef09e9d94fa5e01e0bc4b545e Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 1 Aug 2026 18:28:30 +0300 Subject: [PATCH] Fix payment server: remove global conRide to prevent crashing non-ride DB queries --- backend/core/Database/Database.php | 6 - .../functions/location_controller.dart | 40 +++- .../home/captin/home_captain_controller.dart | 171 ++++++++++++------ loction_server/driver_socket.php | 25 ++- test_jwt.php | 5 + 5 files changed, 175 insertions(+), 72 deletions(-) create mode 100644 test_jwt.php diff --git a/backend/core/Database/Database.php b/backend/core/Database/Database.php index a1ecf35..7e49abd 100644 --- a/backend/core/Database/Database.php +++ b/backend/core/Database/Database.php @@ -53,12 +53,6 @@ class Database $cfg = self::$map[$name]; $dbname = getenv($cfg['name']); - - // 🔥 OVERRIDE: Prevent crash if .env on server still has the old wrong name - if ($name === 'ride' && $dbname === 'intaleq-ridesDB') { - $dbname = 'rideDB'; - } - $host = getenv($cfg['host']) ?: 'localhost'; $user = getenv($cfg['user']); $pass = getenv($cfg['pass']); diff --git a/intaleq_driver/lib/controller/functions/location_controller.dart b/intaleq_driver/lib/controller/functions/location_controller.dart index 319219b..b6dae4c 100755 --- a/intaleq_driver/lib/controller/functions/location_controller.dart +++ b/intaleq_driver/lib/controller/functions/location_controller.dart @@ -187,7 +187,13 @@ class LocationController extends GetxController with WidgetsBindingObserver { }); socket!.disconnect(); } + return; } + + // ‏أي تغيّر آخر (متاح ⇄ مشغول) يجب أن يصل السيرفر فوراً: هو الحدث الذي + // ‏ينقل الكابتن بين geo:drivers:available و geo:drivers:busy. وبلا هذا + // ‏ننتظر أول حدث GPS — وفلتر المسافة قد يؤجّله دقائق إن كان واقفاً. + _emitCurrentLocationNow(); }); bool deps = await _awaitDependencies(); @@ -349,11 +355,11 @@ class LocationController extends GetxController with WidgetsBindingObserver { if (sid != null || eid != null) { isSocketConnected = true; - if (myLocation.latitude != 0) { - print("🚀 Sending initial location to socket after connect..."); - emitLocationToSocket(myLocation, heading, speed); - } - + // ‏هذا الـ emit هو ما يُسجّل الكابتن في geo:drivers:available بعد كل + // ‏اتصال جديد، فلا نتخطاه لمجرد أن أول تثبيت GPS لم يصل بعد. + print("🚀 Sending initial location to socket after connect..."); + _emitCurrentLocationNow(); + _startHeartbeat(); // 🆕 طلب أي رحلات انتظار فاتتنا أثناء انقطاع الاتصال @@ -546,6 +552,30 @@ class LocationController extends GetxController with WidgetsBindingObserver { _socketHeartbeat?.cancel(); } + /// يبثّ الموقع الحالي فوراً، ويجلب تثبيتاً واحداً إن لم يكن لدينا موقع بعد. + /// + /// ضروري بعد كل اتصال جديد وبعد كل تغيّر حالة: ملفّ التتبّع في وضع الانتظار + /// يستخدم فلتر مسافة (50–100 متر)، فالكابتن الواقف قد لا يُصدر أي حدث + /// onLocationChanged إطلاقاً — ولو اعتمدنا عليه وحده لبقي غير مرئي على + /// خريطة الراكب حتى يتحرّك فعلياً. + Future _emitCurrentLocationNow() async { + if (socket == null || !socket!.connected) return; + + if (myLocation.latitude == 0 && myLocation.longitude == 0) { + await getLocation(); + } + if (myLocation.latitude == 0 && myLocation.longitude == 0) { + Log.print("⚠️ No GPS fix yet — initial location emit skipped."); + return; + } + + if (isBusMode) { + emitBusLocationToSocket(myLocation, heading, speed); + } else { + emitLocationToSocket(myLocation, heading, speed); + } + } + // In LocationController.dart void emitLocationToSocket(LatLng pos, double head, double spd) { diff --git a/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart b/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart index 3600b8a..ec7b3e7 100755 --- a/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart +++ b/intaleq_driver/lib/controller/home/captin/home_captain_controller.dart @@ -373,68 +373,115 @@ class HomeCaptainController extends GetxController { } } - void onButtonSelected() { + /// زرّ «متاح / غير متاح» في الواجهة — يقلب الحالة الحالية. + void onButtonSelected() => setActive(!isActive); + + /// تفعيل/إيقاف الكابتن بشكل صريح. + /// مُعامِلة صريحة بدل القلب (toggle) لأن الاستدعاء التلقائي في onInit كان + /// يعتمد على أن isActive تساوي false — فأي إعادة بناء للكونترولر أو + /// استدعاء ثانٍ كانت تُطفئ الكابتن بدل تشغيله. الاستدعاء بنفس القيمة + /// الحالية آمن: يعيد التأكد من أن التتبّع شغّال فقط. + void setActive(bool active) { if (!Get.isRegistered()) { Get.put(CaptainWalletController()); } totalPoints = Get.find().totalPoints; - // Toggle Active State - isActive = !isActive; - - if (isActive) { - try { - _checkFatigueBeforeOnline(); // Throws exception if tired - - if (double.parse(totalPoints) > minPointsThreshold) { - locationController.startLocationUpdates(); - HapticFeedback.heavyImpact(); - activeStartTime = DateTime.now(); - - activeTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - activeDuration = DateTime.now().difference(activeStartTime!); - stringActiveDuration = formatDuration(activeDuration); - - // Increment Fatigue Counter (write to box every 30s) - _fatigueSeconds++; - if (_fatigueSeconds % 30 == 0) { - int totalSeconds = - (box.read('fatigue_total_seconds') ?? 0) + _fatigueSeconds; - box.write('fatigue_total_seconds', totalSeconds); - _fatigueSeconds = 0; - if (totalSeconds >= 12 * 3600) { - // 12 hours - _forceOfflineDueToFatigue(); - } - } - - update(); - }); - } else { - locationController.stopLocationUpdates(); - activeStartTime = null; - activeTimer?.cancel(); - savePeriod(activeDuration); - activeDuration = Duration.zero; - box.write('fatigue_last_offline', DateTime.now().toIso8601String()); - update(); - } - } catch (e) { - // Driver is fatigued, revert state - isActive = false; - update(); - } - } else { - locationController.stopLocationUpdates(); - activeStartTime = null; - activeTimer?.cancel(); - savePeriod(activeDuration); - activeDuration = Duration.zero; - - // Save offline time for Fatigue Monitoring reset - box.write('fatigue_last_offline', DateTime.now().toIso8601String()); - update(); + if (!active) { + _goOffline(); + return; } + + try { + _checkFatigueBeforeOnline(); // يرمي استثناءً إذا تجاوز 12 ساعة + } catch (_) { + isActive = false; + update(); + return; + } + + // الرصيد غير كافٍ: نُطفئ فعلياً بدل إبقاء isActive = true بلا تتبّع، + // فقد كانت الواجهة تعرض «متاح» بينما لا يُبَث أي موقع. + final double? points = double.tryParse(totalPoints); + if (points == null || points <= minPointsThreshold) { + _goOffline(); + return; + } + + final bool wasActive = isActive; + isActive = true; + + // آمنة للاستدعاء المتكرر: تتجاهل الاشتراك إن كان قائماً بالفعل. + locationController.startLocationUpdates(); + + if (!wasActive) { + HapticFeedback.heavyImpact(); + activeStartTime = DateTime.now(); + activeTimer?.cancel(); + activeTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + activeDuration = DateTime.now().difference(activeStartTime!); + stringActiveDuration = formatDuration(activeDuration); + + // Increment Fatigue Counter (write to box every 30s) + _fatigueSeconds++; + if (_fatigueSeconds % 30 == 0) { + int totalSeconds = + (box.read('fatigue_total_seconds') ?? 0) + _fatigueSeconds; + box.write('fatigue_total_seconds', totalSeconds); + _fatigueSeconds = 0; + if (totalSeconds >= 12 * 3600) { + // 12 hours + _forceOfflineDueToFatigue(); + } + } + + update(); + }); + } + + update(); + } + + void _goOffline() { + isActive = false; + locationController.stopLocationUpdates(); + activeStartTime = null; + activeTimer?.cancel(); + if (activeDuration.inSeconds > 0) savePeriod(activeDuration); + activeDuration = Duration.zero; + + // Save offline time for Fatigue Monitoring reset + box.write('fatigue_last_offline', DateTime.now().toIso8601String()); + update(); + } + + /// يضبط حالة الكابتن المخزَّنة عند إقلاع التطبيق. + /// تذكير بمصطلحات التطبيق: 'off' = متاح، 'on' = مشغول برحلة، + /// 'blocked' = محظور، وهي القيمة التي يقرأها emitLocationToSocket ويبني + /// عليها السيرفر عضوية geo:drivers:available. + /// + /// كانت 'off' تُكتب دون شرط في آخر onInit، فتُلغي حظراً ساري المفعول + /// كتبه checkAndShowBlockDialog قبلها بسطر، وتُعيد كابتناً في رحلة نشطة + /// إلى حوض المتاحين بعد إعادة فتح التطبيق. + void _applyInitialDriverStatus() { + final String? blockStr = box.read(BoxName.blockUntilDate); + if (blockStr != null && blockStr.isNotEmpty) { + final DateTime? blockExpiry = DateTime.tryParse(blockStr); + if (blockExpiry != null && DateTime.now().isBefore(blockExpiry)) { + box.write(BoxName.statusDriverLocation, 'blocked'); + return; + } + } + + // رحلة نجت من إغلاق التطبيق: نُبقيه مشغولاً حتى لا يُوزَّع عليه طلب ثانٍ. + final String? rideStatus = box.read(BoxName.rideStatus); + if (rideStatus == 'Apply' || rideStatus == 'Arrived' || + rideStatus == 'Begin') { + box.write(BoxName.statusDriverLocation, 'on'); + return; + } + + box.write(BoxName.statusDriverLocation, 'off'); // متاح } // متغيرات العداد للحظر @@ -815,8 +862,16 @@ class HomeCaptainController extends GetxController { Get.put(FirebaseMessagesController()); addToken(); + + // ⚠️ الترتيب مقصود: الحالة تُكتب قبل تشغيل التتبّع. + // ‏أول update_location يُرسَل بعد اتصال السوكيت مباشرة، وهو الذي يُدخل + // ‏الكابتن إلى geo:drivers:available على السيرفر. وكتابة الحالة بعد + // ‏تشغيل التتبّع (كما كانت في آخر السطور) تعني أن ذلك الـ emit قد يحمل + // ‏حالة قديمة ('on' من رحلة سابقة لم تُغلق مثلاً). + _applyInitialDriverStatus(); + await getlocation(); - onButtonSelected(); + setActive(true); getDriverRate(); addCustomCarIcon(); getKazanPercent(); @@ -829,8 +884,8 @@ class HomeCaptainController extends GetxController { // totalPoints = Get.find().totalPoints.toString(); // getRefusedOrderByCaptain(); // 🔥 الفحص عند تشغيل التطبيق + // ‏(يكتب 'blocked' عند وجود حظر ساري — ولم تعد أي كتابة بعده تدهسه) checkAndShowBlockDialog(); - box.write(BoxName.statusDriverLocation, 'off'); // 2. عدل الليسنر ليصبح مشروطاً // Camera follow timer — only moves when the driver has // actually moved > 15 meters, saving GPU/battery on idle. diff --git a/loction_server/driver_socket.php b/loction_server/driver_socket.php index 0cb9bd3..b837b0d 100755 --- a/loction_server/driver_socket.php +++ b/loction_server/driver_socket.php @@ -761,13 +761,27 @@ $io->on('connection', function ($socket) use ($INTERNAL_KEY) { $driverState[$driverId] = [ 'lat' => 0.0, 'lng' => 0.0, - 'speed' => -999.0, + 'speed' => -999.0, 'heading' => -999.0, 'status' => '', 'expire_ts' => 0, ]; + } else { + // ‏اتصال جديد لا يضمن أن السائق ما زال عضواً في geo:drivers:* — + // ‏api_get_nearby.php يحذف العضوية كلّما وجد driver:profile منتهياً + // ‏(TTL = 900 ثانية)، بينما تبقى هذه الحالة في ذاكرة الـ worker لأن + // ‏سوكيت خدمة الخلفية (background_service) يظل مسجَّلاً فيمنع تنظيفها + // ‏عند إغلاق سوكيت الواجهة. + // ‏وبلا تصفيرها يجد أول update_location بعد فتح التطبيق أن status و + // ‏lat/lng بلا تغيير ⇒ لا GEOADD ⇒ يبقى الكابتن غير مرئي على خريطة + // ‏الراكب حتى يتحرك 10 أمتار أو يضغط زر التوفّر (الذي يقطع السوكيت). + // ‏نصفّر الجزء المتطاير فقط ليُجبَر أول تحديث على إعادة تسجيله. + $driverState[$driverId]['lat'] = 0.0; + $driverState[$driverId]['lng'] = 0.0; + $driverState[$driverId]['status'] = ''; + $driverState[$driverId]['expire_ts'] = 0; } - + logMsg("✅ Driver Connected: #$driverId ($platform)"); // 🆕 Deliver any pending orders missed during disconnection @@ -941,7 +955,12 @@ $io->on('connection', function ($socket) use ($INTERNAL_KEY) { } } - if ($needGeoadd || $statusChanged) { + // ‏نُعيد تأكيد العضوية في geo:drivers:* مع كل تجديد صلاحية (كل + // ‏EXPIRE_REFRESH_SECONDS) لا عند الحركة فقط: الكابتن المتاح والواقف + // ‏لا يُصدر didMove ولا statusChanged أبداً، فلو حُذف من المجموعة + // ‏(انقطاع، إعادة تشغيل السيرفر، تنظيف api_get_nearby) لما عاد إليها + // ‏قط. التكلفة: عملية GEOADD واحدة لكل كابتن كل دقيقتين. + if ($needGeoadd || $statusChanged || $needExpireRefresh) { $eventBuffer[$driverId]['geoadd'] = [ 'status' => $status, 'lng' => $lng, diff --git a/test_jwt.php b/test_jwt.php new file mode 100644 index 0000000..bf92576 --- /dev/null +++ b/test_jwt.php @@ -0,0 +1,5 @@ +generateAccessToken('34feffd3fa72d6bee56b', 'driver', 'wallet-app:ios'); +echo $token;