Всем привет! Мы — архитектор разработки публичных веб-приложений Борис и разработчик системы-шлюза отправки нотификаций Данила. Расскажем о том, как создавались веб-пуши iOS в Тинькофф, как их настраивали и с какими проблемами столкнулись в процессе разработки. С выходом iOS 16.4 Apple добавила возможность отправлять пуши в PWA на телефон, а вместе с этим перешла на стандарт Push API для отправки веб-пушей. Теперь можно отправлять пуши в мобильный браузер Safari. Раньше Apple позволяла отправлять пуши в десктопную версию Safari. У нас в компании они были с незапамятных времен и нашли применение в области инвестиций. Многие пользователи получали уведомления в режиме реального времени во время дневной торговли, что позволяло им быть в курсе последних изменений на рынке. Когда наше приложение удалили из App Store и возможность отправлять пуши пропала, мы начали активно искать альтернативу обычным пушам и решили попробовать веб-вариант в PWA. Отправлять веб-пуши можно, используя компанию-агрегатора или наше существующее решение — собственный Push-шлюз. У варианта с агрегатором мы столкнулись с нюансом, что есть разные подходы при отправке пушей: длинный и короткий. Короткий пуш отправляется в облако со всем контентом. Такой способ требует шифрования. Шифрование нужно для проверки фронтендом, что это именно твой бэкенд прислал тебе пуш. Длинный, или триггерный, пуш сложнее в процессе доставки. На фронтенд отправляется триггер с информацией о том, что для клиента есть уведомление, которое нужно показать. Фронтенд принимает триггер и сам идет за контентом пуша — например, по его ID. С триггерными пушами шифрование не требуется. Бэкенд не посылает контент пуша, а фронтенд сам его забирает и рисует пуш по заданной логике.  Наша интеграция с агрегатором настроена так, что отправка коротких пушей не поддерживается, только триггерных. На фронтенде поддержать оба способа отправки было невозможно, поэтому мы выбрали вариант целевой схемы с использованием нашего собственного Push-шлюза.  В итоге мы выбрали короткие пуши, но во время разработки возникли трудности с их обработкой и процессом запуска. Для доставки пушей мы использовали существующие у нас инструменты — собственную библиотеку и service worker. Библиотека встраивалась в наше приложение, позволяла запрашивать разрешение и создавать подписку. А service worker принимал пуши и отображал их на устройствах пользователей. Такой функционал позволял успешно обеспечивать потребности пользователей, хотя мы столкнулись с рядом проблем. Безопасность процесса доставки транзакционных пушей.  Их нельзя было отправлять на десктопы из-за персональных данных пользователей, а десктоп может использоваться разными людьми.  Решить проблему за пять минут не получилось, пришлось потратить все десять на то, чтобы разобраться: а все ли пуши должны отправляться только на мобильные девайсы?  В результате исследования мы выяснили, что инвестиционные пуши отправляются и на десктоп, и на мобилу. Модуль был общий, поэтому пришлось выносить это на уровень конфигурации приложения. До этого конфигурация была реализована через feature toggle в общей админке, так как не возникало потребности разделять включение веб-пушей. Для этого нам пришлось изобретать обратно совместимый способ передачи настроек, чтобы не перекраивать десятки приложений и можно было настроить все максимально быстро. Невозможность переподписки. Событие pushsubscriptionchange, которое должен отправлять браузер сервис-воркеру, просто не имплементировано в iOS Safari. После небольшого исследования мы поняли, что проблема небольшая, тем более что у нас уже установлена переподписка на каждый вход пользователя в приложение. Например, если адрес устройства — endpoint — перестанет работать через n недель, то вероятность того, что это произойдет у активного пользователя, крайне мала. На практике так и оказалось, устаревший endpoint мы видим крайне мало. Не имплементирована отписка. К счастью, мы быстро поняли, что триггером для отписки должно быть явное действие пользователя, например выход. На него мы и завязались, добавили отписку от пушей при обработке запроса разлогина пользователей. Если пользователь удаляет PWA c рабочего стола, отписка также происходит. Технически это был последний блокер для запуска, все важные требования мы выполнили, но эффективность предприятия выглядела сомнительно: люди часто просто отказываются от системных окон, не успевая понять, что именно это окно им предлагало.  Чтобы минимизировать риски необдуманного отказа, мы разработали баннер, который выглядит более броско, чем системный, запоминает решение пользователя и позволяет напомнить о пушах попозже — не отказываться сразу, а отложить принятие решения до момента, когда приложение станет более знакомым и родным. Имплементация перехватчика. Параллельно с баннером мы разрабатывали тестовую модель для веб-пушей и в процессе выяснили, что при переходе по пушу, когда авторизационные токены протухли, пользователь бесконечно смотрит на спиннер авторизации. Проблема оказалась в том, что service worker содержал имплементацию перехватчика fetch, — пользователю показывалась специальная страничка, если у него не было интернета. При этом devtools Safari не отображал загруженный sw, мы оказались перед trade-off: выключить эту часть sw или ехать на прод с багом в пушах. Мы отказались от перехватчика fetch, и проблема полностью исчезла. После реализации баннера мы были готовы катиться на прод, у нас была хорошая тестовая модель и мониторинг, соблюдены все функциональные требования. Но мы не понимали, как фича будет работать на больших масштабах, ведь она новая для iOS. Решили катить небольшими сегментами — повезло, что механизм конфигурации, разработанный ранее, поддерживал это. Запущенные веб-пуши работали в проде и доставлялись пользователям: мы отслеживаем это по статусам доставки — так же, как и обычные пуши. Но оказалось, что при нажатии на уведомление ничего не происходит или происходит переход на главную страницу интернет-банка. Это плохо, потому что многие веб-пуши должны вести клиента на какое-то окно с дальнейшим действием. Тем более что в PWA не подразумевается отображение главной страницы банка, оно должно выглядеть как приложение. Например, для подтверждения операции или оформления карты по маркетинговому предложению пользователь не может перейти на следующий этап и компания теряет лояльность клиента. Пуши работают, но не нацелены на пользователя. Смысла запуска таких уведомлений нет: вместо уведомлений клиенту могла бы прийти СМС со ссылкой для перехода, поэтому функционал откатили.  Проблема была в том, что диплинки для обычных пушей не подходят для PWA. Мы придумали два варианта решения проблемы: Во всех пушах кидать переход на главную страницу.  Добавить дополнительный параметр в тело пуша с url-адресом, куда перенаправлять пользователя. Первый вариант решает проблему, когда пользователя нужно вести куда-то из пуша. Но не решает проблему, что вести нужно в определенное место, например на страницу с операцией, по которой пришел пуш. Выбрали второй вариант. Новым полем мы убили сразу двух зайцев: мы решили проблему с переходом на нужное действие и сохранили клиентский опыт. А еще ссылка стала служить фича-тоглом для отправки нотификации в PWA. Команды, которые могли привести клиента в нужное место, начали передавать нам ссылку для перехода. А те, что не смогли, стали дорабатывать веб-версию приложения для поддержания клиентского опыта. Ссылка работает так, что если хост в ней совпадает с хостом PWA, то тап открывает раздел установленного PWA-приложения. В другом случае — просто открывает ссылку в мобильном Safari. Происходит контролируемый запуск отправки нотификаций, которые не ломают процессы, завязанные на переходы в пушах, а клиенты радуются, что им вернулись привычные пуш-уведомления, максимально приближенные к тем, что были от приложения. Параметр добавили, осталось командам, отправляющим пуши, переделать все сценарии по отправке. На самом деле командами была проделана огромная работа по изменению сценариев. На текущий момент большая часть пушей запустилась и может отправляться в PWA, но есть некоторые типы сообщений, которые сейчас находятся в процессе переключения. Еще одна проблема, с которой мы столкнулись, — отсутствие звуков и вибрации при получении уведомления в Safari. На текущий момент стандарт Safari такого не поддерживает. UPDATE: это исправили, теперь доставляется с звуком по умолчанию. Иногда пуши могут приходить пачками. Так может работать APNS для того, чтобы сохранить время работы устройства, просыпаясь только для получения сообщений, когда батарея разряжена. На текущий момент есть проблемы, которые мы решить не можем: иногда iOS просто перестает принимать пуши. Нашли такую же проблему на форуме Apple. К сожалению, остается только ждать.  Доставляемость держится на уровне 75%. На процент доставки влияет еще много факторов: работа облака Apple, работа устройства клиента. Например, наличие интернета на телефоне. Если подытожить всю эту историю, то мы добавили отправку веб-пушей в PWA на iOS. Нам удалось решить большинство трудностей при разработке, переработать сценарии отправки нотификаций. Приятным бонусом стало то, что эти доработки сподвигли нас перевести большую часть отправки пушей с вендороского решения на отправки через собственные системы.  Java developer