@lithium-platform-sdk/chatbot
v0.1.0
Published
Drop-in React Native chat for the Lithium platform: a thin WebView host + capability bridge (clients embed this; UI/logic are served and updated OTA by us)
Readme
@lithium-platform-sdk/chatbot
Встраиваемый чат Lithium для React Native. Это тонкий хост: нативный
react-native-webview + типизированный capability-мост. Сам интерфейс и логика
чата живут в веб-приложении, которое хостим и обновляем мы (OTA), поэтому
обновления чата не требуют релиза приложения клиента в сторах.
[ваше RN-приложение]
└─ <LithiumChatView/> ← этот пакет (WebView + мост)
└─ apps/chat-web (наш URL) ← UI/логика, обновляем мы
└─ apps/server ← bridge → Voximplant Kit + чатбот ЛитиумУстановка
npm install @lithium-platform-sdk/chatbot react-native-webview
# iOS:
cd ios && pod installreact, react-native, react-native-webview — peer-зависимости.
Быстрый старт
import { LithiumChatView } from '@lithium-platform-sdk/chatbot';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function CareChatScreen({ navigation, user }) {
const insets = useSafeAreaInsets();
return (
<LithiumChatView
webUrl="https://chat.lithium.example/" // наш хостинг chat-web
serverUrl="https://chat-api.lithium.example" // bridge-сервер
identity={{
clientId: user.id,
displayName: user.fullName,
phone: user.phone,
email: user.email,
}}
context={{ source: 'care', title: 'Служба заботы' }}
theme={{ primaryColor: '#D4016A', backgroundColor: '#F9F3F3' }}
locale="ru"
safeAreaInsets={insets}
onClose={() => navigation.goBack()}
/>
);
}Identity
identity инъектируется в страницу до загрузки контента (не попадает в URL,
логи и историю). Поля: clientId (для восстановления сессии), displayName,
phone, email, avatar, locale.
Capability-мост
Мост даёт веб-чату доступ к нативным возможностям. Ключевой принцип:
Возможность включается, только если её реализует ваш хендлер, использующий уже подключённые в приложении модули и уже выданные разрешения. Если хендлер не передан — веб скрывает фичу (graceful degradation), новых системных permission-промптов пакет не создаёт.
import { LithiumChatView, nativeUploadAttachment, type CapabilityHandlers } from '@lithium-platform-sdk/chatbot';
import ImagePicker from 'react-native-image-crop-picker'; // уже в бинаре клиента
import { pick, types, isCancel } from '@react-native-documents/picker';
const capabilities: Partial<CapabilityHandlers> = {
pickImage: async ({ multiple }) => {
const result = await ImagePicker.openPicker({
mediaType: 'photo',
multiple: multiple ?? false,
maxFiles: multiple ? 10 : 1,
});
const images = Array.isArray(result) ? result : [result];
return {
files: images.map((img) => ({
uri: img.path,
name: img.filename ?? 'photo.jpg',
type: img.mime ?? 'image/jpeg',
size: img.size,
})),
};
},
takePhoto: async () => {
const img = await ImagePicker.openCamera({ mediaType: 'photo' });
return {
file: {
uri: img.path,
name: img.filename ?? 'photo.jpg',
type: img.mime ?? 'image/jpeg',
size: img.size,
},
};
},
pickFile: async ({ multiple }) => {
const docs = await pick({ allowMultiSelection: multiple ?? true, type: [types.allFiles] });
return {
files: docs.map((d) => ({
uri: d.uri,
name: d.name ?? 'file',
type: d.type ?? 'application/octet-stream',
size: d.size,
})),
};
},
/** Upload on the native side — WebView cannot read file:// URIs or RN FormData. */
uploadAttachment: (params) => nativeUploadAttachment(params),
};CodePush и нативные модули
CodePush (Revopush) обновляет только JS-бандл и ассеты. Новые native-модули,
изменения Info.plist / AndroidManifest.xml требуют релиза в сторах.
| Что | Доставка |
| --- | --- |
| apps/chat-web (UI, action sheet, upload flow) | Наш хостинг — обновляем без участия клиента |
| @lithium-platform-sdk/chatbot + handlers в клиенте | CodePush — если модуль уже в package.json бандла |
| react-native-webview, image-crop-picker, @react-native-documents/picker | Уже в бинаре kdf-patient-app — не добавляем |
| uploadAttachment handler | JS-only — nativeUploadAttachment() использует только fetch + { uri } FormData |
Не используйте includeBase64: true и передачу base64 через bridge для больших
файлов — это перегружает RN bridge (image-picker docs).
Правильный поток: pickImage → { uri } → uploadAttachment (native fetch) →
sendMessage с URL от сервера.
На native host веб-чат показывает action sheet «Галерея / Камера / Файл».
Полный список — тип Capability из @lithium-platform-sdk/bridge-protocol.
Вариатор (пилотная раскатка)
Решение «нативный Voximplant (vox) или Lithium-чат (lithium)» принимает
основной сервис Lithium, а не приложение и не bridge-сервер — так процентом
раскатки управляем централизованно, без релиза клиента.
import { resolveChatVariant } from '@lithium-platform-sdk/chatbot';
const variant = await resolveChatVariant({
endpoint: 'https://api.lithium.example/care-chat/variant',
payload: { userId: user.id, phone: user.phone, platform: Platform.OS, appVersion },
timeoutMs: 2000,
fallback: 'vox', // безопасный откат при недоступности вариатора
});
if (variant === 'lithium') navigation.navigate('LithiumCareScreen');
else openVoximplantChat();Есть и хук useVariator(req) → { variant, loading }.
Props
| Prop | Тип | Описание |
| --- | --- | --- |
| webUrl | string | URL хостимого веб-чата (наш). |
| serverUrl | string | URL bridge-сервера (инъектируется в страницу). |
| identity | LithiumIdentity | Идентификация пользователя. |
| context | object? | Произвольный контекст диалога. |
| theme | LithiumChatTheme? | Цвета (по умолчанию палитра клиники). |
| locale | string? | Локаль UI. |
| variant | string? | Назначенный вариант (для аналитики). |
| capabilities | Partial<CapabilityHandlers>? | Нативные хендлеры возможностей. |
| safeAreaInsets | SafeAreaInsets? | Инсеты (например из safe-area-context). |
| onClose | () => void | Веб попросил закрыть чат. |
| onLoadError | (error) => void? | WebView не загрузил webUrl (аналитика / fallback). |
| originWhitelist | string[]? | Лок навигации (по умолчанию origin webUrl). |
Клавиатура и WebView
LithiumChatView настраивает WebView так, чтобы веб-чат сам управлял layout
композера без конфликта с нативной клавиатурой:
- iOS:
keyboardLayoutMode: 'webVisualViewport'— веб читаетwindow.visualViewport; native не добавляет padding. WebView:scrollEnabled={false},contentInsetAdjustmentBehavior="never",hideKeyboardAccessoryView. - Android (API ≤ 34):
'nativeResize'— окно сжимается черезadjustResize, веб inset = 0. - Android (API 35+, edge-to-edge):
'nativePadding'— контейнер получаетpaddingBottom: safeAreaInsets.bottom + keyboardHeight; веб используетhost:keyboard.height.
Режим передаётся в host:hello; веб не должен одновременно применять тот же
inset (double-count → «улет» input при focus).
Bridge: надёжность
- Сообщения native → web буферизуются до первого
web:ready. - При возврате из background (
AppState→active) повторяется handshake. - На Android bootstrap инъектируется и через
injectedJavaScriptObject(race сbeforeContentLoaded); веб ретраит приgetBootstrap() === null.
Dev: HTTP по LAN
- iOS:
NSAllowsLocalNetworkingвInfo.plist(см.apps/demo-rn). - Android:
usesCleartextTraffic="true"только в debug manifest (android/app/src/debug/AndroidManifest.xml).
Протокол
Контракт postMessage (общий с веб-частью) — в пакете
@lithium-platform-sdk/bridge-protocol. Версионируется PROTOCOL_VERSION; при
handshake хост сообщает версию и список доступных capability.
