К содержимому
PWA·гид
  1. Главная
  2. Вопросы и ответы
  3. Диагностика web-push в PWA

Диагностика web-push в PWA: почему не приходят пуши

Проверено: 26.10.2026

Коротко

Диагностика web-push в PWA по слоям: разрешение, service worker, подписка и сервер. Чек-лист помогает найти, почему не приходят пуши на Android и iOS.

Цепочка доставки уведомления в PWA состоит из пяти слоёв: разрешение браузера, зарегистрированный service worker, живая push-подписка, отправка с вашего сервера и приём на стороне push-сервиса платформы. Чаще всего «пуши не приходят» ломается на одном из первых трёх слоёв — с них и начинайте, а не с переустановки приложения и не с переписывания серверного кода. Ниже — порядок проверок, при котором причина находится за один проход.

Почему web push в PWA ломается: цепочка из пяти слоёв

Каждый следующий слой работает только при живом предыдущем. Страница просит разрешение, браузер выдаёт его на уровне origin (источника — домена с протоколом). Дальше service worker, фоновый скрипт, живущий отдельно от вкладки, получает от push-сервиса событие push и решает, что показать. Между этими точками стоит подписка: объект с endpoint и ключами шифрования, который браузер хранит локально и который вы отправляете на свой сервер.

Если разрешение отозвано, вызов pushManager.subscribe() вернёт ошибку или не покажет диалог вовсе. Если service worker не активирован, подписке не к чему привязаться. Если сервер шлёт запрос на старый endpoint, push-сервис ответит ошибкой и сообщение никуда не уедет. А если сообщение доехало до устройства, но в service worker нет обработчика push, вы увидите ровно ничего: система не покажет уведомление за вас. Общая теория по теме — в разделе про пуши в PWA, здесь разбираем диагностику.

PWA и обычная вкладка одного домена делят один origin, а значит, и разрешение, и подписку, и service worker. Установка приложения на домашний экран не создаёт отдельную копию этих данных, удаление иконки их не стирает. Причину ищут в «слетевшей установке», хотя изменилось состояние одного из слоёв.

Быстрый чек-лист диагностики: откуда начинать

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

  1. Откройте DevTools на том же домене, где живёт PWA, и посмотрите текущее состояние разрешения на уведомления.
  2. Проверьте вкладку Service Workers: статус activated, нет ли воркера в состоянии waiting, совпадает ли scope с путём, откуда вы ждёте уведомления.
  3. Выполните в консоли navigator.serviceWorker.ready.then(r => r.pushManager.getSubscription()) и посмотрите, что вернётся: объект подписки или null.
  4. Отправьте тестовый пуш из DevTools кнопкой Push на панели Service Workers. Локальный пуш приходит — проблема в отправке или в сервере; не приходит — копайте в устройстве и платформе.
  5. Сверьте endpoint подписки в базе с тем, что реально вернул браузер. Расхождение в одну строку ломает всё остальное.

Этот проход закрывает большинство случаев. Дальше — тонкости по слоям.

Слой 1: разрешение на уведомления

Статус читается через Notification.permission и navigator.permissions.query({name: 'notifications'}). Значений три: default (ещё не спрашивали), granted (можно показывать) и denied (заблокировано). В DevTools то же видно на панели Application в разделе для сайта, в настройках Chrome — в разделе конфиденциальности для конкретного адреса.

Состояние denied не исправляется повторным вызовом Notification.requestPermission(): браузер запоминает решение пользователя и молча возвращает отказ, диалог больше не всплывает. Снять блокировку можно только руками в настройках сайта, а иногда и в системных настройках уведомлений для самого браузера. Проверяйте оба уровня: на Android системный запрет уведомлений для Chrome перекрывает разрешение сайта, и в консоли при этом будет бодрое granted.

Вторая тонкость — жест пользователя. Запрос разрешения сразу при загрузке страницы часть браузеров игнорирует или считает нежелательным, поэтому вызывайте его из обработчика клика по кнопке. Если кнопка «включить уведомления» есть, но обработчик навешивается позже, чем пользователь успевает нажать, вы получите тишину без ошибок.

Слой 2: service worker и его область действия

На панели Service Workers видно адрес скрипта, статус (installing, waiting, activated, redundant) и scope — путь, в пределах которого воркер управляет страницами. Воркер, отданный по адресу /app/sw.js, по умолчанию контролирует только /app/ и вложенные пути. Если PWA открывается на корне домена, а воркер лежит в подкаталоге, push-событие не дойдёт до нужной страницы, хотя формально регистрация успешна.

