اذهب إلى المحتوى

تشغيل تطبيقات الويب التقدمية PWA في وضع انقطاع الاتصال


Bassel Alkhatib

تشترك معظم التطبيقات الشهيرة مثل مُساعد جوجل Google Assistant وتطبيق الدردشة سلاك Slack وتطبيق اللقاءات الافتراضية زووم Zoom بأنها تُقدّم خدمات محدودة جدًا في وضع انقطاع الاتصال، أي أنه يُمكن تشغيلها والدخول إليها ومعاينة بعض الواجهات فيها على الرغم من انقطاع الاتصال.

تُبين اللقطات التالية أن تطبيقات المنصات الخاصة تعرض شيئًا ما في وضع انقطاع الاتصال. مثلًا اللقطة التالية للمُساعد Google Assistant:

001Google.jpg

أما اللقطة التالية لتطبيق سلاك Slack:

002Slack.jpg

واللقطة التالية لتطبيق زووم Zoom:

003Zoom.jpg

على النقيض من ذلك، لا نحصل على شيء على الويب عند انقطاع الاتصال. مثلًا، قد يُظهر المتصفح Chrome اللعبة البسيطة dino على أنظمة التشغيل iOS:

004iOS.png

وعلى أنظمة التشغيل macOS:

005macOS.png

سنتعلم في هذا المقال كيفية إنشاء صفحة احتياطية لوضع انقطاع الاتصال وإضافتها إلى تطبيقات الويب التقدمية PWA.

إنشاء صفحة احتياطية باستخدام عامل خدمة مخصص

من المناسب تخصيص سلوك صفحات الويب التقدميّة في حال انقطاع الاتصال لاسيما أنه أصبح بالإمكان توفير تجربة استخدام مخصصة باستخدام عامل خدمة service worker وواجهة برمجة التطبيقات ذات التخزين المؤقت Cache Storage API . يُمكن عادًة إنشاء صفحة احتياطية بسيطة تُبين للمستخدم أنه في وضع انقطاع الاتصال، إلا أنه يُمكن أيضًا إيجاد حلول إبداعية أجمل. لنلقي نظرة مثلًا على موقع الحجز trivago والذي يوفر في صفحته الاحتياطية لعبة متاهة مسلية للمستخدم خلال انقطاع الاتصال مع زر لتجريب إعادة الاتصال Reconnect وعدّاد تنازلي لإظهار الزمن المتبقي لإعادة محاولة الاتصال تلقائيًا كما يُبين الشكل التالي:

006trivago.png

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

لإنشاء الصفحة الاحتياطية، يجب أولًا تسجيل عامل خدمة في الصفحة الأساسية كما تُبين الشيفرة التالية والتي تُنفّذ عند تحميل التطبيق:

window.addEventListener("load", () => {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("service-worker.js");
  }
});

شيفرة عامل الخدمة

نعرض فيما يلي مثالًا عن شيفرة عامل الخدمة والتي قد تبدو للوهلة الأولى معقدة قليلًا ولذا أُضيفت بعض التعليقات المُساعدّة. تكمن الفكرة بالتخزين المؤقت المسبق لملف ندعوه offline.html والذي يعرضه المتصفح في حال فشل طلبات التنقل navigation فقط، وفي بقية الحالات يتابع المتصفح معالجاته الاعتيادية.

