通知您通知設定有所變更

Matt Gaunt

首先,我要為糟糕的標題致歉,但我無法不這麼做。

在 Chrome 44 中,我們新增了 Notfication.dataServiceWorkerRegistration.getNotifications(),並開放/簡化一些常見的用途,以便處理含推播訊息的通知。

通知資料

您可以使用 Notification.data 將 JavaScript 物件與 Notification 建立關聯。

這基本上是指,當您收到推播訊息時,可以建立含有部分資料的通知,然後在通知點擊事件中取得已點選的通知,並取得其資料。

例如,建立資料物件並將其新增至通知選項,如下所示:

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

這表示我們可以在 notificationclick 事件中取得資訊:

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

在此之前,您必須將資料儲存在 IndexDB 中,或在圖示網址結尾處放入某些內容。

ServiceWorkerRegistration.getNotifications()

處理推播通知的開發人員經常要求取得這些通知,以便進一步掌控顯示的通知。

舉例來說,如果使用者在聊天應用程式中傳送多則訊息,而收件者顯示多則通知,這就是一個用途。理想情況下,網頁應用程式會注意到您有數則未查看的通知,並將這些通知收合成一則通知。

如果沒有 getNotifications(),您可以做的最佳做法是將先前的通知取代為最新訊息。如果系統已顯示通知,您可以使用 getNotifications() 方法「收合」通知,進而提供更優質的使用者體驗。

將通知分組的示例。

這項操作的程式碼相對簡單。在推送事件中,呼叫 ServiceWorkerRegistration.get 通知() 來取得目前通知的陣列,並從該處決定正確的行為,無論是收合所有通知,還是使用 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);
    }));
    }
});

這個程式碼片段最值得注意的地方,就是我們會將篩選器物件傳遞至 getNotifications(),藉此篩選通知。也就是說,我們可以取得特定標記的通知清單 (在本例中為特定對話)。

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

接著,我們會查看可見的通知,並檢查是否有與該通知相關聯的通知計數,並根據該計數遞增。這樣一來,如果有一個通知會告訴使用者有兩則未讀訊息,我們就會在收到新推播時指出有三則未讀訊息。

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

值得一提的是,您需要對通知呼叫 close(),確保通知已從通知清單中移除。這是 Chrome 的錯誤,因為系統會使用相同的標記,導致每則通知都會被下一個通知取代。目前,這項替換作業不會反映在 getNotifications() 傳回的陣列中。

這只是 getNotifications() 的一個範例,如您所見,這個 API 可用於多種其他用途。

NotificationOptions.vibrate

自 Chrome 45 起,您可以在建立通知時指定震動模式。在支援 Vibration API 的裝置上 (目前僅適用於 Android 版 Google),您可以自訂顯示通知時使用的震動模式。

振動模式可以是數字陣列,也可以是單一數字,並視為一數字陣列。陣列中的值以毫秒為單位表示,偶數 (0、2、4、...) 代表震動的時間長度,奇數則是在下次震動前暫停的時間長度。

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

其他常見功能要求

開發人員還有一項常見的功能要求,就是可在特定時間範圍過後關閉通知,或是能夠傳送推播通知,只在通知顯示時關閉。

目前無法執行此操作,規格中也沒有允許這麼做的規定 :( 不過 Chrome 工程團隊知道這個用途。

Android 通知

在電腦上,您可以使用下列程式碼建立通知:

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

由於平台的限制,Android 完全不支援這個功能:具體來說,Chrome 不支援在通知物件上回呼,例如 click。但在電腦上,這項功能會用於顯示您目前可能已開啟的網路應用程式通知。

我提及這點的原因,是因為原本,像是以下這類的簡單功能偵測功能,就能協助您支援電腦,且不會在 Android 上造成任何錯誤:

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

不過,由於 Chrome 適用於 Android 的推播通知功能現已支援,因此通知可以透過 ServiceWorker 建立,但無法透過網頁建立,這表示這項功能偵測不再適合。如果您嘗試在 Android 版 Chrome 上建立通知,就會收到以下錯誤訊息:

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

目前偵測 Android 和電腦的最佳做法如下:

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

使用方式如下:

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