أنماط الإشعارات الشائعة

سنلقي نظرة على بعض أنماط التنفيذ الشائعة للرسائل الفورية على الويب.

سيتضمّن ذلك استخدام بعض واجهات برمجة التطبيقات المختلفة المتوفّرة في الخدمة العاملة.

في القسم الأخير، تعرّفنا على كيفية الاستماع إلى أحداث notificationclick.

هناك أيضًا حدث notificationclose يتمّ استدعاؤه إذا أغلق المستخدِم أحد إشعاراتك (أي بدلاً من النقر على الإشعار، ينقر المستخدِم على علامة الحذف أو يمرِّر الإشعار سريعًا).

يُستخدَم هذا الحدث عادةً في التحليلات لتتبُّع تفاعل المستخدِم مع الإشعارات.

self.addEventListener('notificationclose', function (event) {
  const dismissedNotification = event.notification;

  const promiseChain = notificationCloseAnalytics();
  event.waitUntil(promiseChain);
});

إضافة بيانات إلى إشعار

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

إنّ أسهل طريقة لأخذ البيانات من حدث دفع وإرفاقها بأحد الإشعارات هي إضافة مَعلمة data إلى كائن الخيارات الذي تم تمريره إلى showNotification()، على النحو التالي:

const options = {
  body:
    'This notification has data attached to it that is printed ' +
    "to the console when it's clicked.",
  tag: 'data-notification',
  data: {
    time: new Date(Date.now()).toString(),
    message: 'Hello, World!',
  },
};
registration.showNotification('Notification with Data', options);

داخل معالِج النقرات، يمكن الوصول إلى البيانات باستخدام event.notification.data.

const notificationData = event.notification.data;
console.log('');
console.log('The notification data has the following parameters:');
Object.keys(notificationData).forEach((key) => {
  console.log(`  ${key}: ${notificationData[key]}`);
});
console.log('');

فتح نافذة

من أكثر الردود شيوعًا على الإشعار هو فتح نافذة أو علامة تبويب لعنوان URL محدّد. يمكننا إجراء ذلك باستخدام واجهة برمجة تطبيقات clients.openWindow() .

في الحدث notificationclick، سننفّذ بعض التعليمات البرمجية على النحو التالي:

const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

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

التركيز على نافذة حالية

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

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

بالاستناد إلى المثال السابق، سنغيّر الرمز لمعرفة ما إذا كان /demos/notification-examples/example-page.html مفتوحًا أم لا.

const urlToOpen = new URL(examplePage, self.location.origin).href;

const promiseChain = clients
  .matchAll({
    type: 'window',
    includeUncontrolled: true,
  })
  .then((windowClients) => {
    let matchingClient = null;

    for (let i = 0; i < windowClients.length; i++) {
      const windowClient = windowClients[i];
      if (windowClient.url === urlToOpen) {
        matchingClient = windowClient;
        break;
      }
    }

    if (matchingClient) {
      return matchingClient.focus();
    } else {
      return clients.openWindow(urlToOpen);
    }
  });

event.waitUntil(promiseChain);

لنطّلِع على التعليمات البرمجية خطوة بخطوة.

