Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Всю вторую половину 2023 года мы превращали наш сайт в PWA-приложение, чтобы им было удобно пользоваться на устройствах iOS. Рассказываем, что из этого получилось и какие у нас планы на будущее. 🙌

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Меня зовут Оля, я ведущий программист в отделе разработки интерфейсов ЮMoney. Работаю в команде «Портал» и занимаюсь главной страницей, страницами настроек, онлайн-оплаты и аналитики расходов.

В 2022 году мы, как и многие компании, столкнулись с удалением своих приложений из сторов и искали альтернативные решения. Одним из вариантов было сделать своё прогрессивное веб-приложение (Progressive Web App, PWA).

Что такое PWA

Википедия определяет PWA как технологию в веб-разработке, которая визуально и функционально трансформирует сайт в приложение. А так её видит ChatGPT:

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Я же определяю PWA как набор технологий, которые позволяют превратить приложение в нечто нативное. Вот какие это могут быть технологии:

  • Push API,
  • Web App Manifest,
  • Service Worker,
  • Notifications API,
  • Shape Detection API,
  • JavaScript, CSS, HTML и другие веб-технологии.

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

Как работают пуш-уведомления в PWA ЮMoney

  • Когда мы хотим подписать пользователя на пуш-сообщения, он заходит на сайт и видит предложение подписаться на уведомления от нас.

  • Если пользователь даёт разрешение на подписку, мы идём на наш сервер и запрашиваем генерацию специального ключа. Сервер отдаёт нам публичную часть этого ключа.

  • Дальше идём с этим ключом в службу пуш-сообщений, где создаётся подписка. Подписка возвращается обратно в браузер, и мы сохраняем этот объект у себя в бэкенде для дальнейшего использования.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

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

1. Подключить манифест.

2. Зарегистрировать Service Worker.

3. Запросить разрешение на получение уведомлений.

4. Получить VAPID-ключ от бэкенда.

5. Создать подписку.

6. Сохранить её данные на бэкенде.

Рассмотрим технологии, используемые в этих шести шагах

1) WebAppManifest. Это JSON-файл с информацией о нашем приложении. В нём можно настроить различные параметры того, как будет выглядеть веб-приложение: цвет фона (background_color), иконка (icons), название приложения (name), стартовый URL, который будет открываться при запуске. Можно также добавить, например, UTM-метки для статистики.

Так выглядит иконка приложения ЮMoney
Так выглядит иконка приложения ЮMoney

И самое главное — это свойство display, определяющее предпочитаемый браузером вид сайта. Свойство может принимать такие значения, как browser (когда есть элементы управления браузером) и fullscreen (когда элементов управления браузером нет, как и иконок операционной системы). Мы в ЮMoney используем standalone, когда нет элементов управления браузером, но есть иконки операционной системы.

Вот так может выглядеть файл манифеста:

