React Native Chatbot SDK

View Release Notes →

dialogs_chatbot_reactnative embeds a fully functional EnableX Dialogs chatbot into any React Native application on iOS and Android. It uses a dual-path communication architecture — a single EnxWebViewBridge class detects the platform at runtime and routes all communication accordingly: a native WKWebView bridge module on iOS, and a hidden react-native-webview on Android.

React Native Chatbot SDK v1.0.0  ·  Released June 25, 2026

First release of the React Native Chatbot SDK, published on npm. See Release Notes for what's included.

  View on NPM
Architecture

Architecture Overview

Platform.OS === 'ios' && NativeModules.EnxChatBridge available?
          │
     YES  │                              NO
          ▼                              ▼
  iOS Native Path                 Android WebView Path
  ─────────────────               ──────────────────────
  WKWebView owned by              react-native-webview
  EnxChatBridge native            (hidden 1×1 view)
  module (Swift + ObjC)
          │                              │
  NativeEventEmitter             WebView.onMessage
  'EnxChatEvent'                 (JSON over postMessage)
          │                              │
          └──────────┬───────────────────┘
                     ▼
              EnxWebViewBridge
              (bridge.ts)
                     │
              EnxChatController
              (controller.ts)
                     │
              EnxChatWidget UI
              (ChatWidget.tsx)
ConcerniOSAndroid
WebView ownerNative EnxChatBridge modulereact-native-webview
Events inboundNativeEventEmitterhandleNativeEvent()WebView.onMessageonMessage()
Events outboundNativeModules.EnxChatBridge.<method>()webView.injectJavaScript()
JS injectionWKUserScript at document startinjectedJavaScriptBeforeContentLoaded
console.log routingwindow.webkit.messageHandlers.chatCallbackwindow.ReactNativeWebView.postMessage()
PermissionsInfo.plist usage descriptionsAndroidManifest.xml + runtime requests

Project Structure

packages/
├── sdk/                               # The publishable SDK package
│   ├── src/
│   │   ├── index.ts                   # Public exports
│   │   ├── bridge.ts                  # Platform bridge (iOS + Android paths)
│   │   ├── controller.ts              # State management wrapper
│   │   ├── types.ts                   # All TypeScript types & enums
│   │   ├── hooks.ts                   # React hooks
│   │   ├── parser.ts                  # Message parsing utilities
│   │   ├── mediaPermissions.ts        # Android runtime permission helpers
│   │   ├── cachedMedia.ts             # Media caching utilities
│   │   └── components/
│   │       ├── ChatWidget.tsx         # Main widget (platform check lives here)
│   │       ├── ChatScreen.tsx         # Full-screen wrapper
│   │       ├── ChatList.tsx           # Scrollable message list
│   │       ├── MessageCell.tsx        # Individual message renderer
│   │       ├── InputBar.tsx           # Text / file input bar
│   │       ├── ChatMarkdown.tsx       # Markdown renderer
│   │       └── TypingBubble.tsx       # Animated typing indicator
│   ├── ios/
│   │   ├── EnxChatBridge.h            # ObjC protocol declarations
│   │   ├── EnxChatBridge.m            # RCTEventEmitter module (ObjC)
│   │   └── EnxChatBridge.swift        # WKWebView manager (Swift)
│   ├── dialogs_chatbot_reactnative.podspec
│   └── package.json
│
└── example/                           # Test / reference app
    ├── App.tsx
    ├── ios/
    └── android/
Setup

Installation

Add the SDK to Your Project

npm install dialogs_chatbot_reactnative
# or
yarn add dialogs_chatbot_reactnative

Install Peer Dependencies

The SDK depends on the following packages that must be installed in your app:

npm install \
  react-native-webview \
  react-native-image-picker \
  @react-native-documents/picker \
  react-native-video \
  react-native-svg \
  react-native-markdown-display \
  lucide-react-native \
  @react-native-community/datetimepicker

Important: All of the above packages ship native code. You must run pod install on iOS and rebuild after installing them.

iOS Setup

Podfile Configuration

