Featured image of post إمكانيات وتنفيذ تطبيقات الويب التقدمية (PWA) (قوة Service Worker)

إمكانيات وتنفيذ تطبيقات الويب التقدمية (PWA) (قوة Service Worker)

شرح شامل لتطبيقات الويب التقدمية (PWA)، بدءًا من النظرة العامة، مرورًا بدورة حياة عامل الخدمة (Service Worker)، والتخزين المؤقت دون اتصال (Offline Cache)، وصولاً إلى إشعارات الدفع (Push Notifications).

1. مقدمة: ما هي تطبيقات الويب التقدمية (PWA)؟

شهدت تقنيات الويب تطوراً جذرياً خلال العقود الماضية. بدءًا من مجموعة روابط لمستندات HTML ثابتة، مرورًا بمعالجة DOM الديناميكية، والاتصالات غير المتزامنة باستخدام Ajax، وظهور تطبيقات الصفحة الواحدة (SPA)، وصولاً إلى إمكانية بناء تطبيقات تقدم تجربة مستخدم (UX) تضاهي أو حتى تتفوق على التطبيقات الأصلية (Native Apps). في طليعة هذا التطور تقع ** تطبيقات الويب التقدمية (PWA) **.

تطبيقات الويب التقدمية (PWA)، باختصار، هي “تطبيقات ويب تجمع بين سهولة الوصول للويب والأداء العالي وتجربة المستخدم الممتازة للتطبيقات الأصلية”. في تطبيقات الويب التقليدية، كان من المعتاد رؤية شاشة خطأ (مثل لعبة الديناصور الشهيرة في Chrome) عند الوصول إليها دون اتصال بالإنترنت. ولكن، من خلال التطبيق السليم لتقنيات PWA، يصبح من الممكن تشغيل التطبيق حتى في وضع عدم الاتصال، تصفح المحتوى المخزن مؤقتاً، وتنفيذ عمليات مزامنة البيانات في الخلفية.

في هذه المقالة، سنقدم شرحاً شاملاً وتفصيلياً لتطبيقات الويب التقدمية (PWA)، بدءاً من نظرة عامة، مروراً بدورة حياة ** عامل الخدمة (Service Worker) ** الذي يمثل جوهرها، واستراتيجيات التخزين المؤقت المتقدمة، والتكامل مع IndexedDB، وصولاً إلى التطلعات المستقبلية.


2. التطبيقات الأصلية (Native Apps) مقابل PWA

عند تطوير تطبيق ويب، دائماً ما يُثار نقاش حول “هل يجب اعتماد تطبيق أصلي أم PWA؟”. الفهم العميق لمزايا وعيوب كليهما يمكّنك من اختيار التكنولوجيا الأنسب لمشروعك.

2.1. نقاط قوة وضعف التطبيقات الأصلية

تتمثل نقطة القوة الأكبر للتطبيقات الأصلية (التطبيقات المطورة باستخدام Swift/Objective-C لنظام iOS، و Kotlin/Java لنظام Android، وغيرها) في امتلاكها وصولاً كاملاً إلى واجهات برمجة التطبيقات (API) الخاصة بنظام التشغيل. يتيح ذلك تنفيذ وظائف متقدمة تستفيد بالكامل من الكاميرا، و GPS، و Bluetooth، و NFC، والمستشعرات المختلفة. كما أنها محسنة لنظام التشغيل، مما يوفر أداء رسم عالٍ جداً، ويجعل التطبيقات الأصلية تتفوق بشكل ساحق في الألعاب التي تستخدم الكثير من الرسوم المتحركة المعقدة والرسومات ثلاثية الأبعاد (3D).

من ناحية أخرى، تعاني التطبيقات الأصلية من نقاط الضعف (التحديات) الرئيسية التالية:

  • ** تكاليف التطوير والتعلم ** : تحتاج إلى الحفاظ على قواعد بيانات برمجية (Codebases) منفصلة لكل من iOS و Android (يمكن التخفيف من ذلك باستخدام أطر عمل عبر المنصات مثل React Native و Flutter، لكنه لا يلغيها تماماً).
  • ** فحص متجر التطبيقات ** : لا يمكنك إطلاق التطبيق دون اجتياز فحص متجر Apple (App Store) أو Google Play، وقد يكون هناك انتظار لعدة أيام عند إجراء التحديثات.
  • ** عوائق اكتساب المستخدمين ** : تعتبر عملية فتح متجر التطبيقات، البحث، التنزيل، ثم التثبيت، عائقاً كبيراً (احتكاكاً) بالنسبة للمستخدم.

