# إضافة إشعارات المتصفح إلى PlusCash

**إعداد: Manus AI**

## النتيجة المنفذة

تمت إضافة **Web Push حقيقي** إلى المشروع، وليس مجرد إشعار JavaScript يعمل أثناء فتح الصفحة. بعد أن يسجل المستخدم دخوله لأول مرة، تظهر له نافذة صغيرة تشرح فائدة الإشعارات. عند الضغط على **Enable** يظهر طلب الإذن الأصلي الخاص بالمتصفح، ثم يُحفظ اشتراك الجهاز مرتبطًا بحساب المستخدم.

تستطيع Push API تشغيل عامل الخدمة واستقبال رسالة الخادم حتى إذا لم يكن الموقع في الواجهة أو لم تكن صفحته محملة حاليًا.[1] ويشترط المتصفح أن يمنح المستخدم الإذن، كما توصي المتصفحات بطلب هذا الإذن بعد تفاعل مباشر مثل الضغط على زر؛ لذلك استُخدمت نافذة تمهيدية بدل محاولة فتح طلب الإذن تلقائيًا عند تحميل الصفحة.[2]

| الحدث | وقت الإرسال | الصفحة المفتوحة عند النقر |
|---|---|---|
| عرض تم اعتماده فورًا من postback | بعد إضافة النقاط وإنشاء الإشعار الداخلي | `/history` |
| عرض كان معلقًا ثم انتهت مدة التعليق | بعد تحديث الرصيد والخبرة وحالة العرض في المهمة الدورية | `/history` |
| طلب سحب تم دفعه بنجاح | بعد نجاح مزود الدفع وتحديث الطلب إلى مدفوع | `/withdraw` |

## خطوات التركيب

ابدأ بأخذ نسخة احتياطية من ملفات الموقع وقاعدة البيانات. بعد ذلك ارفع محتويات حزمة التعديل إلى **جذر المشروع نفسه** مع المحافظة على أسماء المجلدات، ووافق على استبدال الملفات المعدلة. الحزمة تحتوي مكتبة Web Push كاملة داخل المشروع، ولذلك لا تحتاج إلى تشغيل Composer على الاستضافة.

ثم افتح phpMyAdmin واختر قاعدة بيانات PlusCash، وبعدها استورد الملف التالي مرة واحدة:

```text
database/browser_push_notifications.sql
```

ينشئ الملف جدول `push_subscriptions` فقط ولا يحذف أو يغير بيانات المستخدمين أو العروض أو السحوبات الموجودة.

يجب أن يعمل الموقع عبر **HTTPS**؛ لأن عوامل الخدمة وطلب إذن الإشعارات متاحان في السياقات الآمنة فقط، باستثناء `localhost` أثناء التطوير.[2] [3] كما يجب أن تكون إضافات PHP التالية مفعلة: `curl` و`mbstring` و`openssl`. مكتبة الإرسال المضمنة هي `minishlink/web-push` من سلسلة `v7` المتوافقة مع PHP 7.4.[4]

| فحص ما بعد الرفع | النتيجة المطلوبة |
|---|---|
| فتح `https://pluscash.live/push-sw.js` | يظهر كود JavaScript ولا تظهر صفحة 404 أو صفحة HTML |
| تسجيل الدخول بحساب مستخدم | تظهر نافذة **Turn on notifications** بعد وقت قصير |
| الضغط على **Enable** | يظهر طلب الإذن الأصلي من المتصفح |
| فحص جدول `push_subscriptions` | يظهر صف جديد مرتبط بـ `user_id` للمستخدم |
| إتمام عرض أو سحب تجريبي | يظهر إشعار النظام ويُفتح المسار الصحيح عند النقر |

## آلية العمل داخل المشروع

كل عرض يتم اعتماده مباشرةً يمر أصلًا عبر `M_core::addNotification()`. تم توسيع هذه النقطة المركزية لكي ترسل Push عندما يكون النص خاصًا باعتماد عرض، ولذلك تغطي جميع مزودي العروض الموجودين في `Wh.php` من دون تكرار كود الإرسال في كل postback.

العروض ذات مدة التعليق لا تنشئ إشعار الاعتماد عند وصول postback، لأنها لم تدخل الرصيد بعد. لهذا أضيف إرسال مستقل داخل `Cronjob.php` بعد انتقال العرض إلى الحالة المعتمدة وإضافة قيمته إلى الرصيد. أما السحب، فلا يُرسل إشعاره لمجرد وجود الطلب؛ بل بعد نجاح الدفع واستدعاء المسار الحالي الذي يغير حالته إلى مدفوع.

