W3docs

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-сообщения от начала до конца выглядит так:

  1. Ваша страница регистрирует service worker и оформляет подписку на push. Браузер возвращает объект PushSubscription, содержащий уникальный URL конечной точки.
  2. Ваша страница отправляет эту подписку на ваш сервер и сохраняет её.
  3. Позднее ваш сервер подписывает сообщение своим закрытым VAPID-ключом и отправляет его на конечную точку подписки, которая принадлежит стороннему сервису push-рассылки (Mozilla, Google, Apple и др.).
  4. Сервис push-рассылки будит браузер пользователя и инициирует событие push в вашем service worker.
  5. 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-уведомлений способна значительно расширить функциональность и привлекательность веб-приложений, удерживая пользователей информированными и вовлечёнными.

Практика

Практика
Каковы возможности и требования JavaScript Push API?
Каковы возможности и требования JavaScript Push API?
Was this page helpful?