2.2. التحديات التي تحلها PWA

تهدف PWA إلى الاستفادة من نقاط قوة الويب مع التغلب على نقاط ضعف التطبيقات الأصلية.

  • ** مصدر واحد لاستخدامات متعددة ** : قاعدة برمجية واحدة مطورة بتقنيات الويب القياسية (HTML، CSS، JavaScript) تعمل على جميع الأجهزة المزودة بمتصفح (الهواتف المحمولة، الأجهزة اللوحية، أجهزة الكمبيوتر المكتبية).
  • ** تحديثات فورية دون الحاجة لفحص ** : نظراً لأن PWA هي مجرد مواقع ويب، فلا حاجة لاجتياز فحص متجر التطبيقات. من خلال تحديث الملفات على الخادم فقط، يمكن للمستخدمين استخدام أحدث إصدار دائماً.
  • ** تجربة سلسة دون تثبيت ** : يمكن للمستخدمين البدء في استخدام التطبيق بمجرد الوصول إلى عنوان URL. إذا أعجبهم، يمكنهم “إضافته إلى الشاشة الرئيسية (Install)” وتشغيله من أيقونة التطبيق تماماً كالتطبيق الأصلي.
  • ** قابلية المشاركة عبر الروابط ** : تعد القدرة على مشاركة شاشة أو حالة معينة كعنوان URL سلاحاً قوياً تنفرد به الويب.

بالطبع، PWA لها قيود أيضاً. خاصة في بيئة iOS (Safari)، تأخر تنفيذ واجهات برمجة تطبيقات الويب بسبب سياسات Apple، وكان دعم إشعارات الدفع (Push Notifications) غير كافٍ حتى وقت قريب، وهناك قيود صارمة على العمل في الخلفية. ومع ذلك، في السنوات الأخيرة، عمل متصفح Safari أيضاً على تعزيز دعم PWA، وتقلصت هذه الفجوة تدريجياً.


3. الركائز الثلاث التي تتكون منها PWA

لتحقيق PWA، فإن العناصر التقنية الرئيسية الثلاثة التالية مطلوبة:

3.1. HTTPS (الاتصال الآمن)

لا تعمل ميزات PWA القوية (Service Worker، إشعارات الدفع، تحديد الموقع الجغرافي، إلخ) إلا في بيئة ** HTTPS ** لأسباب أمنية (يُسمح بـ localhost كاستثناء لأنه بيئة تطوير محلية). هذا لمنع الأطراف الثالثة الخبيثة من العبث بهذه الميزات أو إساءة استخدامها من خلال هجمات الوسيط (Man-in-the-Middle) وغيرها.

3.2. بيان تطبيق الويب (Web App Manifest)

بيان تطبيق الويب ( manifest.json ) هو ملف JSON يوفر بيانات وصفية (Metadata) حول تطبيق الويب للمتصفح. من خلال هذا، يتم تحديد أيقونة التطبيق، اسمه، لون السمة، وضع العرض، وما إلى ذلك، ويتحكم في المظهر الشبيه بالتطبيق الأصلي عند تثبيته على الجهاز.

3.3. عامل الخدمة (Service Worker)

عامل الخدمة هو العصا السحرية التي ترتقي بـ PWA من مجرد موقع ويب إلى “تطبيق”. وهو بيئة JavaScript ينفذها المتصفح في الخلفية، ويعمل في سلسلة عمليات (Thread) منفصلة عن صفحة الويب. يمكنه اعتراض (Proxy) طلبات الشبكة، إدارة التخزين المؤقت، وتلقي إشعارات الدفع.


4. الإعدادات التفصيلية لبيان تطبيق الويب (Web App Manifest)

بيان تطبيق الويب هو ملف إعداد يُعد بمثابة واجهة PWA. فهو يحدد المظهر والسلوك عند قيام المستخدم بتثبيت التطبيق.

فيما يلي مثال لإعداد manifest.json نموذجي:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "name": "Progressive Web App Example",
  "short_name": "PWA Example",
  "description": "A comprehensive example of a Progressive Web App.",
  "start_url": "/?source=pwa",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0055ff",
  "icons": [
    {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "orientation": "portrait",
  "scope": "/"
}

شرح الخصائص الرئيسية

  • name و short_name : الاسم الذي يُعرض في مطالبة التثبيت وأسفل أيقونة التطبيق على الشاشة الرئيسية. يتم إعطاء الأولوية لـ short_name على الشاشة الرئيسية حيث تكون المساحة محدودة.
  • start_url : عنوان URL الذي يتم تحميله أولاً عندما يقوم المستخدم بتشغيل التطبيق من أيقونة الشاشة الرئيسية. من خلال إضافة معلمة تتبع (Tracking Parameter) (مثال: ?source=pwa )، يصبح من الممكن تمييز الوصول من PWA في أدوات تحليل الوصول.
  • display : يحدد وضع عرض التطبيق.
    • standalone : يُخفي واجهة المستخدم الخاصة بالمتصفح (شريط URL وزر الرجوع وما إلى ذلك) بالكامل ويعرضه كأنه تطبيق أصلي. هذا هو الإعداد الموصى به بشدة.
    • fullscreen : يستخدم الشاشة بأكملها، بل ويخفي شريط الحالة (مثالي للألعاب وتطبيقات الفيديو).
    • minimal-ui : يعرض فقط واجهة مستخدم التنقل الأساسية.
    • browser : يعرضه كعلامة تبويب متصفح عادية.
  • theme_color و background_color : يحدد لون سمة التطبيق ولون خلفية شاشة البداية (Splash Screen) عند بدء التشغيل.
  • icons : مصفوفة من الصور المستخدمة كأيقونات للتطبيق. يوصى بتوفير أحجام متعددة (على الأقل 192x192 و 512x512) لدعم درجات دقة الأجهزة المختلفة. عن طريق تحديد purpose: "maskable" ، يمكنك تحسين قص الأيقونات على أنظمة مثل Android.

5. جوهر عامل الخدمة (Service Worker) ودورة حياته

يجب أن يُطلق على عامل الخدمة “قلب” PWA. على عكس JavaScript الذي يتم تنفيذه داخل صفحات الويب التقليدية، فإنه لا يملك حق الوصول إلى DOM. بدلاً من ذلك، فإنه يتوسط طلبات الشبكة، ويعالج التخزين المؤقت، ويدير عمليات المزامنة في الخلفية.

5.1. دورة حياة عامل الخدمة

يتمتع عامل الخدمة بدورة حياة فريدة مستقلة عن الصفحة. الفهم الدقيق لدورة الحياة هذه هو المفتاح لمنع مشاكل التخزين المؤقت غير المتوقعة (مثل عدم تغير الشاشة رغم التحديث).

يوضح مخطط Mermaid التالي انتقال حالة عامل الخدمة:

  stateDiagram-v2
    direction TB
    "تم التحليل (Parsed)" --> "قيد التثبيت (Installing)" : "التسجيل (Registration)"
    "قيد التثبيت (Installing)" --> "تم التثبيت (Waiting)" : "نجاح (Success)"
    "قيد التثبيت (Installing)" --> "مستغنى عنه (Redundant)" : "خطأ (Error)"
    "تم التثبيت (Waiting)" --> "قيد التفعيل (Activating)" : "إغلاق جميع العملاء / skipWaiting()"
    "قيد التفعيل (Activating)" --> "مُفعل (Activated)" : "نجاح (Success)"
    "قيد التفعيل (Activating)" --> "مستغنى عنه (Redundant)" : "خطأ (Error)"
    "مُفعل (Activated)" --> "مستغنى عنه (Redundant)" : "استبدال بعامل خدمة جديد"
  1. ** تم التحليل (Parsed)** : الحالة التي ينتهي فيها المتصفح من تنزيل برنامج Service Worker النصي وتحليله.
  2. ** قيد التثبيت (Installing)** : الحالة التي يتم فيها إطلاق حدث install. تُستخدم هذه المرحلة بشكل أساسي للتخزين المؤقت المسبق (Pre-caching) للأصول الثابتة (HTML، CSS، JS، الصور، إلخ) الضرورية لتشغيل التطبيق. إذا فشل التثبيت (مثل فشل حفظ التخزين المؤقت)، يتم إتلاف Service Worker.
  3. ** تم التثبيت / قيد الانتظار (Installed / Waiting)** : اكتمل التثبيت، ولكنه ينتظر الاستبدال لأن Service Worker القديم لا يزال نشطاً في علامات تبويب أخرى. ينتقل إلى المرحلة التالية إما بإغلاق المستخدم لجميع علامات التبويب وإعادة فتحها، أو باستدعاء self.skipWaiting().
  4. ** قيد التفعيل (Activating)** : الحالة التي يتم فيها إطلاق حدث activate. تُستخدم هذه المرحلة بشكل أساسي لتنظيف التخزين المؤقت القديم غير الضروري الذي أنشأه Service Worker القديم.
  5. ** مُفعل (Activated)** : يعمل بالكامل وقادر على التحكم في أحداث fetch و push من الصفحة ومعالجتها.
  6. ** مستغنى عنه (Redundant)** : الحالة التي فشل فيها التثبيت، أو فشل التفعيل، أو تم استبداله بإصدار جديد من Service Worker.

5.2. تسجيل عامل الخدمة

لاستخدام Service Worker، يجب عليك أولاً إجراء عملية التسجيل من سلسلة عمليات JavaScript الرئيسية.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// main.js أو داخل علامة <script> في index.html
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/sw.js", { scope: "/" })
      .then((registration) => {
        console.log("نجاح تسجيل عامل الخدمة مع النطاق: ", registration.scope);
      })
      .catch((error) => {
        console.error("فشل تسجيل عامل الخدمة: ", error);
      });
  });
}

