React Native и Expo

@unolink/react-native — это тонкий мост поверх нативных SDK для iOS и Android, которые поставляются внутри пакета. Ему не нужны config plugin, разрешения кроме доступа в интернет и записи в Info.plist. Тариф Pro и выше.

Требования

  • React Native 0.79+ (новая архитектура).
  • Expo: development build, npx expo prebuild или EAS Build. В Expo Go он не работает, потому что содержит нативный код.
  • Ключ SDK: в панели откройте приложение → Настройки → Ключи SDK → Новый ключ. Он показывается один раз, поэтому скопируйте его сразу.

Установка

Expo:

npx expo install @unolink/react-native
npx expo prebuild        # or: eas build

React Native без Expo:

npm install @unolink/react-native
cd ios && pod install

Затем пересоберите приложение. После добавления нативного кода перезагрузки JavaScript недостаточно.

Если pod install падает с ошибкой кодировки (Ruby 4 или локаль не в UTF-8), запустите его в UTF-8:

cd ios && LANG=en_US.UTF-8 pod install

Не устанавливайте через npm install --legacy-peer-deps: эта команда перезаписывает package-lock.json и выбрасывает необязательные peer-зависимости.

Настройка

Вызовите configure один раз, на верхнем уровне, до отрисовки первого экрана: в app/_layout.tsx с Expo Router или в index.js.

import { Unolink } from "@unolink/react-native"

Unolink.configure("ulk_…", { debug: __DEV__ })

В версии 0.3.1 и новее:

  • Вызовы, сделанные до configure, ждут и отправляются сразу после него, так что сначала восстановить сессию — нормально.
  • Отсутствующий ключ (например, нет записи в .env) сообщается в консоли, а не проходит незаметно. Unolink.isConfigured() сообщает, работает ли SDK.
  • debug по умолчанию равен __DEV__.

Параметры

ПараметрПо умолчаниюЧто делает
debug__DEV__ (0.3.1+; до этого false)Помечает эту установку как тестовое устройство. Панели скрывают тестовые устройства, если не включено Включать тестовые устройства.
logLevel"warn"Сколько выводится в консоль Xcode или Logcat (тег Unolink): "none", "error", "warn", "info", "debug".
baseUrlhttps://api.unolink.devSDK API. Оставьте как есть.

Ключ публичный: он поставляется внутри вашего приложения и может отправлять данные только для этого одного приложения. См. Ключи SDK.

События воронки

Добавьте по одной строке в каждый важный момент. Они станут шагами в Воронке и «Путях».

Unolink.onboardingCompleted()            // the last onboarding screen is done
Unolink.paywallViewed("onboarding")      // a paywall appeared; placement says which one
Unolink.paywallSkipped("onboarding")     // closed without buying
Unolink.signUp("apple")                  // a new account was created
Unolink.signIn("apple")                  // signed in to an account they already had
Unolink.signUpSkipped()                  // went on as a guest (0.3.1+)

Экраны онбординга, по одному вызову на экран, когда он появляется:

import { useOnboardingStep } from "@unolink/react-native"

useOnboardingStep("goal", { index: 0 })   // in each onboarding screen component
Unolink.onboardingSkipped("goal")         // on Skip

ID экранов состоят из латинских букв, цифр, _ и . (how_heard, а не how-heard). В разработке StrictMode в React монтирует экраны дважды, поэтому там экран может отправиться два раза; release-сборки отправляют его один раз. Development-сборки предупреждают в консоли об ID или свойствах, которые сервер отклонит (0.3.1+).

Без RevenueCat сообщайте о пробных периодах и покупках сами. Передавайте сумму строкой, чтобы она никогда не округлялась:

Unolink.trialStarted("pro_yearly")
Unolink.purchase("49.99", "USD", "pro_yearly")
Unolink.purchaseCancelled("pro_yearly")               // closed the purchase sheet
Unolink.purchaseFailed("pro_yearly", "payment_failed")

Ваши собственные события принимают имя и до 20 свойств (строки, числа или логические значения):

Unolink.track("level_up", { level: 3 })

Полный список с названиями, которые попадают в панель, — в разделе События.

ID пользователя и RevenueCat

Если вы используете RevenueCat, подключите его в приложении → Настройки → RevenueCat. Тогда не вызывайте purchase и trialStarted: RevenueCat сам отправляет пробные периоды, покупки, продления, отмены и возвраты, а Unolink игнорирует вызовы покупок из приложения, чтобы ничего не учитывалось дважды.

Чтобы привязать эти покупки к установке, сообщайте собственный app user ID RevenueCat. В SDK 0.3.2 и новее одна строка поддерживает его в актуальном состоянии. Вызовите её один раз при запуске, до или после configure:

import Purchases from "react-native-purchases"

Unolink.connectRevenueCat(Purchases)

Unolink считывает ID сразу, при каждом обновлении данных клиента (покупки, logIn, logOut) и каждый раз, когда приложение возвращается на передний план, и отправляет его, только если он изменился. Если RevenueCat ещё не настроен, чтение тихо завершается неудачей и повторяется позже. Вызов возвращает функцию для отключения. Поэтому на React Native Unolink.revenueCatUserChanged() обычно не нужен.

