【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

AlienZHOU發表於2018-05-01

《PWA學習與實踐》系列文章已整理至gitbook - PWA學習手冊,文字內容已同步至learning-pwa-ebook。轉載請註明作者與出處。

本文是《PWA學習與實踐》系列的第七篇文章。

PWA作為時下最火熱的技術概念之一,對提升Web應用的安全、效能和體驗有著很大的意義,非常值得我們去了解與學習。對PWA感興趣的朋友歡迎關注《PWA學習與實踐》系列文章。

本文中的程式碼可以在learning-pwa的notification分支上找到(git clone後注意切換到notification分支)。

1. 引言

在第五篇文章《Web中進行服務端訊息推送》中,我介紹瞭如何使用Push API進行服務端訊息推送。提到Push就不得不說與其聯絡緊密的另一個API——Notification API。它讓我們可以在“網站外”顯示訊息提示:

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

即使當你切換到其他Tab,也可以通過提醒互動來快速讓使用者回到你的網站;甚至當使用者離開當前網站,仍然可以收到系統的提醒訊息,並且可以通過訊息提醒快速開啟你的網站。

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

Notification的功能本身與Push並不耦合,你完全可以只使用Notification API或者Push API來構建Web App的某些功能。因此,本文會先介紹如何使用Notification API。然後,作為Notification的“黃金搭檔”,本文還會介紹如何組合使用Push & Notification(訊息推送與提醒)。

2. 使用Notification API

在這第二節裡,我們先來了解如何獨立使用Notification功能。相較於第五篇中的Push功能,Notification API更加簡潔易懂。

2.1. 獲取提醒許可權

首先,進行呼叫訊息提醒API需要獲得使用者的授權。

在呼叫Notification相關API之前,需要先使用Notification物件上的靜態方法Notification.requestPermission()來獲取授權。由於Notification.requestPermission()在某些版本瀏覽器中會接收一個回撥函式(Notification.requestPermission(callback))作為引數,而在另一些瀏覽器版本中會返回一個promise,因此將該方法進行包裝,統一為promise呼叫:

// index.js
function askPermission() {
    return new Promise(function (resolve, reject) {
        var permissionResult = Notification.requestPermission(function (result) {
            resolve(result);
        });
  
        if (permissionResult) {
            permissionResult.then(resolve, reject);
        }
    }).then(function (permissionResult) {
        if (permissionResult !== 'granted') {
            throw new Error('We weren\'t granted permission.');
        }
    });
}


registerServiceWorker('./sw.js').then(function (registration) {
    return Promise.all([
        registration,
        askPermission()
    ])
 })
複製程式碼

我們建立了一個askPermission()方法來統一Notification.requestPermission()的呼叫形式,並在Service Worker註冊完成後呼叫該方法。呼叫Notification.requestPermission()獲取的permissionResult可能的值為:

  • denied:使用者拒絕了通知的顯示
  • granted:使用者允許了通知的顯示
  • default:因為不知道使用者的選擇,所以瀏覽器的行為與denied時相同

chrome中,可以在chrome://settings/content/notifications裡進行通知的設定與管理。

2.2. 設定你的提醒內容

獲取使用者授權後,我們就可以通過registration.showNotification()方法進行訊息提醒了。

當我們註冊完Service Worker後,then方法的回撥函式會接收一個registration引數,通過呼叫其上的showNotification()方法即可觸發提醒:

// index.js
registerServiceWorker('./sw.js').then(function (registration) {
    return Promise.all([
        registration,
        askPermission()
    ])
}).then(function (result) {
    var registration = result[0];
    /* ===== 新增提醒功能 ====== */
    document.querySelector('#js-notification-btn').addEventListener('click', function () {
        var title = 'PWA即學即用';
        var options = {
            body: '邀請你一起學習',
            icon: '/img/icons/book-128.png',
            actions: [{
                action: 'show-book',
                title: '去看看'
            }, {
                action: 'contact-me',
                title: '聯絡我'
            }],
            tag: 'pwa-starter',
            renotify: true
        };
        registration.showNotification(title, options);
    });
    /* ======================= */
})
複製程式碼

上面這段程式碼為頁面上的button新增了一個click事件監聽:當點選後,呼叫registration.showNotification()方法來顯示訊息提醒,該方法接收兩個引數:titleoptiontitle用來設定該提醒的主標題,option中則包含了一些其他設定。

  • body:提醒的內容
  • icon:提醒的圖示
  • actions:提醒可以包含一些自定義操作
  • tag:相當於是ID,通過該ID標識可以操作特定的notification
  • renotify:是否允許重複提醒,預設為false。當不允許重複提醒時,同一個tag的notification只會顯示一次

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

