Back

使用 Web Push API 实现推送通知

使用 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 — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay