12k
All articles

使用 Web Push API 实现推送通知

基于 Service Workers、VAPID 密钥与加密机制构建推送通知,涵盖订阅管理及各浏览器的具体实现要求。

OpenReplay Team
OpenReplay Team
使用 Web Push API 实现推送通知

推送通知能让用户即使在未主动使用 Web 应用时也保持参与度。Web Push API 使您能够直接向用户设备发送及时更新,而无需依赖第三方服务。本指南将展示如何使用 Service Workers、VAPID 身份验证和适当的加密来实现类原生通知。

核心要点

  • Web Push API 无需第三方服务即可实现原生推送通知
  • Service Workers 和 VAPID 身份验证是必要组件
  • HTTPS 连接是实现的强制要求
  • 不同浏览器对推送通知有不同的要求

前置条件和浏览器支持

在实现推送通知之前,请确保您的应用满足以下要求:

  • HTTPS 连接(Service Workers 所必需)
  • 现代浏览器支持(Chrome、Firefox、Edge、Safari 16.4+)
  • Service Worker 兼容性
// Feature detection
if ('serviceWorker' in navigator && 'PushManager' in window) {
  // Push notifications supported
}

注册 Service Worker

Service Worker 充当 Web 应用与推送服务之间的代理。在页面加载时注册它:

navigator.serviceWorker.register('/sw.js')
  .then(registration => {
    console.log('Service Worker registered:', registration);
  })
  .catch(error => {
    console.error('Registration failed:', error);
  });

设置 VAPID 密钥

VAPID(自愿应用服务器识别)用于向推送服务验证您的服务器身份。为您的应用生成一次密钥对:

# Using web-push library (Node.js)
npm install web-push
npx web-push generate-vapid-keys

将私钥安全地存储在服务器上,并在客户端订阅中使用公钥。

创建推送订阅

请求权限并为用户订阅推送通知:

async function subscribeToPush() {
  const registration = await navigator.serviceWorker.ready;
  
  // Request permission (only on user gesture)
  const permission = await Notification.requestPermission();
  if (permission !== 'granted') return;
  
  // Subscribe with VAPID public key
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
  });
  
  // Send subscription to your server
  await fetch('/api/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
}

// Helper function to convert base64 to Uint8Array
function urlBase64ToUint8Array(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;
}

订阅对象包含:

  • Endpoint: 用于发送消息的唯一 URL
  • Keys: 用于有效载荷安全的加密密钥(p256dh 和 auth)

服务器端实现

您的服务器必须加密消息并将其发送到推送服务端点。使用 web-push 库可以简化此过程:

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

webpush.setVapidDetails(
  'mailto:your-email@example.com',
  VAPID_PUBLIC_KEY,
  VAPID_PRIVATE_KEY
);

// Send notification
const payload = JSON.stringify({
  title: 'New Message',
  body: 'You have a new update',
  icon: '/icon-192.png',
  url: 'https://example.com/updates'
});

webpush.sendNotification(subscription, payload, {
  TTL: 86400, // 24 hours
  urgency: 'high'
})
  .catch(error => {
    console.error('Error sending notification:', error);
  });

在 Service Worker 中处理推送事件

Service Worker 接收并显示推送通知:

// sw.js
self.addEventListener('push', event => {
  const data = event.data?.json() || {};
  
  const options = {
    body: data.body || 'Default message',
    icon: data.icon || '/icon.png',
    badge: '/badge.png',
    vibrate: [200, 100, 200],
    data: { url: data.url }
  };
  
  event.waitUntil(
    self.registration.showNotification(data.title || 'Notification', options)
  );
});

// Handle notification clicks
self.addEventListener('notificationclick', event => {
  event.notification.close();
  
  event.waitUntil(
    clients.openWindow(event.notification.data?.url || '/')
  );
});

浏览器特定要求

不同浏览器对 Web Push API 实现有不同的要求:

  • Chrome/Edge: 每条推送消息都需要可见通知
  • Firefox: 允许有配额系统的有限静默推送
  • Safari: 需要可见通知且不支持静默推送

注意:有效载荷大小有限制:Chrome/Edge/Firefox 支持最多 4KB,而 Safari 支持 2KB。保持消息轻量化,如需要可在应用内获取额外数据。

始终在收到推送事件时立即显示通知,以在所有浏览器中保持权限。

安全最佳实践

保护您的推送通知实现:

  1. 保护端点安全: 切勿公开暴露订阅端点
  2. 加密有效载荷: 所有消息数据必须使用 ECDH 加密
  3. 保护 VAPID 密钥: 安全存储,仅在泄露时重新生成
  4. 实施 CSRF 保护: 验证订阅请求
  5. 速率限制: 通过服务器端限流防止滥用

管理订阅生命周期

处理订阅变更和过期:

// In Service Worker
self.addEventListener('pushsubscriptionchange', event => {
  event.waitUntil(
    self.registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
    })
      .then(subscription => {
        // Update server with new subscription
        return fetch('/api/update-subscription', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(subscription)
        });
      })
  );
});

总结

Web Push API 提供了一种基于标准的方法来实现推送通知,而无需供应商锁定。通过结合 Service Workers、VAPID 身份验证和适当的加密,您可以在现代浏览器中提供及时的通知。请记住尊重用户偏好,处理订阅生命周期事件,并遵循特定平台的要求以实现稳健的实现。

从基本的通知传递开始,然后随着实现的成熟添加操作按钮、图像附件和通知分组等功能。

常见问题

推送通知能在没有互联网连接的情况下工作吗?

不能,推送通知需要活动的互联网连接才能从推送服务接收消息。但是,Service Workers 可以缓存通知并在连接恢复时显示它们。

如果用户拒绝通知权限会发生什么?

当权限被拒绝时,您无法向该用户发送推送通知。您必须等待他们在浏览器设置中手动更改权限。考虑实施替代的参与方法,如应用内通知。

我可以在推送通知有效载荷中发送多少数据?

大多数推送服务将有效载荷大小限制为 4KB。Chrome 和 Firefox 支持最多 4KB,而 Safari 支持最多 2KB。保持有效载荷最小化,如需要可在收到通知时获取额外数据。

浏览器关闭时推送通知能工作吗?

是的,在大多数平台上,浏览器关闭时可以接收推送通知。但是,这取决于操作系统和浏览器。移动浏览器可能会根据电池优化设置有所限制。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.