الگوهای رایج اطلاع رسانی

مت گانت

ما قصد داریم به برخی از الگوهای پیاده سازی رایج برای فشار وب نگاهی بیندازیم.

این شامل استفاده از چند API مختلف است که در سرویس‌کار موجود است.

رویداد بستن اعلان

در بخش آخر دیدیم که چگونه می توانیم به رویدادهای notificationclick گوش دهیم.

همچنین یک رویداد notificationclose وجود دارد که در صورتی که کاربر یکی از اعلان‌های شما را رد کند، فراخوانی می‌شود (یعنی به جای کلیک کردن روی اعلان، کاربر روی ضربدر کلیک می‌کند یا اعلان را به سمت چپ می‌کشد).

این رویداد معمولاً برای تجزیه و تحلیل برای ردیابی تعامل کاربر با اعلان ها استفاده می شود.

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

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

افزودن داده به اعلان

هنگامی که یک پیام فشار دریافت می شود، معمول است که داده هایی داشته باشید که فقط در صورتی مفید باشند که کاربر روی اعلان کلیک کرده باشد. به عنوان مثال، آدرس اینترنتی که باید با کلیک بر روی یک اعلان باز شود.

ساده ترین راه برای گرفتن داده از یک رویداد فشار و پیوست کردن آن به یک اعلان، افزودن یک پارامتر 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 به ما امکان می دهد همه برگه ها را از مبدأ شما جستجو کنیم که توسط سرویس دهنده فعلی کنترل نمی شوند، یعنی سرویس دهنده ای که این کد را اجرا می کند. به طور کلی، همیشه باید هنگام فراخوانی matchAll() includeUncontrolled درست باشد.

ما وعده بازگشتی را به عنوان 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 پیچیده تر شوید. یک برنامه چت را در نظر بگیرید، جایی که توسعه دهنده ممکن است بخواهد یک اعلان جدید برای نشان دادن پیامی شبیه به "You have two messages from Matt" به جای نمایش آخرین پیام، نشان دهد.

می‌توانید این کار را انجام دهید، یا اعلان‌های فعلی را به روش‌های دیگری دستکاری کنید، با استفاده از API register.getNotifications() که به شما امکان می‌دهد به تمام اعلان‌های قابل مشاهده در حال حاضر برای برنامه وب خود دسترسی داشته باشید.

بیایید ببینیم چگونه می‌توانیم از این API برای پیاده‌سازی مثال چت استفاده کنیم.

در برنامه چت ما، بیایید فرض کنیم هر اعلان داده هایی دارد که شامل یک نام کاربری است.

اولین کاری که می خواهیم انجام دهیم این است که هر گونه اعلان باز برای کاربری با نام کاربری خاص را پیدا کنیم. ما 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);
});

در این شنونده پیام، می توانید هر کاری که می خواهید انجام دهید، یک رابط کاربری سفارشی را در صفحه خود نشان دهید یا به طور کامل پیام را نادیده بگیرید.

همچنین شایان ذکر است که اگر شنونده پیام را در صفحه وب خود تعریف نکنید، پیام های سرویس دهنده کاری انجام نمی دهند.

یک صفحه را کش کنید و یک پنجره باز کنید

یکی از سناریوهایی که خارج از محدوده این راهنما است، اما ارزش بحث را دارد، این است که می‌توانید UX کلی برنامه وب خود را با ذخیره کردن صفحات وب که انتظار دارید کاربران پس از کلیک بر روی اعلان شما بازدید کنند، بهبود بخشید.

این مستلزم این است که سرویس‌کارگر خود را برای مدیریت رویدادهای 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

منبع

برای اطلاعات بیشتر، این مقدمه برای پست کارگران خدماتی را بررسی کنید.

بعد کجا بریم

آزمایشگاه های کد