حياة عامل الخدمات

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

قبل الانتقال إلى Workbox، فمن المهم فهم دورة حياة عامل الخدمة كي يكون ما يفعله مربع العمل منطقيًا.

تعريف المصطلحات

قبل الدخول في دورة حياة عامل الخدمة، يستحق الأمر تعريف بعض المصطلحات حول كيفية عمل هذه الدورة.

التحكّم والنطاق

تُعد فكرة التحكّم أمرًا بالغ الأهمية لفهم آلية عمل عاملي الخدمة. الصفحة التي يتم وصفها على أنها يتحكم فيها مشغّل الخدمات، أي صفحة تسمح لعامل الخدمة باعتراض طلبات الشبكة نيابةً عنه. عامل الخدمة متوفّر وقادر على تنفيذ مهام للصفحة ضمن نطاق معيّن

النطاق

يتم تحديد نطاق عامل الخدمة حسب موقعه على خادم الويب. إذا كان مشغّل الخدمات يعمل على صفحة تقع في /subdir/index.html ويقع في /subdir/sw.js، يكون نطاق مشغّل الخدمات /subdir/. لرؤية مفهوم النطاق عمليًا، اطلع على هذا المثال:

  1. الانتقال إلى https://service-worker-scope-viewer.glitch.me/subdir/index.html. ستظهر رسالة مفادها أنّه ما مِن عامل خدمة يتحكّم في الصفحة. ومع ذلك، تسجِّل هذه الصفحة مشغّل الخدمات من https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. إعادة تحميل الصفحة بما أنّه قد تم تسجيل مشغّل الخدمات وهو نشط الآن، فهو يتحكم في الصفحة. نموذج يحتوي على نطاق مشغّل الخدمات والحالة الحالية، وسيكون عنوان URL مرئيًا. ملاحظة: لا علاقة لحاجة إعادة تحميل الصفحة بالنطاق، بل بالأحرى دورة حياة عامل الخدمة، والتي سيتم شرحها لاحقًا.
  3. الآن، انتقِل إلى https://service-worker-scope-viewer.glitch.me/index.html. وعلى الرغم من تسجيل مشغّل الخدمات على هذا المصدر، ما زالت هناك رسالة تفيد بأنّه ما مِن عامل خدمة حالي. ويرجع ذلك إلى أنّ هذه الصفحة ليست ضمن نطاق مشغّل الخدمات المسجَّل.

يحدّ النطاق من الصفحات التي يتحكّم فيها عامل الخدمة. في هذا المثال، يعني ذلك أنّ مشغّل الخدمة الذي تم تحميله من /subdir/sw.js يمكنه فقط التحكّم في الصفحات المتوفّرة في /subdir/ أو شجرته الفرعية.

يوضح ما ورد أعلاه طريقة عمل تحديد النطاق بشكل افتراضي، ولكن يمكن تجاوز الحد الأقصى المسموح به من خلال تعيين عنوان استجابة Service-Worker-Allowed، بالإضافة إلى اجتياز scope إلى طريقة register.

ما لم يكن هناك سبب وجيه جدًا لقصر نطاق مشغّل الخدمات على مجموعة فرعية من المصدر، تحميل عامل خدمة من الدليل الجذري لخادم الويب بحيث يكون نطاقه واسعًا قدر الإمكان، ولا داعي للقلق بشأن عنوان Service-Worker-Allowed. يكون الأمر أبسط بكثير لجميع الأشخاص بهذه الطريقة.

العميل

عندما يُقال إن عامل خدمة يتحكم في صفحة، فهو يتحكم حقًا في عميل. العميل هو أي صفحة مفتوحة يقع عنوان URL الخاص بها ضمن نطاق مشغّل الخدمات هذا. على وجه التحديد، هذه هي حالات WindowClient.

دورة حياة عامل الخدمة الجديد

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

تسجيل

التسجيل هو الخطوة الأولى في دورة حياة عامل الخدمة:

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

