Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Browser push: веб-уведомления в браузере

Browser push: веб-уведомления через Push API

Веб-push-уведомления: запрос разрешения, подписка через service worker, ключи VAPID, отправка с сервера и обработка истёкших подписок.

Прямой ответ

Веб-push работает через три компонента: сервис-воркер в браузере, подписка на push-сервис и отправка сообщения с сервера приложения. Требуется защищённое соединение и явное разрешение пользователя.

Получение разрешения и создание подписки:

const permission = await Notification.requestPermission();
if (permission !== "granted") return;

const reg = await navigator.serviceWorker.register("/sw.js");
const sub = await reg.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});
await fetch("/api/push-subscriptions", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify(sub),
});

Подписка содержит адрес push-сервиса и публичные ключи получателя, поэтому хранится на сервере в привязке к пользователю.

CREATE TABLE push_subscriptions (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id     bigint NOT NULL,
    endpoint    text NOT NULL UNIQUE,
    p256dh_key  text NOT NULL,
    auth_key    text NOT NULL,
    is_active   boolean NOT NULL DEFAULT true,
    created_at  timestamptz NOT NULL DEFAULT now()
);

Отправка выполняется запросом на адрес из подписки с заголовками аутентификации VAPID:

from pywebpush import webpush

webpush(
    subscription_info={"endpoint": ep,
                       "keys": {"p256dh": p256dh, "auth": auth}},
    data=json.dumps({"title": "Заказ 1001", "body": "Заказ подтверждён", "url": "/orders/1001"}),
    vapid_private_key=VAPID_PRIVATE_KEY,
    vapid_claims={"sub": "mailto:ops@noutmarket.example"},
)

Параметр userVisibleOnly означает, что каждое push-сообщение отображается пользователю. Скрытая передача данных без отображения не поддерживается.

Компонент Назначение
Service worker Получает событие push и показывает уведомление
Подписка Связывает браузер с сервером приложения
VAPID Аутентифицирует сервер приложения перед push-сервисом
Push-сервис Хранит сообщение до доставки в браузер

Срок жизни подписки и обработка отказов

Подписка не бессрочна. Четыре причины делают её недействительной.

Отзыв разрешения. Пользователь отключил уведомления в настройках браузера, и push-сервис возвращает ошибку при отправке.

Удаление сервис-воркера. Обновление либо очистка данных сайта удаляет сервис-воркер, а с ним подписку.

Истечение подписки. Push-сервис прекращает хранение подписки после длительного периода без активности.

Смена браузера либо устройства. Подписка привязана к конкретному браузеру на конкретном устройстве, поэтому переход пользователя требует новой подписки.

Практическое следствие: отказ при отправке обрабатывается как признак недействительной подписки, и запись помечается неактивной.

try:
    webpush(subscription_info=sub, data=payload, ...)
except WebPushException as e:
    if e.response and e.response.status_code in (404, 410):
        deactivate_subscription(sub["endpoint"])

Коды 404 и 410 от push-сервиса означают, что подписка больше не существует. Повторные отправки на недействительную подписку не дают результата и создают лишний трафик.

Отдельный аспект — доставка при закрытом браузере. Push-сервис хранит сообщение и доставляет его при следующем запуске браузера, но срок хранения ограничен, поэтому сообщение может не дойти. Уведомление о критичном событии дублируется другим каналом; о выборе канала — в статье про архитектуру уведомлений.

Предпочтения пользователя определяют, отправляется ли уведомление; о настройке предпочтений — в статье про предпочтения уведомлений.

Что показывает сервис-воркер при получении push

Получение push-сообщения и отображение уведомления — два независимых шага, и ошибка на втором приводит к тому, что сообщение доставлено, но не видно пользователю.

Событие push в сервис-воркере. Браузер вызывает обработчик события, передавая данные сообщения. Обработчик выполняется в отдельном контексте, поэтому обращение к объектам страницы недоступно.

Отображение уведомления. Обработчик обязан показать уведомление, иначе браузер считает обработку незавершённой. Отсутствие отображения приводит к предупреждению и ограничению дальнейших push-сообщений.

Действие по клику. Отдельный обработчик открывает страницу по ссылке из сообщения. Отсутствие обработчика приводит к тому, что клик не даёт результата.

Практическое следствие: обработчик события push должен завершаться отображением уведомления, а тяжёлая обработка данных выполняется на странице после перехода.

