Powiadamianie o zmianach w powiadomieniach

Po pierwsze, przepraszam za ten okropny tytuł, ale nie było innego wyjścia.

W Chrome 44 dodano metody Notfication.dataServiceWorkerRegistration.getNotifications(), które umożliwiają uproszczenie obsługi niektórych typowych przypadków użycia powiadomień z wiadomościami push.

Dane dotyczące powiadomień

Notification.data umożliwia powiązanie obiektu JavaScript z powiadomieniem.

Oznacza to, że gdy otrzymasz wiadomość push, możesz utworzyć powiadomienie z niektórymi danymi, a potem w zdarzeniu notificationclick możesz uzyskać powiadomienie, które zostało kliknięte, oraz jego dane.

Na przykład możesz utworzyć obiekt danych i dodać go do opcji powiadomień w ten sposób:

self.addEventListener('push', function(event) {
    console.log('Received a push message', event);

    var title = 'Yay a message.';
    var body = 'We have received a push message.';
    var icon = '/images/icon-192x192.png';
    var tag = 'simple-push-demo-notification-tag';
    var data = {
    doge: {
        wow: 'such amaze notification data'
    }
    };

    event.waitUntil(
    self.registration.showNotification(title, {
        body: body,
        icon: icon,
        tag: tag,
        data: data
    })
    );
});

Oznacza to, że możemy uzyskać informacje ze zdarzenia notificationclick:

self.addEventListener('notificationclick', function(event) {
    var doge = event.notification.data.doge;
    console.log(doge.wow);
});

Wcześniej trzeba było przechowywać dane w IndexDB lub dodawać coś na końcu adresu URL ikony.

ServiceWorkerRegistration.getNotifications()

Deweloperzy pracujący nad powiadomieniami push często proszą o możliwość lepszej kontroli nad wyświetlanymi powiadomieniami.

Przykładem może być aplikacja do obsługi czatu, w której użytkownik wysyła wiele wiadomości, a odbiorca wyświetla wiele powiadomień. W idealnej sytuacji aplikacja internetowa powinna wykryć, że masz kilka nieotwartych powiadomień, i połączyć je w jedno powiadomienie.

Bez funkcji getNotifications() możesz jedynie zastąpić poprzednie powiadomienie najnowszą wiadomością. Za pomocą funkcji getNotifications() możesz „zwijać” powiadomienia, jeśli są już wyświetlane. Dzięki temu użytkownicy będą mieli lepsze wrażenia.

Przykład grupowania powiadomień

Kod, który to umożliwia, jest stosunkowo prosty. Wewnątrz zdarzenia push wywołaj ServiceWorkerRegistration.get Notification(), aby uzyskać tablicę bieżących powiadomień i zdecyduj, jakie działanie ma być odpowiednie, niezależnie od tego, czy ma być zwijane wszystkie powiadomienia, czy używać elementu Notification.tag.

function showNotification(title, body, icon, data) {
    var notificationOptions = {
    body: body,
    icon: icon ? icon : 'images/touch/chrome-touch-icon-192x192.png',
    tag: 'simple-push-demo-notification',
    data: data
    };

    self.registration.showNotification(title, notificationOptions);
    return;
}

self.addEventListener('push', function(event) {
    console.log('Received a push message', event);

    // Since this is no payload data with the first version
    // of Push notifications, here we'll grab some data from
    // an API and use it to populate a notification
    event.waitUntil(
    fetch(API_ENDPOINT).then(function(response) {
        if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
            response.status);
        // Throw an error so the promise is rejected and catch() is executed
        throw new Error();
        }

        // Examine the text in the response
        return response.json().then(function(data) {
        var title = 'You have a new message';
        var message = data.message;
        var icon = 'images/notification-icon.png';
        var notificationTag = 'chat-message';

        var notificationFilter = {
            tag: notificationTag
        };
        return self.registration.getNotifications(notificationFilter)
            .then(function(notifications) {
            if (notifications && notifications.length > 0) {
                // Start with one to account for the new notification
                // we are adding
                var notificationCount = 1;
                for (var i = 0; i < notifications.length; i++) {
                var existingNotification = notifications[i];
                if (existingNotification.data &&
                    existingNotification.data.notificationCount) {
                    notificationCount +=
existingNotification.data.notificationCount;
                } else {
                    notificationCount++;
                }
                existingNotification.close();
                }
                message = 'You have ' + notificationCount +
                ' weather updates.';
                notificationData.notificationCount = notificationCount;
            }

            return showNotification(title, message, icon, notificationData);
            });
        });
    }).catch(function(err) {
        console.error('Unable to retrieve data', err);

        var title = 'An error occurred';
        var message = 'We were unable to get the information for this ' +
        'push message';

        return showNotification(title, message);
    })
    );
});

self.addEventListener('notificationclick', function(event) {
    console.log('On notification click: ', event);

    if (Notification.prototype.hasOwnProperty('data')) {
    console.log('Using Data');
    var url = event.notification.data.url;
    event.waitUntil(clients.openWindow(url));
    } else {
    event.waitUntil(getIdb().get(KEY_VALUE_STORE_NAME,
event.notification.tag).then(function(url) {
        // At the moment you cannot open third party URL's, a simple trick
        // is to redirect to the desired URL from a URL on your domain
        var redirectUrl = '/redirect.html?redirect=' +
        url;
        return clients.openWindow(redirectUrl);
    }));
    }
});

