利用消息推送增強(qiáng)PWA用戶粘度【翻譯】

PWA:

  1. PWA簡介【翻譯】
  2. PWA架構(gòu)【翻譯】
  3. 利用Service workders使得PWA支持離線工作【翻譯】
  4. 讓PWA可安裝【翻譯】
  5. 利用消息推送增強(qiáng)PWA用戶粘度【翻譯】

通過緩存數(shù)據(jù)讓app支持離線工作是個很好的特性。如果能讓用戶將web app安裝到主屏幕就更好了。但是除了這些依賴用戶操作的特性,我們還可以更進(jìn)一步,通過自動給用戶推送信息來增強(qiáng)用戶粘性,并且可以隨時提供最新的可用數(shù)據(jù)。

兩個API,一個目標(biāo)

Push APINotification API是獨(dú)立的兩個API,但這兩個API可以合作提供更進(jìn)一步的功能。Push讓我們可以在客戶端不介入的情況下由服務(wù)器推送新內(nèi)容,由app的service worker控制。service worker還可以利用notifications向用戶推送信息,至少是通知用戶有新信息到達(dá)。

就像service worker一樣這兩個特性都工作于瀏覽器窗口之外,因此即使用戶沒有查看app的頁面,數(shù)據(jù)的更新和消息推送仍然能夠進(jìn)行。

消息推送(Notification)

讓我們從消息推送開始——消息推送不依賴于push,但是這兩者結(jié)合起來會更加強(qiáng)大。讓我們單獨(dú)的來研究他們。

請求許可(Request permission)

為了使用消息推送,首先我們需要取得許可。最佳實踐為先跳出窗口向用戶獲得許可權(quán),然后再獲得許可權(quán)的情況下使用消息推送。

var button = document.getElementById("notifications");
button.addEventListener('click', function(e) {
    Notification.requestPermission().then(function(result) {
        if(result === 'granted') {
            randomNotification();
        }
    });
});

操作系統(tǒng)自己的消息推送服務(wù)會幫助顯示彈窗:

enter image description here

當(dāng)用戶同意接收消息推送時,app就可以利用這項功能了。彈窗可以返回三個結(jié)果,分別是默認(rèn),許可和拒絕。默認(rèn)表示用戶不作出選擇,其他兩個就是字面的意思。

當(dāng)用戶許可后,notification和push就都可以正常工作了。

創(chuàng)建一個notification

樣例代碼中,app創(chuàng)建了一個notification——從外部的數(shù)據(jù)中隨機(jī)獲取一個game。將game名設(shè)置為notification的title,author設(shè)置在body中,并利用icon顯示圖片。

function randomNotification() {
    var randomItem = Math.floor(Math.random()*games.length);
    var notifTitle = games[randomItem].name;
    var notifBody = 'Created by '+games[randomItem].author+'.';
    var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
    var options = {
        body: notifBody,
        icon: notifImg
    }
    var notif = new Notification(notifTitle, options);
    setTimeout(randomNotification, 30000);
}

這樣我們就創(chuàng)建了一個每30秒通知一次的notification。(當(dāng)然真實的app中,不可能這么頻繁的推送消息)Notification API的優(yōu)勢在于其利用了操作系統(tǒng)自己的消息推送功能。這樣無論用戶是否在利用web app,消息都可以被推送給用戶,同時這些notification就好像本地應(yīng)用的消息一樣。

Push

Push相較notification來說更加復(fù)雜一些——我們需要向服務(wù)器進(jìn)行注冊以便服務(wù)器可以向app發(fā)送數(shù)據(jù)。app的Service Worker將會接收從server端push來的數(shù)據(jù),然后可以進(jìn)一步利用notification來進(jìn)行消息推送,或者是用在其他的地方。

這項技術(shù)才剛剛起步——一些樣例使用了Google Cloud Messaging平臺,但又被重寫以便支持VAPID,這提供了一層安全保障。你可以試驗Service Workers Cookbook examples,設(shè)置一個例如Firebase的消息推送服務(wù)器,或者自己搭建一個服務(wù)器(比如使用Node.js)。

之前提到了,想要接收消息,你必須有一個service worker,# 利用Service workders使得PWA支持離線工作【翻譯】中已經(jīng)有所介紹。我們在service worker中創(chuàng)建push服務(wù)訂閱功能。

registration.pushManager.getSubscription() .then( /* ... */ );

一旦用戶訂閱成功,他們就可以接收到服務(wù)器push過來的內(nèi)容。

對于服務(wù)端,所有的過程都通過公有私有密鑰被嚴(yán)格的加密——讓用戶在不加密的環(huán)境中推送消息是個很糟糕的主意。查看更多的信息:Web Push data encryption test page關(guān)于如何使服務(wù)器更加安全。服務(wù)器保存了所有需要push的數(shù)據(jù),以便在用戶訂閱成功后可以推送給他們。

我們通過監(jiān)聽push事件來接受推送過來的消息:

self.addEventListener('push', function(e) { /* ... */ });

我們可以將接受到的消息直接用notification通知給用戶。例如提醒用戶做某事,或者告訴用戶有新消息到達(dá)。

