React Native
@kavchat/react-native adds the scripted support chat to your app as a provider, a chat screen and an optional floating launcher. Answers come from the scripts you publish in the dashboard and run on the device.
Install
The package is pure JavaScript, with no native code of its own and no icon library, so it works the same in a regular React Native app and in Expo. Peer dependencies: react 18+, react-native 0.72+.
React Native CLI (no Expo)
yarn add @kavchat/react-native @react-native-async-storage/async-storage
cd ios && pod installAsyncStorage is a native module: pod install links it on iOS, and autolinking handles Android.
Expo
Works in Expo Go and in development builds:
yarn add @kavchat/react-native
npx expo install @react-native-async-storage/async-storageWithout AsyncStorage
AsyncStorage keeps the published release and the conversation between launches. It is optional: without it the SDK keeps everything in memory, so the bot still works but downloads the release again on every launch. To use another store, such as MMKV, pass the storage prop with any { getItem, setItem, removeItem? }, sync or async:
import { MMKV } from 'react-native-mmkv';
const mmkv = new MMKV();
const storage = {
getItem: (k: string) => mmkv.getString(k) ?? null,
setItem: (k: string, v: string) => mmkv.set(k, v),
removeItem: (k: string) => mmkv.delete(k),
};
<KavchatProvider publicKey="pk_live_..." storage={storage}>Usage
import { KavchatProvider, KavchatChat, KavchatLauncher, useKavchat } from '@kavchat/react-native';
export default function App() {
return (
<KavchatProvider publicKey="pk_live_..." onScreen={(screen, id) => navigation.navigate(screen, { id })}>
<Navigation />
{/* floating button, bottom end corner; opens the chat in a modal */}
<KavchatLauncher />
</KavchatProvider>
);
}
// or as a screen in your own navigator
function SupportScreen() {
return <KavchatChat />;
}
// or your own button
function HelpButton() {
const { available, open } = useKavchat();
if (!available) return null; // bot is off, limited to testers, or over quota
return <Button title="Help" onPress={open} />;
}KavchatProvider props
| Prop | Description |
|---|---|
publicKey | pk_live_... from the dashboard. Safe to ship in the app. |
apiBase | Default https://api.kavchat.com. |
locale | UI language (he, en, ar, ru, es, fr, de, pt, it, tr). Default: the device language, else the bot language. Scripts are matched in the bot language either way. |
userToken, userName | Identity, see below. |
onScreen(screen, targetId?) | Called for buttons of type "screen" in a script. Without it those buttons do nothing. |
screens | Screens your app can open, for the editor dropdown. See The screen list in the editor. |
onEvent(event) | ready, unavailable, effect, ticket. |
storage | Any { getItem, setItem, removeItem? }, sync or async. Default AsyncStorage, else memory. |
theme | Color overrides, see Theming and RTL. |
useKavchat() returns { status, available, offline, locale, isOpen, open, close, toggle, setUser, startNewConversation, openContactForm, controller }.
KavchatChat props: onClose? (shows a close button), hideHeader?, style?. KavchatLauncher props: style? (position), onPress? (use your own screen instead of the built-in modal), attention? (false turns the message off).
When the dashboard sets a message next to the chat button (a speech bubble, or curved text above the button), KavchatLauncher shows it after the configured delay, at most once a day: closing it or opening the chat hides it until tomorrow. It is never shown while the chat is open, and it does not move when the system asks for reduced motion. With "Don't show on phones" it shows only on tablets (shortest side 600 points or more).
Identity token
Without a token the user is a guest, and the contact form asks for an email or a phone. To identify a signed-in user, sign a short-lived JWT on your server with the secret key (never in the app, see Identify users) and pass it in:
<KavchatProvider publicKey="pk_live_..." userToken={session?.kavchatToken} userName={session?.firstName}>Changing userToken (sign-in, sign-out) reloads the release, because the testers audience depends on who the user is. You can also call useKavchat().setUser({ token, name }). The token is sent only as Authorization: Bearer and is never written to storage. A saved conversation is tied to a fingerprint of the token, so another account on the same device starts fresh.
Links, screens and the contact form
- Buttons of type
linkopen onlyhttps:andtel:URLs, throughLinking.openURL. Other schemes are ignored. - Buttons of type
screencallonScreen. The chat stays where it was. - "Talk to our team" opens the contact form with the conversation attached. The request number is shown after sending.
The screen list in the editor
Declare the screens your app can open, once in the screens prop of KavchatProvider. The SDK sends the list to Kavchat by itself, and the script editor then offers these screens in a dropdown with their names, instead of a free text field. It also asks for an item id when a screen needs one, and warns about a button that points to a screen you did not declare. Nobody types the screen list by hand in the dashboard.
const screens = [
{ id: 'orders', label: 'My orders' },
{ id: 'product', label: 'Product page', needsTarget: true }, // the button also carries a product id
];
<KavchatProvider
publicKey="pk_live_..."
screens={screens}
onScreen={(screen, targetId) => navigation.navigate(screen, targetId ? { id: targetId } : undefined)}
>idis the valueonScreenreceives: letters, digits,_,.and-, up to 40 characters.labelis the name the admin sees, up to 60 characters. Up to 100 screens. Invalid entries and duplicate ids are dropped.needsTarget: the button also carries an item id (for example a product id), andonScreenreceives it astargetId.- The list is sent in the background after the release loads, only when it changed or once a week. A failure is silent and retried at the next launch. The user never sees anything.
- The registered screens appear in the dashboard under Bot, Settings, "App screens", with the platforms that reported each one. A screen you removed from the app can be deleted there.
Theming and RTL
Colors come from the bot settings in the dashboard (primaryColor), and the text color on it is picked by WCAG contrast. Override any of them with primary, background, botBubble, userBubble, text, muted, accent, onPrimary and error:
<KavchatProvider
publicKey="pk_live_..."
theme={{ primary: '#0f766e', background: '#ffffff' }}
>The SDK does not change the layout direction of your app. It follows I18nManager.isRTL (start and end styles), and each text is aligned by its own language, so Hebrew and Arabic read correctly in an LTR app too. If your app is in Hebrew or Arabic, set RTL once in your app as usual: I18nManager.forceRTL(true) and a restart.
The "Powered by Kavchat by maxsave" badge appears on all plans, with a link to kavchat.com. On Premium, you can hide it in the bot settings.
Live chat with a representative
There is nothing to set up to get started. When live chat is turned on in the dashboard, the chat in your app offers it exactly like the website widget.
- The option: "Talk to a representative" appears before "Talk to our team", only after the server confirms that a representative is free. Availability is checked again when the button is tapped. Outside business hours, or when everyone is busy, only the contact form is offered, with a short line saying why, and when the team is back if that is known. When the monthly quota is used up, the form is shown without an explanation.
- The request: guests leave an email or a phone, in case nobody is free in time, and can add a name and a first message. When more than one department is open, they choose one. Signed-in users (
userToken) do not fill in contact details. If nobody can take the chat, the contact form opens with what the user already typed. - Waiting: "Waiting for a representative" with a countdown to the end of the wait time you set, and the buttons "Leave a message instead" and "End chat". Users can write while they wait. When the time is up the chat becomes a request automatically, and the user sees that nobody was free in time and the request number.
- The chat: the representative's name, messages, a typing indicator, transfers to another representative, and ending the chat. Everything from the server is shown as plain text, with no links.
- Real time: a Server-Sent Events connection over
XMLHttpRequest(fetch streaming where available). If it drops, the chat reconnects with growing delays and catches up on everything it missed, so no message is lost or shown twice. On networks that block this connection it switches to repeated requests (long-poll). Every message has an id, so a retry never creates a duplicate. A message that was not sent is marked, and a tap sends it again. - Background and restart: when the app goes to the background the connection is closed, and when it comes back the chat catches up. The chat access credential is valid for up to 12 hours or until the chat ends, and is kept in the storage you choose, so after the app restarts an ongoing chat comes back on screen. It is never put in a URL or a log.
- No push notifications in this version: messages from the representative arrive only while the app is open.
Keep the credential in secure storage (Keychain on iOS, Keystore on Android). Without secureStorage it is kept in the same storage as the conversation. A button of your own opens the request form when a representative is free, and otherwise the contact form with the explanation:
import * as SecureStore from 'expo-secure-store';
// expo-secure-store allows only [A-Za-z0-9._-] in keys
const safe = (k: string) => k.replace(/[^A-Za-z0-9._-]/g, '_');
const secureStorage = {
getItem: (k: string) => SecureStore.getItemAsync(safe(k)),
setItem: (k: string, v: string) => SecureStore.setItemAsync(safe(k), v),
removeItem: (k: string) => SecureStore.deleteItemAsync(safe(k)),
};
<KavchatProvider publicKey="pk_live_..." secureStorage={secureStorage} livePage="checkout">
// your own button
const { openLive } = useKavchat();
<Button title="Chat with us" onPress={openLive} />Events: onEvent receives { type: 'live', phase } when the chat changes state (queued, active, closed, ticket, replaced, expired).
Questions, conditions and leads
No app configuration is needed. A script can ask for details (a name, phone number, email, number or text), use conditions to decide what happens next (Premium) and send a lead. Everything runs on the device.
- Matching keyboard: When the bot is waiting for an answer, the text field opens a phone, email or numeric keyboard with autocomplete. If the answer is invalid, the bot explains what is missing and the field is highlighted. Optional questions show a "Skip" button.
- Lead: When consent text is present, "Send" and "No, thanks" buttons appear. If there is no network connection, the lead is saved on the device and sent when the connection returns, without duplicates.
- Prefill: Answers the user has already provided fill in the contact form and the live chat request form, and are attached to the request and the live chat. The name, email and phone number from the identity token count as known answers, so the bot does not ask for them again.
- Current screen:
livePagealso identifies the screen that conditions check and that is sent with leads. Changing the prop updates it.
AI answers (Premium)
There is nothing to set up in your app. When AI answers are turned on in the dashboard (on your own AI provider key), a typed question that no script matches is sent to Kavchat, and the chat shows a typing indicator while it waits, for up to 20 seconds. The answer is shown as plain text with a small "AI answer" label in the user's language, followed by a button for a related topic when there is one, and "Talk to our team".
If there is no answer, or the request fails, takes too long or hits the limit (10 answers per conversation every 15 minutes), the chat shows its regular "no match" reply. The question still appears in the unanswered questions report, and AI answers are kept in the saved conversation like any other message.
Behavior
- The published release is cached with its ETag (
If-None-Match, 304). Offline, the cached release is shown with a banner. - The conversation is saved on the device and restored for 24 hours on the same release and user.
- The conversation path is reported 2 seconds after the last action and when the app goes to the background.
- Requests send no custom headers, only
Authorization,Content-TypeandIf-None-Match.