| الملف | التعديل |
|---|---|
| `application/models/M_core.php` | ربط الإشعارات الداخلية بأحداث Web Push المطلوبة |
| `application/controllers/Cronjob.php` | إشعار اعتماد العروض بعد انتهاء مدة التعليق |
| `application/controllers/Push_notifications.php` | حفظ وإلغاء اشتراك الجهاز وإرجاع المفتاح العام |
| `application/models/M_push_notification.php` | إدارة اشتراكات المستخدمين متعددة الأجهزة |
| `application/libraries/Push_service.php` | توقيع VAPID والإرسال والتنظيف التلقائي للاشتراكات المنتهية |
| `application/config/push_notifications.php` | إعدادات Push ومفاتيح VAPID الخاصة بهذا المشروع |
| `application/config/routes.php` | مسارات الاشتراك والإلغاء والإعدادات |
| `application/views/user_template/template.php` | تحميل عميل الإشعارات للمستخدم المسجل |
| `assets/js/vie/push-notifications.js` | نافذة التفعيل وتسجيل عامل الخدمة والاشتراك |
| `push-sw.js` | استقبال Push وإظهار الإشعار وفتح الصفحة عند النقر |
| `application/third_party/webpush/` | مكتبة الإرسال وتبعياتها الجاهزة |
| `database/browser_push_notifications.sql` | جدول الاشتراكات والفهارس |

## مفاتيح VAPID والأمان

تم إنشاء زوج مفاتيح VAPID ثابت وفريد ووضعه في `application/config/push_notifications.php`. المفتاح العام يُرسل للمتصفح، بينما يجب أن يبقى المفتاح الخاص على الخادم فقط. لا تغيّر المفتاحين بعد بدء اشتراك المستخدمين؛ لأن تغييرهما قد يجعل الاشتراكات القديمة غير قابلة للاستخدام.

نقطة حفظ الاشتراك لا تقبل إلا مستخدمًا مسجلًا، وتتحقق من أن الطلب `POST` وبصيغة JSON ومن نفس أصل الموقع. كما لا يُستخدم endpoint الذي يرسله المتصفح في أي إعادة توجيه، وتُحصر روابط النقر داخل نطاق الموقع. تؤكد وثائق Push API أهمية حماية إدارة الاشتراكات من CSRF/XSRF والمحافظة على سرية endpoint.[1]

فشل خدمة Push لا يلغي زيادة نقاط العرض ولا يعكس نجاح السحب؛ إذ يتم تسجيل خطأ الإرسال في سجل CodeIgniter ثم يستمر المسار المالي. الاشتراك الذي ينتهي يُحذف، والاشتراك الذي يفشل بشكل مؤقت يُعطل بعد ثلاث محاولات متتالية.

## اختبار السيناريوهات

بعد الاستيراد والرفع، سجل الدخول من Chrome أو Edge في نافذة عادية، واضغط **Enable** ثم اختر **Allow** من المتصفح. أكمل عرضًا تجريبيًا من أحد مزودي العروض أو استخدم بيئة الاختبار الرسمية للمزود إن كانت متاحة. بعد اعتماد النقاط يجب أن يظهر إشعار بعنوان **Offer points credited**، مع استمرار ظهور السجل الداخلي المعتاد داخل الموقع.

لاختبار السحب، أنشئ طلبًا تجريبيًا بالطريقة المعتادة ثم نفذ الدفع من لوحة الإدارة. يجب أن يصل إشعار **Withdrawal paid** فقط عندما ينجح مزود الدفع وتصبح حالة الطلب مدفوعة. أما العرض المعلق، فيظهر إشعاره عند تشغيل المهمة الدورية بعد حلول `available_at`.

## استكشاف المشكلات

| المشكلة | الإجراء |
|---|---|
| المستخدم ضغط Block سابقًا | افتح إعدادات الموقع في المتصفح، أعد الإذن إلى Allow، ثم حدّث الصفحة |
| النافذة لا تظهر | تأكد من تسجيل الدخول وHTTPS، وامسح قيمة `pluscash_push_prompt_dismissed_until` من Local Storage إذا تم اختيار Not now |
| `push-sw.js` يعرض HTML | تأكد من رفعه في جذر الموقع بجانب `index.php` ومن أن `.htaccess` يسمح بالملفات الموجودة |
| لا يُحفظ صف في الجدول | تأكد من استيراد ملف SQL ومن أن مسارات `/push-notifications/*` تعمل |
| لا يصل الإشعار مع وجود الاشتراك | راجع `application/logs/` وتأكد من تفعيل إضافات PHP المطلوبة وعدم حظر اتصالات HTTPS الخارجية |
| وصل الإشعار الداخلي فقط | راجع حالة `is_active` و`failure_count` في `push_subscriptions`، ثم اختبر من متصفح آخر |

> **ملاحظة:** ملفات الصور والأصول التي حُذفت من النسخة المرسلة ليست مطلوبة لتنفيذ المنطق. عامل الخدمة يستخدم `/logo.png` كأيقونة إذا كان الملف موجودًا على الموقع الفعلي، وإن لم يوجد يستمر الإشعار ويستخدم المتصفح المظهر الافتراضي.

## References

[1]: https://developer.mozilla.org/en-US/docs/Web/API/Push_API "MDN Web Docs — Push API"
[2]: https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API "MDN Web Docs — Using the Notifications API"
[3]: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API "MDN Web Docs — Service Worker API"
[4]: https://packagist.org/packages/minishlink/web-push "Packagist — minishlink/web-push"
