إنشاء صفحة احتياطية بلا اتصال بالإنترنت

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

تطبيق "مساعد Google" المتوافق مع الأجهزة الجوّالة بلا إنترنت.
مساعد Google:

تطبيق Slack للأجهزة الجوّالة بلا اتصال بالإنترنت
Slack:

تطبيق Zoom المتوافق مع الأجهزة الجوّالة بلا اتصال بالإنترنت
تكبير/تصغير:

باستخدام التطبيقات الخاصة بالنظام الأساسي، لن تحصل على أي شيء مطلقًا، حتى إذا لم يتوفر لديك اتصال بالشبكة.

وعلى النقيض من الويب، لا تحصل عادةً على أي شيء عندما تكون غير متصل بالإنترنت. يمنحك Chrome لعبة الديناصور بلا إنترنت، وهذا كل ما في الأمر.

تطبيق Google Chrome للأجهزة الجوّالة يعرض لعبة الديناصور بلا إنترنت.
Google Chrome لنظام التشغيل iOS.

تطبيق Google Chrome المتوافق مع أجهزة سطح المكتب يعرض لعبة الديناصور بلا إنترنت.
Google Chrome لنظام التشغيل macOS:

على الويب، عندما لا يتوفر لديك اتصال بالشبكة، لن تحصل على أي شيء بشكل تلقائي.

صفحة احتياطية بلا اتصال بالإنترنت تتضمن مشغّل خدمات مخصّص

ومع ذلك، لا يجب أن تكون هذه هي الطريقة. بفضل مشغِّلي الخدمات وواجهة برمجة التطبيقات Cache Storage API، يمكنك توفير تجربة مخصّصة للمستخدمين بلا اتصال بالإنترنت. ويمكن أن تكون هذه الصفحة عبارة عن صفحة علامة تجارية بسيطة تحتوي على معلومات تفيد بأنّ المستخدم غير متصل بالإنترنت في الوقت الحالي، ولكنها قد تكون أيضًا حلاً أكثر إبداعًا، مثل لعبة المتاهة المشهورة في trivago بلا اتصال بالإنترنت، مع زر إعادة الربط اليدوي والعد التنازلي لمحاولة إعادة الربط تلقائيًا.

صفحة trivago بلا اتصال بالإنترنت التي تحتوي على متاهة trivago بلا اتصال.
متاهة trivago بلا اتصال بالانترنت.

تسجيل مشغّل الخدمات

هناك عامل خدمة يتيح إجراء ذلك. يمكنك تسجيل مشغّل الخدمات من صفحتك الرئيسية كما في عيّنة التعليمات البرمجية أدناه. عادةً ما يمكنك القيام بذلك بمجرد تحميل التطبيق.

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

رمز مشغّل الخدمات

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

/*
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.
*/

// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
// Add a comment for your linter if you want:
// eslint-disable-next-line no-unused-vars
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      // Setting {cache: 'reload'} in the new request ensures that the
      // response isn't fulfilled from the HTTP cache; i.e., it will be
      // from the network.
      await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
    })()
  );
  // Force the waiting service worker to become the active service worker.
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      // Enable navigation preload if it's supported.
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })()
  );

  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === "navigate") {
    event.respondWith(
      (async () => {
        try {
          // First, try to use the navigation preload response if it's
          // supported.
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            return preloadResponse;
          }

          // Always try the network first.
          const networkResponse = await fetch(event.request);
          return networkResponse;
        } catch (error) {
          // catch is only triggered if an exception is thrown, which is
          // likely due to a network error.
          // If fetch() returns a valid HTTP response with a response code in
          // the 4xx or 5xx range, the catch() will NOT be called.
          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;
        }
      })()
    );
  }

  // If our if() condition is false, then this fetch handler won't
  // intercept the request. If there are any other fetch handlers
  // registered, they will get a chance to call event.respondWith().
  // If no fetch handlers call event.respondWith(), the request
  // will be handled by the browser as if there were no service
  // worker involvement.
});

الصفحة الاحتياطية بلا اتصال بالإنترنت

وملف offline.html هو المكان الذي يمكنك فيه الابتكار وتكييفه حسب احتياجاتك وإضافة علامتك التجارية إليه. يوضح المثال أدناه الحد الأدنى لما هو ممكن. وهي تعرض كلاً من إعادة التحميل اليدوي استنادًا إلى الضغط على الزرّ فضلاً عن إعادة التحميل التلقائية استنادًا إلى حدث online والاستطلاع المنتظم للخادم.

<!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>
      // Manual reload feature.
      document.querySelector("button").addEventListener("click", () => {
        window.location.reload();
      });

      // Listen to changes in the network state, reload when online.
      // This handles the case when the device is completely offline.
      window.addEventListener('online', () => {
        window.location.reload();
      });

      // Check if the server is responding and reload the page if it is.
      // This handles the case when the device is online, but the server
      // is offline or misbehaving.
      async function checkNetworkAndReload() {
        try {
          const response = await fetch('.');
          // Verify we get a valid response from the server
          if (response.status >= 200 && response.status < 500) {
            window.location.reload();
            return;
          }
        } catch {
          // Unable to connect to the server, ignore.
        }
        window.setTimeout(checkNetworkAndReload, 2500);
      }

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

الخصائص الديموغرافية

يمكنك الاطّلاع على الصفحة الاحتياطية بلا إنترنت في العرض التوضيحي المضمّن أدناه. إذا كنت مهتمًا بذلك، يمكنك الاطّلاع على رمز المصدر على Glitch.

ملاحظة جانبية حول جعل تطبيقك قابلاً للتثبيت

بعد أن أصبح موقعك يحتوي على صفحة احتياطية بلا اتصال بالإنترنت، قد تتساءل عن الخطوات التالية. ليصبح تطبيقك قابلاً للتثبيت، عليك إضافة بيان تطبيق الويب واختيار استراتيجية تثبيت.

ملاحظة جانبية حول عرض صفحة احتياطية بلا اتصال بالإنترنت باستخدام Workbox.js

ربما سمعت عن Workbox. Workbox هي مجموعة من مكتبات JavaScript لإضافة الدعم بلا اتصال بالإنترنت إلى تطبيقات الويب. إذا كنت تفضّل كتابة عدد أقل من رموز عاملي الخدمات بنفسك، يمكنك استخدام وصفة Workbox لصفحة بلا إنترنت فقط.

بعد ذلك، تعرَّف على كيفية تحديد استراتيجية تثبيت لتطبيقك.