Giới thiệu
Trong nhiều tổ chức, việc sử dụng các kết nối liên tục như WebSocket có thể bị hạn chế, nhưng đội ngũ vẫn cần nhận thông báo kịp thời ngay cả khi ứng dụng không được mở hoặc không được chú ý. Với Web Push — bao gồm Push API, Service Worker và VAPID — các máy chủ có thể gửi tin nhắn một cách đáng tin cậy mà không cần duy trì một kết nối liên tục, bao gồm cả khi trang đang ở chế độ nền hoặc đã đóng.
Tại sao chọn Web Push
- Hoạt động ở chế độ nền: Web Push sử dụng Service Worker để hiển thị thông báo gốc thông qua Notifications API, đảm bảo trải nghiệm người dùng nhất quán và tích hợp với hệ thống.
- Dựa trên tiêu chuẩn: Web Push yêu cầu HTTPS và sử dụng các khóa VAPID để đảm bảo máy chủ của bạn được xác định một cách an toàn với các dịch vụ đẩy thông báo.
Cách hoạt động của Web Push
- Ứng dụng đăng ký một Service Worker và yêu cầu quyền thông báo từ người dùng trên một nguồn gốc bảo mật.
- Ứng dụng đăng ký với Push Manager để nhận một điểm cuối đăng ký duy nhất và các khóa cho trình duyệt/thiet bị đó.
- Máy chủ lưu trữ các đăng ký và gửi payload được ký với VAPID bằng một thư viện nhẹ sau đó.
- Service Worker nhận sự kiện đẩy và ngay lập tức hiển thị thông báo gốc.
Client: Đăng ký SW và đăng ký nhận thông báo
javascript
// Chuyển đổi khóa công khai VAPID từ base64 sang Uint8Array
function base64ToUint8Array(base64) {
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
const b64 = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(b64);
const output = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; ++i) output[i] = raw.charCodeAt(i);
return output;
}
async function subscribeToPush(vapidPublicKeyBase64) {
const registration = await navigator.serviceWorker.register('/sw.js');
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(vapidPublicKeyBase64),
});
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
}
Ứng dụng đăng ký thông qua Push API trong một ngữ cảnh bảo mật và gửi đăng ký kết quả đến backend để sử dụng sau này.
Service Worker: Nhận và thông báo
javascript
// /sw.js
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : { title: 'Cập nhật', body: 'Thông báo mới' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png',
data: data.url || '/',
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data || '/';
event.waitUntil(clients.openWindow(url));
});
Service Worker xử lý payload sự kiện đẩy và hiển thị thông báo gốc bằng cách sử dụng Notifications API.
Máy chủ (Node/Express): VAPID và gửi tin nhắn
javascript
// npm i express web-push
import express from 'express';
import webpush from 'web-push';
const app = express();
app.use(express.json());
// 1) Cấu hình VAPID (tạo một lần và thiết lập qua env)
webpush.setVapidDetails(
'mailto:admin@example.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
);
// 2) Lưu trữ đăng ký (thay thế bằng MongoDB trong sản xuất)
const subscriptions = new Map();
app.get('/api/push/public-key', (_req, res) => {
res.json({ publicKey: process.env.VAPID_PUBLIC_KEY });
});
app.post('/api/push/subscribe', (req, res) => {
const sub = req.body;
subscriptions.set(sub.endpoint, sub);
res.status(201).json({ ok: true });
});
app.post('/api/push/send', async (req, res) => {
const payload = JSON.stringify({
title: 'Cập nhật chính sách',
body: 'Nhấn để xem các thay đổi',
url: '/inbox',
});
const results = [];
for (const sub of subscriptions.values()) {
try {
await webpush.sendNotification(sub, payload);
results.push({ ok: true });
} catch {
results.push({ ok: false });
}
}
res.json({ sent: results.length });
});
app.listen(3000, () => console.log('Máy chủ đang chạy trên cổng 3000'));
Thư viện web-push ký payload bằng VAPID và gửi đến mỗi điểm cuối đăng ký đã lưu, cho phép máy chủ gửi tin nhắn mà không cần duy trì một kết nối liên tục.
Thực tiễn tốt nhất
- Yêu cầu quyền thông báo vào thời điểm có ý nghĩa: Điều này giúp tránh việc mệt mỏi khi nhận thông báo và cải thiện tỷ lệ đồng ý.
- Đăng ký có thể hết hạn: Xử lý các lỗi gửi bằng cách loại bỏ các điểm cuối không hợp lệ và đăng ký lại khi cần thiết.
- Push yêu cầu HTTPS và các ngữ cảnh bảo mật: Giữ an toàn các khóa VAPID và tái sử dụng cùng một cặp khóa qua các môi trường triển khai theo chính sách.
Những cạm bẫy thường gặp
- Không yêu cầu quyền thông báo đúng thời điểm: Điều này có thể dẫn đến việc người dùng không muốn nhận thông báo.
- Không xử lý hết hạn đăng ký: Điều này có thể khiến bạn không gửi được thông báo đến người dùng.
Mẹo tối ưu hóa hiệu suất
- Giảm kích thước payload: Sử dụng payload nhỏ gọn để tiết kiệm băng thông và thời gian gửi.
- Cách ly xử lý thông báo: Xử lý thông báo trong background để không làm chậm trải nghiệm người dùng.
FAQ
Q: Tôi có thể sử dụng Web Push mà không cần HTTPS không?
A: Không, Web Push yêu cầu HTTPS để bảo đảm an toàn cho dữ liệu.
Q: Thời gian sống của một subscription là bao lâu?
A: Subscription có thể hết hạn, vì vậy nên theo dõi và xử lý khi cần.
Kết luận
Nếu WebSocket không phải là lựa chọn khả thi, Web Push cung cấp khả năng gửi tin nhắn đáng tin cậy, an toàn và hoạt động trong nền với kích thước nhỏ — hoàn hảo cho những thông báo quan trọng trong các môi trường bị hạn chế. Hãy thử triển khai Web Push cho ứng dụng của bạn ngay hôm nay để cải thiện trải nghiệm người dùng!
Hãy bắt đầu với Web Push ngay hôm nay!