دليل ربط فحص حالة التطبيق
تم تحديث آلية التعامل مع "وضع الصيانة" و"إجبار التحديث" لتصبح أكثر احترافية، بحيث لا تتسبب في مسح التوكن الخاص بالمستخدم (Logout) بشكل مفاجئ.
يعتمد النظام الجديد على:
- فحص مبدئي (Init API) عند فتح التطبيق (Splash Screen).
- شبكة أمان (Global Interceptor) تلتقط أي تغيرات في حالة التطبيق أثناء استخدامه في الخلفية دون إغلاقه.
1. الفحص المبدئي (Init API - عند تشغيل التطبيق)
يجب على التطبيق استدعاء هذا الرابط مرة واحدة عند بدء التشغيل (مثلاً في شاشة الـ Splash Screen):
GET /api/v1/app-status
المتطلبات (تُرسل في الـ Headers أو الـ Query Params):
Client-Type: نوع الجهاز (androidأوiosأوflutter)Client-Version: إصدار التطبيق المثبت (مثال:1.85.1)Accept-Languageأوlang: لغة التطبيق الحالية (arأوen)
شكل الاستجابة (Response):
الكود المسترجع هنا سيكون 200 OK، وشكل البيانات كالتالي (في حالة الصيانة):
{
"status": "success",
"type": "maintenance",
"data": {
"title": "تحت الصيانة",
"message": "نعمل على تحسين النظام حالياً. يرجى المحاولة لاحقاً."
}
}
ملاحظة هامة: إذا لم تكن هناك صيانة، وكان هناك تحديث متوفر، سيكون شكل البيانات هكذا:
{
"status": "success",
"type": "update",
"data": {
"is_force": true,
"version": "1.86.0",
"store_link": "https://play.google.com/store/apps/details?id=...",
"title": "تحديث التطبيق",
"message": "يتوفر تحديث جديد قم بتحديث التطبيق لتجربة أفضل."
}
}
(إذا لم يكن هناك صيانة أو تحديث، سيكون الـ type عبارة عن "none" و data كائن فارغ {}).
طريقة تعامل واجهة الموبايل (UI Logic):
- إذا كان
type == 'maintenance': اعرض شاشة الصيانة باستخدام تفاصيل الـdataالمرفقة وامنع المستخدم من الدخول. (الصيانة لها الأولوية القصوى دائماً). - إذا كان
type == 'update': اعرض نافذة التحديث (Pop-up) بالعنوان والنص المرفق في الـdata. - في حالة التحديث، إذا كان
data.is_force == true: قم بإخفاء زر الإغلاق/التخطي من نافذة التحديث (لإجبار المستخدم على التحديث).
2. شبكة الأمان (Global Interceptor - أثناء استخدام التطبيق)
السيناريو: ماذا لو ترك المستخدم التطبيق مفتوحاً في الخلفية لساعات، وتم تفعيل "وضع الصيانة" أو "إجبار التحديث" فجأة من لوحة التحكم؟
الحل وسلوك النظام:
- في حالة "التحديث الاختياري": الـ APIs لن تتوقف ولن تُرجع رسالة تحديث. الموبايل سيعرف بالتحديث مرة واحدة فقط عند فتح التطبيق (عبر مناداة الرابط الأول
/app-status). - في حالة "التحديث الإجباري": كل الـ APIs في التطبيق ستقوم بإيقاف طلب المستخدم فوراً وتُرجع له كود
426 Upgrade Requiredمع بيانات التحديث في الـ Body. هذا يضمن أنه لو حاول المستخدم إرسال رسالة، السيرفر سيرفض ويوجه الموبايل لإظهار شاشة الإجبار. - في حالة "الصيانة": كل الـ APIs بلا استثناء ستتوقف فوراً وتُرجع كود
503 Service Unavailableمع بيانات الصيانة في الـ Body.
المطلوب من مبرمج الموبايل:
تحديث الـ Interceptor العام لشبكة الاتصال بحيث إذا كان الكود المسترجع 503 أو 426:
- لا تقم بمسح التوكن (لا تسجل خروج المستخدم).
- قم بقراءة جسم الاستجابة (JSON Response) بنفس هيكل بيانات
app-status. - اعرض شاشة الصيانة أو نافذة التحديث فوراً بناءً على البيانات القادمة من السيرفر.
مثال تقريبي لمطوري Flutter (باستخدام Dio):
dio.interceptors.add(InterceptorsWrapper(
onError: (DioError e, handler) {
// اصطياد الصيانة والتحديث الإجباري
if (e.response?.statusCode == 503 || e.response?.statusCode == 426) {
// 1. استخراج بيانات الصيانة والتحديث
var data = e.response?.data['data'];
// 2. إظهار نافذة الصيانة أو التحديث دون مسح التوكن
showAppStatusDialog(data);
// 3. منع الخطأ من التسبب في Crash لباقي التطبيق
return handler.reject(e);
}
// معالجة الأخطاء الأخرى مثل 401 و 440 (وهي التي نمسح فيها التوكن كالمعتاد)
if (e.response?.statusCode == 401 || e.response?.statusCode == 440) {
logoutUser();
}
return handler.next(e);
}
));
ملاحظات لمطوري Android و iOS (Retrofit / Alamofire):
يجب التقاط الأكواد 503 و 426 في الـ AuthInterceptor أو الـ OkHttpClient Interceptor قبل أن يقوم منشئ الـ Data Class (مثل GSON أو Moshi) بمحاولة تحويل الرد إلى الـ Model المطلوب للواجهة، لتجنب حدوث Parsing Exception.