Отдельное ограничение — время выполнения обработчика. Сервис-воркер останавливается браузером после короткого интервала, поэтому длительные операции в обработчике не завершаются. Проверка состояния и загрузка данных выполняются на странице, а не в обработчике события.

Распространённая ошибка — ожидание ответа сервера внутри обработчика push. Запрос выполняется, но воркер останавливается до получения ответа, поэтому уведомление не отображается. Обработчик показывает уведомление сразу, а уточнение данных выполняется после перехода пользователя.

Практика: внедрение веб-push-уведомлений

1. Запрашивайте разрешение после объяснения причины. Запрос разрешения при первом открытии страницы приводит к отказу, поскольку пользователь не понимает назначения.

2. Храните подписку в привязке к пользователю. Одна подписка соответствует одному браузеру на одном устройстве, поэтому у пользователя их несколько.

3. Обрабатывайте коды 404 и 410 как недействительную подписку. Запись помечается неактивной, и повторные отправки не выполняются.

4. Ограничивайте размер сообщения. Push-сообщение передаётся через push-сервис, поэтому большой объём данных не подходит.

5. Передавайте ссылку, а не содержимое. Уведомление содержит краткий текст и адрес, а подробности показываются на странице.

6. Отправляйте через очередь. Массовая отправка по всем подпискам выполняется фоново, а не в запросе пользователя; о повторах — в статье про очередь уведомлений.

7. Проверяйте отображение уведомления. Сервис-воркер показывает уведомление с заголовком и текстом, а необработанное событие не отображается.

Рекомендации:

  1. Не отправляйте push без отображения. Параметр userVisibleOnly обязателен, поэтому скрытая передача данных не поддерживается.
  2. Различайте подписку и разрешение. Подписка существует при выданном разрешении, а отзыв разрешения делает подписку недействительной.
  3. Учитывайте срок хранения сообщения. Сообщение не доходит, если браузер не запущен в течение срока хранения в push-сервисе.
  4. Мониторьте долю недействительных подписок. Рост доли указывает на устаревание базы подписок.
  5. Не используйте push для маркетинга без согласия. Уведомления, не связанные с действием пользователя, приводят к отзыву разрешения.

Практический сценарий: уведомления перестали приходить после обновления сайта

Контекст: пользователи сообщили о прекращении получения уведомлений после очередного развёртывания.

  1. Диагностика. Отправка возвращала код 410 для большинства подписок. В базе подписки оставались активными, поскольку отказ не обрабатывался.
  2. Причина. Обновление удалило сервис-воркер без сохранения регистрации, поэтому браузер отозвал подписки. Сервер продолжал отправку на недействительные адреса, а пользователи не получили запроса на повторную подписку.
  3. Действия. (а) Добавили обработку кодов 404 и 410 с пометкой подписки как неактивной; (б) реализовали проверку наличия регистрации сервис-воркера при загрузке страницы и повторный запрос подписки при её отсутствии; (в) зафиксировали имя скрипта сервис-воркера в конфигурации, чтобы обновление не меняло адрес регистрации; (г) добавили метрику доли подписок, возвращающих ошибку при отправке; (д) добавили уведомление пользователя о необходимости повторно разрешить уведомления.
  4. Результат. Подписки восстанавливаются автоматически при загрузке страницы, а недействительные записи исключаются из отправки.

Вывод: подписка на веб-push привязана к сервис-воркеру, поэтому его удаление отзывает подписку. Обработка кодов отказа с пометкой недействительных записей и повторная регистрация при загрузке страницы восстанавливают доставку без участия пользователя.

FAQ

Нужен ли HTTPS для веб-push? Да. Push API доступен только в защищённом контексте, поэтому на незащищённом соединении подписка невозможна.

Можно ли отправить push без отображения уведомления? Нет. Параметр userVisibleOnly обязателен, поэтому каждое сообщение отображается пользователю.

Дойдёт ли уведомление при закрытом браузере? Push-сервис хранит сообщение ограниченное время и доставляет при запуске браузера. По истечении срока сообщение не доходит.

Что делать при кодах 404 и 410 от push-сервиса? Пометить подписку недействительной и исключить из отправки. Повторные отправки на недействительную подписку не дают результата.

Источники: MDN Push API, MDN Notifications API, RFC 8292 (VAPID) — ссылки в шапке.