Pattern di notifica comuni

Matt Gaunt

Esamineremo alcuni pattern di implementazione comuni per il push web.

Sarà necessario utilizzare alcune API diverse disponibili nel service worker.

Evento di chiusura notifica

Nell'ultima sezione abbiamo visto come ascoltare gli eventi notificationclick.

Esiste anche un evento notificationclose che viene chiamato se l'utente ignora uno dei tuoi (ovvero, invece di fare clic sulla notifica, l'utente fa clic sulla croce o scorre notifica).

Normalmente questo evento viene utilizzato per dati e analisi al fine di monitorare il coinvolgimento degli utenti con le notifiche.

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

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

Aggiunta di dati a una notifica

Quando un messaggio push viene ricevuto, è normale che i dati siano utile se l'utente ha fatto clic sulla notifica. Ad esempio, l'URL che dovrebbe essere aperto quando si fa clic su una notifica.

Il modo più semplice per acquisire i dati da un evento push e collegarli a un la notifica consiste nell'aggiungere un parametro data all'oggetto opzioni passato in showNotification(), in questo modo:

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);

All'interno di un gestore dei clic, è possibile accedere ai dati con 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('');

Aprire una finestra

Una delle risposte più comuni a una notifica è aprire un finestra / scheda a un URL specifico. Possiamo farlo con clients.openWindow() tramite Google Cloud CLI o tramite l'API Compute Engine.

Nel nostro evento notificationclick, eseguiremo del codice come questo:

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

Nella sezione successiva, vedremo come verificare se la pagina a cui vogliamo indirizzare l'utente sia è già aperto o meno. In questo modo, possiamo impostare lo stato attivo sulla scheda aperta invece di aprirne una nuova schede.

Imposta lo stato attivo su una finestra esistente

Quando è possibile, è consigliabile concentrare l'attenzione su una finestra piuttosto che aprirne una nuova ogni volta che l'utente fa clic su una notifica.

Prima di vedere come ottenere questo risultato, vale la pena sottolineare che è possibile solo per le pagine sulla tua origine. Questo perché possiamo vedere solo quali pagine sono aperte e appartengono al nostro sito. In questo modo agli sviluppatori di vedere tutti i siti visitati dai loro utenti.

Riprendendo l'esempio precedente, modificheremo il codice per vedere /demos/notification-examples/example-page.html è già aperto.

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);

Esaminiamo il codice.

Innanzitutto, analizziamo la pagina di esempio utilizzando l'API URL. È un bel trucco che ho preso da Jeff Posnick. La chiamata a new URL() con l'oggetto location restituiscono un URL assoluto se la stringa passata è relativa (ad esempio, / diventerà https://example.com/).

Rendiamo l'URL assoluto in modo da poterlo confrontare in un secondo momento con l'URL della finestra.

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

Quindi si ottiene un elenco degli oggetti WindowClient, ovvero l'elenco schede e finestre attualmente aperte. Ricorda che queste sono schede solo per la tua origine.

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

Le opzioni trasmesse a matchAll comunicano al browser che vogliamo solo per cercare "finestra" client di tipo (ad es. cerca schede e finestre ed escludono i web worker). includeUncontrolled ci consente di cercare tutte le schede della tua origine che non sono controllate dal servizio attuale ovvero il service worker che esegue questo codice. In genere, vuoi sempre che il valore includeUncontrolled sia impostato su true quando chiami matchAll().

Conquistiamo la promessa restituita come promiseChain in modo da poterla trasmettere event.waitUntil() in seguito, mantenendo attivo il nostro service worker.

Una volta risolta la promessa matchAll(), eseguiamo l'iterazione tramite i client finestra restituiti e confrontare i loro URL con l'URL che vogliamo aprire. Se troviamo una corrispondenza, concentriamo per attirare l'attenzione degli utenti. La messa a fuoco si ottiene Chiamata matchingClient.focus().

Se non riusciamo a trovare un cliente corrispondente, apriamo una nuova finestra, come nella sezione precedente.

.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);
  }
});

Unione delle notifiche

Abbiamo notato che l'aggiunta di un tag a una notifica comporta l'attivazione di un comportamento che prevede la notifica esistente con lo stesso tag viene sostituita.

Puoi tuttavia diventare più sofisticato con la compressione delle notifiche utilizzando il API Notifications. Prendi in considerazione un'app di chat per la quale lo sviluppatore potrebbe voler ricevere una nuova notifica mostra un messaggio simile a "Hai due messaggi da Matt" anziché mostrare solo le informazioni .