Push用例

Push需要服務(wù)器配合來進(jìn)行工作,我們無法在js13kPWA中實現(xiàn)這個功能,因為js13kPWA搭建在GitHub Pages上,GitHub Pages只支持靜態(tài)文件。具體的細(xì)節(jié)請查看Service Worker CookbookPush Payload Demo。

這個demo包含三個文件:

來具體看看吧

index.js

navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
  return registration.pushManager.getSubscription()
  .then(async function(subscription) {
      // registration part
  });
})
.then(function(subscription) {
    // subscription part
});

這比我們之前看的js13kPWA的例子稍復(fù)雜一點(diǎn)。在這個例子中,當(dāng)注冊Service Worker成功后,我們可以用registration對象來進(jìn)行訂閱,然后用獲得的subscription對象來完成后續(xù)的操作。

在注冊部分(registration part),代碼大概長這樣:

if(subscription) {
    return subscription;
}

如果用戶已經(jīng)訂閱了,我們直接返回subscription對象并移動到訂閱部分(subscription part)。否則,我們初始化一個新的subscription。

const response = await fetch('./vapidPublicKey');
const vapidPublicKey = await response.text();
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);

app獲取服務(wù)器的公有密鑰,然后將response轉(zhuǎn)換成text,為了支持Chrome,我們還需要將其轉(zhuǎn)換成Uint8Array。查看Sending VAPID identified WebPush Notification via Mozilla's Push Service獲取關(guān)于VAPID密鑰的更多信息。

app這時就可以使用PushManager來訂閱。PushManager.subscribe()需要兩個參數(shù)——第一個是userVisibleOnly: true,表示所有推送給用戶的消息均可見,第二個參數(shù)是applicationServerKey,包含了我們獲得的VAPID密鑰。

return registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: convertedVapidKey
});

再看下訂閱部分(subscription part)——app首先將訂閱的詳細(xì)信息通過Fetch以JSON的形式發(fā)送給服務(wù)器。

fetch('./register', {
    method: 'post',
    headers: {
        'Content-type': 'application/json'
    },
    body: JSON.stringify({
        subscription: subscription
    }),
});

接著綁定在Subscribe button上的GlobalEventHandlers.onclick如下定義:

document.getElementById('doIt').onclick = function() {
    const payload = document.getElementById('notification-payload').value;
    const delay = document.getElementById('notification-delay').value;
    const ttl = document.getElementById('notification-ttl').value;

    fetch('./sendNotification', {
        method: 'post',
        headers: {
            'Content-type': 'application/json'
        },
        body: JSON.stringify({
            subscription: subscription,
            payload: payload,
            delay: delay,
            ttl: ttl,
        }),
    });
};

當(dāng)點(diǎn)擊按鈕后,fetch讓服務(wù)器發(fā)送消息:payload是顯示在notification中的文本,delay為notification顯示的延遲時間,ttl為time-to-live,表示服務(wù)器保存notification的有效時間,也是以秒來計算。

下一個 JavaScript文件。

server.js

server部分用Node.js編寫,我們這里簡單的看一下:

web-push module用來設(shè)置VAPID密鑰,同時可以在密鑰不可用時有選擇的生成密鑰。

const webPush = require('web-push');

if (!process.env.VAPID_PUBLIC_KEY || !process.env.VAPID_PRIVATE_KEY) {
  console.log("You must set the VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY "+
    "environment variables. You can use the following ones:");
  console.log(webPush.generateVAPIDKeys());
  return;
}

webPush.setVapidDetails(
  'https://serviceworke.rs/',
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY
);

下一步,module定義了app將會用到的routes:獲取VAPID公有密鑰,注冊,發(fā)送notifications。注意我們在index.js中使用的payload,delay,ttl在這里用上了。

module.exports = function(app, route) {
  app.get(route + 'vapidPublicKey', function(req, res) {
    res.send(process.env.VAPID_PUBLIC_KEY);
  });

  app.post(route + 'register', function(req, res) {

    res.sendStatus(201);
  });

  app.post(route + 'sendNotification', function(req, res) {
    const subscription = req.body.subscription;
    const payload = req.body.payload;
    const options = {
      TTL: req.body.ttl
    };

    setTimeout(function() {
      webPush.sendNotification(subscription, payload, options)
      .then(function() {
        res.sendStatus(201);
      })
      .catch(function(error) {
        console.log(error);
        res.sendStatus(500);
      });
    }, req.body.delay * 1000);
  });
};

service-worker.js

最后我們來看下service worker:

self.addEventListener('push', function(event) {
    const payload = event.data ? event.data.text() : 'no payload';
    event.waitUntil(
        self.registration.showNotification('ServiceWorker Cookbook', {
            body: payload,
        })
    );
});

我們給push事件加了一個監(jiān)聽器,用數(shù)據(jù)中創(chuàng)建payload變量(若數(shù)據(jù)為空則生成字符串),然后等待直到notification推送給用戶。

至此為止就是一些全部內(nèi)容了,包括基本用法,web push,緩存策略,性能,離線工作等等。
完整例子請查看full source code is available on GitHub。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容