摘要:在的的監聽事件下,可以接收任何傳入的消息。這個也將負責向已激活推送消息的所有用戶發送消息。上面的代碼負責彈出請求用戶是否允許或阻止瀏覽器中的推送消息。這個處理了保存和刪除訂閱同時也處理了消息推送的功能。我們將使用作為我們的消息服務。
在第一篇:介紹一下漸進式 Web App(離線) - Part 1中我們介紹了一個典型的PWA應該是什么樣子的,并且介紹了一下sercer worker和應用殼(app shell),在第二篇[介紹一下漸進式 Web App(即時加載) - Part 2
](https://juejin.im/post/5a3245...,我們緩存了動態數據,并實現了從本地保存的數據中即時加載數據到頁面中,也順便介紹了web的一些數據庫。
這篇文章也是這系列的完結篇,我們將實現:
在Web應用程序中激活推送通知
使Web應用程序的變成是可以可安裝的
消息推送push API使Web應用程序能夠接收從服務器推送來的消息并通知用戶。這個功能需要service worker配合起來,在Web應用程序中典型的推送通知流程的過程是這樣子滴:
Web應用程序彈出一個彈出窗口,要求用戶訂閱通知。
用戶訂閱接收推送通知。
service worker 負責管理、處理用戶的訂閱。
當服務器推送消息時都會用到用戶訂閱的ID,每個用戶根據他們的訂閱ID都可以有一個自定義的功能。
在service worker的push的監聽事件下,可以接收任何傳入的消息。
讓我們開始吧。我們先快速總結一下,消息推送時怎么在我們的web app中實現的
給用戶一個開關,單擊一個按鈕激活或停用推送通知。
如果用戶激活,請訂閱用戶通過service worker的管理接收推送通知。
構建一個API 去處理用戶刪除或者保存訂閱ID。這個API也將負責向已激活推送消息的所有用戶發送消息。
建立一個GitHub Webhook去實現立即發送通知,讓新的提交被推送到resources-i-like倉庫
代碼行動起來在項目中新建js/notification.js文件,并且在index.html中引用。
js/notification.js 代碼如下
(function (window) { "use strict"; //Push notification button var fabPushElement = document.querySelector(".fab__push"); var fabPushImgElement = document.querySelector(".fab__image"); //To check `push notification` is supported or not function isPushSupported() { //To check `push notification` permission is denied by user if (Notification.permission === "denied") { alert("User has blocked push notification."); return; } //Check `push notification` is supported or not if (!("PushManager" in window)) { alert("Sorry, Push notification isn"t supported in your browser."); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error("Error occurred while enabling push ", error); }); }); } // Ask User if he/she wants to subscribe to push notifications and then // ..subscribe and send push notification function subscribePush() { navigator.serviceWorker.ready.then(function(registration) { if (!registration.pushManager) { alert("Your browser doesn"t support push notification."); return false; } //To subscribe `push notification` from push manager registration.pushManager.subscribe({ userVisibleOnly: true //Always show notification when received }) .then(function (subscription) { toast("Subscribed successfully."); console.info("Push notification subscribed."); console.log(subscription); //saveSubscriptionID(subscription); changePushStatus(true); }) .catch(function (error) { changePushStatus(false); console.error("Push notification subscription error: ", error); }); }) } // Unsubscribe the user from push notifications function unsubscribePush() { navigator.serviceWorker.ready .then(function(registration) { //Get `push subscription` registration.pushManager.getSubscription() .then(function (subscription) { //If no `push subscription`, then return if(!subscription) { alert("Unable to unregister push notification."); return; } //Unsubscribe `push notification` subscription.unsubscribe() .then(function () { toast("Unsubscribed successfully."); console.info("Push notification unsubscribed."); console.log(subscription); //deleteSubscriptionID(subscription); changePushStatus(false); }) .catch(function (error) { console.error(error); }); }) .catch(function (error) { console.error("Failed to unsubscribe push notification."); }); }) } //To change status function changePushStatus(status) { fabPushElement.dataset.checked = status; fabPushElement.checked = status; if (status) { fabPushElement.classList.add("active"); fabPushImgElement.src = "../images/push-on.png"; } else { fabPushElement.classList.remove("active"); fabPushImgElement.src = "../images/push-off.png"; } } //Click event for subscribe push fabPushElement.addEventListener("click", function () { var isSubscribed = (fabPushElement.dataset.checked === "true"); if (isSubscribed) { unsubscribePush(); } else { subscribePush(); } }); isPushSupported(); //Check for push notification support })(window);
上面的代碼做了很多事情。放心啦,我將會解釋一波代碼的功能滴。
//Push notification button var fabPushElement = document.querySelector(".fab__push"); var fabPushImgElement = document.querySelector(".fab__image");
上面的代碼獲取推送通知激活和停用按鈕的節點。
function isPushSupported() { //To check `push notification` permission is denied by user if (Notification.permission === "denied") { alert("User has blocked push notification."); return; } //Check `push notification` is supported or not if (!("PushManager" in window)) { alert("Sorry, Push notification isn"t supported in your browser."); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error("Error occurred while enabling push ", error); }); }); }
上面的代碼是檢查瀏覽器以是否支持推送通知?,F在,最重要的是 service worker 必須注冊并且在您嘗試訂閱用戶以接收推送通知之前,已經做好了準備(ready)。因此,上面的代碼也檢查service worker是否ready并獲得用戶的訂閱。
//To change status function changePushStatus(status) { fabPushElement.dataset.checked = status; fabPushElement.checked = status; if (status) { fabPushElement.classList.add("active"); fabPushImgElement.src = "../images/push-on.png"; } else { fabPushElement.classList.remove("active"); fabPushImgElement.src = "../images/push-off.png"; } }
用戶訂閱按鈕的樣式改變
changePushStatus函數代表著只需更改按鈕的顏色來指示用戶是否已訂閱。
// Ask User if he/she wants to subscribe to push notifications and then // ..subscribe and send push notification function subscribePush() { navigator.serviceWorker.ready.then(function(registration) { if (!registration.pushManager) { alert("Your browser doesn"t support push notification."); return false; } //To subscribe `push notification` from push manager registration.pushManager.subscribe({ userVisibleOnly: true //Always show notification when received }) .then(function (subscription) { toast("Subscribed successfully."); console.info("Push notification subscribed."); console.log(subscription); //saveSubscriptionID(subscription); changePushStatus(true); }) .catch(function (error) { changePushStatus(false); console.error("Push notification subscription error: ", error); }); }) }
上面的代碼負責彈出請求用戶是否允許或阻止瀏覽器中的推送消息。如果用戶允許推送消息,就是彈出一個toast的已經允許的提示,然后更改按鈕的顏色并保存訂閱ID。如果推瀏覽器不支持,那么它會通知用戶它不受支持。
注意:保存訂閱ID的功能現在已被注釋掉。
// Unsubscribe the user from push notifications function unsubscribePush() { navigator.serviceWorker.ready .then(function(registration) { //Get `push subscription` registration.pushManager.getSubscription() .then(function (subscription) { //If no `push subscription`, then return if(!subscription) { alert("Unable to unregister push notification."); return; } //Unsubscribe `push notification` subscription.unsubscribe() .then(function () { toast("Unsubscribed successfully."); console.info("Push notification unsubscribed."); //deleteSubscriptionID(subscription); changePushStatus(false); }) .catch(function (error) { console.error(error); }); }) .catch(function (error) { console.error("Failed to unsubscribe push notification."); }); }) }
上面的是負責退訂推送消息,彈出一個toast提示小心,然后更改按鈕的顏色并刪除訂閱ID。
注意:刪除訂閱ID的功能現在已被注釋掉了。
//Click event for subscribe push fabPushElement.addEventListener("click", function () { var isSubscribed = (fabPushElement.dataset.checked === "true"); if (isSubscribed) { unsubscribePush(); } else { subscribePush(); } });
上面代碼是添加一個按鈕單擊事件實現訂閱和取消訂閱用戶的切換。
處理訂閱ID我們已經能夠看到推送訂閱了?,F在,我們需要能夠保存每個用戶的訂閱ID,當用戶退訂的推送通知時我們還需要能夠刪除這些訂閱ID。
添加下面的代碼到你的js/notification.js中
function saveSubscriptionID(subscription) { var subscription_id = subscription.endpoint.split("gcm/send/")[1]; console.log("Subscription ID", subscription_id); fetch("http://localhost:3333/api/users", { method: "post", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ user_id : subscription_id }) }); } function deleteSubscriptionID(subscription) { var subscription_id = subscription.endpoint.split("gcm/send/")[1]; fetch("http://localhost:3333/api/user/" + subscription_id, { method: "delete", headers: { "Accept": "application/json", "Content-Type": "application/json" } }); }
在上面的代碼中,我們從服務器請求一個接口,來獲取訂閱ID和和刪除訂閱ID,saveSubscriptionID函數創建了一個新的用戶并且保存了用戶的訂閱ID,deleteSubscriptionID刪除了用戶和用戶的訂閱ID
看起來怪怪的。為什么要請求到服務器?簡單,因為我們需要一個數據庫來存儲所有的訂閱ID,這樣子就可以向所有的用戶發送消息推送。
API Service這個API Service 處理了保存和刪除訂閱ID同時也處理了消息推送的功能。這API的分解。它將有3個api路由:
POST /api/users創建新用戶并存儲其訂閱ID
DELETE /api/user/:user_id刪除和取消訂閱用戶
POST /api/notify向所有訂閱用戶發送通知
很高興,我有API Service的源碼,道友可以點擊鏈接查看,運行時候確保你的node和mongodb是事先安裝過的。克隆xi下來并且在命令行中運行node server.js
確保你先創建.env文件,如下圖所示
溫馨提醒:您可以通過這個良好的教程去了解如何設置API服務。在本教程我只是實現了node.js版本的API服務。
我們將使用Firebase Cloud Messaging 作為我們的消息服務。所以,現在用Firebase去建立一個新的項目。新建項目完了之后就去Project settings > Cloud Messaging這里
拿到你的 Server Key然后復制粘貼到你的.env文件在的FCM_API_KEY,通過我們的API Server我們需要將Server Key傳給Firebase,下面看看我們的看看我們的消息推送控制器的代碼:
.... notifyUsers: function(req, res){ var sender = new gcm.Sender(secrets.fcm); // Prepare a message to be sent var message = new gcm.Message({ notification: { title: "New commit on Github Repo: RIL", icon: "ic_launcher", body: "Click to see the latest commit"" } }); User.find({}, function(err, users) { // user subscription ids to deliver message to var user_ids = _.map(users, "user_id"); console.log("User Ids", user_ids); // Actually send the message sender.send(message, { registrationTokens: user_ids }, function (err, response) { if (err) { console.error(err); } else { return res.json(response); } }); }); }, .....
現在返回我們的js/notification.js并且去掉我們之前說的saveSubscriptionID函數和deleteSubscriptionID的注釋,然后你的notification.js應該是長這樣子滴:
(function (window) { "use strict"; //Push notification button var fabPushElement = document.querySelector(".fab__push"); var fabPushImgElement = document.querySelector(".fab__image"); //To check `push notification` is supported or not function isPushSupported() { //To check `push notification` permission is denied by user if (Notification.permission === "denied") { alert("User has blocked push notification."); return; } //Check `push notification` is supported or not if (!("PushManager" in window)) { alert("Sorry, Push notification isn"t supported in your browser."); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error("Error occurred while enabling push ", error); }); }); } // Ask User if he/she wants to subscribe to push notifications and then // ..subscribe and send push notification function subscribePush() { navigator.serviceWorker.ready.then(function(registration) { if (!registration.pushManager) { alert("Your browser doesn"t support push notification."); return false; } //To subscribe `push notification` from push manager registration.pushManager.subscribe({ userVisibleOnly: true //Always show notification when received }) .then(function (subscription) { toast("Subscribed successfully."); console.info("Push notification subscribed."); console.log(subscription); saveSubscriptionID(subscription); changePushStatus(true); }) .catch(function (error) { changePushStatus(false); console.error("Push notification subscription error: ", error); }); }) } // Unsubscribe the user from push notifications function unsubscribePush() { navigator.serviceWorker.ready .then(function(registration) { //Get `push subscription` registration.pushManager.getSubscription() .then(function (subscription) { //If no `push subscription`, then return if(!subscription) { alert("Unable to unregister push notification."); return; } //Unsubscribe `push notification` subscription.unsubscribe() .then(function () { toast("Unsubscribed successfully."); console.info("Push notification unsubscribed."); console.log(subscription); deleteSubscriptionID(subscription); changePushStatus(false); }) .catch(function (error) { console.error(error); }); }) .catch(function (error) { console.error("Failed to unsubscribe push notification."); }); }) } //To change status function changePushStatus(status) { fabPushElement.dataset.checked = status; fabPushElement.checked = status; if (status) { fabPushElement.classList.add("active"); fabPushImgElement.src = "../images/push-on.png"; } else { fabPushElement.classList.remove("active"); fabPushImgElement.src = "../images/push-off.png"; } } //Click event for subscribe push fabPushElement.addEventListener("click", function () { var isSubscribed = (fabPushElement.dataset.checked === "true"); if (isSubscribed) { unsubscribePush(); } else { subscribePush(); } }); function saveSubscriptionID(subscription) { var subscription_id = subscription.endpoint.split("gcm/send/")[1]; console.log("Subscription ID", subscription_id); fetch("http://localhost:3333/api/users", { method: "post", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ user_id : subscription_id }) }); } function deleteSubscriptionID(subscription) { var subscription_id = subscription.endpoint.split("gcm/send/")[1]; fetch("http://localhost:3333/api/user/" + subscription_id, { method: "delete", headers: { "Accept": "application/json", "Content-Type": "application/json" } }); } isPushSupported(); //Check for push notification support })(window);
讓我們嘗試激活消息推送,看看是否創建了新的用戶,并存儲在我們的API服務數據庫中。重新刷新網頁并按下激活按鈕,然后看到控制臺居然有錯誤。
別煩惱!著原因是我們沒有在我們的程序中創一個manifest.json 的文件。
現在有趣的事情是,添加上了manifest.json 的文件將不會報錯并且在我們的程序中添加了一個新的功能。有了這個manifest.json 的文件,我們可以將我們的應用程序安裝到我們的屏幕上。Viola?。?!
現在我們去創建一個manifest.json 的文件吧,代碼如下
{ "name": "PWA - Commits", "short_name": "PWA", "description": "Progressive Web Apps for Resources I like", "start_url": "./index.html?utm=homescreen", "display": "standalone", "orientation": "portrait", "background_color": "#f5f5f5", "theme_color": "#f5f5f5", "icons": [ { "src": "./images/192x192.png", "type": "image/png", "sizes": "192x192" }, { "src": "./images/168x168.png", "type": "image/png", "sizes": "168x168" }, { "src": "./images/144x144.png", "type": "image/png", "sizes": "144x144" }, { "src": "./images/96x96.png", "type": "image/png", "sizes": "96x96" }, { "src": "./images/72x72.png", "type": "image/png", "sizes": "72x72" }, { "src": "./images/48x48.png", "type": "image/png", "sizes": "48x48" } ], "author": { "name": "Prosper Otemuyiwa", "website": "https://twitter.com/unicodeveloper", "github": "https://github.com/unicodeveloper", "source-repo": "https://github.com/unicodeveloper/pwa-commits" }, "gcm_sender_id": "571712848651" }
現在快速的掃盲一下manifest.json 上的key的含義吧。
name:表示應用程序的名稱,因為它通常顯示在屏幕上給用戶看滴。
short_name:表示Web應用程序名稱的縮寫。
description:表示Web應用程序的一般描述。
start_url:是用戶啟動Web應用程序時加載的URL。
display:定義Web應用程序的默認顯示模式。不同的模式有fullscreen, standalone, minimal-ui
orientation:
background_color:表示Web應用程序的背景顏色。
theme_color:表示應用程序的默認主題顏色。它將Android上的狀態欄著色。
icons:主屏幕的icon圖標
author:作者的一些信息
gcm_sender_id:用于識別應用的Firebase的sender_id,在下面取得。如下圖
在你的index.html 和latest.html引用這個manifest.json 文件。
現在,清楚緩存,刷新應用,然后點擊消息推動按鈕
然后看到 訂閱ID在控制臺中打印了出來,查看下出數據庫,
Yaaay!!,中于起作用了呢
在數據庫中你可以看到用戶的訂閱ID了,這意味著,我們的請求是成功滴
小提示:RoboMongo是一個管理mongodb數據庫的圖形界面。
您可以嘗試取消訂閱,查看它如何從API服務數據庫中刪除用戶。
發送和接收推送消息在我們的service API中,我們做一個POST請求到/api/notify的路由,然后后臺接收到前端的請求繼續推送到Firebase Cloud Messaging 的服務中?,F在,這還是不夠滴,所以,我們還需要一種在瀏覽器中監聽和接受此通知的方法。
然后到Service Worker 閃亮登場了,用它來監聽一個push的事件,在sw.js中,代碼如下:
self.addEventListener("push", function(event) { console.info("Event: Push"); var title = "New commit on Github Repo: RIL"; var body = { "body": "Click to see the latest commit", "tag": "pwa", "icon": "./images/48x48.png" }; event.waitUntil( self.registration.showNotification(title, body) ); });
這段代碼添加到sw.js。清緩存,重新加載你的應用,現在我們利用postman去發起http://localhost:3333/api/notify請求
當發出通知時,我們的瀏覽器會歡迎這樣的通知:
接到通知后,當用戶單擊這個通知時,我們可以決定該怎么做。然后添加下面這個代碼到sw.js中
self.addEventListener("notificationclick", function(event) { var url = "./latest.html"; event.notification.close(); //Close the notification // Open the app and navigate to latest.html after clicking the notification event.waitUntil( clients.openWindow(url) ); });
這里,上面的代碼監聽用戶單擊通知時被觸發的事件。event.notification.close()是單擊后關閉通知。然后,將打開一個瀏覽器新窗口或選項卡重新指向localhost:8080/latest.html地址。
提示:event.waitUntil()在我們的新窗口打開之前,它就被調用了以確保瀏覽器不會終止我們的server worker。自動推送消息
之前我們是通過Postman手動發起一個請求去推送消息的,實際上,我們是想,用于一旦有了提交到https://github.com/unicodeveloper/resources-i-like/,我們就自動接收收到一個消息通知。那么,我們如何使這個過程自動化呢?
有聽說過Webhooks么???
有!!!
那么好~~我們就用GitHub Webhooks
提示: 使用您自己的倉庫地址,因為看到了這里,你就要自己提交commit了
到你選擇的倉庫去,在這里我的是https://github.com/unicodeveloper/resources-i-like/,到 Settings > Webhooks中:
點擊add webhook添加一個hook,當用戶提交的時候就觸發了pushs是事件,這個hook講通知我們的notify API ,利用這個webhook當用戶提交commit時候,發出一個post請求到我們的/api/notify,然后順利成章的發送一個瀏覽器的消息推送啦。 開森~~~~
看上面那個圖,慢著,等一下,是怎么得到https://ea71f5aa.ngrok.io/api/notifyz這個地址的??實際上是在本地開發需要用ngrok工具,把內網轉發出去。明白了吧
設置Ngrok非常簡單,我們不能使用localhost,GitHub上需要一個存在在網絡上URL,我利用ngrok可以將本地服務器暴露到Internet上。
安裝ngrok之后,在命令行中,敲上這樣子的代碼
./ngrok http 3333
得到
提示:ngrok輸出HTTP和HTTPS地址,它們都映射到本地服務。
現在,一旦你添加webhook,GitHub立即提交測試post請求來決定是否設置正確。
測試一個commit吧我們把一切都做好了,現在我們去提交一個commit,一旦你這么做了,一個消息推送就發送到我們的瀏覽器中了。 如下圖
Host PWA一個PWA的要求服務是通過HTTPS的。用Firebase hosting部署我們的應用程序服務器并且支持HTTPS協議是一個非常好的選擇。
我們app線上的地址:https://ril-pwa.firebaseapp.com/
服務器api線上地址:https://rilapi.herokuapp.com/api
打開你的設備上的瀏覽器,尤其是Chrome,并像這樣添加它:
看到桌面上已經有了 應用圖標了。然后本教程也是總結鳥。
~ 完結,散花散花散花散花 ~~~~
附:
點擊鏈接查看
原文地址
第一篇: 介紹一下漸進式 Web App(離線) - Part 1
第二篇: 介紹一下漸進式 Web App(即時加載)- Part 2
項目PWA代碼
項目API code代碼
個人博客地址
如果有那個地方翻譯出錯或者失誤,請各位大神不吝賜教,小弟感激不盡
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92190.html
摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。 Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Prog...
摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。 Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Prog...
摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。 Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Prog...
摘要:在上一篇,介紹一下漸進式離線的文章中,我們討論了典型的應該是什么樣子的并且同時也介紹了。暴露了一個異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應該是什么樣子的并且同時也介紹了 server worker。到目前為止,我們已經緩...
閱讀 1297·2021-11-04 16:09
閱讀 3484·2021-10-19 11:45
閱讀 2396·2021-10-11 10:59
閱讀 1010·2021-09-23 11:21
閱讀 2762·2021-09-22 10:54
閱讀 1129·2019-08-30 15:53
閱讀 2600·2019-08-30 15:53
閱讀 3477·2019-08-30 12:57