Puoi farlo o manipolare le notifiche correnti in altri modi, utilizzando la registration.getNotifications() API che ti permette di accedere a tutte le notifiche attualmente visibili per la tua applicazione web.

Vediamo come potremmo utilizzare questa API per implementare l'esempio della chat.

Nell'app di chat, supponiamo che ogni notifica contenga dati che includono un nome utente.

La prima cosa da fare è trovare tutte le notifiche aperte per un utente con un nome utente. Provvederemo a controllare registration.getNotifications() e a controllare notification.data per un nome utente specifico:

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;
});

Il passaggio successivo consiste nel sostituire questa notifica con una nuova.

In questa falsa app di messaggi, terremo traccia del numero di nuovi messaggi aggiungendo un conteggio alla nuova i dati della notifica e lo incrementano a ogni nuova notifica.

.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
  );
});

Se al momento è visualizzata una notifica, aumenteremo il conteggio dei messaggi e impostiamo il valore titolo della notifica e corpo del messaggio di conseguenza. Se ci sono non sono presenti notifiche, viene creata una nuova notifica con newMessageCount pari a 1.

Il risultato è che il primo messaggio avrà il seguente aspetto:

Prima notifica senza unire.

Una seconda notifica comprimeva le notifiche in questa:

Seconda notifica con unione.

L'aspetto positivo di questo approccio è che se l'utente rileva le notifiche che appaiono una sopra l'altra, sembreranno più coerenti che sostituisca la notifica con il messaggio più recente.

L'eccezione alla regola

Ti dico che devi mostrare una notifica quando ricevi un push, che è true nella maggior parte delle volte. L'unico scenario in cui non devi mostrare una notifica è quando l'utente abbia il tuo sito aperto e mirato.

All'interno dell'evento push, puoi verificare se devi mostrare o meno una notifica. esaminare i client della finestra e cercare una finestra con lo stato attivo.

Il codice per recuperare tutte le finestre e cercarne una con lo stato attivo è simile al seguente:

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;
    });
}

Usiamo clients.matchAll() per ottenere tutti i client finestra e poi li controlliamo controllando il parametro focused.

All'interno dell'evento push, utilizziamo questa funzione per decidere se dobbiamo mostrare una notifica:

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);

Inviare messaggi a una pagina da un evento push

Abbiamo notato che puoi saltare la visualizzazione di una notifica se l'utente si trova attualmente sul tuo sito. Ma cosa succede se vuoi comunque far sapere all'utente che si è verificato un evento, ma viene inviata una notifica troppo pesante?

Un approccio è inviare un messaggio dal service worker alla pagina, in questo modo la pagina web può mostrare una notifica o un aggiornamento all'utente per informarlo dell'evento. Questo è utile per situazioni in cui una notifica discreta nella pagina risulta migliore e più amichevole per l'utente.

Supponiamo che abbiamo ricevuto un push, verificato che la nostra app web sia attualmente selezionata. quindi possiamo "pubblicare un messaggio" su ogni pagina aperta, in questo modo:

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);

In ogni pagina ascoltiamo i messaggi aggiungendo un evento apposito listener:

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

In questo listener di messaggi, puoi fare tutto ciò che vuoi, visualizzare un'interfaccia utente personalizzata sulla pagina o ignorarlo completamente.

Vale anche la pena notare che, se non definisci un listener di messaggi nella pagina web, dal service worker non farà nulla.

Memorizzare una pagina nella cache e aprire una finestra

Uno scenario che esula dall'ambito di questa guida, ma che vale la pena discutere, è che puoi migliorare l'esperienza utente complessiva della tua app web memorizzando nella cache le pagine web che ti aspetti che gli utenti visitino dopo facendo clic sulla notifica.

È necessario che il service worker sia configurato per gestire gli eventi fetch, ma se implementi un listener di eventi fetch, assicurati di utilizzare sfruttalo nel tuo evento push memorizzando nella cache la pagina e gli asset che ti occorre per mostrare la notifica.

Compatibilità del browser

L'evento notificationclose

Supporto dei browser

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

Origine

Clients.openWindow()

Supporto dei browser

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Origine

ServiceWorkerRegistration.getNotifications()

Supporto dei browser

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Origine

clients.matchAll()

Supporto dei browser

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 54.
  • Safari: 11.1.

Origine

Per ulteriori informazioni, consulta questa introduzione ai service worker post.

Passaggi successivi

Codelab