Состояние waiting означает, что новая версия воркера скачалась, но ждёт закрытия всех вкладок со старой. Пока она ждёт, работает прежний код, и правки в обработчике push не применяются: разработчик добавил showNotification, перезагрузил страницу, ничего не изменилось. Лечится закрытием всех вкладок домена либо self.skipWaiting() в обработчике install вместе с clients.claim() в activate, если вы понимаете последствия для кэша.

Проверьте и количество регистраций: два service worker с разными scope на одном origin — источник путаницы, подписка живёт в одном, а страница общается с другим. Ещё один частый случай — воркер отдаётся с неверным MIME-типом или с редиректом, регистрация падает, а в консоли остаётся невнятное сообщение. Как собрать всё это с нуля — в материале как сделать PWA-приложение из сайта.

Слой 3: push-подписка и её актуальность

Подписка — это JSON с полями endpoint, keys.p256dh и keys.auth. Она живёт в профиле браузера и привязана к конкретному service worker registration. getSubscription() возвращает null, когда подписки нет: пользователь её отменил, вы удалили её в коде, браузер сбросил данные сайта или подписка создана с одним ключом приложения, а теперь ожидается другой.

Тихая инвалидация — реальная головная боль. Пользователь почистил данные, переустановил браузер, переехал на новое устройство, и старый endpoint стал недействительным. Сервер продолжает слать туда запросы, push-сервис отвечает, что адресат неизвестен, а вы этого не видите, потому что не логируете ответы. Отсюда правило: на каждый ответ push-сервиса должна быть реакция, а коды «подписки больше нет» требуют удаления записи из базы — иначе вы копите мёртвые адреса.

Создание подписки требует ключа приложения (applicationServerKey) — публичной части VAPID-пары. Один пользователь, подписавшийся с разными ключами, даст разные подписки. Перегенерация пары ключей на сервере обнуляет доверие ко всем прежним подпискам: старые endpoint продолжат существовать, но отправка на них будет отклоняться. После смены ключей нужна принудительная переподписка на клиенте.

Слой 4: доставка: сервер, VAPID и push-сервис платформы

Ошибки здесь самые неочевидные, потому что происходят вне браузера. Первое, что смотреть, — тело и код ответа push-сервиса. Отказ по авторизации почти всегда означает несовпадение ключей: сервер подписывает запрос приватным ключом, который не соответствует публичному ключу из подписки. Второй частый сценарий — неправильная сборка запроса: забыли заголовок с TTL (время жизни сообщения), не зашифровали payload, перепутали схему шифрования.

TTL заслуживает отдельного внимания. Если устройство было офлайн дольше указанного срока, push-сервис выбросит сообщение и не попробует доставить его позже: пользователь вернётся к телефону, увидит пустоту и решит, что пуши сломаны. Для уведомлений, которые должны дожить до момента, ставьте срок с запасом; для служебных — короткий, чтобы не присылать вчерашние новости сегодня.

Отправку удобно проверять curl-ом или отдельным скриптом вне продакшена: один тестовый endpoint, один ключ, лог полного ответа. Так вы отделите проблемы транспорта от проблем кода приложения. Заодно убедитесь, что сервер не ходит в интернет через прокси, режущий исходящие запросы к push-сервисам, и что его часы синхронизированы: расхождение времени ломает подпись JWT (JSON Web Token, компактный токен авторизации).

Слой 5: приём и показ: обработчик push, payload, ограничения ОС

Сообщение доехало, но экран пуст — смотрите обработчик push в service worker. Он обязан явно вызвать self.registration.showNotification(...): сам факт события ничего не рисует. Внутри обработчика нельзя рассчитывать на доступ к DOM и переменным страницы — service worker работает в отдельном контексте, и всё нужное должно быть либо в payload, либо в IndexedDB (локальной базе в браузере).

Проверено: 26.10.2026. Правила сторов и возможности платформ описаны на дату проверки; точные формулировки смотрите в официальной справке Apple, Google и MDN. Нашли неточность — напишите на redakciya@pwa-arbitrazh.com.

В этом разделе Вопросы и ответы

  • Диагностика установки PWA

    Пошаговая диагностика установки PWA: критерии, DevTools и чистый профиль. Разбор причин, почему нет кнопки…

  • PWA и клоака

    PWA и клоака: объясняем принцип, зачем страницы делят для модератора и пользователя, какие риски у PWA.…

  • Трекер и PWA

    Трекер и PWA на практике: что считать установкой, как передать клик-id, настроить постбек, учесть пуши и…

  • Нужен ли стор для PWA

    Нужен ли стор для PWA и можно ли без Google Play и App Store: отвечаем на 7 вопросов о TWA, iOS, потерях и…

  • Чем PWA отличается от сайта

    Чем PWA отличается от обычного сайта: установка, работа без сети, пуши, адресная строка, индексация и влияние…