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

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

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

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

في القسم الأخير، رأينا كيف يمكننا الاستماع إلى فعاليات 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() API.

في حدث 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 المتصفّح بأنّنا نريد البحث فقط عن برامج من النوع "نافذة" (أي البحث فقط عن علامات التبويب والنوافذ واستبعاد العاملين على الويب). تتيح لنا علامة 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.

النتيجة هي أن الرسالة الأولى ستبدو على النحو التالي:

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

سيؤدي الإشعار الثاني إلى تصغير الإشعارات إلى هذا:

الإشعار الثاني يشتمِل على عملية الدمج.

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

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

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

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

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

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"

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

  • 50
  • 17
  • 44
  • 16

المصدر

Clients.openWindow()

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

  • 40
  • 17
  • 44
  • 11.1

المصدر

ServiceWorkerRegistration.getNotifications()

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

  • 40
  • 17
  • 44
  • 16

المصدر

clients.matchAll()

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

  • 42
  • 17
  • 54
  • 11.1

المصدر

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

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

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