Fix: update destination limits to 3 and sync with Redis
This commit is contained in:
@@ -19,6 +19,37 @@ $destLat = filterRequest('destination_lat') ?? filterRequest('target_latitude')
|
|||||||
$destLng = filterRequest('destination_lng') ?? filterRequest('target_longitude');
|
$destLng = filterRequest('destination_lng') ?? filterRequest('target_longitude');
|
||||||
$destName = filterRequest('destination_name') ?? 'Destination';
|
$destName = filterRequest('destination_name') ?? 'Destination';
|
||||||
|
|
||||||
|
function notifySocketServerDestination($userId, $hasDestination, $destLat = '', $destLng = '', $destName = '') {
|
||||||
|
$url = getenv('LOCATION_SOCKET_URL');
|
||||||
|
$internalKey = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||||
|
|
||||||
|
if (empty($url)) {
|
||||||
|
error_log("[save_driver_destination] LOCATION_SOCKET_URL env variable not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$postData = [
|
||||||
|
'action' => 'update_driver_destination',
|
||||||
|
'driver_id' => (string)$userId,
|
||||||
|
'has_destination' => (int)$hasDestination,
|
||||||
|
'destination_lat' => (string)$destLat,
|
||||||
|
'destination_lng' => (string)$destLng,
|
||||||
|
'destination_name' => (string)$destName,
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 400);
|
||||||
|
if ($internalKey) {
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $internalKey"]);
|
||||||
|
}
|
||||||
|
curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($action === 'get') {
|
if ($action === 'get') {
|
||||||
$stmtGet = $con->prepare("
|
$stmtGet = $con->prepare("
|
||||||
@@ -47,6 +78,10 @@ try {
|
|||||||
AND is_active = 1
|
AND is_active = 1
|
||||||
");
|
");
|
||||||
$stmtDeactivate->execute([':did' => $user_id]);
|
$stmtDeactivate->execute([':did' => $user_id]);
|
||||||
|
|
||||||
|
// Sync with Redis on Socket Server
|
||||||
|
notifySocketServerDestination($user_id, 0);
|
||||||
|
|
||||||
jsonSuccess(null, "تم إلغاء تفعيل الوجهة الشخصية بنجاح.");
|
jsonSuccess(null, "تم إلغاء تفعيل الوجهة الشخصية بنجاح.");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -56,19 +91,34 @@ try {
|
|||||||
jsonError("Missing required parameters: destination_lat and destination_lng are required.");
|
jsonError("Missing required parameters: destination_lat and destination_lng are required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Enforce Limit: Max 2 times daily
|
// 3. Enforce Limit: Max 3 times daily
|
||||||
|
// Check local Redis rate limit first
|
||||||
|
if (isset($redis)) {
|
||||||
|
$redisKey = "driver:dest_count:" . $user_id;
|
||||||
|
$redisCount = intval($redis->get($redisKey));
|
||||||
|
if ($redisCount >= 3) {
|
||||||
|
jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (3 مرات في اليوم).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check MySQL database limit
|
||||||
$stmtCount = $con->prepare("
|
$stmtCount = $con->prepare("
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM driver_destinations
|
FROM driver_destinations
|
||||||
WHERE driver_id = :did
|
WHERE driver_id = :did
|
||||||
AND usage_date = CURDATE()
|
AND usage_date = CURDATE()
|
||||||
AND is_active = 1
|
|
||||||
");
|
");
|
||||||
$stmtCount->execute([':did' => $user_id]);
|
$stmtCount->execute([':did' => $user_id]);
|
||||||
$dailyCount = intval($stmtCount->fetchColumn());
|
$dailyCount = intval($stmtCount->fetchColumn());
|
||||||
|
|
||||||
if ($dailyCount >= 2) {
|
if ($dailyCount >= 3) {
|
||||||
jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (مرتان في اليوم).");
|
// Sync Redis counter just in case
|
||||||
|
if (isset($redis)) {
|
||||||
|
$redisKey = "driver:dest_count:" . $user_id;
|
||||||
|
$redis->set($redisKey, $dailyCount);
|
||||||
|
$redis->expire($redisKey, 86400); // 24 hours TTL
|
||||||
|
}
|
||||||
|
jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (3 مرات في اليوم).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Deactivate previous active destinations for this driver
|
// 4. Deactivate previous active destinations for this driver
|
||||||
@@ -93,6 +143,16 @@ try {
|
|||||||
':name' => $destName
|
':name' => $destName
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Sync with Redis on Socket Server
|
||||||
|
notifySocketServerDestination($user_id, 1, $destLat, $destLng, $destName);
|
||||||
|
|
||||||
|
// Increment local Redis counter
|
||||||
|
if (isset($redis)) {
|
||||||
|
$redisKey = "driver:dest_count:" . $user_id;
|
||||||
|
$redis->incr($redisKey);
|
||||||
|
$redis->expire($redisKey, 86400); // 24 hours TTL
|
||||||
|
}
|
||||||
|
|
||||||
jsonSuccess(null, "تم حفظ وجهتك كابتن بنجاح! سيتم توجيه الطلبات المطابقة لوجهتك.");
|
jsonSuccess(null, "تم حفظ وجهتك كابتن بنجاح! سيتم توجيه الطلبات المطابقة لوجهتك.");
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ try {
|
|||||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
|
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
|
||||||
$url = getenv('LOCATION_SOCKET_URL');
|
$url = getenv('LOCATION_SOCKET_URL');
|
||||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||||
|
|
||||||
$marketPayload = [
|
$marketPayload = [
|
||||||
'id' => (string)$rideId,
|
'id' => (string)$rideId,
|
||||||
'start_lat' => $lat,
|
'start_lat' => $lat,
|
||||||
'start_lng' => $lng,
|
'start_lng' => $lng,
|
||||||
|
'end_lat' => $payloadData[3],
|
||||||
|
'end_lng' => $payloadData[4],
|
||||||
'price' => $payloadData[2],
|
'price' => $payloadData[2],
|
||||||
'carType' => $payloadData[31],
|
'carType' => $payloadData[31],
|
||||||
'startName' => $payloadData[29],
|
'startName' => $payloadData[29],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class BoxName {
|
|||||||
static const String hmac = "hmac";
|
static const String hmac = "hmac";
|
||||||
static const String ttsEnabled = "ttsEnabled";
|
static const String ttsEnabled = "ttsEnabled";
|
||||||
static const String deviceFingerprint = "deviceFingerprint";
|
static const String deviceFingerprint = "deviceFingerprint";
|
||||||
|
static const String deviceFpEncrypted = "deviceFpEncrypted";
|
||||||
static const String security_check = "security_check";
|
static const String security_check = "security_check";
|
||||||
static const String rideType = "rideType";
|
static const String rideType = "rideType";
|
||||||
static const String walletType = "walletType";
|
static const String walletType = "walletType";
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ class LoginDriverController extends GetxController {
|
|||||||
isPhoneVerified() async {
|
isPhoneVerified() async {
|
||||||
// If the phone is already verified locally and we have a driver ID, skip the API check
|
// If the phone is already verified locally and we have a driver ID, skip the API check
|
||||||
// This prevents asking for OTP again if the user restarts the app during registration.
|
// This prevents asking for OTP again if the user restarts the app during registration.
|
||||||
if (box.read(BoxName.phoneVerified) == '1' && box.read(BoxName.driverID) != null) {
|
if (box.read(BoxName.phoneVerified) == '1' &&
|
||||||
|
box.read(BoxName.driverID) != null) {
|
||||||
Get.offAll(() => RegistrationView());
|
Get.offAll(() => RegistrationView());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -177,6 +178,34 @@ class LoginDriverController extends GetxController {
|
|||||||
|
|
||||||
getJWT() async {
|
getJWT() async {
|
||||||
await EncryptionHelper.initialize();
|
await EncryptionHelper.initialize();
|
||||||
|
|
||||||
|
// 1. Check secure storage first to avoid redundant API calls
|
||||||
|
String? secureJwt = await storage.read(key: BoxName.jwt);
|
||||||
|
if (secureJwt != null && secureJwt.isNotEmpty) {
|
||||||
|
bool isTokenValid = false;
|
||||||
|
try {
|
||||||
|
final parts = secureJwt.split('.');
|
||||||
|
if (parts.length == 3) {
|
||||||
|
String payload = parts[1];
|
||||||
|
switch (payload.length % 4) {
|
||||||
|
case 2: payload += '=='; break;
|
||||||
|
case 3: payload += '='; break;
|
||||||
|
}
|
||||||
|
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||||
|
final exp = decoded['exp'];
|
||||||
|
if (exp != null) {
|
||||||
|
// Check if token is valid with a 30-second buffer
|
||||||
|
isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (isTokenValid) {
|
||||||
|
Log.print('🔑 Valid JWT found in secure storage. Skipping generation.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||||
Log.print(
|
Log.print(
|
||||||
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
|
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
|
||||||
@@ -209,8 +238,8 @@ class LoginDriverController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jwt != null) {
|
if (jwt != null) {
|
||||||
box.write(BoxName.jwt, c(jwt));
|
// box.write(BoxName.jwt, c(jwt));
|
||||||
await storage.write(key: BoxName.jwt, value: c(jwt));
|
await storage.write(key: BoxName.jwt, value: jwt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ بعد التأكد أن كل المفاتيح موجودة
|
// ✅ بعد التأكد أن كل المفاتيح موجودة
|
||||||
@@ -249,8 +278,8 @@ class LoginDriverController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jwt != null) {
|
if (jwt != null) {
|
||||||
await box.write(BoxName.jwt, c(jwt));
|
// await box.write(BoxName.jwt, c(jwt));
|
||||||
await storage.write(key: BoxName.jwt, value: c(jwt));
|
await storage.write(key: BoxName.jwt, value: jwt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// await AppInitializer().getKey();
|
// await AppInitializer().getKey();
|
||||||
@@ -266,32 +295,6 @@ class LoginDriverController extends GetxController {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
String generateUniqueIdFromEmail(String email) {
|
|
||||||
// Step 1: Extract the local part of the email
|
|
||||||
String localPart = email.split('@')[0];
|
|
||||||
|
|
||||||
// Step 2: Replace invalid characters (if any)
|
|
||||||
String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
|
|
||||||
|
|
||||||
// Step 3: Ensure it does not exceed 24 characters
|
|
||||||
if (cleanLocalPart.length > 24) {
|
|
||||||
cleanLocalPart = cleanLocalPart.substring(0, 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Generate a random suffix if needed
|
|
||||||
String suffix = generateRandomSuffix(24 - cleanLocalPart.length);
|
|
||||||
|
|
||||||
return cleanLocalPart + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateRandomSuffix(int length) {
|
|
||||||
const String chars =
|
|
||||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
Random random = Random();
|
|
||||||
return List.generate(length, (index) => chars[random.nextInt(chars.length)])
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInviteDriverFound = false;
|
bool isInviteDriverFound = false;
|
||||||
|
|
||||||
Future updateInvitationCodeFromRegister() async {
|
Future updateInvitationCodeFromRegister() async {
|
||||||
@@ -335,7 +338,8 @@ class LoginDriverController extends GetxController {
|
|||||||
// await SecurityHelper.performSecurityChecks();
|
// await SecurityHelper.performSecurityChecks();
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
||||||
// await getJWT();
|
// await getJWT();
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
|
var res = await CRUD().get(
|
||||||
|
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
|
||||||
Log.print('loginDriver: ${res}');
|
Log.print('loginDriver: ${res}');
|
||||||
if (res == 'failure') {
|
if (res == 'failure') {
|
||||||
await isPhoneVerified();
|
await isPhoneVerified();
|
||||||
@@ -391,10 +395,10 @@ class LoginDriverController extends GetxController {
|
|||||||
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
|
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
|
||||||
Log.print('🔑 Getting access token after login...');
|
Log.print('🔑 Getting access token after login...');
|
||||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
await storage.write(
|
// await storage.write(
|
||||||
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
// key: BoxName.fingerPrint, value: fingerPrint.toString());
|
||||||
await getJWT();
|
// await getJWT();
|
||||||
Log.print('🔑 Access token obtained.');
|
// Log.print('🔑 Access token obtained.');
|
||||||
|
|
||||||
// add invitations
|
// add invitations
|
||||||
if (box.read(BoxName.isInstall) == null ||
|
if (box.read(BoxName.isInstall) == null ||
|
||||||
@@ -416,10 +420,15 @@ class LoginDriverController extends GetxController {
|
|||||||
// '963992952235@intaleqapp.com') {
|
// '963992952235@intaleqapp.com') {
|
||||||
if (token != 'failure') {
|
if (token != 'failure') {
|
||||||
var serverData = jsonDecode(token);
|
var serverData = jsonDecode(token);
|
||||||
if ((serverData['data'][0]['token'].toString()) !=
|
final serverToken = serverData['data'][0]['token'].toString();
|
||||||
box.read(BoxName.tokenDriver).toString() ||
|
final storedToken =
|
||||||
serverData['data'][0]['fingerPrint'].toString() !=
|
box.read(BoxName.tokenDriver)?.toString() ?? '';
|
||||||
fingerPrint.toString()) {
|
final serverFp = serverData['data'][0]['fingerPrint'].toString();
|
||||||
|
final tokenMismatch =
|
||||||
|
storedToken.isNotEmpty && serverToken != storedToken;
|
||||||
|
Log.print(
|
||||||
|
'🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken');
|
||||||
|
if (tokenMismatch) {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
title: 'Device Change Detected'.tr,
|
title: 'Device Change Detected'.tr,
|
||||||
@@ -485,7 +494,8 @@ class LoginDriverController extends GetxController {
|
|||||||
// await SecurityHelper.performSecurityChecks();
|
// await SecurityHelper.performSecurityChecks();
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
||||||
|
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
|
var res = await CRUD().get(
|
||||||
|
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
|
||||||
|
|
||||||
// print('res is $res');
|
// print('res is $res');
|
||||||
// if (res == 'failure') {
|
// if (res == 'failure') {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:siro_driver/env/env.dart';
|
import 'package:siro_driver/env/env.dart';
|
||||||
import 'package:siro_driver/print.dart';
|
import 'package:siro_driver/print.dart';
|
||||||
|
import 'package:siro_driver/controller/functions/package_info.dart';
|
||||||
|
|
||||||
import '../../constant/api_key.dart';
|
import '../../constant/api_key.dart';
|
||||||
import '../../views/widgets/error_snakbar.dart';
|
import '../../views/widgets/error_snakbar.dart';
|
||||||
@@ -21,7 +22,6 @@ import 'ssl_pinning.dart';
|
|||||||
class CRUD {
|
class CRUD {
|
||||||
final NetGuard _netGuard = NetGuard();
|
final NetGuard _netGuard = NetGuard();
|
||||||
final _client = SslPinning.createPinnedClient();
|
final _client = SslPinning.createPinnedClient();
|
||||||
|
|
||||||
static bool _isRefreshingJWT = false;
|
static bool _isRefreshingJWT = false;
|
||||||
static String _lastErrorSignature = '';
|
static String _lastErrorSignature = '';
|
||||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||||
@@ -93,15 +93,15 @@ class CRUD {
|
|||||||
// نفس القيمة المرسلة عند login وعُملها hash في JWT
|
// نفس القيمة المرسلة عند login وعُملها hash في JWT
|
||||||
// السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
|
// السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
String _getFpHeader() {
|
Future<String> _getFpHeader() async {
|
||||||
return box.read(BoxName.deviceFingerprint)?.toString() ?? '';
|
return box.read(BoxName.deviceFingerprint)?.toString() ?? await DeviceHelper.getDeviceFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getJwt() {
|
Future<String> _getJwt() async {
|
||||||
try {
|
try {
|
||||||
final jwt = box.read(BoxName.jwt);
|
final jwt = await storage.read(key: BoxName.jwt);
|
||||||
if (jwt == null || jwt.toString().isEmpty) return '';
|
if (jwt == null || jwt.toString().isEmpty) return '';
|
||||||
return r(jwt).toString().split(Env.addd)[0];
|
return jwt;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -172,6 +172,7 @@ class CRUD {
|
|||||||
final body = response.body;
|
final body = response.body;
|
||||||
|
|
||||||
Log.print('📥 [RES-$requestId] [$sc] $link');
|
Log.print('📥 [RES-$requestId] [$sc] $link');
|
||||||
|
Log.print('payload: $payload');
|
||||||
Log.print('📄 [BODY-$requestId] $body');
|
Log.print('📄 [BODY-$requestId] $body');
|
||||||
|
|
||||||
// 2xx
|
// 2xx
|
||||||
@@ -217,14 +218,14 @@ class CRUD {
|
|||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
String token = _getJwt();
|
String token = await _getJwt();
|
||||||
|
|
||||||
// فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض
|
// فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض
|
||||||
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
||||||
_isRefreshingJWT = true;
|
_isRefreshingJWT = true;
|
||||||
try {
|
try {
|
||||||
await Get.put(LoginDriverController()).getJWT();
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
token = _getJwt();
|
token = await _getJwt();
|
||||||
} finally {
|
} finally {
|
||||||
_isRefreshingJWT = false;
|
_isRefreshingJWT = false;
|
||||||
}
|
}
|
||||||
@@ -233,7 +234,7 @@ class CRUD {
|
|||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
|
||||||
};
|
};
|
||||||
|
|
||||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
@@ -249,12 +250,12 @@ class CRUD {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
// فحص صلاحية التوكن قبل الإرسال
|
// فحص صلاحية التوكن قبل الإرسال
|
||||||
String token = _getJwt();
|
String token = await _getJwt();
|
||||||
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
if (!_isJwtValid(token) && !_isRefreshingJWT) {
|
||||||
_isRefreshingJWT = true;
|
_isRefreshingJWT = true;
|
||||||
try {
|
try {
|
||||||
await Get.put(LoginDriverController()).getJWT();
|
await Get.put(LoginDriverController()).getJWT();
|
||||||
token = _getJwt();
|
token = await _getJwt();
|
||||||
} finally {
|
} finally {
|
||||||
_isRefreshingJWT = false;
|
_isRefreshingJWT = false;
|
||||||
}
|
}
|
||||||
@@ -267,7 +268,7 @@ class CRUD {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(),
|
'X-Device-FP': await _getFpHeader(),
|
||||||
},
|
},
|
||||||
).timeout(const Duration(seconds: 60));
|
).timeout(const Duration(seconds: 60));
|
||||||
|
|
||||||
@@ -321,7 +322,7 @@ class CRUD {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $jwt',
|
'Authorization': 'Bearer $jwt',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
|
||||||
};
|
};
|
||||||
|
|
||||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
@@ -346,7 +347,7 @@ class CRUD {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $s',
|
'Authorization': 'Bearer $s',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
|
||||||
},
|
},
|
||||||
).timeout(const Duration(seconds: 60));
|
).timeout(const Duration(seconds: 60));
|
||||||
|
|
||||||
@@ -392,7 +393,7 @@ class CRUD {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $s',
|
'Authorization': 'Bearer $s',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
|
||||||
},
|
},
|
||||||
).timeout(const Duration(seconds: 60));
|
).timeout(const Duration(seconds: 60));
|
||||||
|
|
||||||
@@ -571,17 +572,17 @@ class CRUD {
|
|||||||
// ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ─────────
|
// ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ─────────
|
||||||
Future<void> sendEmail(String link, Map<String, String>? payload) async {
|
Future<void> sendEmail(String link, Map<String, String>? payload) async {
|
||||||
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
|
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
|
||||||
String token = _getJwt();
|
String token = await _getJwt();
|
||||||
|
|
||||||
if (!_isJwtValid(token)) {
|
if (!_isJwtValid(token)) {
|
||||||
await LoginDriverController().getJWT();
|
await LoginDriverController().getJWT();
|
||||||
token = _getJwt();
|
token = await _getJwt();
|
||||||
}
|
}
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
|
||||||
};
|
};
|
||||||
|
|
||||||
final request = http.Request('POST', Uri.parse(link));
|
final request = http.Request('POST', Uri.parse(link));
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ class EncryptionHelper {
|
|||||||
debugPrint("EncryptionHelper initialized successfully.");
|
debugPrint("EncryptionHelper initialized successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encrypts a string using AES-256-CBC with constant IV (deterministic)
|
||||||
|
/// Same input always produces the same output
|
||||||
|
String encryptDataCbc(String plainText) {
|
||||||
|
final cbcEncrypter =
|
||||||
|
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
||||||
|
final encrypted = cbcEncrypter.encrypt(plainText, iv: iv);
|
||||||
|
return encrypted.base64;
|
||||||
|
}
|
||||||
|
|
||||||
/// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV
|
/// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV
|
||||||
/// Format: "GCM:<base64_iv>:<base64_ciphertext>"
|
/// Format: "GCM:<base64_iv>:<base64_ciphertext>"
|
||||||
String encryptData(String plainText) {
|
String encryptData(String plainText) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:siro_driver/constant/box_name.dart';
|
|||||||
import 'package:siro_driver/constant/colors.dart';
|
import 'package:siro_driver/constant/colors.dart';
|
||||||
import 'package:siro_driver/constant/links.dart';
|
import 'package:siro_driver/constant/links.dart';
|
||||||
import 'package:siro_driver/controller/functions/crud.dart';
|
import 'package:siro_driver/controller/functions/crud.dart';
|
||||||
|
import 'package:siro_driver/controller/functions/encrypt_decrypt.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -16,7 +17,6 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import '../../constant/info.dart';
|
import '../../constant/info.dart';
|
||||||
import '../../main.dart';
|
import '../../main.dart';
|
||||||
import '../../print.dart';
|
import '../../print.dart';
|
||||||
import 'encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
Future<void> checkForUpdate(BuildContext context) async {
|
Future<void> checkForUpdate(BuildContext context) async {
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
@@ -168,13 +168,6 @@ void showUpdateDialog(BuildContext context) {
|
|||||||
|
|
||||||
class DeviceHelper {
|
class DeviceHelper {
|
||||||
static Future<String> getDeviceFingerprint() async {
|
static Future<String> getDeviceFingerprint() async {
|
||||||
await EncryptionHelper.initialize();
|
|
||||||
|
|
||||||
final cached = box.read(BoxName.deviceFingerprint);
|
|
||||||
if (cached != null && cached.toString().isNotEmpty) {
|
|
||||||
return cached.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||||
var deviceData;
|
var deviceData;
|
||||||
|
|
||||||
@@ -190,19 +183,20 @@ class DeviceHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String deviceId = Platform.isAndroid
|
final String deviceId = Platform.isAndroid
|
||||||
? deviceData['id'] ?? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
|
? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
|
||||||
: deviceData['identifierForVendor'] ?? 'unknown';
|
: deviceData['identifierForVendor'] ?? 'unknown';
|
||||||
|
|
||||||
final String deviceModel = deviceData['model'] ?? 'unknown';
|
final String deviceModel = deviceData['model'] ?? 'unknown';
|
||||||
|
|
||||||
final String fingerprint =
|
final String fingerprint = '${deviceId}_$deviceModel';
|
||||||
EncryptionHelper.instance.encryptData('${deviceId}_$deviceModel');
|
final String encryptedFp =
|
||||||
|
EncryptionHelper.instance.encryptDataCbc(fingerprint);
|
||||||
box.write(BoxName.deviceFingerprint, fingerprint);
|
box.write(BoxName.deviceFpEncrypted, encryptedFp);
|
||||||
return (fingerprint);
|
box.write(BoxName.deviceFingerprint, encryptedFp);
|
||||||
|
Log.print('fingerprint: $encryptedFp');
|
||||||
|
return encryptedFp;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error generating device fingerprint: $e');
|
throw Exception('Failed to generate device fingerprint');
|
||||||
throw Exception('Failed to generate device fingerprint: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,44 +38,9 @@ class AppInitializer {
|
|||||||
List<Map<String, dynamic>> links = [];
|
List<Map<String, dynamic>> links = [];
|
||||||
|
|
||||||
Future<void> initializeApp() async {
|
Future<void> initializeApp() async {
|
||||||
if (box.read(BoxName.jwt) == null) {
|
// getJWT() now internally checks flutter_secure_storage and validates the token
|
||||||
String? secureJwt = await storage.read(key: BoxName.jwt);
|
// before making any network requests.
|
||||||
if (secureJwt != null) {
|
|
||||||
box.write(BoxName.jwt, secureJwt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (box.read(BoxName.jwt) == null) {
|
|
||||||
await LoginDriverController().getJWT();
|
await LoginDriverController().getJWT();
|
||||||
} else {
|
|
||||||
String token =
|
|
||||||
r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0];
|
|
||||||
bool isTokenValid = false;
|
|
||||||
try {
|
|
||||||
final parts = token.split('.');
|
|
||||||
if (parts.length == 3) {
|
|
||||||
String payload = parts[1];
|
|
||||||
switch (payload.length % 4) {
|
|
||||||
case 2:
|
|
||||||
payload += '==';
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
payload += '=';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
|
||||||
final exp = decoded['exp'];
|
|
||||||
if (exp != null) {
|
|
||||||
isTokenValid =
|
|
||||||
DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
if (!isTokenValid) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// await getKey();
|
// await getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import '../../functions/background_service.dart';
|
|||||||
import '../../functions/crud.dart';
|
import '../../functions/crud.dart';
|
||||||
import '../../functions/location_background_controller.dart';
|
import '../../functions/location_background_controller.dart';
|
||||||
import '../../functions/location_controller.dart';
|
import '../../functions/location_controller.dart';
|
||||||
|
import '../../functions/package_info.dart';
|
||||||
import '../payment/captain_wallet_controller.dart';
|
import '../payment/captain_wallet_controller.dart';
|
||||||
|
|
||||||
class HomeCaptainController extends GetxController {
|
class HomeCaptainController extends GetxController {
|
||||||
@@ -507,6 +508,7 @@ class HomeCaptainController extends GetxController {
|
|||||||
|
|
||||||
// late SiroMapController mapHomeCaptainController;
|
// late SiroMapController mapHomeCaptainController;
|
||||||
IntaleqMapController? mapHomeCaptainController;
|
IntaleqMapController? mapHomeCaptainController;
|
||||||
|
CameraPosition? currentCameraPosition;
|
||||||
LatLng? _lastCameraLoc; // لتتبع آخر موقع حرك الكاميرا
|
LatLng? _lastCameraLoc; // لتتبع آخر موقع حرك الكاميرا
|
||||||
|
|
||||||
// --- FIX 2: Smart Map Creation ---
|
// --- FIX 2: Smart Map Creation ---
|
||||||
@@ -740,7 +742,7 @@ class HomeCaptainController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToken() async {
|
addToken() async {
|
||||||
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
|
String? fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
final payload = {
|
final payload = {
|
||||||
'token': (box.read(BoxName.tokenDriver)),
|
'token': (box.read(BoxName.tokenDriver)),
|
||||||
'captain_id': (box.read(BoxName.driverID)).toString(),
|
'captain_id': (box.read(BoxName.driverID)).toString(),
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import 'views/home/Captin/orderCaptin/order_request_page.dart';
|
|||||||
import 'views/home/Captin/driver_map_page.dart';
|
import 'views/home/Captin/driver_map_page.dart';
|
||||||
import 'controller/profile/setting_controller.dart';
|
import 'controller/profile/setting_controller.dart';
|
||||||
import 'controller/voice_call_controller.dart';
|
import 'controller/voice_call_controller.dart';
|
||||||
|
import 'controller/functions/tts.dart';
|
||||||
|
|
||||||
final box = GetStorage();
|
final box = GetStorage();
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
@@ -547,6 +548,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final LocaleController localController = Get.put(LocaleController());
|
final LocaleController localController = Get.put(LocaleController());
|
||||||
final SettingController settingController = Get.put(SettingController());
|
final SettingController settingController = Get.put(SettingController());
|
||||||
|
Get.put(TextToSpeechController(), permanent: true);
|
||||||
|
|
||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
|
|||||||
@@ -155,7 +155,8 @@ class HomeCaptain extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final pickerPos = homeCaptainController.mapHomeCaptainController?.cameraPosition?.target;
|
final pickerPos = homeCaptainController.currentCameraPosition?.target ??
|
||||||
|
homeCaptainController.mapHomeCaptainController?.cameraPosition?.target;
|
||||||
if (pickerPos != null) {
|
if (pickerPos != null) {
|
||||||
destCtrl.isSelectingDestinationOnMap = false;
|
destCtrl.isSelectingDestinationOnMap = false;
|
||||||
destCtrl.update();
|
destCtrl.update();
|
||||||
@@ -403,6 +404,9 @@ class _MapView extends StatelessWidget {
|
|||||||
myLocationEnabled: false,
|
myLocationEnabled: false,
|
||||||
compassEnabled: false,
|
compassEnabled: false,
|
||||||
zoomControlsEnabled: false,
|
zoomControlsEnabled: false,
|
||||||
|
onCameraMove: (CameraPosition position) {
|
||||||
|
ctrl.currentCameraPosition = position;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,13 +77,8 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
tooltip: 'Active Ride'.tr,
|
tooltip: 'Active Ride'.tr,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await checkForPendingOrderFromServer();
|
await checkForPendingOrderFromServer();
|
||||||
if (box.read(BoxName.rideArgumentsFromBackground) !=
|
if (box.read(BoxName.rideArgumentsFromBackground) ==
|
||||||
'failure') {
|
'failure') {
|
||||||
Get.to(
|
|
||||||
() => PassengerLocationMapPage(),
|
|
||||||
arguments: box.read(BoxName.rideArgumentsFromBackground),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog(
|
MyDialog().getDialog(
|
||||||
'Ride info'.tr,
|
'Ride info'.tr,
|
||||||
'you dont have accepted ride'.tr,
|
'you dont have accepted ride'.tr,
|
||||||
@@ -213,7 +208,7 @@ Future<void> checkForPendingOrderFromServer() async {
|
|||||||
);
|
);
|
||||||
Log.print('response: $response');
|
Log.print('response: $response');
|
||||||
|
|
||||||
if (response['status'] == 'success') {
|
if (response != 'failure') {
|
||||||
final Map<String, dynamic> orderInfo = response['message'];
|
final Map<String, dynamic> orderInfo = response['message'];
|
||||||
final Map<String, dynamic> rideArgs =
|
final Map<String, dynamic> rideArgs =
|
||||||
_transformServerDataToAppArguments(orderInfo);
|
_transformServerDataToAppArguments(orderInfo);
|
||||||
@@ -234,7 +229,7 @@ Future<void> checkForPendingOrderFromServer() async {
|
|||||||
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// silent
|
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
||||||
} finally {
|
} finally {
|
||||||
_isCheckingPendingOrder = false;
|
_isCheckingPendingOrder = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) {
|
|||||||
$rideId = $payload['id'] ?? null;
|
$rideId = $payload['id'] ?? null;
|
||||||
$lat = (float)($payload['start_lat'] ?? 0);
|
$lat = (float)($payload['start_lat'] ?? 0);
|
||||||
$lng = (float)($payload['start_lng'] ?? 0);
|
$lng = (float)($payload['start_lng'] ?? 0);
|
||||||
|
$endLat = isset($payload['end_lat']) ? (float)$payload['end_lat'] : null;
|
||||||
|
$endLng = isset($payload['end_lng']) ? (float)$payload['end_lng'] : null;
|
||||||
|
|
||||||
if (!$redis || !$rideId || $lat == 0 || $lng == 0) {
|
if (!$redis || !$rideId || $lat == 0 || $lng == 0) {
|
||||||
$connection->send('Error: Redis unavailable or invalid coords');
|
$connection->send('Error: Redis unavailable or invalid coords');
|
||||||
@@ -333,6 +335,20 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) {
|
|||||||
$count = 0;
|
$count = 0;
|
||||||
foreach ($nearbyDrivers as $driverId) {
|
foreach ($nearbyDrivers as $driverId) {
|
||||||
if (isset($connectedDrivers[$driverId])) {
|
if (isset($connectedDrivers[$driverId])) {
|
||||||
|
// Check if driver has a destination constraint in Redis
|
||||||
|
$profileKey = "driver:profile:$driverId";
|
||||||
|
$profile = $redis->hgetall($profileKey);
|
||||||
|
if ($profile && isset($profile['has_destination']) && $profile['has_destination'] == 1 && $endLat !== null && $endLng !== null) {
|
||||||
|
$driverDestLat = (float)($profile['destination_lat'] ?? 0);
|
||||||
|
$driverDestLng = (float)($profile['destination_lng'] ?? 0);
|
||||||
|
|
||||||
|
$destDistance = haversineDistance($endLat, $endLng, $driverDestLat, $driverDestLng);
|
||||||
|
// Filter out driver if destination is > 5km (5000 meters) away
|
||||||
|
if ($destDistance > 5000.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$io->to('driver_' . $driverId)->emit('market_new_ride', $payload);
|
$io->to('driver_' . $driverId)->emit('market_new_ride', $payload);
|
||||||
$count++;
|
$count++;
|
||||||
}
|
}
|
||||||
@@ -390,6 +406,37 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) {
|
|||||||
$connection->send('Driver not connected');
|
$connection->send('Driver not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 6. Update Driver Destination ──────────────────────
|
||||||
|
} elseif ($action === 'update_driver_destination') {
|
||||||
|
$driverId = $post['driver_id'] ?? null;
|
||||||
|
$hasDest = isset($post['has_destination']) ? intval($post['has_destination']) : 0;
|
||||||
|
|
||||||
|
if (!$driverId || !$redis) {
|
||||||
|
$connection->send('Error: Missing driver_id or Redis unavailable');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileKey = "driver:profile:$driverId";
|
||||||
|
if ($hasDest === 1) {
|
||||||
|
$destLat = $post['destination_lat'] ?? '';
|
||||||
|
$destLng = $post['destination_lng'] ?? '';
|
||||||
|
$destName = $post['destination_name'] ?? '';
|
||||||
|
|
||||||
|
$redis->hmset($profileKey, [
|
||||||
|
'has_destination' => 1,
|
||||||
|
'destination_lat' => $destLat,
|
||||||
|
'destination_lng' => $destLng,
|
||||||
|
'destination_name' => $destName
|
||||||
|
]);
|
||||||
|
$redis->expire($profileKey, 86400); // 24 Hours
|
||||||
|
logMsg("🎯 Destination set for Driver #$driverId: $destName ($destLat, $destLng)");
|
||||||
|
} else {
|
||||||
|
$redis->hmset($profileKey, ['has_destination' => 0]);
|
||||||
|
$redis->hdel($profileKey, ['destination_lat', 'destination_lng', 'destination_name']);
|
||||||
|
logMsg("🎯 Destination cleared for Driver #$driverId");
|
||||||
|
}
|
||||||
|
$connection->send('OK');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$connection->send('Unknown action');
|
$connection->send('Unknown action');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user