React Native SDK 0.1.0

Use @limelink/react-native to resolve LimeLink URLs and deferred deep links in iOS and Android React Native applications.

Requirements

RequirementSupported
React19.1.0 or later
React Native0.81.0 or later
RuntimeHermes with the New Architecture
HostReact Native Community CLI or Expo Development Build
AndroidAPI 24 or later
iOSiOS 12 or later

The package uses the Legacy Native Module interoperability layer under the New Architecture. It is not a direct TurboModule. Legacy Architecture, JSC, Expo Go, and web are unsupported.

React Native 0.81.0 is the declared installation floor. The 0.81.5 verification target passed Android Debug/Release and iOS clean pod installation, while its iOS simulator builds remain pending. Full Android and iOS build rows passed for RN 0.85 Community CLI and RN 0.86.2 packed-package fixtures. Expo SDK 57/RN 0.86 passed Development Build prebuild/build checks; this is build/configuration evidence rather than a separate Expo runtime matrix.

Install

npm install @limelink/react-native
yarn add @limelink/react-native
pnpm add @limelink/react-native

The wrapper pins Android SDK 0.4.0 and iOS SDK 0.4.0.

Community CLI Setup

Android repository

Add the public LimeLink Maven repository to dependencyResolutionManagement.repositories in android/settings.gradle:

maven {
  url = uri("https://hellovelop.github.io/limelink-aos-sdk-binary/repository")
  content { includeGroup("org.limelink") }
}

If your project uses project-level repositories, add the same entry to allprojects.repositories. No GitHub credentials are required.

Update MainActivity.kt so the current Activity retains every warm App Link Intent:

override fun onNewIntent(intent: Intent) {
  super.onNewIntent(intent)
  setIntent(intent)
}

Configure your app's App Link intent filters and domains separately. Android SDK 0.4.0 handles normal cold and warm App Link lifecycle delivery. Do not read Linking.getInitialURL() on Android and recreate it as a new Intent.

iOS autolinking

Run your normal CocoaPods installation after adding the package. React Native autolinking resolves the binary LimelinkIOSSDK CocoaPod at 0.4.0. Configure Associated Domains or URL schemes for your application; autolinking does not add them.

Expo Development Build Setup

Add the package to your Expo config, then create a Development Build:

{
  "expo": {
    "plugins": ["@limelink/react-native"]
  }
}

The config plugin adds the LimeLink Maven repository and Android onNewIntent update idempotently. The current plugin requires a Kotlin MainActivity. Your app still owns Android intent filters and iOS Associated Domains. Expo Go and web cannot load this native package.

Initialize and Listen

Register the listener before initialization so startup results can be delivered. Use the canonical lowercase, hyphenated Project UUID from Project settings; this is a public resource identifier, not an Organization API credential.

import { useEffect } from 'react';
import { Linking, Platform } from 'react-native';
import LimeLink from '@limelink/react-native';

export function LimeLinkSetup() {
  useEffect(() => {
    const subscription = LimeLink.addLinkListener({
      onDeeplinkReceived(result) {
        authorizeAndNavigate(result.resolvedUri);
      },
      onDeferredDeepLinkNotFound() {
        // Continue to the app's default destination.
      },
      onDeeplinkError(error) {
        console.warn(error.code, error.message, error.platform);
      },
    });

    LimeLink.initialize({
      projectId: '12345678-1234-1234-1234-123456789abc',
      loggingEnabled: false,
      deferredDeeplinkEnabled: true,
    });

    let acceptInitialUrl = true;
    const linkingSubscription =
      Platform.OS === 'ios'
        ? Linking.addEventListener('url', ({ url }) => {
            LimeLink.handleUniversalLink(url);
          })
        : undefined;

    if (Platform.OS === 'ios') {
      void Linking.getInitialURL().then((url) => {
        if (acceptInitialUrl && url) LimeLink.handleUniversalLink(url);
      });
    }

    return () => {
      acceptInitialUrl = false;
      linkingSubscription?.remove();
      subscription.remove();
    };
  }, []);

  return null;
}

isInitialized() synchronously reads native state. The JavaScript layer retains only the newest pending result, not-found event, and error in process memory until an eligible listener receives each category once. It is not persistent deferred storage.

iOS applications forward both the initial URL and later React Native Linking events. The setup component above initializes LimeLink first, installs one later-event listener before requesting the initial URL, and removes that listener during effect cleanup. Keeping all forwarding in that single iOS-only effect prevents rerenders from adding duplicate listeners; the cleanup guard also prevents a late getInitialURL() resolution from forwarding after unmount.

handleUniversalLink(url) is also available for explicitly supplied URLs. Android normal App Links should remain owned by the native cold/warm lifecycle described above.

Manual deferred handling returns a discriminated outcome:

const outcome = await LimeLink.handleDeferredDeepLink();

switch (outcome.status) {
  case 'matched':
    authorizeAndNavigate(outcome.result.resolvedUri);
    break;
  case 'notFound':
    break;
  case 'failed':
    console.warn(outcome.error);
    break;
}

Native listeners also receive manual deferred outcomes. Choose either the returned Promise or the listener as the navigation owner; using both for navigation can process one result twice. Malformed native outcomes and bridge failures may reject the Promise.

Destination Security

Treat resolvedUri as untrusted application input. Parse it and exactly compare its scheme and HTTPS host with an explicit app-owned allowlist before navigation. Reject unknown destinations and use a safe fallback. Do not authorize destinations with wildcards or string-prefix checks.

The wrapper performs no HTTP request, URL reconstruction, source-query merge, or persistent deferred storage. Native SDKs own Link resolution; your application owns destination authorization and navigation.

Public API

initialize(config: LimeLinkConfig): void
isInitialized(): boolean
addLinkListener(listener: LimeLinkListener): LimeLinkSubscription
handleUniversalLink(url: string): void
handleDeferredDeepLink(): Promise<DeferredDeepLinkOutcome>

LimeLinkConfig requires projectId and optionally accepts loggingEnabled and deferredDeeplinkEnabled. The listener requires onDeeplinkReceived; not-found and error callbacks are optional. The subscription exposes remove(): void.

A successful result includes optional originalUrl and resolvedUri, queryParams, pathParams, and isDeferred. Errors include numeric code, message, and platform (android or ios). Deferred status is exactly matched, notFound, or failed.

On Expo Go and web, isInitialized() returns false; the other methods throw an unsupported-native-build error.

Troubleshooting