浏览器通过Notification API提供了消息通知的能力,由浏览器为载体,发送系统通知,可以在不安装APP的情况下进行消息推送,⚠️消息推送需要在HTTPS环境中使用。
熟悉API
这里需要用到两个API,一个用于浏览器触发通知,另一个用于注册Google的推送服务。
Notification
构造函数
new Notification(title[, options])
执行一个构造函数就会发送一条通知,返回一个通知实例用于控制通知的关闭以及设置句柄,如果在ServiceWorker中发送通知需要通过ServiceWorkerRegistration.showNotification
方法来发送通知,参数是一样的。
参数比较多,详情可见MDN——Notification,这里列举一些常用的参数。
- title:消息的标题
- options:设置通知的自定义内容
- icon:一个图标URL字符串,用于展示消息图标⚠️图标会有跨域限制
- body:消息主体内容
- requireInteraction:布尔值,控制消息是否会自动折叠
new Notification('通知标题', {
body: '通知内容',
icon: 'http://localhost:8000/favicon.ico',
})
上面的代码会显示这样的通知。
静态属性
Permission
,只读属性,用于表明当前用于是否授权当前网站发送通知,可能的值有:
- granted:用户已经授权通知。
- denied:用户拒绝通知。
- default:未知,即没有授权也没有拒绝过通知。
静态方法
requestPermission()
,用于向用户请求通知授权,返回一个Promise对象,值的内容和静态属性Notification.permission
的值一样。
实例方法
close()
,用于关闭或移除当前通知,⚠️即使消息被收到托盘中也会被移除,一般用于移除过期的通知,例如用户已经在页面中阅读了消息内容。
事件
click
,点击消息时触发
close
,关闭消息时触发
error
,调用通知出错时触发,用于阻止错误的消息
show
,消息显示时触发
PushManager.subscribe
返回一个 Promise
形式的 PushSubscription
对象,该对象包含了推送订阅详情。如果当前 service worker 没有已存在的订阅,则会创建一个新的推送订阅。
PushManager.subscribe(options)
userVisibleOnly
: 布尔值,表示返回的推送订阅将只能被用于对用户可见的消息。- applicationServerKey:推送服务器用来向客户端应用发送消息的公钥。
返回值一个 PushSubscription
的Promise对象,格式如下
{
"endpoint": "https://fcm.googleapis.com/fcm/send/fsD_4pKPPs4:APA91bHHlK6AupiRhvpnvO01jD_4b-HnSDDbQ0Pz17njO0……",
"expirationTime": null,
"keys": {
"p256dh": "BGLh9okhkFg_KNyoWJ-fdVTThfOeUvda9-pRxXAaCT5nSXjzXu_oxj5isY9v……",
"auth": "Uld6AooHCyfr75_uYc……"
}
}
实战练习
实战部分我们来实现一个前端+后端的消息推送,底层利用了Google FCM(Chrome 浏览器内置),后端推送部分利用web-push
这个工具库来完成。
前端部分
const PUBLIC_KEY = "和后端一样的公钥";
/** @type PushSubscription */
let subscription;
(async function () {
const register = await navigator.serviceWorker.register("./worker.js", {
scope: "/",
});
subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: PUBLIC_KEY,
});
})();
async function send() {
const value = document.getElementById('message')?.value
await fetch("/api/notice", {
method: "POST",
body: JSON.stringify({ subscription, title: '系统通知', message: value }),
headers: {
"Content-Type": "application/json",
},
});
}
service worker用于接收消息通知,并弹出消息提醒,这里利用的是push
事件。
self.addEventListener('push', function (e) {
const data = e.data.json();
self.registration.showNotification(
data.title,
{
body: data.message,
}
);
})
后端部分
初始化一个Koa环境,除了提供接口服务之外,也把上面的前端资源通过静态文件服务器进行提供。
$ npm init -y
$ pnpm install koa @koa/router koa-static koa-bodyparser web-push -S
依赖安装完成之后把基础的Koa代码写一下,把刚才的前端部分的代码放到client目录
import Koa from 'koa'
import serve from 'koa-static'
import router from './router.js'
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import bodyParser from 'koa-bodyparser'
import Router from '@koa/router'
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = new Koa()
const router = new Router({
prefix: '/api' // 设置统一前缀
});
app.use(bodyParser())
app.use(serve(resolve(__dirname, '../client')))
app.use(router.routes())
.use(router.allowedMethods())
app.listen(3001, () => {
console.log('server is running at http://127.0.0.1:3001')
})
现在环境已经完善了,然后来写一个接口,用于下发通知,这里就要利用web-push的能力了。
const PUBLIC_KEY = "公钥"
const PRIVATE_KEY = "私钥"
webpush.setVapidDetails("mailto:1984779164@qq.com", PUBLIC_KEY, PRIVATE_KEY);
router.post('/notice', (ctx, next) => {
const { title, message, subscription } = ctx.request.body
webpush.sendNotification(subscription, JSON.stringify({ title, message }));
ctx.status = 200;
ctx.body = { success: true, data: true }
})
这里实现的是当接口被请求时,对当前请求接口的设备进行推送,需要根据实际的业务调整,这里仅作示例。
公钥和私钥可以用web-push提供的命令行工具生成:
web-push generate-vapid-keys
。如果通知下发失败,可以等一会儿看下控制台是否有超时的报错,毕竟用的是google的服务。
或者可以使用FCM SDK来进行推送。
常见问题
正确的代码,但是没有触发通知
这种情况多半是浏览器没有获取到通知权限,浏览器都没有通知权限怎么把能力放给网站呢。Mac系统下需要在【系统设置】->【通知】中打开浏览器通知的权限
Windows系统搜索【通知和操作】,在找到的设置页面打开浏览器通知权限即可。
消息图标未显示
大概率是由于浏览器的跨域策略进行限制,net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep 200 (OK)
,出现类似报错即为跨域限制,将提供图片的服务器开启CORS或者设为同源即可解决。