React Native Chatbot SDK
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.
First release of the React Native Chatbot SDK, published on npm. See Release Notes for what's included.
View on NPMArchitecture 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)
| Concern | iOS | Android |
|---|---|---|
| WebView owner | Native EnxChatBridge module | react-native-webview |
| Events inbound | NativeEventEmitter → handleNativeEvent() | WebView.onMessage → onMessage() |
| Events outbound | NativeModules.EnxChatBridge.<method>() | webView.injectJavaScript() |
| JS injection | WKUserScript at document start | injectedJavaScriptBeforeContentLoaded |
| console.log routing | window.webkit.messageHandlers.chatCallback | window.ReactNativeWebView.postMessage() |
| Permissions | Info.plist usage descriptions | AndroidManifest.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/
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:
| File | Language | Role |
|---|---|---|
sdk/ios/EnxChatBridge.h | Objective-C Header | Protocol declarations |
sdk/ios/EnxChatBridge.m | Objective-C | RCT module, RCTEventEmitter |
sdk/ios/EnxChatBridge.swift | Swift | WKWebView manager |
Steps:
- In Xcode, right-click your app target folder → Add Files to “<YourApp>”.
- Select all three files above. Make sure “Add to targets” is checked for your app target.
- 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.
- Xcode will add
DEFINES_MODULE = YESautomatically to framework targets. For an app target, verify it in Build Settings → Packaging → Defines Module is set toYES.
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
}
}
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 / Method | Type | Description |
|---|---|---|
messages | EnxMessage[] | All messages (user + bot) in order |
status | EnxConnectionStatus | Current connection state |
isConnected | boolean | true when status is Connected |
isTyping | boolean | true while bot is typing |
botInfo | Record<string, unknown> | Bot info payload received on connect |
conversationId | string | Current 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) | () => void | Subscribe to state changes; returns unsubscribe |
Props Reference
EnxChatWidget Props
| Prop | Type | Default | Description |
|---|---|---|---|
config | EnxBotConfig | — | Required when controller is not provided |
controller | EnxChatController | — | Use an external controller instance |
theme | Partial<EnxChatTheme> | defaultTheme | Visual theming overrides |
showCallingOptions | boolean | false | Show audio/video call buttons in the input bar |
proactiveLocationPermission | boolean | false | Android only: request location permission on mount |
onMessageReceived | (msg: EnxMessage) => void | — | Called for every incoming bot message |
onMessageSent | (msg: EnxMessage) => void | — | Called after user sends a message |
onBotInfo | (info: Record<string, unknown>) => void | — | Called when bot info is received |
onError | (message: string, type: string) => void | — | Called on connection or JS errors |
onAudioCall | () => void | — | Called when user taps the audio call button |
onVideoCall | () => void | — | Called when user taps the video call button |
onVideoCallRequest | (url: string) => void | — | Called when the bot sends a video-room URL |
onVideoCallEnded | () => void | — | Called 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:
| Export | Description |
|---|---|
defaultTheme | WhatsApp-style greens |
mobileClassicTheme | Lighter green / off-white classic mobile layout |
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 (v1.0.0)
| Feature | Supported | Notes |
|---|---|---|
| Full chat UI | Yes | Drop-in EnxChatWidget with platform detection built in. |
| Auto-reconnect | Yes | Configurable reconnect status banner. |
| Typing indicator | Yes | Animated bubble shown during bot response delay. |
| Text, markdown, and rich message types | Yes | Image, video, file, link card, single/multi-choice, action buttons, calendar, centered system notices. |
| Video call support | Yes | Full-screen WebView modal launched on audio_video_action: start; dismissed on stop or back press. |
| Audio/video call buttons | Yes | Optional input-bar UI wired to onAudioCall / onVideoCall props. |
| File upload | Yes | Camera capture, gallery picker, document picker. |
| Theming | Yes | Full colour customization via EnxChatTheme; defaultTheme and mobileClassicTheme built in. |
| External controller | Yes | EnxChatController keeps the session alive across navigation. |
| Message queueing before ready | Yes | Messages sent before the chat is Ready are buffered and flushed automatically. |
| Objective-C / native module on iOS | Yes | Native EnxChatBridge module (Swift + ObjC) — Android has no native module. |
| Offline / cache-first mode | No | Requires network on every session start. |
| Picture-in-picture video calls | No | Not supported in v1.0. |
| Markdown tables | No | Not supported by the markdown renderer in v1.0. |
Troubleshooting
iOS — EnxChatBridgeImpl class not found
Cause: The Swift file is not included in the build target, or DEFINES_MODULE is not YES.
Fix:
- In Xcode, select
EnxChatBridge.swift→ File Inspector → confirm your target is checked under Target Membership. - In Build Settings, search
Defines Module→ set toYES. - 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:
- Verify
EnxChatBridge.swift,EnxChatBridge.m, andEnxChatBridge.hare all in the same pod/target. - Check that
NativeModules.EnxChatBridgeis notundefinedin the JS console — if it is, the native module was not linked. - 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:
- Confirm
injectedJavaScriptBeforeContentLoadedis set on the hiddenWebViewinChatWidget.tsx. - Check
INTERNETpermission is inAndroidManifest.xml. - Verify the hidden WebView rendered by checking
!managed.bridge.usesNativePathevaluates totrueon 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:
- Ensure
CAMERAandRECORD_AUDIOare declared inAndroidManifest.xml. - The SDK calls
PermissionsAndroid.requestMultiple()automatically before a video call. If you overrideonVideoCallRequest, callrequestCameraAndMicrophoneForMedia()frommediaPermissions.tsyourself 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 ..