本文是客户端和服务器端通信最后一个系列,主要介绍h5的桌面通知和web push,h5的Notification主要用于向用户展示通知,而web push 主要用于订阅推送消息。
关于h5的Notification已经不是什么新的技术,但是最新chrome浏览器也是只有在https协议下面才有效。下面简单介绍一下Notification
let myNotification = new Notification(title, options);
一、 title 定义一个通知的标题,当它被触发时,它将显示在通知窗口的顶部。
二、 options 可选 options对象包含应用于通知的任何自定义设置选项。选项有:
dir: 显示通知的方向。默认是auto,跟随浏览器语言设置行为,你也可以通过设置ltr和rtl的值来覆盖该行为(虽然大多数浏览器似乎忽略这些设置)
lang: 通知的语言,如使用代表一个BCP 47语言标签的 DOMString 指定的。请参阅Sitepoint ISO 2字母语言代码页面,以获得简单的参考。
badge: 一个 USVString 包含用于表示通知的图像的URL, 当没有足够的空间来显示通知本身时。
body: 一个 DOMString 表示通知的正文,将显示在标题下方。
tag: 一个 DOMString 代表通知的 一个识别标签。
icon: 一个 USVString 包含要在通知中显示的图标的URL。
image: 一个 USVSTring包含要在通知中显示的图像的URL。
data: 您想要与通知相关联的任意数据。这可以是任何数据类型。
vibrate: 一个振动模式 vibration pattern 设备的振动硬件在通知触发时发出。
renotify: 一个 Boolean 指定在新通知替换旧通知后是否应通知用户。默认值为false,这意味着它们不会被通知。
requireInteraction: 表示通知应保持有效,直到用户点击或关闭它,而不是自动关闭。默认值为false。
以下选项列在最新规范中,但是目前浏览器可能尚未支持,大家可以随时尝试!
silent: 一个 Boolean 指明通知是否应该是无声的,即,不需要发出声音或振动,无论设备设置如何。默认值为false,这意味着它不会保持静默。
sound:一个 USVString 包含通知触发时要播放的音频文件的URL。
noscreen: 一个 Boolean 指定通知触发是否应启用设备的屏幕。 默认值为false,这意味着它将启用屏幕。
sticky: 一个 Boolean 指明通知是否应该是“粘”, 即不易被用户清理。默认值为false,这意味着它不会粘。
案例
function spawnNotification(theBody,theIcon,theTitle) {
var options = {
body: theBody,
icon: theIcon
}
var n = new Notification(theTitle,options);
}
用户订阅了一个站点的 Web Push 服务后,即使用户关闭了浏览器,一旦站点主动发送推送消息,用户都能收到,只要你的电脑是开着的。这是目前谷歌和苹果在 Chrome 和 Safari 上都力推的一种全新推送服务,Firefox最近也加入了这个阵营。
一、 可以完美替代原来的Email订阅服务,因为 Email 订阅这个动作要用户主动发起,不管你在页面内放了多显眼的订阅标志,都得用户去主动点击,填写自己 Email 地址才行。而且,如果不使用邮件客户端,很多订阅的 Email 发出去犹如石沉大海,到达率很低。
Web Push 完全由浏览器开发商的标准协议发起,一旦用户访问开通了 Web Push 的站点 ,浏览器就会主动询问你是否要订阅,弹出的订阅框也都是浏览器开发者的标准规范。首先,它增加了用户订阅的可能性。
二、 一旦用户点阅,只要你打开电脑,就会收到推送通知,没有邮件订阅时用户主动去查收邮件的问题,大大增加了订阅推送到达率。
三、 Web Push 让站点为主,APP 为辅的网站可以摆脱 APP 安装量少的困扰,访问你站点的人,只有一小部分会安装你的 APP,而Web Push 不需要安装任何 APP 在电脑上,只要用户点击一次Allow来订阅消息即可。
1、 Web Push 使用了 GCM 服务,而 GCM 在国内基本上很难连通,国内用户除非翻了,否则压根不可能订阅成功 Chrome 的 Web Push。
2、 谷歌要求 Chrome 的 Web Push 必须用 https 加密传输,国内目前很多网站还在使用http协议。 所以目前国内web push 使用较少。
1、客户端完成请求订阅一个用户的逻辑 2、服务端调用遵从web push协议的接口,传送消息推送(push message)到推送服务器(该服务器由浏览器决定,开发者所能做的只有控制发送的数据) 3、推送服务器将该消息推送至对应的浏览器,用户收到该推送
第一步, 客户端请求订阅用户,过程如下:
说明一下这三步,在第一步之前,应用服务器需要生成应用服务器密钥(application server keys),其作用是标识该服务器,保证每次发消息推送的都是同一个服务器。然后,客户端将会请求用户授权消息推送,一旦用户授权,浏览器就会生成一个PushScription,然后这个PushScription将会被发送至服务器,存入数据库,在后面的消息推送中使用。
第二步, 应用服务器发送web push协议标准的api,触发推送服务器的消息推送,其中headers必须配置正确,且传送的数据必须是比特流。
应用服务器发送消息推送请求(目的是为了将更新推送到用户的浏览器),为了向推送服务器发出请求,需要查看先前获得的PushScription,取出其中的endpoint,即为推送服务器配置给该用户的访问点。
一个PushScription对象如下:
{
"endpoint": "https://random-push-service.com/some-kind-of-unique-id-1234/v2/",
"keys": {
"p256dh" :
"BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM=",
"auth" : "tBHItJI5svbpez7KI4CCXg=="
}
}
其中的endpoint包含了推送服务器域名,path后面的部分为推送服务器为每个用户分配的一个标识符。
发送数据时,数据必须编码(出于安全性考虑)。推送服务器在接收到这样一个请求之后,立即开始监听用户浏览器是否处于在线状态,若是,则将消息推送发送至浏览器。
第三步, 浏览器端接收消息推送,触发push事件并展示
浏览器在接收到推送服务器发来的推送后,将其解码并触发一个push事件。Service Worker由于它可以在浏览器页面未打开,浏览器未打开时执行,因此一般选择它完成web push的最后一步,即响应push事件完成展示通知等业务逻辑。
1、 按照上一部分所说,首先进行用户订阅。 首先注册一个Service Worker,若注册成功,返回的Promise为resolve状态,如下:
function registerServiceWorker() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
}
(关于Service Worker,这方面的详细文章,大家可以自行网上搜索。)
2、 随后测试window环境下是否有Notification对象(此处以chrome为例,若使用firefox,uc等浏览器,需要遵循其相应标准,调用对应对象方法或引入JS SDK包), 测试成功,调用Notification.requestPermission请求用户授权发送推送,若授权成功,将会返回'granted'。
3、 接下来要做的就是使用注册好的Service Worker对象,调用pushManager.subscribe方法,从客户端获得刚刚所说的PushScription对象。
function subscribeUserToPush() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
1、userVisibleOnly是为了保证推送对用户可见,
2、application server key则如前文所说,是推送服务器用以识别应用服务器的密钥,这里的密钥包含了公钥和私钥,传输的是公钥。
3、同时,PushScription的endpoint也是在这个过程中生成的,生成公钥和私钥可以使用web-push库。
这里再次说明一下推送服务器的不可选择性,在调用subscribe生成PushScription时,浏览器会向它指定的中转服务器发送请求来生成endpoint和其余部分,这是没法控制的。
4、PushScription中的auth和p256dh是用来控制带载荷的push message的。
4、 获取到PushScription对象后,将其发往应用服务器,此处简化了存储,使用nedb存下PushScription并返回Promise:
function saveSubscriptionToDatabase(subscription) {
return new Promise(function(resolve, reject) {
db.insert(subscription, function(err, newDoc) {
if (err) {
reject(err);
return;
}
resolve(newDoc._id);
});
});
};
5、 存储完毕后,接下来就是开发后台管理逻辑,使得管理员能够触发向用户推送消息的事件,应用服务器所做的逻辑就是遍历在数据库中存储的所有PushScription并推送消息,以下是使用web-push库完成配置密钥及联系邮箱的示例:
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls'
};
webpush.setVapidDetails(
'mailto:web-pust-test@liaobu.de',
vapidKeys.publicKey,
vapidKeys.privateKey
);
6、 不要忘了配置你在谷歌云服务(例如FCM)申请到的GCMApiKey:
webpush.setGCMAPIKey('<Your GCM API Key Here>');
7、 配置完成后,就可以将subscription发送出去,使用web-push的sendNotification接口:
webpush.sendNotification(pushSubscription, 'Your Push Payload Text');
推送服务器发送消息后,会触发浏览器的push事件,为了控制service worker的逻辑,需要使用event.waitUntil方法,此方法接收一个promise参数,在promise变为resolved状态后,浏览器就会检查通知是否已被展示,若是,则关闭service worker。
如果不处理未正常执行的promise,部分浏览器如chrome会展示默认消息框:
展示一个通知调用的为showNotification方法,传的参数包括title等,如下:
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
而展示notification时,除了控制它的视图层以外,也可以控制它的逻辑层, 例如点击消息通知后进行某些操作等等,在先前调用showNotification时可以传入一些参数, 例如,根据不同的action执行不同的操作:
self.addEventListener('notificationclick', function(event) {
if (!event.action) {
// Was a normal notification click
console.log('Notification Click.');
return;
}
switch (event.action) {
case 'coffee-action':
console.log('User ❤️️\'s coffee.');
break;
case 'doughnut-action':
console.log('User ❤️️\'s doughnuts.');
break;
case 'gramophone-action':
console.log('User ❤️️\'s music.');
break;
case 'atom-action':
console.log('User ❤️️\'s science.');
break;
default:
console.log(`Unknown action clicked: '${event.action}'`);
break;
}
});
与ajax轮询、http长连接、WebSocket的对比
不同浏览器兼容性
在注册完service worker后,调用subscribe询问用户是否允许接收通知,如下代码所示:
navigator.serviceWorker.register("sw-4.js").then(function(reg){
console.log("Yes, it did register service worker.");
if (window.PushManager) {
reg.pushManager.getSubscription().then(subscription => {
// 如果用户没有订阅
if (!subscription) {
subscribeUser(reg);
} else {
console.log("You have subscribed our notification");
}
});
}
}).catch(function(err) {
console.log("No it didn't. This happened: ", err)
});
上面代码在发起订阅前先看一下之前已经有没有订阅过了,如果没有的话再发起订阅。
发起订阅的subscribeUser实现如下代码所示:
function subscribeUser(swRegistration) {
const applicationServerPublicKey = "BBlY_5OeDkp2zl_Hx9jFxymKyK4kQKZdzoCoe0L5RqpiV2eK0t4zx-d3JPHlISZ0P1nQdSZsxuA5SRlDB0MZWLw";
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
// 用户同意
.then(function(subscription) {
console.log('User is subscribed:', JSON.stringify(subscription));
jQuery.post("/add-subscription.php", {subscription: JSON.stringify(subscription)}, function(result) {
console.log(result);
});
})
// 用户不同意或者生成失败
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
});
}
subscribe传两个参数,一个是userVisibleOnly,这个表示消息是否必须要可见,如果设置为不可见,Chrome将会报错:
Chrome currently only supports the Push API for subscriptions that will result in user-visible messages. You can indicate this by calling pushManager.subscribe({userVisibleOnly: true}) instead. See https://goo.gl/yqv4Q4 for more details.
但其实这个并不影响,我们设置成true,但是收到消息后可以不用弹框,可以调postMessage去通知页面做相应的操作。
第二个参数applicationServerKey是服务端的公钥,这个可以用web push的Node包生成,先安装一个:
npm install web-push --save
然后用以下代码生成:
const webpush = require('web-push');
//VAPID keys should only be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys.publicKey, vapidKeys.privateKey);
每运行一次就会生成一对新的密钥对,如:
publicKey: BMgkd1qfOfI6vFBbxIFMgdxDGC6-j8NYTwF_MXOIZ-St9lPhhMdPuUyFfwg1DLY59WP0FEaX84ZJRwgztdpfBHs
privateKey: LUeSF8DCv-NBxIfaeWeKTux858H45_V75vT0zZQLEbY
公密钥只要能配套就好,公钥在浏览器端使用,用来生成subscription,密钥在服务端使用,用来发Push。
如果用户同意浏览器就会向FCM服务请求生成subscription,然后执行Promise链里的then,返回该subscription,在这个then里面把这个subscription发给服务端存起来。
反之,如果用户不同意,或者用户无法连到FCM的服务器将会抛异常:DOMException: Registration failed - push service error
生成的subscription大概长这样:
{"endpoint":"https://fcm.googleapis.com/fcm/send/ci3-kIulf9A:APA91bEaQfDU8zuLSKpjzLfQ8121pNf3Rq7pjomSu4Vg-nMwLGfJSvkOUsJNCyYCOTZgmHDTu9I1xvI-dMVLZm1EgmEH0vDA7QFLjPKShG86W2zwX0IbtBPHEDLO0WgQ8OIhZ6yTnu-S","expirationTime":null,"keys":{"p256dh":"BAdAo6ldzRT5oCN8stqYRemoihPGOEJjrUDL6y8zhdA_swao_q-HlY_69mzIVobWX2MH02TzmtRWj_VeWUFMnXQ=","auth":"SS1PBnGwfMXjpJEfnoUIeQ=="}}
FCM官方是这么介绍的:
Firebase 云信息传递 (FCM) 是一种跨平台消息传递解决方案,可供您免费、可靠地传递消息。
使用 FCM,您可以通知客户端应用存在可同步的新电子邮件或其他数据。您可以发送通知消息以再次吸引用户并促进用户留存。在即时消息传递等使用情形中,一条消息可将最大 4KB 的有效负载传送至客户端应用。
FCM是一种可靠的消息传递平台,它最大的优点是同一套Push机制可以在IOS/Android/Web三端使用:
这个意义是很大的,因为Android的推送一直都比较乱,国内有些APP使用小米的Push服务,有些使用百度的,还有些使用腾讯的信鸽等等,这些Push都需要在后台运行线程,并且不能休眠,这就导致了手机在休眠状态时仍然有很多线程在运行着,使得手机耗电速度很快。最后还直接导致今年工信部出台要成立安卓统一推送联盟。 而苹果有一套统一的推送机制,大家把Push发给苹果的服务器,然后再由苹果下发给相应的苹果设备。Safari现在不支持Service Worker,但是可以用Apple Push,缺点是这种推送苹果说不能用来发送重要的数据,并且目测只能弹框显示,没办法在后台处理消息而不弹框。
发送推送可以用FCM提供的web push的库,它支持多种语言,包括Node.js/PHP等版本。 用Node.js可以这样发Push:
const webpush = require('web-push');
// 从数据库取出用户的subsciption
const pushSubscription = {"endpoint":"https://fcm.googleapis.com/fcm/send/ci3-kIulf9A:APA91bEaQfDU8zuLSKpjzLfQ8121pNf3Rq7pjomSu4Vg-nMwLGfJSvkOUsJNCyYCOTZgmHDTu9I1xvI-dMVLZm1EgmEH0vDA7QFLjPKShG86W2zwX0IbtBPHEDLO0WgQ8OIhZ6yTnu-S","expirationTime":null,"keys":{"p256dh":"BAdAo6ldzRT5oCN8stqYRemoihPGOEJjrUDL6y8zhdA_swao_q-HlY_69mzIVobWX2MH02TzmtRWj_VeWUFMnXQ=","auth":"SS1PBnGwfMXjpJEfnoUIeQ=="}};
// push的数据
const payload = {
title: '一篇新的文章',
body: '点开看看吧',
icon: '/html/app-manifest/logo_512.png',
data: {url: "https://www.rrfed.com"}
//badge: '/html/app-manifest/logo_512.png'
};
webpush.sendNotification(pushSubscription, JSON.stringify(payload));
经实验,在大多数情况下这个延迟基本在1s以内,这边刚按下回车运行完,那边浏览器就收到了,但是有时候会发送失败(国内网络问题?)。 如果这个代码要在服务端运行的话,那么你应该需要一台中国香港的服务器。假如把发Push的数据和服务放在中国香港的服务器,需要发Push的时候由华北的服务器做个中转向这台服务器发请求。 只要用户能连上FCM那就可以愉快地发Push了,如果用户连不上那就没办法。
用运行在后台的Service Worker接收,监听push事件:
this.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();
const title = notificationData.title;
// 可以发个消息通知页面
//util.postMessage(notificationData);
// 弹消息框
event.waitUntil(self.registration.showNotification(title, notificationData));
});
主要是调用showNotification进行弹框,或者是使用postMessage通知页面相应地做些处理。 经实验,如果用户关闭了浏览器,在关闭期间如果有Push的话等到用户重新打开浏览器会再弹出来。 然后用户可以点击弹出来的框打开一个指定的页面,这个需要监听notificationclick事件:
this.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
let notification = event.notification;
notification.close();
event.waitUntil(
clients.openWindow(notification.data.url)
);
});
调用clients.openWindow打开一个新的页面,这样就基本完成了一个push推送的搭建。
Service Worker让我们在Web端也能有像原生APP一样的Push通知,使得Web端越来越像原生APP端,随着HTML5的其它新功能如WebAssembly提高运行速度,WebWorker多线程支持,数据库支持大量数据的管理和支持,Websocket进行实时通信,WebRTC进行P2P多媒体传输,还有WebGL、新进的WebVR等,使得在浏览器端能够做的事情越来越多,体验越来越丰富,而且这种Web APP还是跨平台的。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有