注意,由於不同瀏覽器中,對於option屬性的支援情況並不相同。部分屬性在一些瀏覽器中並不支援。

2.3. 捕獲使用者的點選

在上一部分中,我們已經為Web App新增了提醒功能。點選頁面中的“提醒”按鈕,系統就會彈出提醒框,並展示相關提醒訊息。

然而更多的時候,我們並不僅僅希望只展示有限的資訊,更希望能引導使用者進行互動。例如推薦一本新書,讓使用者點選閱讀或購買。在上一部分我們設定的提醒框中,包含了“去看看”和“聯絡我”兩個按鈕選項,那麼怎麼做才能捕獲使用者的點選操作,並且知道使用者點選了哪個呢?這一小節,就會告訴你如何實現。

還記的上一部分裡我們定義的actions麼?

…
actions: [{
    action: 'show-book',
    title: '去看看'
    }, {
    action: 'contact-me',
    title: '聯絡我'
}]
…
複製程式碼

為了能夠響應使用者對於提醒框的點選事件,我們需要在Service Worker中監聽notificationclick事件。在該事件的回撥函式中我們可以獲取點選的相關資訊:

// sw.js
self.addEventListener('notificationclick', function (e) {
    var action = e.action;
    console.log(`action tag: ${e.notification.tag}`, `action: ${action}`);
    
    switch (action) {
        case 'show-book':
            console.log('show-book');
            break;
        case 'contact-me':
            console.log('contact-me');
            break;
        default:
            console.log(`未處理的action: ${e.action}`);
            action = 'default';
            break;
    }
    e.notification.close();
});
複製程式碼

e.action獲取的值,就是我們在showNotification()中定義的actions裡的action。因此,通過e.action就可以知道使用者點選了哪一個操作選項。注意,當使用者點選提醒本身時,也會觸發notificationclick,但是不包含任何action值,所以在程式碼中將其置於default預設操作中。

現在試一下,我們就可以捕獲使用者對於不同選項的點選了。點選後在Console中會有不同的輸出。

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

2.4. Service Worker與client通訊

到目前為止,我們已經可以順利得給使用者展示提醒,並且在使用者操作提醒後準確捕獲到使用者的操作。然而,還缺最重要的一步——針對不同的操作,觸發不同的互動。例如,

  • 點選提醒本身會彈出書籍簡介;
  • 點選“看一看”會給使用者展示本書的詳情;
  • 點選“聯絡我”會嚮應用管理者發郵件等等。

這裡有個很重要的地方:我們在Service Worker中捕獲使用者操作,但是需要在client(這裡的client是指前端頁面的指令碼環境)中觸發相應操作(呼叫頁面方法/進行頁面跳轉…)。因此,這就需要讓Service Worker與client進行通訊。通訊包括下面兩個部分:

  1. 在Service Worker中使用Worker的postMessage()方法來通知client:
// sw.js
self.addEventListener('notificationclick', function (e) {
    …… // 略去上一節內容
    
    e.waitUntil(
        // 獲取所有clients
        self.clients.matchAll().then(function (clients) {
            if (!clients || clients.length === 0) {
                return;
            }
            clients.forEach(function (client) {
                // 使用postMessage進行通訊
                client.postMessage(action);
            });
        })
    );
});
複製程式碼
  1. 在client中監聽message事件,判斷data,進行不同的操作:
// index.js
navigator.serviceWorker.addEventListener('message', function (e) {
    var action = e.data;
    console.log(`receive post-message from sw, action is '${e.data}'`);
    switch (action) {
        case 'show-book':
            location.href = 'https://book.douban.com/subject/20515024/';
            break;
        case 'contact-me':
            location.href = 'mailto:someone@sample.com';
            break;
        default:
            document.querySelector('.panel').classList.add('show');
            break;
    }
});
複製程式碼

當使用者點選提醒後,我們在notificationclick監聽中,將action通過postMessage()通訊給client;然後在client中監聽message事件,基於action(e.data)來進行不同的操作(跳轉到圖書詳情頁/傳送郵件/顯示簡介皮膚)。

至此,一個比較簡單與完整的訊息提醒(Notification)功能就完成了。

然而目前的訊息提醒還存在一定的侷限性。例如,只有在使用者訪問網站期間才能有機會觸發提醒。正如本文一開始所說,Push & Notification的結合將會幫助我們構築一個強大推送與提醒功能。下面就來看下它們的簡單結合。

3. 訊息推送與提醒

在第五篇《Web中進行服務端訊息推送》最後,我們通過監聽push事件來處理服務端推送:

// sw.js
self.addEventListener('push', function (e) {
    var data = e.data;
    if (e.data) {
        data = data.json();
        console.log('push的資料為:', data);
        self.registration.showNotification(data.text);        
    } 
    else {
        console.log('push沒有任何資料');
    }
});
複製程式碼

