npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ada-cx/messaging-react-native

v1.5.2

Published

Ada Messaging React Native SDK — embeds Ada Chat in a WebView with a secure native bridge

Readme

Ada Messaging React Native SDK

This package is for React Native teams embedding Ada inside an iOS and Android app through react-native-webview.

Requirements

  • react >= 18.0.0
  • react-native >= 0.73.0
  • react-native-webview >= 13.0.0

This package does not ship a separate Ada native module of its own. Complete the normal react-native-webview native installation steps for your app first, then add this package on top.

Your minimum iOS and Android OS versions follow the versions supported by your React Native app and react-native-webview. The Ada package does not add a separate deployment-target requirement beyond that.

Install

npm install @ada-cx/messaging-react-native react-native-webview

If your app does not already include compatible react and react-native versions, upgrade those first.

Quick Start

import { useRef } from "react";
import {
  AdaMessagingView,
  type AdaMessagingViewHandle,
} from "@ada-cx/messaging-react-native";

export function SupportScreen() {
  const adaRef = useRef<AdaMessagingViewHandle>(null);

  return (
    <AdaMessagingView
      ref={adaRef}
      handle="my-bot"
      language="en"
      metaFields={{ plan: "pro", signedIn: true }}
      onReady={() => {
        adaRef.current?.setSensitiveMetaFields({
          authToken: "secure-session-token",
        });
        adaRef.current?.setDeviceToken("push-token");
      }}
      onEvent={(key, data) => {
        console.log("[Ada]", key, data);
      }}
      style={{ flex: 1 }}
    />
  );
}

Most customer apps only need handle. Leave cluster unset unless Ada tells you your AI agent is hosted on a non-default production region.

Imperative handle methods:

  • reset(...)
  • setLanguage(...)
  • selectQuickReply(messageId, index)
  • sendMessage(...)
  • triggerPlaybook(playbookId, options)
  • setMetaFields(...)
  • setSensitiveMetaFields(...)
  • setDeviceToken(...)
  • deleteHistory()

Useful component props:

  • handle
  • cluster
  • language
  • greeting
  • metaFields
  • styles
  • identityToken (see Secure Identity Tokens)
  • appUrl (add your app's origin to the handle's Allowed Websites list in the Ada dashboard, under Channels > Chat)
  • headless (see Headless Mode)
  • sessionStorageAdapter (see Native Session Persistence)
  • loadTimeoutMs
  • domain (legacy runtime only)
  • onReady
  • onEvent
  • onStateCache
  • onError

Upgrade From The Legacy React Native SDK

The safest migration is:

  1. replace the old npm package
  2. keep the runtime on legacy first
  3. use the AdaEmbedView alias only as a temporary bridge if that reduces churn
  4. move to AdaMessagingView and the new prop patterns once the package upgrade is stable

Side-by-side mapping

| Legacy | Messaging SDK | |---|---| | @ada-support/react-native-sdk | @ada-cx/messaging-react-native | | AdaEmbedView | AdaMessagingView | | require_relative '../node_modules/@ada-support/react-native-sdk/react_native_pods' in Podfile | remove it | | use_ada!() in Podfile | remove it |

The new package also exports a deprecated AdaEmbedView alias to make the first migration smaller if needed.

Before / after: package

# Before
npm install @ada-support/react-native-sdk

# After
npm install @ada-cx/messaging-react-native

If your old integration added Ada-specific Podfile helpers from @ada-support/react-native-sdk, remove them. The new package relies on the standard React Native and react-native-webview native setup only.

Before / after: imports

Smallest possible migration:

import { AdaEmbedView } from "@ada-cx/messaging-react-native";

Recommended end state:

import { AdaMessagingView } from "@ada-cx/messaging-react-native";

Important Code Changes To Make

1. Most customer apps should only set handle

For the normal production path, keep your setup simple:

<AdaMessagingView handle="my-bot" />

If Ada tells you your bot is hosted on a non-default production region such as Maple, pass the exact cluster value they give you:

<AdaMessagingView
  handle="my-bot"
  cluster="maple"
/>

2. Move sensitive metadata and device tokens to the ref in onReady

This is the preferred pattern in the new package:

onReady={() => {
  adaRef.current?.setSensitiveMetaFields({ authToken: "secure-session-token" });
  adaRef.current?.setDeviceToken(deviceToken);
}}

3. Move event handling to onEvent