/* معلومات رخصة الاستخدام
Copyright 2015, 2019, 2020, 2021 Google LLC. All Rights Reserved.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

// (1)
// يُمكن إضافة أي تعليق مناسب لمنقح الصياغة
// eslint-disable-next-line no-unused-vars
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
يُمكن تخصيص مُحدّد موارد URL مختلف إذا دعت الحاجة //
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      // (2) 
      await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
    })()
  );
 إرغام عامل الخدمة المنتظر ليصبح العامل النشط //
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      // السماح بالتنقل قبل التحميل إذا كان ذلك مدعومًا
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })()
  );

  // الطلب من عامل الخدمة أن يتحكم بالصفحة فورًا
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
 إذا كان الطلب لصفحة HTML نستدعي event.respondWith//
  if (event.request.mode === "navigate") {
    event.respondWith(
      (async () => {
        try {
         نحاول أولًا استخدام جواب التنقل قبل التحميل إذا كان مدعومًا //        
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            return preloadResponse;
          }

          تجريب الشبكة أولًا //        
          const networkResponse = await fetch(event.request);
          return networkResponse;
        } catch (error) {
          // (3)
          console.log("Fetch failed; returning offline page instead.", error);

          const cache = await caches.open(CACHE_NAME);
          const cachedResponse = await cache.match(OFFLINE_URL);
          return cachedResponse;
        }
      })()
    );

  }
  // (4)
});
  • (1) تؤدي زيادة قيمة المتغير OFFLINE_VERSION إلى تنشيط حدث التثبيت وتحديث الموارد المخبئة سابقًا عبر الشبكة، المتغير مُعرّف عمدًا مع أنه غير مستخدم.
  • (2) يؤدي ضبط {cache: 'reload'‎} في الطلب الجديد إلى استيفاء الطلب HTTP من الشبكة وليس من الذاكرة المؤقتة.
  • (3) رُفع استثناء يُنشّط الالتقاط catch والذي يكون على الأغلب بسبب خطأ في الشبكة، ولن تُستدعى catch إذا أعادت fetch جواب HTTP صالح، مع كود الجواب محصور بين
  • 4xx - 5xx. (4) إذا كان شرط التعليمة if خاطئًا فلن يقاطع معالج الجلب fetch الطلب، يُمكن أن يُستدعى التابع event.respondWith في حال وجود معالجات جلب أخرى مسجلة، إذا لم يستدع أي معالج جلب التابع event.respondWith فعندها يعالج المتصفح الطلب كما لو أنه لا يوجد عامل خدمة.

الصفحة الاحتياطية لوضع انقطاع الاتصال

يُعدّ ملف الصفحة الاحتياطية offline.html المكان المناسب لإظهار بعض الإبداع لجذب المستخدم وإبراز العلامة التجارية للمنتج مثلًا، حيث يُمكن تكييف هذا الملف مع الاحتياجات المطلوبة. يُبين المثال التالي الحد الأدنى الممكن القيام به كإظهار زر إعادة التحميل، إضافًة لمحاولات إعادة التحميل الآلية والتي تعتمد على حدث الاتصال online وعلى اقتراع خادم منتظم regular server polling.

يجب تخزين جميع الموارد التي تتطلبها الصفحة الاحتياطية لوضع انقطاع الاتصال. من الحلول المُمكنة لذلك تضمين كل شيء وبذا تُصبح الصفحة الاحتياطية حاوية لنفسها، وهو ما يُبينه المثال التالي:

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="utf-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1" />

   <title>You are offline</title>

   <!-- Inline the page's stylesheet. -->
   <style>
     body {
       font-family: helvetica, arial, sans-serif;
       margin: 2em;
     }

     h1 {
       font-style: italic;
       color: #373fff;
     }

     p {
       margin-block: 1rem;
     }

     button {
       display: block;
     }
   </style>
 </head>
 <body>
   <h1>You are offline</h1>

   <p>Click the button below to try reloading.</p>
   <button type="button">⤾ Reload</button>

   <!-- Inline the page's JavaScript file. -->
   <script>
     //  ميزة إعادة التحميل اليدوي
     document.querySelector("button").addEventListener("click", () => {
       window.location.reload();
     });
     //(1)
     window.addEventListener('online', () => {
       window.location.reload();
     });
//(2)

     async function checkNetworkAndReload() {
       try {
         const response = await fetch('.');
          // التحقق من الحصول على جواب صالح من الخادم
         if (response.status >= 200 && response.status < 500) {
           window.location.reload();
           return;
         }
       } catch {
// تجاهل إذ لا يُمكن الاتصال مع الخادم
       }
       window.setTimeout(checkNetworkAndReload, 2500);
     }

     checkNetworkAndReload();
   </script>
 </body>
</html>

حيث أن:

  • (1) متابعة تغييرات حالة الشبكة وإعادة التحميل عند انقطاع الاتصال حيث ينشط المعالج التالي في حالة انقطاع الاتصال كليًا.
  • (2) إعادة تحميل الصفحة إذا كان الخادم متجاوبًا حيث ينشط المعالج التالي في حال انقطاع الاتصال عن الجهاز أو عدم تجاوب الخادم بشكل صحيح.

مثال توضيحي

يُمكن معاينة الصفحة الاحتياطية في المثال والموضح لقطة منه أدناه. كما يُمكن الحصول على الشيفرة المصدرية الموافقة من المستودع Glitch.

007Demo.png

يُمكن، بعد الانتهاء من إنشاء الصفحة الاحتياطية لوضع عدم الاتصال، تحقيق إمكانية تثبيت التطبيق بإضافة بيان تطبيق الويب واختيار استراتيجية التثبيت. كما يُمكن، للسهولة، استخدام مكتبات Workbox.js والتي توفر الشيفرة اللازمة لإنشاء الصفحة الاحتياطية.

ترجمة -وبتصرف- للمقال Create an offline fallback page للمؤلفين: Thomas Steiner و Pete LePage.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...