Push-уведомления PWA: почему не работают в фоне и как исправить
Вы столкнулись с типичной проблемой гибридных веб-приложений: push-уведомления отлично работают на десктопе (даже в свёрнутой вкладке), но на мобильном устройстве приходят только когда PWA активно. Разберём причины и предложим рабочее решение без бекенда.
Как работает текущая реализация
Ваш код использует postMessage для отправки данных из основного потока в Service Worker (SW). Это корректно, когда SW уже активен. Однако на мобильных устройствах браузер может приостановить SW, если приложение не в фокусе. В результате postMessage не доходит, и уведомление не показывается.
Почему на телефоне не приходят уведомления в фоне
Основная причина - жизненный цикл Service Worker. На десктопе SW остаётся активным дольше, на мобильных - браузер может его «усыпить» для экономии ресурсов. Кроме того, postMessage работает только если SW уже запущен. Если SW «спит», сообщение теряется.
Как исправить: архитектура без postMessage
Чтобы уведомления приходили в любом состоянии приложения, нужно перенести логику получения данных и формирования текста уведомления непосредственно в Service Worker. Вот пошаговое решение:
1. Храните данные в IndexedDB или Cache API
Когда приложение активно, сохраняйте последние значения рейтинга и локализованные строки в IndexedDB (например, через библиотеку idb) или в Cache API. SW имеет доступ к этим хранилищам.
2. Используйте Push API с серверной отправкой
Даже если вы хотите избежать бекенда, для фоновых уведомлений на мобильных устройствах обязательно использовать Push API. Это означает, что сервер (или сервис вроде Firebase Cloud Messaging) должен отправлять push-сообщение на устройство. SW получит событие push, сможет прочитать данные из IndexedDB и показать уведомление.
3. Альтернатива: периодическая синхронизация
Вы уже используете Periodic Sync - это правильный подход. Но на мобильных устройствах он срабатывает нерегулярно (браузер сам решает, когда запустить). Чтобы гарантировать доставку, лучше комбинировать: Periodic Sync + Push API. Когда SW получает событие periodicsync, он может сам выполнить fetch-запрос к вашему API (если сервер доступен) и показать уведомление.
4. Пример кода для Service Worker
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'check-rating') {
event.waitUntil(
fetch('/api/rating')
.then(response => response.json())
.then(data => {
const title = 'Рейтинг изменился';
const options = {
body: `Новый рейтинг: ${data.rating}`,
icon: '/icon.png'
};
return self.registration.showNotification(title, options);
})
);
}
});В этом примере SW сам делает запрос на сервер и показывает уведомление - без postMessage.
Что делать с локализацией
Если текст уведомления зависит от языка пользователя, сохраните выбранную локаль в IndexedDB. SW сможет её прочитать и подставить нужные строки. Либо передавайте локаль как параметр в URL при fetch-запросе.
Вывод
Для корректной работы push-уведомлений на мобильных устройствах в фоне необходимо:
- Перенести логику формирования уведомлений в Service Worker
- Использовать Push API (с серверной отправкой) или Periodic Sync с самостоятельным fetch-запросом
- Хранить данные для уведомлений в IndexedDB или Cache API
- Отказаться от
postMessageкак основного канала для фоновых уведомлений
Реализовать полностью без бекенда, к сожалению, не получится - для Push API нужен сервер (хотя бы для отправки триггера). Но вы можете использовать бесплатные сервисы вроде Firebase Cloud Messaging или собственный мини-сервер на Vercel/Netlify Functions.