For new code, prefer one event callback surface:

onEvent={(key, data) => {
  if (key === "ada:end_conversation") {
    console.log("Conversation ended", data);
  }
}}

Legacy Prop Compatibility

Some older props still work for compatibility, but they are no longer the preferred integration pattern.

| Legacy prop / pattern | Status in the new package | Recommended approach | |---|---|---| | sensitiveMetaFields prop | supported but deprecated | call ref.setSensitiveMetaFields() in onReady | | deviceToken prop | supported but deprecated | call ref.setDeviceToken() in onReady | | eventCallbacks | supported but deprecated | use onEvent | | endConversationCallback | supported but deprecated | use onEvent and check for ada:end_conversation | | thirdPartyCookiesEnabled | still present but deprecated; Android only | normally omit it | | styles | legacy runtime only | pass a CSS string on the legacy runtime; the Messaging runtime ignores it | | zdChatterAuthCallback | supported | authenticate Zendesk Chat and Zendesk Messaging handoffs |

The native auth bridge echoes request IDs for token and null responses. The web runtime drops answers whose IDs do not match the pending request. Existing host callbacks need no change.

For Zendesk Messaging handoffs, use webSdk="messaging". Return a JWT from your server through zdChatterAuthCallback. Sign the JWT with your Zendesk Messaging signing secret using HS256. Keep the secret on your server.

Include the signing key ID in the kid header. Include the scope: "user", external_id, and exp claims. Use an expiry time in Unix seconds. The external_id must be a non-empty string of at most 255 characters. It must not contain /, ?, #, %, whitespace, or control characters. Send the required external_id claim and the optional name, email, and email_verified claims. Verified email linking requires the Messaging SDK. Ada uses the JWT's name and email for display and linking only with an email and boolean email_verified: true. Without a verified email, Ada uses only external_id. See Authenticate end users for claim storage, display, and matching rules.

The Messaging runtime requests one token at startup, including when both Zendesk platforms are configured. Zendesk Messaging authentication does not require the Zendesk Chat feature. Existing Zendesk Chat callbacks need no changes. If the token is missing or invalid, the handoff uses an anonymous Sunshine user when no other identity mapping applies. Removing the Messaging signing secret also disables the stored Messaging identity. See the Zendesk Messaging setup for signing key configuration.

Upload And Media Permissions

If your bot flow lets users upload files or capture media, make sure your app includes the normal platform permissions required by react-native-webview and the device features you use.

For iOS, that usually means Info.plist usage descriptions such as:

  • NSCameraUsageDescription
  • NSPhotoLibraryUsageDescription
  • NSMicrophoneUsageDescription

For Android, add any permissions your app's upload or capture flow requires in AndroidManifest.xml. If you are migrating from much older legacy guidance, re-check those permissions against your current Android target SDK rather than copying them forward blindly.

State Restoration

If your app wants to restore chat immediately after process recreation, use:

  • onStateCache to persist the latest non-sensitive state snapshot
  • initialState to inject that snapshot the next time the view mounts

That removes the loading spinner on WebView restart flows.

The SDK filters the snapshot, your app stores it

Your app owns the storage, but the component now controls what may reach it. Before onStateCache runs, the component reduces the snapshot to the canonical allowlist and stamps __ada_cached_at__. This is the same filter the iOS and Android SDKs apply before they write to device storage. The snapshot you receive carries branding and feature configuration only. It carries no tokens and no conversation content.

Persist only these keys, and drop anything else the snapshot contains:

chatEnabled, tintColor, button, intro, fallbackUi, advancedColorsEnabled, allowedProtocols, textOverAccentColor, proactiveConversations, features, __ada_cached_at__

The PERSISTABLE_STATE_KEYS export carries the same list, and the exported toPersistableStateSnapshot() helper applies it. Use the helper to sanitize snapshots that an older package version persisted, because those snapshots can contain session tokens.

On the next mount, pass the stored snapshot back as initialState. The component filters it again and discards it when __ada_cached_at__ is older than the 10-minute TTL. A snapshot without a stamp still injects (deliberate back-compat for older persisted state), but it never expires. Keep the stamp so stale state is discarded instead of hydrated.

Native Session Persistence

The chat session lives in WebView storage. Android wipes that storage when the user force-quits the app, and iOS can lose it too. Pass sessionStorageAdapter to mirror the session into storage that your app owns. On the next launch, the web runtime asks the component for the mirror, and the component answers with a live read of your adapter. The session survives a force-quit. A live agent handoff survives with it. A clear that runs before that read makes the answer empty, so a cleared session cannot come back.