The SDK ships a podspec (dialogs_chatbot_reactnative.podspec). CocoaPods picks it up automatically when you reference the SDK package.

Ensure your ios/Podfile targets iOS 15.0 or later:

platform :ios, '15.0'

The podspec declares the following build settings automatically — you do not need to add these manually:

s.swift_version  = '5.0'
s.frameworks     = 'WebKit'
s.pod_target_xcconfig = {
  'DEFINES_MODULE'             => 'YES',
  'SWIFT_VERSION'              => '5.0',
  'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'NO'
}
s.dependency "React-Core"

DEFINES_MODULE = YES tells Xcode to generate the <pod>-Swift.h umbrella header automatically so that EnxChatBridge.m (ObjC) can see the Swift class at build time — with no manual import required.

Bridging Header — NOT Required

Do NOT add a bridging header (YourApp-Bridging-Header.h) for this SDK.

The Swift files in this SDK do not import any React Native headers directly. All React types stay inside the ObjC file (EnxChatBridge.m). The ObjC ↔ Swift communication happens through protocols declared in EnxChatBridge.h — both sides see that header automatically through the pod's module umbrella.

Adding SWIFT_OBJC_BRIDGING_HEADER to a framework pod target causes a build error; the podspec intentionally omits it.

Adding Native Files to Xcode

If you are embedding the SDK as source files (not via CocoaPods), add the following three files to your Xcode target:

FileLanguageRole
sdk/ios/EnxChatBridge.hObjective-C HeaderProtocol declarations
sdk/ios/EnxChatBridge.mObjective-CRCT module, RCTEventEmitter
sdk/ios/EnxChatBridge.swiftSwiftWKWebView manager

Steps:

  1. In Xcode, right-click your app target folder → Add Files to “<YourApp>”.
  2. Select all three files above. Make sure “Add to targets” is checked for your app target.
  3. When Xcode asks “Would you like to configure an Objective-C bridging header?” — click Create Bridging Header only if your app already uses Swift. Leave it empty unless your app itself needs it.
  4. Xcode will add DEFINES_MODULE = YES automatically to framework targets. For an app target, verify it in Build Settings → Packaging → Defines Module is set to YES.

Info.plist Permissions

Add the following keys to your app's ios/<YourApp>/Info.plist. The SDK reads camera, microphone, and location through the WKWebView (for video calls and chat geolocation features).

<!-- Camera — required for video calls and image/video file uploads -->
<key>NSCameraUsageDescription</key>
<string>Camera is used for video calls with the support agent.</string>

<!-- Microphone — required for audio and video calls -->
<key>NSMicrophoneUsageDescription</key>
<string>Microphone is used for audio and video calls with the support agent.</string>

<!-- Location — optional; needed if the chat page uses geolocation -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location may be used when the chat page requests it (e.g. maps or regional features).</string>

Without these keys the app will crash with a privacy usage description exception the first time the WKWebView requests the hardware.

App Transport Security

The chat widget loads https://voiceassist.enablex.io over TLS. No ATS exceptions are required for this host.

If your organisation restricts ATS with NSAllowsArbitraryLoads = false (recommended), your Info.plist should look like:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

Running pod install

cd ios
pod install --repo-update
cd ..

Then open YourApp.xcworkspace (not .xcodeproj) and build.

Android Setup

Android uses a standard React Native WebView component — there are no native Android files to add. All communication goes through the WebView's postMessage / injectJavaScript bridge.

AndroidManifest.xml Permissions

Add the following inside the <manifest> tag of android/app/src/main/AndroidManifest.xml:

<!-- Network access — required for loading the chat page -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Camera — required for video calls and media uploads -->
<uses-permission android:name="android.permission.CAMERA" />

<!-- Audio — required for audio and video calls -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- Location — optional; required if chat page uses geolocation -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Hardware feature declarations (android:required="false" keeps the app
     installable on devices without a camera / mic) -->
<uses-feature android:name="android.hardware.camera"     android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

Runtime permissions for camera, microphone, and location are requested automatically by the SDK (mediaPermissions.ts) when the user triggers a relevant action inside the chat widget. You do not need to request them yourself.