Без этого (или до версии 0.3.2) вызывайте setUserId с ID из RevenueCat после Purchases.configure, после каждого Purchases.logIn и Purchases.logOut и при каждом запуске. Используйте что-то одно, а не оба способа сразу:

// after Purchases.configure, logIn and logOut, and on every launch
Unolink.setUserId(await Purchases.getAppUserID())

Для гостя это анонимный ID RevenueCat, поэтому покупка, сделанная до регистрации, сразу привязывается к установке. После logIn это ID аккаунта. Учитываются все ID, которые сообщала установка, поэтому покупка гостя остаётся привязанной. См. Гости и аккаунты.

Unolink никогда не хранит сам ID, только односторонний хеш, который у каждого вашего приложения свой.

На Android, когда человек устанавливает приложение по ссылке Unolink с диплинком, ссылка приходит один раз, при первом открытии. На iPhone её нет: у Apple нет реферера установки, а Unolink никогда не использует фингерпринтинг.

link.deepLink — это то, что вы задали для ссылки: путь вроде /promo/3for2 или полный URL.

Первое открытие обычно начинается с онбординга. Если экран ссылки находится за онбордингом или входом в аккаунт, откройте его, когда они завершены, а затем отметьте ссылку как использованную (0.3.1+):

// when onboarding (or sign-in) is done, and on every launch after that until it's consumed:
const link = Unolink.getPendingDeferredDeepLink()
if (link?.deepLink) {
  router.push(link.deepLink)               // a path; open a full URL with Linking.openURL
  Unolink.consumeDeferredDeepLink()
}

Начиная с 0.3.3 ссылка ждёт, пока вы её не используете, даже если приложение за это время закрывали (до 30 дней после установки). Новый пользователь, который бросил онбординг на середине, всё равно получит её при следующем запуске, так что хранить её самим не нужно (никакого хранилища в AsyncStorage). Вызовите consumeDeferredDeepLink(), когда откроете её: до этого getPendingDeferredDeepLink() возвращает её при каждом запуске. Он возвращает null до configure и пока Google Play не ответил. В 0.3.1 и 0.3.2 она хранится только в течение текущего запуска.

Если экран доступен сразу, достаточно слушателя. Слушатели вызываются один раз на установку. В 0.3.1 ссылка, пришедшая до того, как вы подписались, достаётся первому добавленному слушателю; в 0.3.0 подписывайтесь сразу после configure.

import { router } from "expo-router"

const subscription = Unolink.addDeferredDeepLinkListener((link) => {
  // link.deepLink: "/promo/3for2", link.params: { offer: "3for2" }
  // link.linkSlug: "tiktok" or null, link.clickId: "clk_01JA…"
  if (link.deepLink) router.push(link.deepLink)
  Unolink.consumeDeferredDeepLink()
})

// when the component unmounts
subscription.remove()

Задайте Диплинк и Параметры ссылки в панели (Ссылки → ⋯ → Изменить). Как это работает, описано в разделе Отложенные диплинки на Android.

Другие вызовы

ВызовЧто делает
Unolink.getInstallId()Случайный ID этой установки или null до configure. Он не идентифицирует ни человека, ни устройство.
Unolink.flush()Отправляет события из очереди прямо сейчас. Обычно не нужен.
Unolink.setEnabled(false)Отключает отслеживание и удаляет события из очереди, для вашей настройки отказа. Запоминается между запусками.

Вызовы никогда не выбрасывают исключений и никогда не блокируют: события ставятся в очередь на устройстве и отправляются в фоне нативным SDK.

Jest

Начиная с 0.3.1 у пакета есть собственный мок, который записывает каждый вызов:

jest.mock("@unolink/react-native", () => require("@unolink/react-native/jest"))
// expect(Unolink.signUp).toHaveBeenCalledWith("apple")

Тестовые устройства

Development-сборки считаются тестовыми устройствами (до 0.3.1 передавайте debug: __DEV__). Треки тестирования Google Play нельзя распознать на телефоне: передавайте debug: true в таких сборках, например из переменной EXPO_PUBLIC_. Их данные настоящие, но скрыты, пока вы не включите Включать тестовые устройства в Воронке и «Путях». Подробнее в разделе Тестирование.

Конфиденциальность

  • Без IDFA, без Google Advertising ID, без запроса App Tracking Transparency, без фингерпринтинга.
  • Случайный ID установки, версия приложения, версия ОС, версия SDK, телефон или планшет, язык. Страна определяется на границе сети. IP-адреса никогда не хранятся.
  • На iOS манифест конфиденциальности нативного SDK входит в пакет: аналитика, не связанная с личностью.
  • Никогда не передавайте имена, email или другие персональные данные в свойствах событий.

Решение проблем

  • «Native module not found» или что-то похожее? Вы в Expo Go или не пересобрали приложение после установки. Сделайте development build. Начиная с 0.3.1 приложение не падает без нативного модуля: вызовы ничего не делают, а предупреждение объясняет почему.
  • В панели ничего нет? Включите Включать тестовые устройства: сборки с __DEV__ по умолчанию скрыты.
  • Всё равно ничего? Настройте с { debug: __DEV__, logLevel: "debug" } и ищите Unolink в консоли Xcode или Logcat. Неверный или отозванный ключ записывается в лог как ошибка.

Подробнее в разделе Решение проблем.