المهم هنا هو نطاق (Scope) عامل الخدمة. افتراضياً، يعترض فقط الطلبات ضمن الدليل الذي يقع فيه ملف Service Worker. بمعنى، إذا كان /sw.js، فيمكنه اعتراض الطلبات الموجهة إلى الموقع بأكمله /، أما إذا تم وضعه في /js/sw.js، فسيعترض فقط الطلبات ضمن /js/.


6. دليل شامل لاستراتيجيات التخزين المؤقت

أكبر ميزة لـ Service Worker هي القدرة على اعتراض طلبات الشبكة (حدث fetch) وتنفيذ استراتيجيات تخزين مؤقت مخصصة. يجب اختيار الاستراتيجية المناسبة بناءً على نوع المورد (صور، استجابات API، HTML) ومتطلبات التطبيق.

6.1. التخزين المؤقت أولاً (Cache First)

هذه هي الاستراتيجية الأساسية والأسرع. يتم التحقق من التخزين المؤقت أولاً، وإذا كان المورد موجوداً، يتم إرجاعه. وإذا لم يكن موجوداً، يتم جلبه من الشبكة، ويتم حفظ النتيجة في التخزين المؤقت. إنها مثالية للموارد الثابتة التي لا تتغير كثيراً مثل ملفات الصور والخطوط.

  flowchart TD
    "الصفحة (Page)" -->|"1. طلب (Request)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"2. تحقق من التخزين المؤقت (Check Cache)"| "التخزين المؤقت (Cache)"
    "التخزين المؤقت (Cache)" -->|"3a. إصابة التخزين المؤقت (Cache Hit)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"4a. استجابة (Response)"| "الصفحة (Page)"
    "التخزين المؤقت (Cache)" -->|"3b. خطأ في التخزين المؤقت (Cache Miss)"| "الشبكة (Network)"
    "الشبكة (Network)" -->|"4b. استجابة (Response)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"5b. حفظ في التخزين المؤقت (Save to Cache)"| "التخزين المؤقت (Cache)"
    "عامل الخدمة (Service Worker)" -->|"6b. استجابة (Response)"| "الصفحة (Page)"

6.2. الشبكة أولاً (Network First)