Gradle Configuration

No custom Gradle changes are required beyond what react-native-webview and the other peer dependencies install automatically.

Ensure your android/build.gradle references the correct React Native version:

// android/build.gradle
buildscript {
    ext {
        minSdkVersion    = 24
        compileSdkVersion = 35
        targetSdkVersion  = 35
    }
}
Usage

Basic Usage

import React from 'react';
import { SafeAreaView } from 'react-native';
import { EnxChatWidget } from 'dialogs_chatbot_reactnative';

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <EnxChatWidget
        config={{
          botId: 'YOUR_BOT_ID',
          host:  'https://dialogs.enablex.io',
        }}
      />
    </SafeAreaView>
  );
}

Controller Pattern (Advanced)

Use EnxChatController directly when you need to:

  • Keep chat state alive across screen navigation (the controller lives outside the widget).
  • Access messages, connection status, or bot info from outside the widget.
  • Dispose the session manually on logout.
import React, { useEffect, useRef } from 'react';
import { EnxChatController, EnxChatWidget, mobileClassicTheme } from 'dialogs_chatbot_reactnative';

export default function ChatScreen() {
  // Create the controller once and hold it in a ref so it survives re-renders.
  const controllerRef = useRef(
    new EnxChatController({
      botId: 'YOUR_BOT_ID',
      host:  'https://dialogs.enablex.io',
    })
  );

  // Dispose when the user leaves the session entirely (e.g. logout).
  useEffect(() => {
    const ctrl = controllerRef.current;
    return () => { void ctrl.dispose(); };
  }, []);

  return (
    <EnxChatWidget
      controller={controllerRef.current}
      theme={mobileClassicTheme}
      showCallingOptions
      onBotInfo={(info) => console.log('Bot name:', info.name)}
      onMessageReceived={(msg) => console.log('New message:', msg)}
      onError={(msg, type) => console.error(`[${type}] ${msg}`)}
    />
  );
}

Controller API

Property / MethodTypeDescription
messagesEnxMessage[]All messages (user + bot) in order
statusEnxConnectionStatusCurrent connection state
isConnectedbooleantrue when status is Connected
isTypingbooleantrue while bot is typing
botInfoRecord<string, unknown>Bot info payload received on connect
conversationIdstringCurrent conversation ID
sendText(text)Promise<void>Send a text message programmatically
sendFile(data)Promise<void>Send a file (base64 + meta)
resetConversation()Promise<void>Start a new conversation
reconnect()Promise<void>Manually reconnect after disconnect
dispose()Promise<void>Disconnect and clean up
subscribe(fn)() => voidSubscribe to state changes; returns unsubscribe
Configuration

Props Reference

EnxChatWidget Props

PropTypeDefaultDescription
configEnxBotConfigRequired when controller is not provided
controllerEnxChatControllerUse an external controller instance
themePartial<EnxChatTheme>defaultThemeVisual theming overrides
showCallingOptionsbooleanfalseShow audio/video call buttons in the input bar
proactiveLocationPermissionbooleanfalseAndroid only: request location permission on mount
onMessageReceived(msg: EnxMessage) => voidCalled for every incoming bot message
onMessageSent(msg: EnxMessage) => voidCalled after user sends a message
onBotInfo(info: Record<string, unknown>) => voidCalled when bot info is received
onError(message: string, type: string) => voidCalled on connection or JS errors
onAudioCall() => voidCalled when user taps the audio call button
onVideoCall() => voidCalled when user taps the video call button
onVideoCallRequest(url: string) => voidCalled when the bot sends a video-room URL
onVideoCallEnded() => voidCalled after audio_video_action: stop from bot

EnxBotConfig Object

interface EnxBotConfig {
  botId:    string;   // EnableX bot identifier
  host:     string;   // API host, e.g. 'https://dialogs.enablex.io'
  rootPath?: string;  // Optional path prefix
}

Theming

import { EnxChatWidget, mobileClassicTheme } from 'dialogs_chatbot_reactnative';