W tym fragmencie kodu zwracamy uwagę na to, że filtrujemy powiadomienia, przekazując obiekt filtra do funkcji getNotifications(). Oznacza to, że możemy uzyskać listę powiadomień dla określonego tagu (w tym przykładzie dla konkretnej rozmowy).

var notificationFilter = {
    tag: notificationTag
};
return self.registration.getNotifications(notificationFilter)

Następnie sprawdzamy widoczne powiadomienia, aby sprawdzić, czy powiązane z nimi są jakieś liczby powiadomień. Jeśli tak, zwiększamy te liczby. Jeśli użytkownik otrzyma powiadomienie, że ma 2 nieprzeczytane wiadomości, a potem otrzyma nową wiadomość push, chcemy, aby zobaczył, że ma 3 nieprzeczytane wiadomości.

var notificationCount = 1;
for (var i = 0; i < notifications.length; i++) {
    var existingNotification = notifications[i];
    if (existingNotification.data && existingNotification.data.notificationCount) {
    notificationCount += existingNotification.data.notificationCount;
    } else {
    notificationCount++;
    }
    existingNotification.close();
}

Należy pamiętać, że aby usunąć powiadomienie z listy powiadomień, musisz wywołać funkcję close(). To błąd w Chrome, ponieważ każde powiadomienie jest zastępowane przez następne, ponieważ używany jest ten sam tag. Obecnie ta wymiana nie jest odzwierciedlana w zwróconym tablicy z poziomu getNotifications().

To tylko jeden przykład funkcji getPowiadomienia() i jak można sobie wyobrazić, ten interfejs API udostępnia wiele innych zastosowań.

NotificationOptions.vibrate

Od wersji 45 Chrome możesz określać wzór wibracji podczas tworzenia powiadomienia. Na urządzeniach, które obsługują interfejs API wibracji (obecnie tylko Chrome na Androida), możesz dostosować wzór wibracji, który będzie używany podczas wyświetlania powiadomienia.

Wzór wibracji może być tablicą liczb lub pojedynczą liczbą, która jest traktowana jako tablica o jednej liczbie. Wartości w tablicy odzwierciedlają czasy w milisekundach, a indeksy parzyste (0, 2, 4, ...) to czas wibracji, a indeksy nieparzyste to czas przerwy przed kolejną wibracją.

self.registration.showNotification('Buzz!', {
    body: 'Bzzz bzzzz',
    vibrate: [300, 100, 400] // Vibrate 300ms, pause 100ms, then vibrate 400ms
});

Pozostałe częste żądania dotyczące funkcji

Jednym z najczęściej zgłaszanych przez deweloperów problemów jest możliwość zamknięcia powiadomienia po określonym czasie lub wysłanie powiadomienia push, aby po prostu zamknąć powiadomienie, jeśli jest widoczne.

W tej chwili nie ma sposobu, aby to zrobić, i żadna specyfikacja nie na to pozwala. (ale zespół inżynierów Chrome wie o tym przypadku użycia.

Powiadomienia w Androidzie

Na komputerze możesz utworzyć powiadomienie za pomocą tego kodu:

new Notification('Hello', {body: 'Yay!'});

Ta funkcja nigdy nie była obsługiwana na Androidzie ze względu na ograniczenia tej platformy: Chrome nie może obsługiwać wywołań zwrotnych obiektu Notification, takich jak onclick. Na komputerze jest on używany do wyświetlania powiadomień z aplikacji internetowych, które mogą być aktualnie otwarte.

Wymieniam to tylko dlatego, że początkowo proste wykrywanie funkcji, takie jak poniżej, pomagało w obsługiwaniu komputerów, a nie powodowało błędów na Androidzie:

if (!'Notification' in window) {
    // Notifications aren't supported
    return;
}

Jednak dzięki obsłudze powiadomień push w Chrome na Androida powiadomienia można tworzyć za pomocą ServiceWorkera, ale nie za pomocą strony internetowej. Oznacza to, że wykrywanie tej funkcji nie jest już odpowiednie. Jeśli spróbujesz utworzyć powiadomienie w Chrome na Androida, zobaczysz ten komunikat o błędzie:

_Uncaught TypeError: Failed to construct 'Notification': Illegal constructor.
Use ServiceWorkerRegistration.showNotification() instead_

Obecnie najlepszym sposobem wykrywania funkcji na Androidzie i na komputerze jest wykonanie tych czynności:

    function isNewNotificationSupported() {
        if (!window.Notification || !Notification.requestPermission)
            return false;
        if (Notification.permission == 'granted')
            throw new Error('You must only call this \*before\* calling
    Notification.requestPermission(), otherwise this feature detect would bug the
    user with an actual notification!');
        try {
            new Notification('');
        } catch (e) {
            if (e.name == 'TypeError')
                return false;
        }
        return true;
    }

Można go używać w ten sposób:

    if (window.Notification && Notification.permission == 'granted') {
        // We would only have prompted the user for permission if new
        // Notification was supported (see below), so assume it is supported.
        doStuffThatUsesNewNotification();
    } else if (isNewNotificationSupported()) {
        // new Notification is supported, so prompt the user for permission.
        showOptInUIForNotifications();
    }