React Native y Expo

@unolink/react-native es un puente ligero sobre los SDK nativos de iOS y Android, que vienen dentro del paquete. No necesita ningún config plugin, ningún permiso aparte de internet ni entradas en Info.plist. Plan Pro y superiores.

Requisitos

  • React Native 0.79+ (nueva arquitectura).
  • Expo: una build de desarrollo, npx expo prebuild o EAS Build. No funciona en Expo Go, porque tiene código nativo.
  • Una clave del SDK: en el panel, abre tu app → Ajustes → Claves del SDK → Nueva clave. Solo se muestra una vez, así que cópiala en ese momento.

Instalación

Expo:

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

React Native sin Expo:

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

Después vuelve a compilar la app. Recargar el JavaScript no basta después de añadir código nativo.

Si pod install falla con un error de codificación (Ruby 4, o una configuración regional que no es UTF-8), ejecútalo en UTF-8:

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

No instales con npm install --legacy-peer-deps: reescribe package-lock.json y quita las dependencias peer opcionales.

Configuración

Llama a configure una vez, en el nivel superior, antes de que se renderice tu primera pantalla: en app/_layout.tsx con Expo Router, o en index.js.

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

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

Con 0.3.1 o posterior:

  • Las llamadas hechas antes de configure esperan, y se envían justo después, así que no pasa nada si restauras antes una sesión.
  • Una clave que falta (por ejemplo, una entrada que falta en .env) se avisa en la consola en lugar de fallar en silencio. Unolink.isConfigured() indica si está funcionando.
  • debug es __DEV__ por defecto.

Opciones

OpciónPor defectoQué hace
debug__DEV__ (0.3.1+; false antes)Marca esta instalación como dispositivo de prueba. Los paneles ocultan los dispositivos de prueba salvo que Incluir dispositivos de prueba esté activado.
logLevel"warn"Cuánto se escribe en la consola de Xcode o en Logcat (etiqueta Unolink): "none", "error", "warn", "info", "debug".
baseUrlhttps://api.unolink.devLa API del SDK. Déjala como está.

La clave es pública: va dentro de tu app y solo puede enviar datos de esa app. Consulta Claves del SDK.

Eventos del embudo

Añade una línea en cada momento importante. Se convierten en los pasos de Embudo y Recorridos.

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+)

Pantallas de onboarding, una llamada por pantalla cuando aparece:

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

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

Los IDs de pantalla usan letras, dígitos, _ y . (how_heard, no how-heard). En desarrollo, el StrictMode de React monta las pantallas dos veces, así que puede que ahí veas una pantalla enviada dos veces; las builds de release la envían una vez. Las builds de desarrollo avisan en la consola de los IDs o propiedades que el servidor rechazaría (0.3.1+).

Sin RevenueCat, informa tú de las pruebas y compras. Pasa el importe como cadena para que nunca se redondee:

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")

Tus propios eventos aceptan un nombre y hasta 20 propiedades (cadenas, números o booleanos):

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

La lista completa, con los nombres que llegan al panel, está en Eventos.

ID de usuario y RevenueCat

Si usas RevenueCat, conéctalo en tu app → Ajustes → RevenueCat. Después, no llames a purchase ni a trialStarted: RevenueCat envía él mismo las pruebas, compras, renovaciones, cancelaciones y reembolsos, y Unolink ignora las llamadas de compra de la app para que nada cuente dos veces.

Para unir esas compras a la instalación, informa del propio ID de usuario de app de RevenueCat. Con el SDK 0.3.2 o posterior, una línea lo mantiene al día. Llámala una vez al iniciar, antes o después de configure:

import Purchases from "react-native-purchases"

Unolink.connectRevenueCat(Purchases)

Unolink lee el ID en ese momento, en cada actualización de la información del cliente (compras, logIn, logOut) y cada vez que la app vuelve al primer plano, y solo lo envía cuando ha cambiado. Si RevenueCat aún no está configurado, la lectura falla sin hacer ruido y se vuelve a intentar más tarde. Devuelve una función que desconecta. Por eso, Unolink.revenueCatUserChanged() normalmente no hace falta en React Native.

Sin ella (o antes de la 0.3.2), llama a setUserId con el ID de RevenueCat después de Purchases.configure, después de cada Purchases.logIn y Purchases.logOut, y en cada inicio. Usa uno u otro, no los dos:

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