<EnxChatWidget
  config={...}
  theme={{
    primaryColor:        '#075E54',  // Header, send button, etc.
    userBubbleColor:     '#DCF8C6',  // User message bubble background
    botBubbleColor:      '#FFFFFF',  // Bot message bubble background
    backgroundColor:     '#ECE5DD',  // Chat list background
    userAvatarColor:     '#2196F3',  // User avatar circle colour
    botAvatarColor:      '#4CAF50',  // Bot avatar circle colour
    useMobileClassicLayout: false,   // WhatsApp-style layout when true
  }}
/>

Built-in themes exported from the package:

ExportDescription
defaultThemeWhatsApp-style greens
mobileClassicThemeLighter green / off-white classic mobile layout
Advanced

Platform Communication Flow

iOS — Inbound Event Flow

Chat web page
  └─ console.log(JSON.stringify({ event, data }))
       └─ WKUserScript override (injected at document start)
            └─ window.webkit.messageHandlers.chatCallback.postMessage(msg)
                 └─ EnxChatBridgeImpl (Swift) WKScriptMessageHandler
                      └─ eventEmitter?.emitChatEvent(dict)
                           └─ EnxChatBridge (ObjC) RCTEventEmitter
                                └─ sendEventWithName: @"EnxChatEvent"
                                     └─ NativeEventEmitter in bridge.ts
                                          └─ handleNativeEvent() → handleEnvelope()

iOS — Outbound Message Flow

User sends text/file
  └─ EnxWebViewBridge.sendText() / sendFile()
       └─ NativeModules.EnxChatBridge.sendText(text)  [JS → ObjC]
            └─ EnxChatBridge.m  RCT_EXPORT_METHOD
                 └─ [self.impl sendText:text]          [ObjC → Swift]
                      └─ EnxChatBridgeImpl.evalJS()
                           └─ WKWebView.evaluateJavaScript()
                                └─ sendMessageToBot(text, [])

Android — Inbound Event Flow

Chat web page
  └─ console.log(JSON.stringify({ event, data }))
       └─ androidBridge injection (injectedJavaScriptBeforeContentLoaded)
            └─ window.ReactNativeWebView.postMessage(jsonString)
                 └─ WebView onMessage callback in ChatWidget.tsx
                      └─ managed.bridge.onMessage(e)
                           └─ JSON.parse → handleEnvelope()

Android — Outbound Message Flow

User sends text/file
  └─ EnxWebViewBridge.sendText() / sendFile()
       └─ this.inject(`sendMessageToBot('${text}', []);`)
            └─ WebView.injectJavaScript()
                 └─ sendMessageToBot() on the chat page

Adding a New Bridge Method (iOS & Android)

When the chat page exposes a new JavaScript API (e.g. setUserContext()), follow these steps:

Step 1 — Declare the Protocol Method (iOS only)

Open sdk/ios/EnxChatBridge.h and add to the EnxChatWebViewManager protocol:

- (void)setUserContext:(NSDictionary *)context;

Step 2 — Implement in Swift (iOS only)

Open sdk/ios/EnxChatBridge.swift and add inside EnxChatBridgeImpl:

func setUserContext(_ context: [String: Any]) {
    guard let jsonData = try? JSONSerialization.data(withJSONObject: context),
          let jsonStr  = String(data: jsonData, encoding: .utf8) else { return }
    evalJS("typeof setUserContext==='function'&&setUserContext(\(jsonStr));")
}

Step 3 — Export from ObjC (iOS only)

Open sdk/ios/EnxChatBridge.m and add:

RCT_EXPORT_METHOD(setUserContext:(NSDictionary *)context) {
    [self.impl setUserContext:context];
}

Step 4 — Add to the TypeScript Bridge (both platforms)

Open sdk/src/bridge.ts and add a new public method:

async setUserContext(context: Record<string, unknown>): Promise<void> {
    if (this.usesNativePath) {
        // iOS — calls the native module method exported in Step 3
        NativeModules.EnxChatBridge.setUserContext(context);
    } else {
        // Android — injects JavaScript directly into the WebView
        const json = JSON.stringify(context).replace(/'/g, "\\'");
        this.inject(`typeof setUserContext==='function'&&setUserContext(${json});true;`);
    }
}

Step 5 — Expose from the Controller (optional)

If consumers should call this via the controller, add to sdk/src/controller.ts:

async setUserContext(context: Record<string, unknown>): Promise<void> {
    await this.bridge.setUserContext(context);
}

Step 6 — pod install

cd ios && pod install && cd ..
Feature Support

Feature Support (v1.0.0)

FeatureSupportedNotes
Full chat UIYesDrop-in EnxChatWidget with platform detection built in.
Auto-reconnectYesConfigurable reconnect status banner.
Typing indicatorYesAnimated bubble shown during bot response delay.
Text, markdown, and rich message typesYesImage, video, file, link card, single/multi-choice, action buttons, calendar, centered system notices.
Video call supportYesFull-screen WebView modal launched on audio_video_action: start; dismissed on stop or back press.
Audio/video call buttonsYesOptional input-bar UI wired to onAudioCall / onVideoCall props.
File uploadYesCamera capture, gallery picker, document picker.
ThemingYesFull colour customization via EnxChatTheme; defaultTheme and mobileClassicTheme built in.
External controllerYesEnxChatController keeps the session alive across navigation.
Message queueing before readyYesMessages sent before the chat is Ready are buffered and flushed automatically.
Objective-C / native module on iOSYesNative EnxChatBridge module (Swift + ObjC) — Android has no native module.
Offline / cache-first modeNoRequires network on every session start.
Picture-in-picture video callsNoNot supported in v1.0.
Markdown tablesNoNot supported by the markdown renderer in v1.0.
Reference

Troubleshooting

iOS — EnxChatBridgeImpl class not found

Cause: The Swift file is not included in the build target, or DEFINES_MODULE is not YES.

Fix:

  1. In Xcode, select EnxChatBridge.swift → File Inspector → confirm your target is checked under Target Membership.
  2. In Build Settings, search Defines Module → set to YES.
  3. Clean build folder (Cmd+Shift+K) and rebuild.

iOS — Chat Events Never Arrive (no onReady / onConnected)

Cause: The WKWebView's chatCallback message handler was not registered, or the console.log override script failed.

Fix:

  1. Verify EnxChatBridge.swift, EnxChatBridge.m, and EnxChatBridge.h are all in the same pod/target.
  2. Check that NativeModules.EnxChatBridge is not undefined in the JS console — if it is, the native module was not linked.
  3. Confirm iOS 15.0+ deployment target (earlier versions have WKWebView restrictions).

Android — Chat Events Never Arrive (widget stuck on “Connecting…”)

Cause: The androidBridge injection script did not run before the page loaded, or onMessage was not attached.

Fix:

  1. Confirm injectedJavaScriptBeforeContentLoaded is set on the hidden WebView in ChatWidget.tsx.
  2. Check INTERNET permission is in AndroidManifest.xml.
  3. Verify the hidden WebView rendered by checking !managed.bridge.usesNativePath evaluates to true on Android (i.e., the native module is absent).

Android — Camera or Microphone Permission Denied in WebView

Cause: onPermissionRequest from WebChromeClient not handled, or the Android runtime permission was not granted.

Fix:

  1. Ensure CAMERA and RECORD_AUDIO are declared in AndroidManifest.xml.
  2. The SDK calls PermissionsAndroid.requestMultiple() automatically before a video call. If you override onVideoCallRequest, call requestCameraAndMicrophoneForMedia() from mediaPermissions.ts yourself before opening the video screen.

Pod Install Fails with Swift/ObjC Bridging Header Error

Cause: SWIFT_OBJC_BRIDGING_HEADER was manually added to the pod's xcconfig.

Fix: Remove it. The SDK does not use a bridging header for the pod target. DEFINES_MODULE = YES in the podspec is sufficient.

react-native-webview Not Found

Cause: Peer dependencies not installed.

npm install react-native-webview
cd ios && pod install && cd ..