استراتيجية تعطي الأولوية دائماً للحصول على أحدث البيانات. يتم إرسال الطلب إلى الشبكة أولاً، وإذا نجح، يتم حفظ النتيجة في التخزين المؤقت وإعادتها إلى الصفحة. في حالة فشل اتصال الشبكة، كما هو الحال في وضع عدم الاتصال، فإنه يتراجع (Fallback) إلى التخزين المؤقت فقط. مناسبة لبيانات المقالات التي يتم تحديثها بشكل متكرر أو استجابات API.

  flowchart TD
    "الصفحة (Page)" -->|"1. طلب (Request)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"2. جلب (Fetch)"| "الشبكة (Network)"
    "الشبكة (Network)" -->|"3a. نجاح (Success)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"4a. حفظ في التخزين المؤقت (Save to Cache)"| "التخزين المؤقت (Cache)"
    "عامل الخدمة (Service Worker)" -->|"5a. استجابة (Response)"| "الصفحة (Page)"
    "الشبكة (Network)" -->|"3b. خطأ / غير متصل (Error / Offline)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"4b. تحقق من التخزين المؤقت (Check Cache)"| "التخزين المؤقت (Cache)"
    "التخزين المؤقت (Cache)" -->|"5b. إصابة التخزين المؤقت (Cache Hit)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"6b. استجابة التراجع (Fallback Response)"| "الصفحة (Page)"

6.3. القديم أثناء التحقق من الصحة (Stale-while-revalidate)

استراتيجية قوية وحديثة للغاية توازن بين السرعة والحداثة. عند حدوث طلب، يتم إرجاع التخزين المؤقت (البيانات القديمة / Stale) على الفور لرسم الشاشة بسرعة. وفي نفس الوقت (while-revalidate)، يتم إرسال طلب إلى الشبكة في الخلفية لجلب أحدث البيانات وتحديث التخزين المؤقت. سيرى المستخدم البيانات الأحدث في زيارته التالية.

  flowchart TD
    "الصفحة (Page)" -->|"1. طلب (Request)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"2. تحقق من التخزين المؤقت (Check Cache)"| "التخزين المؤقت (Cache)"
    "التخزين المؤقت (Cache)" -->|"3. إصابة التخزين المؤقت (Cache Hit)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -->|"4. إرجاع استجابة قديمة (Return Stale Response)"| "الصفحة (Page)"
    "عامل الخدمة (Service Worker)" -.->|"5. جلب في الخلفية (Fetch - Background)"| "الشبكة (Network)"
    "الشبكة (Network)" -.->|"6. استجابة الشبكة (Network Response)"| "عامل الخدمة (Service Worker)"
    "عامل الخدمة (Service Worker)" -.->|"7. تحديث التخزين المؤقت (Update Cache)"| "التخزين المؤقت (Cache)"

6.4. التخزين المؤقت فقط / الشبكة فقط (Cache Only / Network Only)

  • ** التخزين المؤقت فقط (Cache Only)** : يُرجع استجابة حصرياً من التخزين المؤقت. إذا لم يكن موجوداً، فإنه يعرض خطأ. يُستخدم هذا فقط لأصول معينة مضمون تنزيلها مسبقاً.
  • ** الشبكة فقط (Network Only)** : لا ينظر إلى التخزين المؤقت أبداً ويطلب دائماً من الشبكة. يُستخدم للاتصالات التي يجب عدم تخزينها مؤقتاً، مثل واجهات برمجة تطبيقات المصادقة أو طلبات POST.

7. مثال تطبيقي لعامل الخدمة (شرح تفصيلي للتعليمات البرمجية)

الآن، بناءً على دورة الحياة واستراتيجيات التخزين المؤقت المذكورة أعلاه، دعونا نلقي نظرة على مثال تطبيقي لملف sw.js (ملف عامل الخدمة).

7.1. حدث التثبيت والتخزين المؤقت المسبق

في حدث install، نقوم بتخزين هيكل التطبيق (HTML الأساسي، CSS، و JS) بشكل مسبق. هذا يسمح بعرض إطار التطبيق على الفور للزيارات اللاحقة أو عند عدم الاتصال بالإنترنت.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// sw.js
const CACHE_NAME = "pwa-cache-v1";
const PRECACHE_URLS = [
  "/",
  "/index.html",
  "/css/style.css",
  "/js/app.js",
  "/images/logo.png",
  "/offline.html"
];

