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

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

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

PWA做爲時下最火熱的技術概念之一,對提高Web應用的安全、性能和體驗有着很大的意義,很是值得咱們去了解與學習。對PWA感興趣的朋友歡迎關注《PWA學習與實踐》系列文章。java

本文中的代碼能夠在learning-pwa的notification分支上找到(git clone後注意切換到notification分支)。git

1. 引言

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

即便當你切換到其餘Tab,也能夠經過提醒交互來快速讓用戶回到你的網站;甚至當用戶離開當前網站,仍然能夠收到系統的提醒消息,而且能夠經過消息提醒快速打開你的網站。chrome

Notification的功能自己與Push並不耦合,你徹底能夠只使用Notification API或者Push API來構建Web App的某些功能。所以,本文會先介紹如何使用Notification API。而後,做爲Notification的「黃金搭檔」,本文還會介紹如何組合使用Push & Notification(消息推送與提醒)。json

2. 使用Notification API

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

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只會顯示一次

注意,因爲不一樣瀏覽器中,對於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中會有不一樣的輸出。

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();
複製代碼

4. MacOS Safari中的Web Notification

看一下Web Notification的兼容性

目前移動端瀏覽器廣泛還不支持該特性。可是在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');
});
複製代碼

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

5. 寫在最後

Web Notification是一個很是強大的API,尤爲在和Push結合後,爲WebApp帶來了相似Native的豐富能力。

本文中全部的代碼示例都可以在learn-pwa/notification上找到。

若是你喜歡或想要了解更多的PWA相關知識,歡迎關注我,關注《PWA學習與實踐》系列文章。我會總結整理本身學習PWA過程的遇到的疑問與技術點,並經過實際代碼和你們一塊兒實踐。

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

《PWA學習與實踐》系列

參考資料

相關文章
相關標籤/搜索