朋也的博客 » 首页 » 文章
作者:朋也
日期:2019-05-27
类别:javascript学习笔记
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
上一篇博客介绍了PWA的用法,最后留了个serviceWorker用法没有说,这一篇来介绍一下
先上图
先说一下流程:生成公/私钥 -> 浏览器中使用公钥注册服务 -> 服务端使用私钥推送 -> 浏览器中接收推送展示
流程图网上非常多,我这就不截图展示了
我这使用nodejs语言来实现,其它语言版本也有,不过没有nodejs用起来方便
创建一个文件夹 push-demo
在文件夹内安装 web-push
包依赖,然后创建一个 test.js
加上下面代码,运行即可
const webpush = require("web-push");
// 生成公钥私钥
const vapidKeys = webpush.generateVAPIDKeys(); // 1.生成公私钥
console.log(vapidKeys);
结果如下
{ publicKey:
'BK_LXU7DEQ5rAfk8kCNsa-5E9ntYKUoBemBjO-ZaEjpK3PFKCzpUAfYqlk1p2wmbsNBnYSRALpyddMDhHuspPag',
privateKey: 'bC665pacSeHbHv9iY4H0106oUTJWTTyUAsbwEnXX1Mg' }
有了公钥,就可以在浏览器里通过代码注册服务了,代码如下,注释都写好了,看一下就明白了
function urlB64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
const publicKey =
"BK_LXU7DEQ5rAfk8kCNsa-5E9ntYKUoBemBjO-ZaEjpK3PFKCzpUAfYqlk1p2wmbsNBnYSRALpyddMDhHuspPag";
// 判断浏览器是否支持serviceWorker,支持的话,注册服务
if ("serviceWorker" in navigator) {
send().catch(err => console.error(err));
}
async function send() {
console.log("Registering service worker...");
// 这里要注册serviceWorker,要提前写一个js,(注意路径),后面接收推送就是在这个js里通过代码实现的
// 这个js创建好就可以了,暂时不需要写东西
navigator.serviceWorker.register("/sw.js").then(register => {
// 判断浏览器是否有推送服务支持
if (window.PushManager) {
register.pushManager.getSubscription().then(subscription => {
// 如果用户没有订阅
if (!subscription) {
// 调用浏览器api,提示用户是否开启网站推送服务
register.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(publicKey) // 密钥还要将其转换一下,否则会报错,转换的代码是固定的,我这里用的就是网上找的
})
.then(subscription => {
// 用户同意接收网站的消息推送,则发送一个请求跟服务端创建连接
fetch("/subscription", {
method: "post",
body: JSON.stringify(subscription),
headers: {
"content-type": "application/json"
}
});
})
.catch(err => console.log("用户没有同意开启通知...", err));
} else {
fetch("/subscription", {
method: "post",
body: JSON.stringify(subscription),
headers: {
"content-type": "application/json"
}
});
console.log("You have subscribed our notification");
}
});
}
});
}
原接文链: https://atjiu.github.io/2019/05/27/pwa-service-worker/
如果浏览器端用户同意了接收推送服务的功能,然后发起了一个跟服务器保持连接的请求,服务端要做相应的处理,将传过来的数据持久化,后面发送推送要用到
浏览器如果同意,然后发送一个连接的请求,传给服务端的数据长这个样
如果是Chrome浏览器,则长下面这个样
{
endpoint:
"https://fcm.googleapis.com/fcm/send/cZdEewi209A:APA91bGo9U4c9ycxJkeIGRlpR-sl0eKafNvtLSVJd_kW--IDe7LY-N-YCsVqC4oLBE1DEKD6JHvVMuNxFq3ANgw9orH1Eo3-JLTRodNyYYn-5Xzz0IM80cfBFuDXWby8gdVudLMb96_j",
expirationTime: null,
keys: {
p256dh:
"BGMG0WgSZueddroRLDxxwFezTTtgby1tT7mJnTv-BDynkTAo_hjroyTfuWa6zbpr6R9xkqPt-rN3mvZnIPwMwpk",
auth: "wWYPAHPDCnAE8y6oTPWj-w"
}
}
如果是firefox浏览器则长下面这个样
{
endpoint:
"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABc57EZB3ZRqMvOUXfFkXY_CXowfxF45Kr76pyMPTeA6vCgEBWieKYC2dwoqsXRkibhavFh9xJwK1llZwnDfCzGIEiMJxFMgQcHp69meonmL1Wlu8ij_MTwlsf54Y-die8D9wIpMjkjnl9wiWVezcapOFBSRzbBA_nfwUtHpkTBG1w6iXs",
keys: {
auth: "4kTmhdWGaWLoZReISaXj1w",
p256dh:
"BKadOZ_MoS_GTdtYMhuCdWoO5S_gcerg71dD-Pk0YpHXpzyD5JKLS9FsWPhPXWgB1Ca0H74cRsUlufruMNzc5Mo"
}
}
我这里是将其入库,有推送的时候,直接查询然后调用推送方法直接推送消息
exports.subscription = async (ctx) => {
// 获取user-agent用于判断浏览器
const user_agent = ctx.headers['user-agent'];
// 获取订阅的信息,入库时将其转成字符串保存即可
const subscription = ctx.request.body;
const user = ctx.session.user;
let chrome_subscription, firefox_subscription;
if (user_agent.indexOf('Chrome') > -1) chrome_subscription = subscription;
if (user_agent.indexOf('Firefox') > -1) firefox_subscription = subscription;
await user_service.update_user_subscription_by_id(
user.id,
JSON.stringify(chrome_subscription),
JSON.stringify(firefox_subscription)
);
ctx.response.status = 201;
ctx.body = {};
};
众所周知,国内google的服务是请求不通的,所以我这用firefox的推送服务来演示
首先在上面注册时写的sw.js
文件里实现接收推送消息的代码
原接链文: https://atjiu.github.io/2019/05/27/pwa-service-worker/
// 接收推送消息事件
self.addEventListener("push", function (event) {
console.log("[Service Worker] Push Received.");
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
let notificationData = event.data.json();
// 弹消息框
event.waitUntil(
self.registration.showNotification(notificationData.title, notificationData)
);
});
// 通知被点击了的事件,比如打开一个链接之类的
self.addEventListener("notificationclick", function (event) {
console.log("[Service Worker] Notification click Received.");
let notification = event.notification;
notification.close();
// event.waitUntil(self.clients.openWindow(notification.action));
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(
self.clients
.matchAll({
type: "window"
})
.then(function (clientList) {
for (let i = 0; i < clientList.length; i++) {
let client = clientList[i];
if (client.notification.url === "/" && "focus" in client) {
// return client.focus();
return self.clients.openWindow(client.notification.url);
}
}
if (self.clients.openWindow && notification.data.url) {
return self.clients.openWindow(notification.data.url);
}
})
);
});
服务端推送消息
const webpush = require("web-push");
let publicKey = 'BK_LXU7DEQ5rAfk8kCNsa-5E9ntYKUoBemBjO-ZaEjpK3PFKCzpUAfYqlk1p2wmbsNBnYSRALpyddMDhHuspPag';
let privateKey = 'bC665pacSeHbHv9iY4H0106oUTJWTTyUAsbwEnXX1Mg';
webpush.setVapidDetails(
"mailto:py2qiuse@gmail.com",
publicKey,
privateKey
);
const firefoxSubscription = {
endpoint:
'https://updates.push.services.mozilla.com/wpush/v2/gAAAAABc61hnBVOJZIOLZgSN7x8Pom_AKoVvE4yAaCi0wnWaXv05FAVGx2tlMSPAkKCaj_DnYDzmyWRJGwofpuYuXDiDPX2RSNUmTiXfgTP55t9c82UzFdeZvvkvqr2vdD-H1QIfIGd3LJzh58Pd6SRey2su91F160ilKsz_3AOzGAEv_FloXeY',
keys:
{
auth: '9-beSGAEs_f5JM4Dkit-vw',
p256dh:
'BEfxlwj1XLIEBWFa-Bc7TS2aMQLcJK9H0mSbl5OSkN1vFPAEJJZRfeHDpz09JDUx6QhA9YG7Wjm7d7dXq10ixQ4'
}
};
// push的数据
const payload = {
title: "测试推送",
body: "推送内容,一串链接,点击打开",
icon: "https://yiiu.co/favicon.ico",
data: {url: "https://atjiu.github.io/pybbs/", text: 'hello world'},
};
// 发送通知
webpush.sendNotification(firefoxSubscription, JSON.stringify(payload));
运行代码,firefox上就可以收到一条推送消息了
补充:payload的数据格式这见个是固定的,如果你想自定义一些数据,可以写在data对象里,在sw.js里点击事件里可以获取到,然后做相应的处理
sw.js里的点击通知事件回调参数里有一个 notification 对象,这个对象就是在 接收通知事件里打开通知的方法中传入的第二个参数对象,如果传的是服务端推送的消息对象,那也就是payload对象,可以直接拿data中的url通过调用 openWindow()方法打开这个链接
不得不说,还是chrome方便,可以直接对sw.js里的代码进行调试,在firefox上开发调试真心麻烦 。。。