Without an adapter, sessions do not survive a force-quit. The component then behaves exactly as before this feature. Development builds log a one-time warning at mount when no adapter is set. The warning covers both runtimes.

The adapter is a small async key-value interface:

interface AdaSessionMirrorStorage {
  getItem(key: string): Promise<string | null>;
  setItem(key: string, value: string): Promise<void>;
  removeItem(key: string): Promise<void>;
}

The package adds no storage dependency. Your app supplies the store.

Wiring with AsyncStorage

@react-native-async-storage/async-storage matches the interface directly:

import AsyncStorage from "@react-native-async-storage/async-storage";

<AdaMessagingView handle="my-bot" sessionStorageAdapter={AsyncStorage} />

Session tokens must not move between devices. On Android, Auto Backup and device transfer copy the app data directory onto a different device. Exclude the adapter's storage from Auto Backup and device transfer.

On iOS, AsyncStorage marks its own directory NSURLIsExcludedFromBackupKey, so it stays out of iCloud and encrypted backups. AsyncStorage still holds the session token in plaintext in the app container. Use a Keychain-backed adapter with a ThisDeviceOnly accessibility class to protect the token at rest. See Keep the mirror out of backups.

Wiring with MMKV

react-native-mmkv is synchronous. Wrap it in a thin adapter:

import { MMKV } from "react-native-mmkv";

const storage = new MMKV({ id: "ada-session-mirror" });

const adaSessionStorage = {
  getItem: async (key: string) => storage.getString(key) ?? null,
  setItem: async (key: string, value: string) => {
    storage.set(key, value);
  },
  removeItem: async (key: string) => {
    storage.delete(key);
  },
};

<AdaMessagingView handle="my-bot" sessionStorageAdapter={adaSessionStorage} />

Behavior notes

  • The mirror stores one atomic snapshot per bot and endpoint under keys with the ada-session-mirror: prefix. Do not write to those keys yourself.
  • The web runtime decides session validity after a restore. The mirror has no expiry of its own.
  • reset(), deleteHistory(), and the private and session persistence modes clear the mirror.
  • ref.clearPersistedState() removes every mirror recorded through your adapter: the mounted bot's blob and index, plus all registry-recorded scopes. It does not touch the live WebView session. Mirrors written by an earlier app run through a different adapter can stay behind. To remove those, delete all keys in your store that start with ada-session-mirror:.
  • ref.clearPersistedState() rejects in four cases. An adapter call throws, times out, or leaves the adapter quarantined. A removed key still reads back a value. The removals ran but cannot be confirmed, because an earlier mirror write may still commit. You supplied an adapter earlier in this mount and no adapter is set now. It resolves with no effect only when the component never received an adapter.
  • Retry ref.clearPersistedState() when it reports that it could not confirm the wipe. The blocked write clears within a few seconds, and the retry then confirms. A rejection does not mean nothing was removed.
  • A one-shot identityToken is never written to the mirror. Only the resolved session state is stored.
  • Adapter failures during mirroring are caught. A throwing adapter degrades to no persistence and never breaks chat. Only clearPersistedState() reports failure to the caller.
  • A dropped mirror message or a failing adapter emits the ada.sessionMirror.diagnostic host event once per reason. The payload carries an enum reason string and no other data.
  • The mirror also covers the legacy web runtime (webSdk="legacy").
  • The component advertises mirror support to the web runtime once, at document start. Adding or removing sessionStorageAdapter therefore reloads the WebView, the same as a change to identityToken or appUrl. Only presence matters, so re-creating the adapter object on each render reloads nothing. Set the adapter before first render to avoid the reload.
  • The mirror contains session tokens. Protect the adapter's storage on each platform. See the next section.

Keep the mirror out of backups

The mirror contains session tokens, so it must stay on the device that wrote it. The native Ada SDKs enforce this: the iOS SDK stores the mirror in a ThisDeviceOnly Keychain item, and the Android SDK ships backup-exclusion rules. Your adapter's storage needs the same treatment.

Android: exclude your adapter's storage from Auto Backup and device transfer with dataExtractionRules and fullBackupContent, so backups do not copy sessions to another device.