أولاً، نفكّر نموذج الصفحة باستخدام واجهة برمجة التطبيقات URL API. هذه خدعة أنيقة أختارها من جيف بوسنيك. سيؤدي استدعاء new URL() باستخدام العنصر location إلى عرض عنوان URL مطلق إذا كانت السلسلة التي تم تمريرها نسبية (أي سيصبح / https://example.com/).

نجعل عنوان URL مطلقًا لنتمكّن من مطابقته مع عناوين URL للنوافذ لاحقًا.

const urlToOpen = new URL(examplePage, self.location.origin).href;

بعد ذلك، نحصل على قائمة بكائنات WindowClient، وهي قائمة بعلامات التبويب والنوافذ المفتوحة حاليًا. (تذكَّر أنّ هذه علامات تبويب للمصدر فقط).

const promiseChain = clients.matchAll({
  type: 'window',
  includeUncontrolled: true,
});

الخيارات التي تم تمريرها إلى matchAll تُعلم المتصفّح بأنّنا نريد فقط البحث عن عملاء من النوع "window" (أي البحث عن علامات التبويب والنوافذ فقط واستبعاد مهام Web Worker). إنّ includeUncontrolled يتيح لنا البحث عن جميع علامات التبويب من المصدر والتي لا يتحكّم فيها مشغّل الخدمات الحالي، أي مشغّل الخدمات الذي يشغّل هذا الرمز. بشكل عام، تريد دائمًا أن تكون قيمة includeUncontrolled صحيحة عند الاتصال بـ matchAll().

نُسجِّل الوعد الذي تم إرجاعه على أنّه promiseChain حتى نتمكّن من تمريره إلى event.waitUntil() لاحقًا، ما يحافظ على استمرار عمل الخدمة.

عند انتهاء وعد matchAll()، نكرّر الإجراءات التي ننفّذها على العملاء الذين تم إرجاعهم ونقارن عناوين URL الخاصة بهم بعنوان URL الذي نريد فتحه. إذا وجدنا مطابقة، فإننا نركز على ذلك العميل، مما سيلفت انتباه المستخدمين إلى تلك النافذة. ويتم التركيز من خلال طلب "matchingClient.focus()".

إذا لم نتمكن من العثور على عميل مطابق، سنفتح نافذة جديدة، كما في القسم السابق.

.then((windowClients) => {
  let matchingClient = null;

  for (let i = 0; i < windowClients.length; i++) {
    const windowClient = windowClients[i];
    if (windowClient.url === urlToOpen) {
      matchingClient = windowClient;
      break;
    }
  }

  if (matchingClient) {
    return matchingClient.focus();
  } else {
    return clients.openWindow(urlToOpen);
  }
});

دمج الإشعارات

تبيّن لنا أنّ إضافة علامة إلى إشعار يؤدي إلى تفعيل سلوك يتم فيه استبدال أي إشعار قائم بالعلامة نفسها.

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

يمكنك إجراء ذلك أو معالجة الإشعارات الحالية بطرق أخرى باستخدام واجهة برمجة التطبيقات registration.getNotifications() التي تتيح لك الوصول إلى كل الإشعارات المرئية حاليًا لتطبيق الويب.

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

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

أول إجراء سننفّذه هو البحث عن أي إشعارات مفتوحة لمستخدم لديه اسم مستخدم معيّن. سنحصل على registration.getNotifications() وسنكرّرها ونتحقق من notification.data بحثًا عن اسم مستخدم معيّن:

const promiseChain = registration.getNotifications().then((notifications) => {
  let currentNotification;

  for (let i = 0; i < notifications.length; i++) {
    if (notifications[i].data && notifications[i].data.userName === userName) {
      currentNotification = notifications[i];
    }
  }

  return currentNotification;
});

تتمثّل الخطوة التالية في استبدال هذا الإشعار بإشعار جديد.

في تطبيق الرسائل المزيفة هذا، سنتتبع عدد الرسائل الجديدة من خلال إضافة عدد إلى بيانات الإشعار الجديد ونزيده مع كل إشعار جديد.

.then((currentNotification) => {
  let notificationTitle;
  const options = {
    icon: userIcon,
  }

  if (currentNotification) {
    // We have an open notification, let's do something with it.
    const messageCount = currentNotification.data.newMessageCount + 1;

    options.body = `You have ${messageCount} new messages from ${userName}.`;
    options.data = {
      userName: userName,
      newMessageCount: messageCount
    };
    notificationTitle = `New Messages from ${userName}`;

    // Remember to close the old notification.
    currentNotification.close();
  } else {
    options.body = `"${userMessage}"`;
    options.data = {
      userName: userName,
      newMessageCount: 1
    };
    notificationTitle = `New Message from ${userName}`;
  }

  return registration.showNotification(
    notificationTitle,
    options
  );
});

إذا كان هناك إشعار معروض حاليًا، سنزيد عدد الرسائل ونضبط عنوان الإشعار ونصّه وفقًا لذلك. إذا لم تكن هناك إشعارات، سننشئ إشعارًا جديدًا بقيمة newMessageCount‏= 1.

ونتيجةً لذلك، ستبدو الرسالة الأولى على النحو التالي:

الإشعار الأول بدون دمج

سيؤدي الإشعار الثاني إلى تجميع الإشعارات على النحو التالي:

الإشعار الثاني مع الدمج.

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

الاستثناء من القاعدة

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

داخل حدث الإرسال الفوري، يمكنك التحقّق ممّا إذا كنت بحاجة إلى عرض إشعار أم لا من خلال examining the window clients والبحث عن نافذة تركّز عليها.

يبدو رمز الحصول على جميع النوافذ والبحث عن نافذة مركّزة على النحو التالي:

function isClientFocused() {
  return clients
    .matchAll({
      type: 'window',
      includeUncontrolled: true,
    })
    .then((windowClients) => {
      let clientIsFocused = false;

      for (let i = 0; i < windowClients.length; i++) {
        const windowClient = windowClients[i];
        if (windowClient.focused) {
          clientIsFocused = true;
          break;
        }
      }

      return clientIsFocused;
    });
}

نستخدم clients.matchAll() للحصول على جميع عملاء النوافذ، ثمّ نكرّر ذلك للتحقّق من المَعلمة focused.

في حدث الإشعار الفوري، سنستخدم هذه الدالة لتحديد ما إذا كنا بحاجة إلى عرض إشعار:

const promiseChain = isClientFocused().then((clientIsFocused) => {
  if (clientIsFocused) {
    console.log("Don't need to show a notification.");
    return;
  }

  // Client isn't focused, we need to show a notification.
  return self.registration.showNotification('Had to show a notification.');
});

event.waitUntil(promiseChain);

إرسال رسالة إلى صفحة من حدث دفع

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

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

لنفترض أنّنا تلقّينا إشعارًا، وتحقّقنا من أنّ تطبيق الويب يركّز حاليًا على المحتوى، ثم يمكننا "نشر رسالة" في كل صفحة مفتوحة، على النحو التالي:

const promiseChain = isClientFocused().then((clientIsFocused) => {
  if (clientIsFocused) {
    windowClients.forEach((windowClient) => {
      windowClient.postMessage({
        message: 'Received a push message.',
        time: new Date().toString(),
      });
    });
  } else {
    return self.registration.showNotification('No focused windows', {
      body: 'Had to show a notification instead of messaging each page.',
    });
  }
});

event.waitUntil(promiseChain);

في كل صفحة، نستمع إلى الرسائل من خلال إضافة مستمع لحدث الرسالة:

navigator.serviceWorker.addEventListener('message', function (event) {
  console.log('Received a message from service worker: ', event.data);
});

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

ويجب ملاحظة أيضًا أنه إذا لم تحدد مستمعًا للرسالة في صفحة الويب، فلن تفعل الرسائل من عامل الخدمة أي شيء.

تخزين صفحة في ذاكرة التخزين المؤقت وفتح نافذة

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

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

توافُق المتصفح

الحدث notificationclose

توافق المتصفّح

  • Chrome: 50.
  • ‫Edge: 17
  • Firefox: 44
  • ‫Safari: 16

المصدر

Clients.openWindow()

توافق المتصفّح

  • Chrome:‏ 40
  • ‫Edge: 17
  • Firefox: 44
  • ‫Safari: 11.1

المصدر

ServiceWorkerRegistration.getNotifications()

توافق المتصفّح

  • Chrome:‏ 40
  • ‫Edge: 17
  • Firefox: 44
  • ‫Safari: 16

المصدر

clients.matchAll()

دعم المتصفح

  • Chrome: 42
  • الحافة: 17.
  • Firefox: 54
  • ‫Safari: 11.1

المصدر

لمزيد من المعلومات، يمكنك الاطّلاع على مقالتنا التعريفية عن مهام الخدمة .

الخطوات التالية

الدروس التطبيقية حول الترميز