React Native SDK 1.0.1

@limelink/react-native@1.0.1 is publicly available on npm.

The wrapper pins Android and iOS native SDK 1.0.1 and exposes one cross-platform incoming-link/result contract, including successful unresolved Universal/App Link delivery.

Requirements

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

The package runs through Legacy Native Module interoperability on Hermes with the New Architecture. Legacy Architecture, JSC, Expo Go, and web are unsupported. The recorded RN 0.81.5 packed-package matrix passed Android Debug/Release and iOS clean pod installation plus simulator Debug/Release with Xcode 16.4. RN 0.85 Community, RN 0.86 Expo Development Build, and RN 0.86.2 fixtures provide additional matrix evidence. For wrapper 1.0.1 specifically, the current Community CLI fixture verified Android Debug bridge compilation and an iOS simulator Debug build with native SDK 1.0.1; this does not claim a fresh 1.0.1 run of every matrix configuration.

Install

npm install @limelink/react-native@1.0.1
# or: yarn add @limelink/react-native@1.0.1
# or: pnpm add @limelink/react-native@1.0.1

The package resolves the public org.limelink:limelink-aos-sdk:1.0.1 artifact and LimelinkIOSSDK 1.0.1 CocoaPod.

Community CLI Android consumers add the public Maven repository to android/settings.gradle:

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

Merge this into the existing repository configuration. Templates that prefer project repositories need the same Maven block under allprojects.repositories in android/build.gradle. No GitHub credentials are required.

Expo Development Builds add "plugins": ["@limelink/react-native"]; the plugin configures the repository and warm-link Intent update. Configure app-owned link domains separately and rebuild the native app.

Initialize and listen

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

export function LimeLinkSetup() {
  useEffect(() => {
    const subscription = LimeLink.addLinkListener({
      onDeeplinkReceived(result) {
        const destination = result.deeplinkUrl ?? result.originalUrl;
        if (destination && isAllowedDestination(destination)) {
          navigateTo(destination);
        }
      },
      onDeferredDeepLinkNotFound() {},
      onDeeplinkError(error) {
        showSafeFallback(error.code, error.message, error.platform);
      },
    });

    LimeLink.initialize({
      projectId: '550e8400-e29b-41d4-a716-446655440000',
      loggingEnabled: false,
    });

    return () => subscription.remove();
  }, []);

  return null;
}

Register the listener before initialization so native success replay reaches JavaScript when ready. The public Project UUID belongs in app configuration; Organization API credentials remain server-side credentials.

Platform lifecycle ownership

Android

The native SDK owns cold and warm App Links. Do not read Linking.getInitialURL() and recreate it as an Intent. Keep the current Activity Intent updated:

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

iOS

Forward cold and warm URLs directly from AppDelegate/SceneDelegate to the native LimeLinkReactNative helper. Do not forward React Native Linking.getInitialURL() or JavaScript Linking events back into LimeLink.

import LimeLinkReactNative

func application(
  _ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
  guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let url = userActivity.webpageURL else { return false }
  return LimeLink.handleIncomingLink(url)
}

For an AppDelegate-owned custom scheme, add:

func application(
  _ app: UIApplication, open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  return LimeLink.handleIncomingLink(url)
}

Scene-based apps instead forward their scene's cold/warm URLs in SceneDelegate (with import LimeLinkReactNative):

func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
           options connectionOptions: UIScene.ConnectionOptions) {
  // Keep the host's existing window/React Native setup here.
  for context in connectionOptions.urlContexts {
    _ = LimeLink.handleIncomingLink(context.url)
  }
  for activity in connectionOptions.userActivities {
    if activity.activityType == NSUserActivityTypeBrowsingWeb,
       let url = activity.webpageURL {
      _ = LimeLink.handleIncomingLink(url)
    }
  }
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
  if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
     let url = userActivity.webpageURL {
    _ = LimeLink.handleIncomingLink(url)
  }
}

func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
  for context in contexts { _ = LimeLink.handleIncomingLink(context.url) }
}

Merge callbacks into the lifecycle owner used by your host; do not forward the same event from both delegates or replace existing React Native startup code. Configure Associated Domains and URL schemes as described in the iOS guide.

LimeLink.handleIncomingLink(url) remains available in JavaScript only for a legitimate explicit app-owned handoff; it is not the normal iOS lifecycle integration mechanism.

Result contract

type LimeLinkResult = {
  deeplinkUrl: string | null;
  source: 'directDeepLink' | 'universalLink' | 'deferredDeepLink';
  originalUrl: string | null;
  isDeferred: boolean;
};

For resolved results, route deeplinkUrl. An unresolved Universal/App Link is a successful callback with deeplinkUrl: null, the exact inbound HTTPS URL in originalUrl, source: 'universalLink', and isDeferred: false; it does not emit a separate error callback. If the app chooses browser fallback, use deeplinkUrl ?? originalUrl, then parse and exactly compare the selected URL's scheme and host to an app-owned allowlist.

Native SDKs own pre-initialization buffering, exact-URL five-second deduplication, and capacity-10 successful-result replay. The wrapper signals readiness when the first JS listener is present. Errors and deferred not-found outcomes are live-only.

const outcome = await LimeLink.handleDeferredDeepLink();

if (outcome.status === 'matched') {
  if (isAllowedDestination(outcome.result.deeplinkUrl)) {
    navigateTo(outcome.result.deeplinkUrl);
  }
} else if (outcome.status === 'failed') {
  showSafeFallback(outcome.error);
}

The status is exactly matched, notFound, or failed. Native listeners also receive manual outcomes, so choose either the Promise or listener as navigation owner. Malformed native outcomes and bridge failures can reject the Promise.

Public API

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

Verify and Troubleshoot

Test direct, resolved web, unresolved web, deferred matched, notFound, and failed outcomes. If navigation happens twice, confirm only one native lifecycle path owns ingress and only the Promise or listener owns a manual deferred result.

Migrating from 1.0.0

Migrating from 0.1.1