iOS: AsyncStorage marks its own directory NSURLIsExcludedFromBackupKey, so it stays out of iCloud and encrypted backups unless the app sets RCTAsyncStorageExcludeFromBackup to NO. That covers backups. It does not make the data device-only or encrypted at rest. For a session token, prefer one of these:

  • A Keychain-backed adapter with a ThisDeviceOnly accessibility class. react-native-keychain supports this directly:

    import * as Keychain from "react-native-keychain";
    
    const adaSessionStorage = {
      getItem: async (key: string) => {
        const item = await Keychain.getGenericPassword({ service: `ada:${key}` });
        return item ? item.password : null;
      },
      setItem: async (key: string, value: string) => {
        await Keychain.setGenericPassword("ada", value, {
          service: `ada:${key}`,
          accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
        });
      },
      removeItem: async (key: string) => {
        await Keychain.resetGenericPassword({ service: `ada:${key}` });
      },
    };
  • An MMKV instance at a dedicated path. Mark that path isExcludedFromBackup after you create the instance. Use expo-file-system or react-native-fs to set it:

    import { MMKV } from "react-native-mmkv";
    
    const storage = new MMKV({ id: "ada-session-mirror", path: mirrorDirectory });
    // Mark mirrorDirectory with the iOS isExcludedFromBackup resource value.

Note: Keychain items survive an app uninstall on iOS. If your app must not restore a session after a reinstall, wipe the adapter's Ada keys on first run, or use the backup-excluded file approach instead.

Secure Identity Tokens

Use identityToken to start the session as a known user.

  1. Mint a token from your backend with POST /v2/auth/tokens/. The token is short-lived and single-use.
  2. Pass the token to the view before you mount it.
const token = await fetchAdaIdentityToken(); // your backend call

<AdaMessagingView handle="my-bot" webSdk="messaging" identityToken={token} />

The token is injected into the WebView document before content loads. It never appears in a URL. The injection only runs on the Ada webview origin, so other pages never see the token. The runtime reads it once per document load and then deletes it. A token set after mount only applies when the WebView reloads. Fetch a fresh token before each mount.

Headless Mode

Set headless to run the Ada runtime without its chat UI. The WebView stays mounted, but it is hidden and non-interactive. Events and all imperative handle methods keep working. Use them to build a fully native experience.

const adaRef = useRef<AdaMessagingViewHandle>(null);
const [unread, setUnread] = useState(0);

<AdaMessagingView
  ref={adaRef}
  handle="my-bot"
  webSdk="messaging"
  headless
  onEvent={(key) => {
    if (key === "ada:message:received") {
      setUnread((count) => count + 1); // drive your own red-dot badge
    }
  }}
/>

// Later, from your own composer UI:
adaRef.current?.sendMessage("Where is my order?");

Notes:

  • Keep the component mounted while you need the live session. Unmounting the component ends the session.
  • Persist snapshots with onStateCache and pass them back as initialState to rehydrate on the next mount. See State Restoration.
  • Headless applies to the Messaging runtime only. The SDK never sends the flag on the legacy runtime, the same as iOS and Android.

Select a quick reply

Call adaRef.current?.selectQuickReply(messageId, index) with the received message ID and a zero-based index into message.data.quickReplies. Read the message from data.message in onEvent("ada:message:received", data). The method requires the Messaging runtime and enableProgrammaticControl. Calls queue until the runtime is ready. The method returns void. The host sends only the message ID and index.

Programmatic Control

enableProgrammaticControl is a prop that defaults to true on React Native. At the default the component exposes selectQuickReply(), sendMessage() and triggerPlaybook() and delivers the gated ada:message:sent / ada:message:received events, and the two curated-bridge message reads are available. Those reads are getConversation() and getMessages(). Set enableProgrammaticControl={false} to opt out: those programmatic calls then reject and the gated events are not delivered. The true default is a back-compat choice. The pre-prop behavior was unconditional. On iOS and Android the same setting defaults to false, so a host there enables it explicitly.

Start a Playbook

triggerPlaybook starts an eligible Playbook with optional regular and sensitive metadata. This method is in Early Access. Enable programmatic control. Await the returned Promise<void> to handle acceptance or refusal. See the API reference.

Release Checklist

Before shipping a migration:

  • verify chat renders on both iOS and Android
  • confirm onReady fires
  • confirm your event logging still receives Ada SDK events
  • if you use push notifications, call setDeviceToken() and verify registration
  • test background / foreground and process restart behavior