{ "name": "ЮMoney", "short_name": "ЮMoney", "display": "standalone", "scope": "/", "start_url": "/main?utm_source=pwa&utm_medium=direct", "theme_color": "#ffffff", "background_color": "#ffffff", "icons": [ { "src": "https://static.yoomoney.ru/files-front/resources/head/wallet/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "https://static.yoomoney.ru/files-front/resources/head/wallet/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ] }

Подключение манифеста:

<head> ... <link rel="manifest" href="/site.webmanifest">... </head>

Подключить манифест нужно в общем месте для страниц сайта, чтобы он распространял своё действие на все страницы.

Тут мы столкнулись с проблемой: статические ресурсы, такие как манифест, хранятся у нас на поддомене. Нам пришлось прибегнуть к магии nginx и использовать redirect.

2) Service Worker. Это событийно-управляемый воркер, который представляет собой JavaScript-файл, запускаемый браузером в фоновом режиме. Он обеспечивает кэширование ресурсов для работы в офлайн-режиме, фоновую синхронизацию данных, а также (самое главное для нас) — получение и отправку пуш-уведомлений.

Для работы с Service Worker характерны некоторые особенности:

  • Нет прямого доступа к DOM — взаимодействие с ним происходит через postMessage API.
  • Он запускается в воркер-контексте, в отдельном потоке, не блокируя основной.
  • Он полностью асинхронный — там нельзя использовать синхронный API.
  • Он может перехватывать сетевые запросы, поэтому работает только по HTTPS или локально в целях разработки.

Для начала работы необходимо зарегистрировать Service Worker. Проверяем его доступность в нашем окружении. Если он доступен, то регистрируем его по адресу, передаваемому в метод регистрации. Важно обращать внимание на расположение файла сервис-воркера, потому что этим будет обусловлена его область видимости.

Регистрация Service worker:

if (!('serviceWorker' in navigator) || !('PushManager' in window)) { return; } try { // нет необходимости проверять зарегистрирован ли он уже const registration = await navigator.serviceWorker.register('/service-worker.js'); return registration; } catch (error) { return; }

В этом примере мы подключаем файл, расположенный в корне домена. Он будет распространять своё действие на весь домен.

Здесь мы столкнулись с проблемой, что его нужно хранить на поддомене и нет возможности использовать redirect nginx. Но вместо redirect можно применить rewrite.

В сервис-воркере можно подписаться на множество событий — нас интересуют два: получение пуша и клик на нотификацию.

Пример подписки на события. Так может выглядеть код сервис-воркера, где мы обрабатываем эти два события:

self.addEventListener('push', (event) => { const notification = event.data.json(); event.waitUntil( self.registration.showNotification( notification.title, {body: notification.body, data: notification.data}, ) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.openWindow(event.notification.data.url) ); });
  • При наступлении пуша мы показываем нотификацию с помощью метода showNotification.
  • Во время клика на нотификацию мы открываем URL, который пришёл в данных пуша.

Здесь следует обратить внимание на конструкцию waitUntil. У разработчика мало влияния на то, когда сервис-воркер запускается и останавливается, браузер решает это сам. Этой конструкцией мы говорим, что сервис-воркер занимается важной обработкой и что, пока промис (специальный объект в JavaScript, который используется для написания и обработки асинхронного кода) не завершится, сервис-воркер останавливать не нужно.

3) Notifications API. С помощью этой технологии можно контролировать отображение системных уведомлений, даже если приложение неактивно, пользователь его свернул или переключился на другую вкладку. На этом этапе наша главная задача — инициировать браузером запрос, чтобы пользователь разрешил показывать ему уведомления. Это мы делаем с помощью метода requestPermission.

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

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Состояние разрешения может быть в трёх статусах:

  • Default — когда мы ничего не запрашивали и можем запросить, а пользователь разрешит или запретит.
  • Denied — когда мы запрашивали разрешение, а пользователь нам отказал. В этом случае мы не можем ничего сделать. Но у пользователя будет возможность самостоятельно поставить разрешение в настройках своего устройства.
  • Granted — когда разрешение на отправку уведомлений получено, мы можем дальше создавать подписку. Для этого нам понадобится VAPID-ключ.

4) VAPID-ключ. VAPID — это добровольная идентификация сервера приложений (Voluntary Application Server Identification). Ключ передаётся службе пуш-сообщений и используется для проверки, что отправляющий пуши сервер — это тот же сервер, на пуши которого подписался пользователь. Бэкенд отдаёт нам публичный ключ, с которым мы создаём подписку на устройстве пользователя.

VAPID используется в момент, когда нам нужно отправить сообщения. Наш бэкенд подписывает свои сообщения приватным ключом, потом в POST-заголовке отправляет эту информацию в службу пуш-сообщений, а она с помощью публичного ключа подтверждает, что это сообщение подписано приватным ключом из той же пары, и отвечает нашему сервису, что сообщения приняты к доставке. Затем служба отправляет пуш-сообщение на устройство пользователя.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Чтобы получить VAPID-ключ, мы используем fetch API, потому что любой браузер, который поддерживает сервис-воркеры, поддерживает и fetch API.

5) Push API. Даёт возможность получать сообщения с сервера независимо от того, запущено приложение или нет. Среди методов Push API нас интересуют три:

  • Создание подписки. Вызываем метод subscribe у инстанса pushManager регистрации сервис-воркера и передаём туда два параметра. Первый — userVisibleOnly, он должен быть всегда true и говорить о том, что сообщение, которое мы получаем, будет видно пользователю. False сейчас не поддерживает ни один браузер. Второй параметр — applicationServerKey, тот самый публичный ключ, полученный от нашего бэкенда.
const registration = await navigator.serviceWorker.register('/service-worker.js'); const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID.publicKey });
  • Получение подписки. Метод может понадобиться, если нужно проверить, есть ли уже подписка на устройстве пользователя и нужно ли создавать новую. Или если мы хотим получить какие-то данные из подписки, например endpoint или ключи.
  • Отмена подписки. Понадобится, если нужно отписать пользователя — например, когда он разлогинится. У сервис-воркера нет знания о сессиях пользователя, и этим нужно управлять вручную.

6) Сохранение подписки на бэкенд. Финальный шаг из нашего списка. Используем fetch API и передаём на бэкенд данные подписки.

В итоге мы выполнили все шесть шагов:

  • Подключили манифест.
  • Подключили сервис-воркер.
  • Запросили у пользователя разрешение на отправку уведомлений.
  • Запросили и получили у бэкенда VAPID-ключ.
  • Создали подписку.
  • Cохранили её на бэкенде.

Теперь сервер может отправлять, а устройство — получать пуш-уведомления.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

После запуска пуш-сообщений мы проанализировали, как пользователи применяют наш PWA. Выяснилось, что 75% посетителей сайта ЮMoney заходят на него с мобильных устройств и что более 35% пользователей iOS делают это через PWA. Эта цифра постоянно растёт. Команда нашего B2B-направления (ЮKassa) тоже подключила в свой сервис пуш-сообщения.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

В итоге за первые 30 дней после запуска PWA мы отправили пользователям ЮMoney и ЮKassa около миллиона пуш-уведомлений.

Также мы добавили в наше приложение сканирование QR-кодов для быстрой оплаты на кассе. К сожалению, Safari пока не поддерживает Shape Detection API и Barcode Detection API, поэтому мы используем альтернативные библиотеки.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

Считаем запуск PWA для iOS удачным и планируем cделать то же самое для Android-устройств.

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

А я так впечатлилась разработкой нашего PWA, что сделала собственный сервис уведомлений для любителей настолок.

Совместила, так сказать, два своих хобби! =)

Как PWA помогло клиентам ЮMoney продолжать получать пуши — даже без приложения

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

Если у вас остались вопросы о PWA в ЮMoney (или о моей разработке для любителей настольных игр), с удовольствием отвечу на них в комментариях. 🙌

9
1
4 комментария