Главная / Статьи / Рассылка уведомлений через глубокие ссылки в React Native

Рассылка уведомлений через глубокие ссылки в React Native

Настройте универсальные ссылки и ссылки приложения, подключите Pusher Beams к приложению React Native и сопоставьте веб-адреса с нативными экранами, чтобы нажатие на уведомление открывало нужное место.

1898 слов

Уведомление через 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 или чата, открывают соответствующий экран в нативном приложении.
  • Уведомления типа push используют тот же механизм обработки глубоких ссылок, поэтому нажатие приводит пользователя непосредственно к соответствующему чату.
  • То, что позволяет поддерживать эту систему, — это рассмотрение URL веб-сайта как единственного описания пункта назначения, при этом существует таблица, преобразующая URL в встроенные маршруты. Тестируйте каждый слой отдельно, усильте проверку хоста и следите за официальными SDK Beams: если они передают данные в JavaScript, можно отказаться от форка и сохранить слой маршрутизации.

    Связанные статьи