JavaScript Push API и уведомления
Push API в JavaScript — незаменимый инструмент для разработчиков, которые хотят добавить в веб-приложения уведомления в реальном времени.
Введение в Push API в JavaScript
Push API в JavaScript позволяет веб-приложению получать сообщения, отправленные с сервера, даже когда страница закрыта или браузер работает в фоновом режиме. Это основа веб-пуш-уведомлений: новостные оповещения, сообщения в чате и напоминания о повторном вовлечении, которые приходят без необходимости держать вкладку открытой.
Push API никогда не работает в одиночку. Он опирается на две другие возможности браузера:
- Service Worker — фоновый скрипт, который остаётся активным после закрытия страницы и получает push-сообщения.
- Notifications API — то, что service worker использует для фактического отображения сообщения пользователю.
На этой странице описан полный клиентский процесс: регистрация service worker, запрос разрешения, подписка с VAPID-ключом, получение push-сообщения в service worker и отображение уведомления. Здесь также объясняется роль сервера.
Как работает push-процесс
Путь одного push-сообщения от начала до конца выглядит так:
- Ваша страница регистрирует service worker и оформляет подписку на push. Браузер возвращает объект
PushSubscription, содержащий уникальный URL конечной точки. - Ваша страница отправляет эту подписку на ваш сервер и сохраняет её.
- Позднее ваш сервер подписывает сообщение своим закрытым VAPID-ключом и отправляет его на конечную точку подписки, которая принадлежит стороннему сервису push-рассылки (Mozilla, Google, Apple и др.).
- Сервис push-рассылки будит браузер пользователя и инициирует событие
pushв вашем service worker. - Service worker показывает уведомление в ответ на это событие.
Push API требует защищённого контекста: страница должна обслуживаться через HTTPS (при разработке localhost считается защищённым). Без этого navigator.serviceWorker и PushManager недоступны.
Реализация push-уведомлений
Настройка service worker
Прежде всего нужно зарегистрировать service worker, который будет обрабатывать фоновые задачи отправки уведомлений:
// Registering a service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.log('Service Worker registration failed:', error);
});
}Запрос разрешения на уведомления
Перед отправкой уведомлений необходимо запросить разрешение у пользователя. Запрос должен быть инициирован жестом пользователя (например, кликом) — браузеры отклоняют запросы разрешений, которые срабатывают автоматически при загрузке страницы:
<button id="enable-notif-btn">Enable Notifications</button>
<script>
// Asking user permission for notifications
function requestPermission() {
Notification.requestPermission().then(function(permission) {
console.log('Notification permission:', permission);
});
}
document.getElementById('enable-notif-btn').addEventListener('click', requestPermission);
</script>Подписка на push-уведомления
После получения разрешения приложение может оформить подписку на push-уведомления. Параметр applicationServerKey должен быть Uint8Array, а не строкой base64, которую вы обычно храните, — поэтому сначала выполните преобразование. VAPID (Voluntary Application Server Identification) — это пара ключей, которая позволяет сервису push-рассылки проверить, что push-сообщения действительно поступают с вашего сервера: открытый ключ указывается здесь, закрытый ключ остаётся на вашем бэкенде.
<button id="subscribe-btn">Subscribe to Push Notifications</button>
<script>
// The VAPID public key arrives as a base64url string; the API needs a Uint8Array.
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(base64);
return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
}
const VAPID_PUBLIC_KEY = 'YOUR_VAPID_PUBLIC_KEY'; // base64url string from your server
function subscribeToPush() {
navigator.serviceWorker.ready.then(function(registration) {
// userVisibleOnly: true is required — the browser rejects silent push subscriptions.
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
})
.then(function(subscription) {
console.log('Push subscription:', JSON.stringify(subscription));
// Send the subscription to your backend so it can push to this user later.
return fetch('/api/save-subscription', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
})
.catch(function(error) {
console.log('Failed to subscribe to push:', error);
});
}
document.getElementById('subscribe-btn').addEventListener('click', subscribeToPush);
</script>PushSubscription сериализуется в JSON, содержащий URL endpoint и ключи шифрования keys (p256dh и auth). Для отправки сообщения вашему серверу нужны все эти данные. Установка userVisibleOnly: true является обязательной в современных браузерах: она гарантирует, что каждый push приведёт к показу видимого пользователю уведомления, поэтому скрытые фоновые push-сообщения в вебе недопустимы.
Обработка входящих push-сообщений
Для обработки входящих сообщений service worker прослушивает события push. Переданная полезная нагрузка поступает через event.data; используйте event.data.json() (или .text()) для её чтения. Оборачивание showNotification() в event.waitUntil() удерживает service worker активным до отображения уведомления:
// Inside service-worker.js
self.addEventListener('push', function(event) {
// Read the payload your server sent (fall back gracefully if there is none).
var payload = event.data ? event.data.json() : {};
var options = {
body: payload.body || 'New notification.',
icon: 'icon.png',
vibrate: [100, 50, 100],
data: { primaryKey: 1 }
};
event.waitUntil(
self.registration.showNotification(payload.title || 'Push Notification', options)
);
});
self.addEventListener('notificationclick', function(event) {
event.notification.close();
event.waitUntil(
clients.openWindow('https://example.com')
);
});
self.addEventListener('pushsubscriptionchange', function(event) {
console.log('Subscription changed, re-subscribing...');
// Re-subscribe using the same parameters
event.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_VAPID_PUBLIC_KEY'
}).then(function(newSubscription) {
console.log('Re-subscribed:', newSubscription);
}).catch(function(error) {
console.error('Re-subscription failed:', error);
});
});Этот фрагмент демонстрирует три события, которые стоит обрабатывать: push (показ уведомления), notificationclick (перевод фокуса или открытие окна при клике пользователя) и pushsubscriptionchange (повторная подписка при смене подписки браузером).
Роль сервера
Браузер не может сам отправлять себе push-сообщения — каждое сообщение исходит от вашего бэкенда. Сервер хранит закрытый VAPID-ключ и для каждой сохранённой подписки отправляет зашифрованный подписанный HTTP-запрос на endpoint подписки. На практике для этого используют библиотеку web-push (например, web-push для Node.js), а не реализуют шифрование вручную:
// Server side (Node.js) — conceptual example
const webpush = require('web-push');
webpush.setVapidDetails(
'mailto:[email protected]',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
);
// `subscription` is the JSON object the browser sent to /api/save-subscription
const payload = JSON.stringify({ title: 'Hello', body: 'You have a new message.' });
webpush.sendNotification(subscription, payload)
.catch(err => console.error('Push failed:', err.statusCode));Ответ 410 Gone или 404 означает, что подписка истекла — удалите её из базы данных. Это серверный аналог обработки pushsubscriptionchange на клиенте.
Рекомендации по push-уведомлениям
- Вовлечённость пользователей: разрабатывайте уведомления, которые своевременны, актуальны и точны.
- Соблюдение конфиденциальности: всегда убеждайтесь, что согласие пользователя получено перед отправкой уведомлений.
- Производительность: управляйте частотой и временем уведомлений, чтобы не перегружать пользователя.
- Обновление подписки: push-подписки периодически истекают. Реализуйте логику на стороне клиента для проверки статуса подписки и повторной подписки при необходимости, либо обрабатывайте события истечения срока в service worker.
Заключение
Push API открывает канал для прямого взаимодействия с пользователями и предоставляет мощный инструмент для повышения вовлечённости. Используя этот API, разработчики могут создавать более динамичный и отзывчивый пользовательский опыт. Грамотная реализация push-уведомлений способна значительно расширить функциональность и привлекательность веб-приложений, удерживая пользователей информированными и вовлечёнными.