Рассылка уведомлений через глубокие ссылки в React Native
Настройте универсальные ссылки и ссылки приложения, подключите Pusher Beams к приложению React Native и сопоставьте веб-адреса с нативными экранами, чтобы нажатие на уведомление открывало нужное место.
Уведомление через push будет полезным только в том случае, если после его нажатия пользователь попадет на экран, о котором идет речь. Представьте приложение React Native для международных студентов, которые выбирают колледжи: электронные письма остаются непрочитанными, а SMS ненадежны в разных странах, поэтому функция push должна работать, когда пишет рекрутер. Здесь вы настроите глубокую переадресацию на обеих платформах, привяжете URL веб-сайта к каждому уведомлению Pusher Beams и преобразуете этот URL в маршрут React Navigation, тестируя каждый этап на протяжении всего процесса.
Почему глубокие ссылки указывают место назначения уведомления
В момент создания этой конфигурации пакеты Pusher, использовавшиеся здесь, не могли передавать пользовательский контент уведомления на сторону JavaScript в приложении React Native для Android. Самым простым решением, аналогичным предложению по улучшениям для Android SDK, является использование глубокой привязки: уведомление в Android содержит обычную ссылку на сайт, нажатие на неё рассматривается как клик по этой ссылке, а существующая в приложении система обработки глубокой привязки определяет, какой экран открыть.
С тех пор ситуация могла измениться, поэтому сначала проверьте актуальные версии SDK Beams. Слой маршрутизации в любом случае будет полезен, поскольку он также обрабатывает ссылки из электронной почты, SMS или чата.
Краткий обзор глубокой привязки
Глубокая привязка позволяет приложению получать ссылки на собственный веб-сайт и открывать их нативно. Ссылка вида https://test.com/message/abc должна открывать приложение непосредственно на сообщении abc, а не запускать браузер. В iOS такие ссылки называют универсальными, в Android — ссылками приложения, причем в обоих случаях ваш домен должен опубликовать файл, подтверждающий доверие к приложению.
Настройка глубокой привязки на обеих платформах
Опубликуйте файлы верификации в папке .well-known
Создайте каталог /.well-known/ в корне вашего веб-сайта. Для Android добавьте файл /.well-known/assetlinks.json, в котором указывается, что ваше приложение может обрабатывать все URL в домене. Замените местоимения на название пакета вашего приложения и SHA-256-отпечаток сертификата, подписывающего файл:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "package_name",
"sha256_cert_fingerprints":
["package_cert_fingerprint"]
}
}]
Если Google Play переподписывает ваши публикации, используйте отпечаток ключа подписи Play, а не ключ загрузки, иначе проверка провалится только в режиме производства.
Для iOS добавьте файл с именем /.well-known/apple-app-site-association (без расширения). В нем указывается идентификатор приложения, состоящий из ID команды и ID пакета, а также URL-пути, которые приложение должно перехватывать:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "appId",
"paths": [ "/paths-you-want-to-support", "/messenger"]
}
]
}
}
Обслуживайте оба файла по HTTPS с именно этого домена, без перенаправлений. Это классическая структура applinks; Apple позже добавила более новый формат, поэтому проверьте её актуальную документацию.
Включение связанных доменов в iOS
iOS также требует настройки, указывающей, к каким доменам относится приложение. В Xcode откройте раздел Capabilities, включите опцию Associated Domains и добавьте каждый домен, к которому вы хотите подключиться, обычно в формате applinks:yourdomain.com.
Объявление фильтров намерений в Android
В Android глубокие ссылки объявляются с помощью фильтров намерений в файле AndroidManifest.xml. Чтобы обрабатывать ссылки вида /messenger/*, основному приложению необходим фильтр намерений для действия VIEW с категориями DEFAULT и BROWSABLE, а также элемент data, указывающий схему https, хост-адрес и префикс пути /messenger. Добавление параметра android:autoVerify="true" принудительно заставляет систему проверять файл assetlinks.json, что позволяет приложению открывать эти ссылки без появления диалогового окна выбора.
Прием ссылок в корневом компоненте
Основной компонент приложения должен выполнять две задачи: считывать URL, по которому приложение запускается при первом старте, и отслеживать URL, поступающие во время его работы. Модуль Linking в React Native обеспечивает выполнение обеих задач с помощью функции getInitialURL() и обработчика события url. Оба механизма должны вызывать один обработчик, который пока может просто записывать URL в лог:
const handleDeepLink = (url: string) => console.log(url);
Проверьте ссылки перед добавлением уведомлений
Сначала проверьте работу глубокой привязки по отдельности, прежде чем добавлять к ней уведомления. Простой способ — отправить ссылку себе через приложение для обмена сообщениями, такое как Slack, на каждом тестовом устройстве и открыть её. Если конфигурация верна, ссылка откроет ваше приложение, а не браузер, и записанный URL совпадёт с тем, на который вы нажали. Изоляция этого слоя означает, что последующие ошибки с маршрутизацией уведомлений будут связаны исключительно с проблемами типа push.
Добавление Pusher Beams в приложение
Предварительные требования и мост от сообщества
Вам нужна учетная запись Pusher Beams, и необходимо выполнить шаги настройки для сервиса уведомлений Apple и Google Firebase.
Имейте в виду, что на момент написания этой интеграции Pusher не предоставлял официального пакета для React Native. Здесь используется пакет сообщества react-native-pusher-push-notifications, который соединяет нативные SDK Beams с JavaScript, но не поддерживается самим Pusher. Для его работы в данной конфигурации потребовались небольшие корректировки. На Android ключевым изменением стало форкование SDK push-notifications-android от Pusher, которое преобразует нажатие на уведомление в открытие глубокой ссылки. Использование неофициального моста и форкованного SDK требует постоянного обслуживания: необходимо фиксировать версии и пересматривать выбор при изменениях официальных SDK.
Установка Swift SDK на iOS
Часть для iOS зависит от Swift SDK от Pusher, которая устанавливается здесь с помощью Carthage. Добавьте следующую строку в файл /ios/Cartfile, затем запустите команду carthage bootstrap, чтобы загрузить и скомпилировать её:
github "pusher/push-notifications-swift" ~> 1.3.0
Эта версия считалась актуальной для данной интеграции; в новых проектах, скорее всего, будет использоваться более свежий SDK и, возможно, другой менеджер зависимостей.
Затем следуйте инструкциям по ручной установке пакета-моста для обеих платформ, поскольку нативные компоненты настраиваются индивидуально.
Отправка тестового уведомления
Уведомления в iOS приходят только на физические устройства, а не на симуляторы, поэтому имейте под рукой настоящий iPhone.
Чтобы отправить тестовое уведомление, используйте консоль отладки в панели управления Pusher или HTTP-клиент, такой как Postman, для вызова API Beams publish. Достаточно иметь тело запроса с разделами для iOS и Android; в этой конфигурации раздел для iOS устанавливает количество значков в 5, а раздел для Android содержит URL сайта, который должен открыться.
Когда всё настроено правильно, iOS сам обрабатывает содержимое уведомления, в то время как Android открывает приложение с помощью интента View, передающего URL вашего сайта. На обеих платформах обработчик должен записать что-то вроде https://yourdomain/messenger/abcde. На iOS иконка приложения также должна отображать значок с числом 5.
Преобразование URL в маршруты React Navigation
Последний шаг — заменить временный механизм логирования на реальную систему маршрутизации. Часто используется React Router на веб-сайте и React Navigation в приложении React Native. Небольшой утилитный инструмент, преобразующий веб-маршруты в нативные, позволяет им делить логику и предотвращает сбои в навигации приложения из-за изменения URL на веб-странице.
Общее использование констант маршрутов в веб-версии и нативной версии
В обоих кодовых базах маршруты определяются как константы. В веб-версии инструмент построения маршрутов возвращает либо конкретный путь, либо шаблон, с которым сравнивается React Router; в нативной версии аналогом является имя экрана, причем идентификатор разговора передается отдельно в качестве параметра навигации:
// Web:
ROUTE = {
MESSENGER_CONVERSATION: (conversationId?: string) =>
conversationId
? `/messenger/${conversationId}`
: "/messenger/:conversationId"
}// Native:
APP_STACK_ROUTE: {
MESSENGER_CONVERSATION_SCREEN:
"app_stack_routes/messenger_conversation"
}
// native then has a params object with conversationId included
Поскольку обе части представлены в виде констант, можно указать, какой веб-путь соответствует какому нативному экрану, в одной таблице:
const ROUTE_MATCHES: IRouteMatches = [
{
webPath: ROUTE.MESSENGER_CONVERSATION(),
rnPath: APP_STACK_ROUTES.MESSENGER_CONVERSATION_SCREEN
}
];
При вызове ROUTE.MESSENGER_CONVERSATION() без аргументов возвращается шаблон /messenger/:conversationId, поэтому таблица хранит тот же шаблон, что и маршрутизатор веб-сайта. Переименование веб-маршрута автоматически обновляет сопоставление. Чтобы вспомнить, как работают эти веб-шаблоны, ознакомьтесь с основами React Router.
Дебаунс входящих URL
Обработчик иногда срабатывает более одного раза при каждом нажатии, например, когда проверка начального URL и обработчик событий сообщают о наличии ссылки, что приводит к появлению дублирующихся экранов. Передача каждого URL через объект Subject в RxJS с применением функции debounceTime(100) сокращает серии вызовов до одного, который затем передается в функцию processUrl:
export const handleDeepLink = (url: string): void => {
if (!url) return;
onChangeUrl$.next(url);
};
const onChangeUrl$: Subject<string> = new Subject<string>();
const urlSubscription: Observable<string> = onChangeUrl$.pipe(debounceTime(100));
urlSubscription.subscribe(processUrl);
Компромисс: два разных ссылка в течение 100 мс сливаются в последнюю, что приемлемо для нажатий на уведомления.
Разделение URL на части
processUrl сначала разделяет URL на протокол, хост, путь и строку запроса с помощью регулярного выражения (для настройки такого выражения может помочь инструмент вроде RegExr). Метод деструктуризации игнорирует полное совпадение и группу запроса, в которой все еще присутствует ?:
const REGEX_DECONSTRUCT_URL = /^(.*?):\/\/(.*?)(\/.*?)(\?(.*))?$/;const deconstructedUrl = REGEX_DECONSTRUCT_URL.exec(url);
if (!deconstructedUrl) return;
const [originalUrl, protocol, tld, path, ignore, querystring] = deconstructedUrl;
Обратите внимание, что это выражение требует наличия пути: простой https://yourdomain без заканчивающейся слеша не будет совпадать, и функция вернется раньше времени. Это подходит для ссылок в уведомлениях, но стоит знать об этом, если вы будете использовать этот помощник в других местах.
Сопоставление пути с таблицей маршрутов
После извлечения компонентов обработчик продолжает работу только с HTTPS-ссылками на вашем домене, затем просматривает элементы ROUTE_MATCHES, пока один из них не совпадет с указанным путем:
if (protocol === "https" && tld.includes("yourdomain")) {
for (let i = 0; i < ROUTE_MATCHES.length; i++) {
if (matchPath(ROUTE_MATCHES[i], path, querystring)) {
// loop until one matches
break;
}
}
}
Проверка tld.includes("yourdomain") удобна, но слишком размыта: она также примет хост вроде yourdomain.attacker.example. Операционная система проверяет универсальные и приложенные ссылки, поэтому риск ограничен, но точный список разрешенных хостов — это простое улучшение, особенно если обработчик используется и для других источников URL.
Внутри matchPath шаблон webPath сравнивается с входящим путем с помощью функции path-to-regexp — той же библиотеки, от которой зависит React Router. При совпадении извлеченные параметры, такие как conversationId, становятся параметрами соответствующего экрана rnPath, и приложение переходит туда. Поскольку этот процесс выполняется вне любого компонента, его запускает небольшая служба навигации, хранящая ссылку на корневой навигатор, как описано в документации React Navigation для навигации без использования свойства navigation.
Заключение
Благодаря наличию этих элементов приложение выполняет две задачи по одному кодовому пути:
- Ссылки на ваш веб-сайт, поступающие из электронной почты, SMS или чата, открывают соответствующий экран в нативном приложении.
То, что позволяет поддерживать эту систему, — это рассмотрение URL веб-сайта как единственного описания пункта назначения, при этом существует таблица, преобразующая URL в встроенные маршруты. Тестируйте каждый слой отдельно, усильте проверку хоста и следите за официальными SDK Beams: если они передают данные в JavaScript, можно отказаться от форка и сохранить слой маршрутизации.
Связанные статьи
- Планирование обновления Expo SDK 58: iOS 27, React Native 0.88 и новые инструменты — практический обзор бета-версии Expo SDK 58: какие изменения в iOS 27 и React Native 0.88, какие функции являются экспериментальными и как безопасно тестировать обновление.
- Создание настраиваемого компонента Select для React Native Paper — Пошаговое руководство по разработке и публикации в открытом доступе компонента react-native-paper-select, в котором рассматриваются функции поиска, множественный выбор элементов, разделение списков и аспекты оптимизации производительности.
- Подключение внутриприложенного Turbo-модуля от начала до конца с использованием React Native Codegen — Определение типизированного спецификация, запуск процесса генерации кода и реализация Turbo-модуля на iOS и Android с использованием синхронных методов, Promise, обратных вызовов и механизмов отправки событий.
- Уведомления в React Native: разрешения, каналы и жизненный цикл FCM — Узнайте, как разрешения, каналы Android, токены FCM и обработчики состояний переднего плана, фона и выхода взаимодействуют друг с другом в системе уведомлений React Native с использованием Notifee.