يتم تشغيل هذا الرمز في سلسلة التعليمات الرئيسية ويؤدي ما يلي:

  1. ولأن زيارة المستخدم الأولى إلى أحد المواقع الإلكترونية تحدث بدون مشغّل خدمات مسجَّل، يُرجى الانتظار حتى يتم تحميل الصفحة بالكامل قبل تسجيل صفحة. يؤدي ذلك إلى تجنُّب التضارب في معدل نقل البيانات إذا كان عامل الخدمة يخزِّن أي شيء مؤقتًا.
  2. على الرغم من أنّ مشغّل الخدمات متوافق بشكل جيد، يساعد التحقق السريع في تجنب الأخطاء في المتصفحات التي لا يكون متوافقًا معها.
  3. عندما يتم تحميل الصفحة بالكامل، وإذا كان مشغّل الخدمات متوافقًا، سجِّل /sw.js.

في ما يلي بعض النقاط الأساسية التي يجب فهمها:

  • مشغِّلو الخدمات هم متاحة عبر بروتوكول HTTPS أو المضيف المحلي فقط.
  • إذا كان محتوى مشغّل الخدمات يتضمّن أخطاءً في البنية، فشل التسجيل ويتم تجاهل عامل الخدمة.
  • تذكير: يعمل مشغّلو الخدمات ضمن نطاق معيّن. النطاق هنا هو المصدر بالكامل، إذ تم تحميله من الدليل الجذر.
  • عند بدء التسجيل، يتم ضبط حالة مشغّل الخدمات على 'installing'.

بعد انتهاء التسجيل، يبدأ التثبيت.

تثبيت

عامل خدمة يطلق حدث install بعد التسجيل. يتم استدعاء install مرة واحدة فقط لكل عامل خدمة، ولن يتم تنشيطه مرة أخرى حتى يتم تحديثه. يمكن تسجيل معاودة الاتصال لحدث install في نطاق العامل باستخدام addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

يؤدي ذلك إلى إنشاء مثيل Cache جديد وتخزين مواد العرض مؤقتًا. سيكون لدينا الكثير من الفرص للحديث عن التحميل المسبق لاحقًا، لذلك دعنا نركز على دور event.waitUntil يقبل "event.waitUntil" هذا الوعد، وينتظر حتى يتم الوعد. في هذا المثال، يؤدي هذا الوعد إلى أمرين غير متزامنين:

  1. تنشئ مثيل Cache جديدًا باسم 'MyFancyCache_v1'.
  2. بعد إنشاء ذاكرة التخزين المؤقت، يتم تخزين مصفوفة من عناوين URL لمواد العرض مؤقتًا بشكل مسبق باستخدام خياراتها غير المتزامنة طريقة addAll.

يتعذّر التثبيت إذا كانت الوعود التي تم تمريرها إلى event.waitUntil هي مرفوض. وفي هذه الحالة، يتم تجاهل عامل الخدمة.

إذا تمّ حلّ الوعود، نجاح التثبيت وستتغير حالة مشغّل الخدمة إلى 'installed' وسيتم تفعيلها بعد ذلك.

التفعيل

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

بالنسبة إلى عاملي الخدمات الجدد، يتم تنشيط activate بعد نجاح install مباشرةً. بمجرد انتهاء التفعيل، ستصبح حالة مشغّل الخدمات 'activated'. لاحظ أنه بشكل افتراضي، فلن يبدأ عامل الخدمة الجديد في التحكم في الصفحة حتى إجراء التنقل التالي أو تحديث الصفحة.

التعامل مع إشعارات مشغّل الخدمات

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

عند إجراء التحديثات

ستبحث المتصفحات عن تحديثات عامل الخدمة في الحالات التالية:

  • ينتقل المستخدم إلى صفحة داخل نطاق مشغّل الخدمات.
  • navigator.serviceWorker.register() باستخدام عنوان URL مختلف عن مشغّل الخدمات المثبّت حاليًا، ولكن لا تغيّر عنوان URL لمشغّل الخدمات.
  • navigator.serviceWorker.register() بعنوان URL نفسه الذي يستخدمه مشغّل الخدمات المثبّت، ولكن بنطاق مختلف. مرة أخرى، تجنَّب حدوث ذلك من خلال إبقاء النطاق في جذر المصدر إن أمكن.
  • عندما تكون أحداث مثل 'push' أو 'sync' تم إطلاقها خلال آخر 24 ساعة، ولكن لا داعي للقلق بشأن هذه الأحداث بعد