self.addEventListener("install", (event) => {
  console.log("[عامل الخدمة] حدث التثبيت");
  
  // عن طريق استدعاء self.skipWaiting()، يتم تخطي حالة الانتظار والتفعيل الفوري.
  self.skipWaiting();

  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log("[عامل الخدمة] التخزين المؤقت المسبق للصفحات غير المتصلة");
      return cache.addAll(PRECACHE_URLS);
    })
  );
});

7.2. حدث التفعيل وتنظيف التخزين المؤقت

عند تغيير إصدار اسم التخزين المؤقت (مثلاً من pwa-cache-v1 إلى v2)، من الضروري حذف التخزين المؤقت القديم غير الضروري لتوفير مساحة التخزين. يتم ذلك في حدث activate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
self.addEventListener("activate", (event) => {
  console.log("[عامل الخدمة] حدث التفعيل");
  
  // self.clients.claim() يضع جميع الصفحات المفتوحة حالياً تحت السيطرة على الفور.
  event.waitUntil(self.clients.claim());

  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheName !== CACHE_NAME) {
            console.log("[عامل الخدمة] حذف التخزين المؤقت القديم:", cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

7.3. التعامل مع أحداث الجلب (Fetch)

هذا مثال متقدم لاعتراض حدث fetch وتبديل الاستراتيجيات بناءً على نوع مورد الطلب. الصور تستخدم استراتيجية التخزين المؤقت أولاً (Cache First)، بينما طلبات التنقل بتنسيق HTML تستخدم الشبكة أولاً (Network First) مع خطة تراجع (Fallback).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
self.addEventListener("fetch", (event) => {
  const request = event.request;
  const url = new URL(request.url);

  // تمرير طلبات POST أو الطلبات الموجهة إلى نطاقات خارجية إلى الشبكة مباشرة
  if (request.method !== "GET") return;

  // طلبات HTML (انتقالات الصفحة) تستخدم استراتيجية الشبكة أولاً + تراجع عند انقطاع الاتصال
  if (request.mode === "navigate" || request.headers.get("accept").includes("text/html")) {
    event.respondWith(
      fetch(request)
        .then((response) => {
          return caches.open(CACHE_NAME).then((cache) => {
            cache.put(request, response.clone());
            return response;
          });
        })
        .catch(() => {
          // عند حدوث خطأ في الشبكة (غير متصل)، احصل على الاستجابة من التخزين المؤقت، وإلا ارجع صفحة مخصصة لعدم الاتصال
          return caches.match(request).then((cachedResponse) => {
            return cachedResponse || caches.match("/offline.html");
          });
        })
    );
    return;
  }

  // الأصول الثابتة مثل الصور تستخدم استراتيجية التخزين المؤقت أولاً
  if (url.pathname.match(/\.(png|jpg|jpeg|gif|svg|css|js)$/)) {
    event.respondWith(
      caches.match(request).then((cachedResponse) => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(request).then((networkResponse) => {
          return caches.open(CACHE_NAME).then((cache) => {
            cache.put(request, networkResponse.clone());
            return networkResponse;
          });
        });
      })
    );
    return;
  }

  // تطبيق استراتيجية القديم أثناء التحقق من الصحة لطلبات واجهة برمجة التطبيقات (API) الأخرى
  event.respondWith(
    caches.match(request).then((cachedResponse) => {
      const fetchPromise = fetch(request).then((networkResponse) => {
        return caches.open(CACHE_NAME).then((cache) => {
          cache.put(request, networkResponse.clone());
          return networkResponse;
        });
      });
      // أرجع التخزين المؤقت أولاً إذا كان متاحاً، وواصل المعالجة في الخلفية. إذا لم يتوفر التخزين المؤقت، انتظر fetchPromise.
      return cachedResponse || fetchPromise;
    })
  );
});

8. التكامل مع IndexedDB: إدارة بيانات أكثر تقدماً

تعد واجهة برمجة تطبيقات caches (Cache Storage) التابعة لـ Service Worker مناسبة جداً لحفظ استجابات HTTP بأكملها (ملفات HTML، الصور، CSS، إلخ). ومع ذلك، قد تكون غير كافية لإدارة البيانات المهيكلة التي يتعامل معها التطبيق (استجابات API بتنسيق JSON، بيانات إعدادات المستخدم، أو البيانات النصية المنشورة أثناء عدم الاتصال، إلخ).

هنا يأتي دور IndexedDB.

IndexedDB هي قاعدة بيانات NoSQL تدعم العمليات المتزامنة (Transactional) ومدمجة في المتصفح. يمكنها تخزين كميات كبيرة جداً من البيانات وتدعم البحث المعقد عبر الفهارس.

8.1. لماذا لا يكفي التخزين المؤقت (Cache Storage) وحده؟

على سبيل المثال، لنفترض أنك أضفت مهمة جديدة في تطبيق ToDo أثناء عدم الاتصال بالإنترنت. في هذه الحالة، من الصعب حفظ “طلب POST لإضافة مهمة” نفسه في التخزين المؤقت. في المتطلبات التي تحتاج إلى حفظ الإجراءات أثناء عدم الاتصال وإعادة إرسالها عند استعادة الاتصال بالإنترنت، هناك حاجة إلى تنسيق يتم فيه حفظ بيانات المهمة مؤقتًا في IndexedDB، ثم استرداد البيانات من قاعدة البيانات وإرسالها إلى API أثناء المزامنة في الخلفية (والتي سيتم شرحها لاحقًا).

8.2. استخدام IndexedDB داخل عامل الخدمة

من الممكن الوصول إلى IndexedDB من داخل نطاق Service Worker. نظرًا لأن معالجة واجهة برمجة تطبيقات IndexedDB مباشرة يمكن أن تجعل التعليمات البرمجية معقدة، فمن الشائع استخدام مكتبة تغليف خفيفة الوزن تُسمى idb تقدمها Google.

تلعب IndexedDB دورًا حيويًا في تطبيقات PWA التي تمتلك ميزات متقدمة للعمل دون اتصال، مثل حفظ استجابة JSON لقائمة المقالات التي تم جلبها من API في IndexedDB بدلاً من واجهة برمجة تطبيقات التخزين المؤقت، مما يتيح إدارتها والاستعلام عنها بشكل تفصيلي.


9. إشعارات الدفع (Push Notifications) والمزامنة في الخلفية (Background Sync)

أكثر الميزات التي تقرب PWA من التطبيقات الأصلية هي إشعارات الدفع والعمل في الخلفية.

9.1. واجهة برمجة تطبيقات الدفع عبر الويب (Web Push API)

Web Push هي آلية تقوم بتسليم الإشعارات إلى المستخدمين عن طريق تشغيل عامل الخدمة من الخادم حتى وإن كان التطبيق مغلقاً.

  1. ** الاشتراك (Subscribe)** : يطلب المتصفح إذنًا من المستخدم للحصول على الإشعارات، ويحصل على معلومات اشتراك خدمة Push (نقطة النهاية ومفتاح التشفير) ويحفظها على خادم الشركة الخاص بك.
  2. ** الإرسال (Push)** : يقوم خادم شركتك بإرسال رسالة إلى خدمة الدفع (مثل FCM أو خدمة Apple Push Notification) التابعة لمزود المتصفح.
  3. ** الاستلام (Push Event)** : عندما ترسل خدمة الدفع البيانات إلى الجهاز، يبدأ المتصفح تشغيل عامل الخدمة في الخلفية ويطلق حدث push. يستدعي عامل الخدمة طريقة self.registration.showNotification() لعرض واجهة مستخدم إشعار نظام التشغيل الأصلي.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
self.addEventListener("push", (event) => {
  const data = event.data ? event.data.json() : {};
  const title = data.title || "لديك رسالة جديدة";
  const options = {
    body: data.body || "يرجى فتح التطبيق للتحقق.",
    icon: "/images/icons/icon-192x192.png",
    badge: "/images/icons/badge.png",
  };

  event.waitUntil(self.registration.showNotification(title, options));
});

9.2. المزامنة في الخلفية (Background Sync)

لنفترض أن مستخدمًا ضغط على زر إرسال رسالة وهو في مترو أنفاق غير متصل بالإنترنت. في تطبيقات الويب العادية سيؤدي ذلك إلى حدوث خطأ، لكن عند استخدام Background Sync API، سيراقب المتصفح توقيت “استعادة الاتصال بالشبكة” ويُطلق حدث sync في عامل الخدمة.

يحفظ التطبيق البيانات مؤقتًا في IndexedDB عند انقطاع الاتصال ويسجل مهمة مزامنة في عامل الخدمة ( registration.sync.register('send-messages') ). لاحقًا، عندما يعود الاتصال بالإنترنت ويُطلق حدث sync، فإنه يسترجع البيانات من IndexedDB ويرسلها إلى الخادم. يتيح ذلك للمستخدم الاستمرار في استخدام التطبيق دون القلق على الإطلاق بشأن حالة الشبكة.


10. مستقبل PWA وتحدياتها (التطور من خلال مشروع Fugu)

لا تزال PWA قيد التطور. على وجه الخصوص، المبادرة المعروفة باسم ** مشروع Fugu ** (Web Capabilities) بقيادة Google و Microsoft و Intel وآخرين، والتي تزيد من ضبابية الحدود بين الويب والتطبيقات الأصلية.

الهدف من مشروع Fugu هو إتاحة الوصول الآمن من الويب إلى الميزات القوية لنظام التشغيل التي كان يُسمح بها فقط للتطبيقات الأصلية. نتيجة لذلك، يتم تنفيذ واجهات برمجة تطبيقات جديدة التالية في المتصفحات واحدة تلو الأخرى.

  • Web Bluetooth API : اتصال مباشر مع أجهزة إنترنت الأشياء (IoT)
  • Web USB API / Web Serial API : اتصال بأجهزة متخصصة
  • File System Access API : قراءة وكتابة الملفات مباشرة على نظام الملفات المحلي للمستخدم (مهم لبيئات التطوير المتكاملة والمحررات كـ PWA)
  • Contact Picker API : الوصول إلى بيانات جهات اتصال الجهاز
  • Web Share Target API : تسجيل PWA كوجهة في “قائمة المشاركة” الخاصة بنظام التشغيل

يُذكر أن حالة التوافق الخاصة بشركة Apple (iOS/Safari) لا تزال تمثل تحديًا. تتخذ Apple موقفًا حذرًا تجاه العديد من واجهات برمجة تطبيقات مشروع Fugu نظرًا لاعتبارات الخصوصية والأمان ونموذج أعمال App Store. ومع ذلك، فمن الصحيح أيضًا أنهم يعززون دعم PWA تدريجيًا استجابةً للمطالب القوية من المستخدمين، مثل دعم Web Push في iOS 16.4.

في تطوير تطبيقات الويب المستقبلي، ستصبح PWA بلا شك تقنية أساسية (Baseline) لتوفير أفضل تجربة للمستخدم، وليست مجرد خيار إضافي.


11. الخاتمة

في هذه المقالة، قدمنا شرحاً عميقاً وشاملاً لمفاهيم PWA الأساسية، ودورة حياة عامل الخدمة (Service Worker) المعقدة، واستراتيجيات التخزين المؤقت المتنوعة، والتكامل مع IndexedDB، وأحدث اتجاهات تقنيات الويب.

عند التعامل مع Service Worker لأول مرة، قد تشعر بالارتباك بسبب طبيعته غير المتزامنة وسلوك التخزين المؤقت الخاص به. ومع ذلك، من خلال فهم دورة حياته بشكل صحيح واختيار وتنفيذ استراتيجية تخزين مؤقت مناسبة، يمكنك بناء تطبيقات ويب سريعة ومرنة (Resilient) بشكل مذهل.

إن تجربة “العمل دون اتصال” تخلق ارتباطاً وثقة عميقة بالتطبيق لدى المستخدم تتجاوز مجرد ميزة مريحة. نأمل أن تقوم بدمج تقنية PWA في مشاريعك الخاصة وتستفيد من إمكانيات الويب إلى أقصى حد.

comments powered by Disqus