簡單修改以上程式碼,與我們本文中的提醒功能相結合:

// sw.js
self.addEventListener('push', function (e) {
    var data = e.data;
    if (e.data) {
        data = data.json();
        console.log('push的資料為:', data);
        var title = 'PWA即學即用';
        var options = {
            body: data,
            icon: '/img/icons/book-128.png',
            image: '/img/icons/book-521.png', // no effect
            actions: [{
                action: 'show-book',
                title: '去看看'
            }, {
                action: 'contact-me',
                title: '聯絡我'
            }],
            tag: 'pwa-starter',
            renotify: true
        };
        self.registration.showNotification(title, options);        
    } 
    else {
        console.log('push沒有任何資料');
    }
});
複製程式碼

使用Push來向使用者推送資訊,並在Service Worker中直接呼叫Notification API來展示該資訊的提醒框。這樣,即使是在使用者關閉該Web App時,依然可以收到提醒,類似於Native中的訊息推送與提醒。

我們還可以將這個功能再豐富一些。由於使用者在關閉該網站時仍然可以收到提醒,因此加入一些更強大功能:

  • 當使用者切換到其他Tab時,點選提醒會立刻回到網站的tab;
  • 當使用者未開啟該網站時,點選提醒可以直接開啟網站。
// sw.js
self.addEventListener('notificationclick', function (e) {
    var action = e.action;
    console.log(`action tag: ${e.notification.tag}`, `action: ${action}`);
    
    switch (action) {
        case 'show-book':
            console.log('show-book');
            break;
        case 'contact-me':
            console.log('contact-me');
            break;
        default:
            console.log(`未處理的action: ${e.action}`);
            action = 'default';
            break;
    }
    e.notification.close();

    e.waitUntil(
        // 獲取所有clients
        self.clients.matchAll().then(function (clients) {
            if (!clients || clients.length === 0) {
                // 當不存在client時,開啟該網站
                self.clients.openWindow && self.clients.openWindow('http://127.0.0.1:8085');
                return;
            }
            // 切換到該站點的tab
            clients[0].focus && clients[0].focus();
            clients.forEach(function (client) {
                // 使用postMessage進行通訊
                client.postMessage(action);
            });
        })
    );
});
複製程式碼

注意這兩行程式碼,第一行會在網站關閉時開啟該網站,第二行會在存在tab時自動切換到網站的tab。

self.clients.openWindow && self.clients.openWindow('http://127.0.0.1:8085');

clients[0].focus && clients[0].focus();
複製程式碼

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

4. MacOS Safari中的Web Notification

看一下Web Notification的相容性

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

目前移動端瀏覽器普遍還不支援該特性。但是在Mac OS上的safari裡面是支援該特性的,不過其呼叫方式與上文程式碼有些不太一樣。在safari中使用Web Notification不是呼叫registration.showNotification()方法,而是需要建立一個Notification物件。

// index.js
……
document.querySelector('#js-notification-btn').addEventListener('click', function () {
    var title = 'PWA即學即用';
    var options = {
        body: '邀請你一起學習',
        icon: '/img/icons/book-128.png',
        actions: [{
            action: 'show-book',
            title: '去看看'
        }, {
            action: 'contact-me',
            title: '聯絡我'
        }],
        tag: 'pwa-starter',
        renotify: true
    };
    // registration.showNotification(title, options);

    // 使用Notification建構函式建立提醒框
    // 而非registration.showNotification()方法
    var notification = new Notification(title, options);
});
……
複製程式碼

Notification物件繼承自EventTarget介面,因此在safari中需要通過新增click事件的監聽來觸發提醒框的互動操作:

// index.js
notification.addEventListener('click', function (e) {
    document.querySelector('.panel').classList.add('show');
});
複製程式碼

【PWA學習與實踐】(7)使用Notification API來進行訊息提醒

該功能示例可以在learn-pwa/notify4safari中找到。

5. 寫在最後

Web Notification是一個非常強大的API,尤其在和Push結合後,為WebApp帶來了類似Native的豐富能力。

本文中所有的程式碼示例均可以在learn-pwa/notification上找到。

如果你喜歡或想要了解更多的PWA相關知識,歡迎關注我,關注《PWA學習與實踐》系列文章。我會總結整理自己學習PWA過程的遇到的疑問與技術點,並通過實際程式碼和大家一起實踐。

到目前為止,我們已經學習了Manifest離線快取訊息推送、訊息提醒、Debug等一些基礎知識。在下一篇文章裡,我們會繼續瞭解與學習PWA中的一個重要功能——後臺同步。

《PWA學習與實踐》系列

參考資料

相關文章