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

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

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

حدث إغلاق الإشعار

في القسم الأخير، تعرّفنا على كيفية الاستماع إلى أحداث 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" (أي البحث عن علامات التبويب والنوافذ فقط واستبعاد مهام معالجة الويب). يسمح لنا الرمز 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
  • ‫Edge: 17
  • Firefox: 54
  • ‫Safari: 11.1

المصدر

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

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

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