طريقة إجراء التعديلات

من المهم معرفة الوقت الذي يحدّث فيه المتصفح عامل الخدمة، ولكن هناك "كيف" أيضًا. وبافتراض عدم تغيّر عنوان URL أو نطاق مشغّل الخدمات، تحديث مشغّل الخدمات المثبّت حاليًا إلى إصدار جديد فقط في حال تغيّر محتواه.

تكتشف المتصفحات التغييرات بطريقتين:

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

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

تشغيل عمليات البحث عن التحديثات يدويًا

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

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

بالنسبة لمواقع الويب التقليدية، أو في أي حالة لا تكون جلسات المستخدم طويلة الأمد، تشغيل التحديثات اليدوية.

تثبيت

عند استخدام أداة تجميع لإنشاء أصول ثابتة، أن هذه الأصول ستحتوي على تجزئات في أسمائها مثل framework.3defa9d2.js. لنفترض أنّ بعض مواد العرض هذه مخزّنة مؤقتًا بشكل مسبق للوصول إليها بلا إنترنت في وقت لاحق. ويتطلّب ذلك تحديث مشغِّل الخدمات لتخزين مواد العرض المعدَّلة مؤقتًا:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

يختلف الأمران عن المثال الأول على حدث "install" والمثال السابق:

  1. يتم إنشاء مثيل Cache جديد بمفتاح 'MyFancyCacheName_v2'.
  2. تم تغيير أسماء مواد العرض المخزَّنة مؤقتًا بشكل مسبق.

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

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

التفعيل

عندما يتم تثبيت عامل خدمة محدّث وتنتهي مرحلة الانتظار، فسيتم تنشيطه، ويتم تجاهل عامل الخدمة القديم. من المهام الشائعة لتنفيذ حدث activate الخاص بعامل الخدمة المعدّل، وهي تنقيح ذاكرات التخزين المؤقت القديمة. يمكنك إزالة ذاكرات التخزين المؤقت القديمة من خلال الحصول على مفاتيح جميع الأجهزة الافتراضية المفتوحة التي تتضمن Cache caches.keys وحذف ذاكرات التخزين المؤقت غير الموجودة في قائمة السماح المحددة مع caches.delete:

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

لا يتم ترتيب ذاكرات التخزين المؤقت القديمة من تلقاء نفسها. نحتاج إلى القيام بذلك بأنفسنا أو نخاطر بتجاوز حصص التخزين. بما أنّ الإصدار 'MyFancyCacheName_v1' من مشغّل الخدمات الأول غير محدَّث، يتم تحديث قائمة السماح بذاكرة التخزين المؤقت لتحديد 'MyFancyCacheName_v2'، وهو ما يؤدي إلى حذف ذاكرات التخزين المؤقت التي تحمل اسمًا مختلفًا.

سينتهي الحدث "activate" بعد إزالة ذاكرة التخزين المؤقت القديمة. في هذه المرحلة، سيتحكّم عامل الخدمة الجديد في الصفحة، وتحل في النهاية محل الإصدار القديم!

تسير دورة الحياة إلى الأبد

وما إذا كان يتم استخدام Workbox للتعامل مع نشر مشغّل الخدمة وتحديثاته استخدام واجهة برمجة تطبيقات Service Worker API مباشرةً، فمن المفيد فهم دورة حياة عامل الخدمة. باستخدام هذا الفهم، يجب أن تبدو سلوكيات عاملي الخدمات أكثر منطقية من الغموض.

بالنسبة للمهتمين بالمزيد من التعمق في هذا الموضوع، الأمر يستحق التحقق هذه المقالة من تأليف "جيك أرشيبالد". هناك الكثير من الفروق الطفيفة في جميع مراحل مسار الخدمة، لكنها مفيدة، وستحقق نجاحًا كبيرًا عند استخدام Workbox.