Para un invitado, eso es el ID anónimo de RevenueCat, así que una compra hecha antes de registrarse se une a la instalación de inmediato. Después de logIn, es el ID de la cuenta. Cuenta cada ID que haya informado una instalación, así que la compra del invitado se mantiene unida. Consulta Invitados y cuentas.

Unolink nunca guarda el ID en sí, solo un hash unidireccional distinto para cada una de tus apps.

En Android, cuando alguien instala desde un enlace de Unolink que tiene un deep link, el enlace llega una vez, en la primera apertura. En iPhone no hay ninguno: Apple no tiene install referrer, y Unolink nunca hace fingerprinting.

link.deepLink es lo que configuras en el enlace: una ruta como /promo/3for2, o una URL completa.

Las primeras aperturas suelen empezar en el onboarding. Si la pantalla del enlace está detrás del onboarding o del inicio de sesión, ábrelo cuando eso termine y luego márcalo como consumido (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()
}

Desde 0.3.3, el enlace espera hasta que lo consumas, aunque la app se cierre entretanto (hasta 30 días después de la instalación). Un usuario nuevo que abandona a mitad del onboarding lo recibe igualmente en el siguiente inicio, así que no necesitas guardarlo tú (nada de AsyncStorage). Llama a consumeDeferredDeepLink() en cuanto lo hayas abierto: hasta entonces, getPendingDeferredDeepLink() lo devuelve en cada inicio. Es null antes de configure y hasta que Google Play responde. Con 0.3.1 y 0.3.2 solo se conserva durante el arranque actual.

Si se puede llegar a la pantalla directamente, basta con un listener. Los listeners se llaman una vez por instalación. Con 0.3.1, un enlace que llegó antes de que te suscribieras va al primer listener que añadas; con 0.3.0, suscríbete justo después de 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()

Configura el Enlace profundo y los Parámetros del enlace en el panel (Enlaces → ⋯ → Editar). Cómo funciona está en Deep links diferidos en Android.

Otras llamadas

LlamadaQué hace
Unolink.getInstallId()El ID aleatorio de esta instalación, o null antes de configure. No identifica a ninguna persona ni dispositivo.
Unolink.flush()Envía ya los eventos en cola. Normalmente no hace falta.
Unolink.setEnabled(false)Desactiva la medición y elimina los eventos en cola, para tu ajuste de exclusión voluntaria. Se recuerda entre arranques.

Las llamadas nunca lanzan excepciones ni bloquean: los eventos se ponen en cola en el dispositivo y el SDK nativo los envía en segundo plano.

Jest

Desde 0.3.1 el paquete tiene su propio mock, que registra cada llamada:

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

Dispositivos de prueba

Las builds de desarrollo son dispositivos de prueba (pasa debug: __DEV__ antes de 0.3.1). Los tracks de prueba de Google Play no se pueden reconocer en el teléfono: pasa debug: true en esas builds, por ejemplo desde una variable EXPO_PUBLIC_. Sus datos son reales, pero están ocultos hasta que activas Incluir dispositivos de prueba en Embudo y Recorridos. Más en Pruebas.

Privacidad

  • Sin IDFA, sin Google Advertising ID, sin aviso de App Tracking Transparency, sin fingerprinting.
  • Un ID de instalación aleatorio, versión de la app, versión del sistema operativo, versión del SDK, teléfono o tablet, idioma. El país viene del borde de la red. Las direcciones IP nunca se guardan.
  • En iOS, el manifiesto de privacidad del SDK nativo viene con el paquete: analíticas, no vinculadas a la identidad.
  • Nunca pongas nombres, emails ni otros datos personales en las propiedades de los eventos.

Solución de problemas

  • ¿"Native module not found" o algo parecido? Estás en Expo Go, o no has vuelto a compilar desde la instalación. Haz una build de desarrollo. Desde 0.3.1 la app no se cierra sin el módulo nativo: las llamadas no hacen nada y un aviso explica por qué.
  • ¿No aparece nada en el panel? Activa Incluir dispositivos de prueba: las builds con __DEV__ están ocultas por defecto.
  • ¿Sigue sin aparecer nada? Configura con { debug: __DEV__, logLevel: "debug" } y busca Unolink en la consola de Xcode o en Logcat. Una clave incorrecta o revocada se registra como error.

Más en Solución de problemas.