@parlr/react-native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +918 -0
- package/lib/commonjs/components/AttachmentPicker.js +292 -0
- package/lib/commonjs/components/AttachmentPicker.js.map +1 -0
- package/lib/commonjs/components/AttachmentPreview.js +200 -0
- package/lib/commonjs/components/AttachmentPreview.js.map +1 -0
- package/lib/commonjs/components/ChatBubble.js +391 -0
- package/lib/commonjs/components/ChatBubble.js.map +1 -0
- package/lib/commonjs/components/EmptyState.js +115 -0
- package/lib/commonjs/components/EmptyState.js.map +1 -0
- package/lib/commonjs/components/ParlrChat.js +745 -0
- package/lib/commonjs/components/ParlrChat.js.map +1 -0
- package/lib/commonjs/components/ParlrConversationList.js +509 -0
- package/lib/commonjs/components/ParlrConversationList.js.map +1 -0
- package/lib/commonjs/components/PreChatForm.js +263 -0
- package/lib/commonjs/components/PreChatForm.js.map +1 -0
- package/lib/commonjs/components/RichMessage.js +284 -0
- package/lib/commonjs/components/RichMessage.js.map +1 -0
- package/lib/commonjs/components/SatisfactionSurvey.js +292 -0
- package/lib/commonjs/components/SatisfactionSurvey.js.map +1 -0
- package/lib/commonjs/components/TypingIndicator.js +86 -0
- package/lib/commonjs/components/TypingIndicator.js.map +1 -0
- package/lib/commonjs/core/api.js +310 -0
- package/lib/commonjs/core/api.js.map +1 -0
- package/lib/commonjs/core/config.js +40 -0
- package/lib/commonjs/core/config.js.map +1 -0
- package/lib/commonjs/core/errors.js +73 -0
- package/lib/commonjs/core/errors.js.map +1 -0
- package/lib/commonjs/core/offlineQueue.js +89 -0
- package/lib/commonjs/core/offlineQueue.js.map +1 -0
- package/lib/commonjs/core/pushNotifications.js +21 -0
- package/lib/commonjs/core/pushNotifications.js.map +1 -0
- package/lib/commonjs/core/session.js +130 -0
- package/lib/commonjs/core/session.js.map +1 -0
- package/lib/commonjs/core/theme.js +110 -0
- package/lib/commonjs/core/theme.js.map +1 -0
- package/lib/commonjs/core/types.js +6 -0
- package/lib/commonjs/core/types.js.map +1 -0
- package/lib/commonjs/core/websocket.js +245 -0
- package/lib/commonjs/core/websocket.js.map +1 -0
- package/lib/commonjs/hooks/useChat.js +462 -0
- package/lib/commonjs/hooks/useChat.js.map +1 -0
- package/lib/commonjs/hooks/useParlr.js +44 -0
- package/lib/commonjs/hooks/useParlr.js.map +1 -0
- package/lib/commonjs/index.js +185 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/provider/ParlrContext.js +38 -0
- package/lib/commonjs/provider/ParlrContext.js.map +1 -0
- package/lib/commonjs/provider/ParlrProvider.js +256 -0
- package/lib/commonjs/provider/ParlrProvider.js.map +1 -0
- package/lib/module/components/AttachmentPicker.js +287 -0
- package/lib/module/components/AttachmentPicker.js.map +1 -0
- package/lib/module/components/AttachmentPreview.js +195 -0
- package/lib/module/components/AttachmentPreview.js.map +1 -0
- package/lib/module/components/ChatBubble.js +386 -0
- package/lib/module/components/ChatBubble.js.map +1 -0
- package/lib/module/components/EmptyState.js +110 -0
- package/lib/module/components/EmptyState.js.map +1 -0
- package/lib/module/components/ParlrChat.js +740 -0
- package/lib/module/components/ParlrChat.js.map +1 -0
- package/lib/module/components/ParlrConversationList.js +504 -0
- package/lib/module/components/ParlrConversationList.js.map +1 -0
- package/lib/module/components/PreChatForm.js +258 -0
- package/lib/module/components/PreChatForm.js.map +1 -0
- package/lib/module/components/RichMessage.js +280 -0
- package/lib/module/components/RichMessage.js.map +1 -0
- package/lib/module/components/SatisfactionSurvey.js +287 -0
- package/lib/module/components/SatisfactionSurvey.js.map +1 -0
- package/lib/module/components/TypingIndicator.js +81 -0
- package/lib/module/components/TypingIndicator.js.map +1 -0
- package/lib/module/core/api.js +305 -0
- package/lib/module/core/api.js.map +1 -0
- package/lib/module/core/config.js +36 -0
- package/lib/module/core/config.js.map +1 -0
- package/lib/module/core/errors.js +64 -0
- package/lib/module/core/errors.js.map +1 -0
- package/lib/module/core/offlineQueue.js +82 -0
- package/lib/module/core/offlineQueue.js.map +1 -0
- package/lib/module/core/pushNotifications.js +16 -0
- package/lib/module/core/pushNotifications.js.map +1 -0
- package/lib/module/core/session.js +122 -0
- package/lib/module/core/session.js.map +1 -0
- package/lib/module/core/theme.js +105 -0
- package/lib/module/core/theme.js.map +1 -0
- package/lib/module/core/types.js +4 -0
- package/lib/module/core/types.js.map +1 -0
- package/lib/module/core/websocket.js +241 -0
- package/lib/module/core/websocket.js.map +1 -0
- package/lib/module/hooks/useChat.js +458 -0
- package/lib/module/hooks/useChat.js.map +1 -0
- package/lib/module/hooks/useParlr.js +40 -0
- package/lib/module/hooks/useParlr.js.map +1 -0
- package/lib/module/index.js +58 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/provider/ParlrContext.js +35 -0
- package/lib/module/provider/ParlrContext.js.map +1 -0
- package/lib/module/provider/ParlrProvider.js +251 -0
- package/lib/module/provider/ParlrProvider.js.map +1 -0
- package/lib/typescript/commonjs/components/AttachmentPicker.d.ts +23 -0
- package/lib/typescript/commonjs/components/AttachmentPicker.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/AttachmentPreview.d.ts +16 -0
- package/lib/typescript/commonjs/components/AttachmentPreview.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/ChatBubble.d.ts +14 -0
- package/lib/typescript/commonjs/components/ChatBubble.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/EmptyState.d.ts +10 -0
- package/lib/typescript/commonjs/components/EmptyState.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/ParlrChat.d.ts +34 -0
- package/lib/typescript/commonjs/components/ParlrChat.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/ParlrConversationList.d.ts +17 -0
- package/lib/typescript/commonjs/components/ParlrConversationList.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/PreChatForm.d.ts +20 -0
- package/lib/typescript/commonjs/components/PreChatForm.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/RichMessage.d.ts +41 -0
- package/lib/typescript/commonjs/components/RichMessage.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts +17 -0
- package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/TypingIndicator.d.ts +7 -0
- package/lib/typescript/commonjs/components/TypingIndicator.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/api.d.ts +37 -0
- package/lib/typescript/commonjs/core/api.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/config.d.ts +9 -0
- package/lib/typescript/commonjs/core/config.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/errors.d.ts +35 -0
- package/lib/typescript/commonjs/core/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/offlineQueue.d.ts +16 -0
- package/lib/typescript/commonjs/core/offlineQueue.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/pushNotifications.d.ts +6 -0
- package/lib/typescript/commonjs/core/pushNotifications.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/session.d.ts +15 -0
- package/lib/typescript/commonjs/core/session.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/theme.d.ts +43 -0
- package/lib/typescript/commonjs/core/theme.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/types.d.ts +185 -0
- package/lib/typescript/commonjs/core/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/websocket.d.ts +17 -0
- package/lib/typescript/commonjs/core/websocket.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useChat.d.ts +35 -0
- package/lib/typescript/commonjs/hooks/useChat.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useParlr.d.ts +11 -0
- package/lib/typescript/commonjs/hooks/useParlr.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +30 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/provider/ParlrContext.d.ts +13 -0
- package/lib/typescript/commonjs/provider/ParlrContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/ParlrProvider.d.ts +5 -0
- package/lib/typescript/commonjs/provider/ParlrProvider.d.ts.map +1 -0
- package/lib/typescript/module/components/AttachmentPicker.d.ts +23 -0
- package/lib/typescript/module/components/AttachmentPicker.d.ts.map +1 -0
- package/lib/typescript/module/components/AttachmentPreview.d.ts +16 -0
- package/lib/typescript/module/components/AttachmentPreview.d.ts.map +1 -0
- package/lib/typescript/module/components/ChatBubble.d.ts +14 -0
- package/lib/typescript/module/components/ChatBubble.d.ts.map +1 -0
- package/lib/typescript/module/components/EmptyState.d.ts +10 -0
- package/lib/typescript/module/components/EmptyState.d.ts.map +1 -0
- package/lib/typescript/module/components/ParlrChat.d.ts +34 -0
- package/lib/typescript/module/components/ParlrChat.d.ts.map +1 -0
- package/lib/typescript/module/components/ParlrConversationList.d.ts +17 -0
- package/lib/typescript/module/components/ParlrConversationList.d.ts.map +1 -0
- package/lib/typescript/module/components/PreChatForm.d.ts +20 -0
- package/lib/typescript/module/components/PreChatForm.d.ts.map +1 -0
- package/lib/typescript/module/components/RichMessage.d.ts +41 -0
- package/lib/typescript/module/components/RichMessage.d.ts.map +1 -0
- package/lib/typescript/module/components/SatisfactionSurvey.d.ts +17 -0
- package/lib/typescript/module/components/SatisfactionSurvey.d.ts.map +1 -0
- package/lib/typescript/module/components/TypingIndicator.d.ts +7 -0
- package/lib/typescript/module/components/TypingIndicator.d.ts.map +1 -0
- package/lib/typescript/module/core/api.d.ts +37 -0
- package/lib/typescript/module/core/api.d.ts.map +1 -0
- package/lib/typescript/module/core/config.d.ts +9 -0
- package/lib/typescript/module/core/config.d.ts.map +1 -0
- package/lib/typescript/module/core/errors.d.ts +35 -0
- package/lib/typescript/module/core/errors.d.ts.map +1 -0
- package/lib/typescript/module/core/offlineQueue.d.ts +16 -0
- package/lib/typescript/module/core/offlineQueue.d.ts.map +1 -0
- package/lib/typescript/module/core/pushNotifications.d.ts +6 -0
- package/lib/typescript/module/core/pushNotifications.d.ts.map +1 -0
- package/lib/typescript/module/core/session.d.ts +15 -0
- package/lib/typescript/module/core/session.d.ts.map +1 -0
- package/lib/typescript/module/core/theme.d.ts +43 -0
- package/lib/typescript/module/core/theme.d.ts.map +1 -0
- package/lib/typescript/module/core/types.d.ts +185 -0
- package/lib/typescript/module/core/types.d.ts.map +1 -0
- package/lib/typescript/module/core/websocket.d.ts +17 -0
- package/lib/typescript/module/core/websocket.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useChat.d.ts +35 -0
- package/lib/typescript/module/hooks/useChat.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useParlr.d.ts +11 -0
- package/lib/typescript/module/hooks/useParlr.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +30 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/provider/ParlrContext.d.ts +13 -0
- package/lib/typescript/module/provider/ParlrContext.d.ts.map +1 -0
- package/lib/typescript/module/provider/ParlrProvider.d.ts +5 -0
- package/lib/typescript/module/provider/ParlrProvider.d.ts.map +1 -0
- package/package.json +120 -0
- package/src/components/AttachmentPicker.tsx +310 -0
- package/src/components/AttachmentPreview.tsx +209 -0
- package/src/components/ChatBubble.tsx +424 -0
- package/src/components/EmptyState.tsx +118 -0
- package/src/components/ParlrChat.tsx +863 -0
- package/src/components/ParlrConversationList.tsx +559 -0
- package/src/components/PreChatForm.tsx +313 -0
- package/src/components/RichMessage.tsx +353 -0
- package/src/components/SatisfactionSurvey.tsx +333 -0
- package/src/components/TypingIndicator.tsx +89 -0
- package/src/core/api.ts +406 -0
- package/src/core/config.ts +39 -0
- package/src/core/errors.ts +68 -0
- package/src/core/offlineQueue.ts +94 -0
- package/src/core/pushNotifications.ts +22 -0
- package/src/core/session.ts +156 -0
- package/src/core/theme.ts +133 -0
- package/src/core/types.ts +237 -0
- package/src/core/websocket.ts +270 -0
- package/src/hooks/useChat.ts +534 -0
- package/src/hooks/useParlr.ts +43 -0
- package/src/index.ts +98 -0
- package/src/provider/ParlrContext.ts +40 -0
- package/src/provider/ParlrProvider.tsx +338 -0
package/README.md
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://parlr.chat/logo.svg" alt="Parlr" width="64" height="64" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@parlr/react-native</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>The official Parlr live chat SDK for React Native.</strong><br />
|
|
9
|
+
Add real-time customer messaging to your mobile app in under 5 minutes.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/@parlr/react-native"><img src="https://img.shields.io/npm/v/@parlr/react-native.svg?style=flat-square&color=6366f1" alt="npm version" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@parlr/react-native"><img src="https://img.shields.io/npm/dm/@parlr/react-native.svg?style=flat-square&color=6366f1" alt="npm downloads" /></a>
|
|
15
|
+
<a href="https://github.com/parlr/parlr/blob/main/parlr-react-native/LICENSE"><img src="https://img.shields.io/npm/l/@parlr/react-native.svg?style=flat-square" alt="license" /></a>
|
|
16
|
+
<a href="https://parlr.chat/docs/sdk/react-native"><img src="https://img.shields.io/badge/docs-parlr.chat-6366f1?style=flat-square" alt="docs" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="#quick-start">Quick Start</a> •
|
|
21
|
+
<a href="#components">Components</a> •
|
|
22
|
+
<a href="#hooks">Hooks</a> •
|
|
23
|
+
<a href="#theming">Theming</a> •
|
|
24
|
+
<a href="#api-reference">API Reference</a> •
|
|
25
|
+
<a href="https://parlr.chat/docs/sdk/react-native">Full Docs</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Why Parlr?
|
|
31
|
+
|
|
32
|
+
Parlr is a modern B2B live chat platform. This SDK gives your React Native app:
|
|
33
|
+
|
|
34
|
+
- **Real-time messaging** via WebSocket with automatic reconnection
|
|
35
|
+
- **Optimistic UI** — messages appear instantly, sync in the background
|
|
36
|
+
- **Typing indicators**, read receipts, and delivery status
|
|
37
|
+
- **Pre-built UI components** that match your app's theme
|
|
38
|
+
- **Pre-chat forms** to collect visitor info before the first message
|
|
39
|
+
- **CSAT surveys** after conversation resolution
|
|
40
|
+
- **File attachments** (images, documents)
|
|
41
|
+
- **Rich messages** (cards, carousels, quick replies)
|
|
42
|
+
- **Offline queue** with automatic retry when connectivity returns
|
|
43
|
+
- **Push notifications** (FCM / APNs)
|
|
44
|
+
- **HMAC identity verification** for secure user identification
|
|
45
|
+
- **Dark mode** with automatic system detection
|
|
46
|
+
- **TypeScript-first** with complete type definitions
|
|
47
|
+
- **Tiny footprint** — ~160 KB published, 1 runtime dependency
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install @parlr/react-native
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
yarn add @parlr/react-native
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Peer dependencies
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install react-native-reanimated
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Optional dependencies
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Recommended: secure session persistence
|
|
71
|
+
npm install expo-secure-store
|
|
72
|
+
|
|
73
|
+
# For file/image attachments
|
|
74
|
+
npm install expo-image-picker expo-document-picker
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> Without `expo-secure-store`, sessions are stored in memory only and won't survive app restarts.
|
|
78
|
+
|
|
79
|
+
### Compatibility
|
|
80
|
+
|
|
81
|
+
| Dependency | Minimum version |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `react` | 18.0.0 |
|
|
84
|
+
| `react-native` | 0.72.0 |
|
|
85
|
+
| `react-native-reanimated` | 3.0.0 |
|
|
86
|
+
| `expo-secure-store` | 13.0.0 *(optional)* |
|
|
87
|
+
| `expo-image-picker` | 15.0.0 *(optional)* |
|
|
88
|
+
| `expo-document-picker` | 12.0.0 *(optional)* |
|
|
89
|
+
|
|
90
|
+
Works with **Expo** and **bare React Native** projects.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
### 1. Wrap your app with the provider
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { ParlrProvider } from '@parlr/react-native';
|
|
100
|
+
|
|
101
|
+
export default function App() {
|
|
102
|
+
return (
|
|
103
|
+
<ParlrProvider workspaceId="your-workspace-id">
|
|
104
|
+
<Navigation />
|
|
105
|
+
</ParlrProvider>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. Drop in the chat screen
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { ParlrChat } from '@parlr/react-native';
|
|
114
|
+
|
|
115
|
+
function SupportScreen({ navigation }) {
|
|
116
|
+
return (
|
|
117
|
+
<ParlrChat
|
|
118
|
+
user={{ email: 'alice@acme.com', name: 'Alice' }}
|
|
119
|
+
onBack={() => navigation.goBack()}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. Show unread count anywhere
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useParlr } from '@parlr/react-native';
|
|
129
|
+
|
|
130
|
+
function SupportButton() {
|
|
131
|
+
const { unreadCount } = useParlr();
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<TouchableOpacity onPress={() => navigation.navigate('Support')}>
|
|
135
|
+
<Text>Support</Text>
|
|
136
|
+
{unreadCount > 0 && <Badge count={unreadCount} />}
|
|
137
|
+
</TouchableOpacity>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**That's it.** The SDK handles session management, WebSocket connections, message delivery, and UI rendering automatically.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Components
|
|
147
|
+
|
|
148
|
+
### `<ParlrProvider>`
|
|
149
|
+
|
|
150
|
+
Root wrapper that initializes the SDK. Place it near the top of your component tree.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<ParlrProvider
|
|
154
|
+
workspaceId="your-workspace-id"
|
|
155
|
+
locale="en"
|
|
156
|
+
debug={__DEV__}
|
|
157
|
+
theme={{ colors: { primary: '#E91E63' } }}
|
|
158
|
+
onError={(err) => Sentry.captureException(err)}
|
|
159
|
+
>
|
|
160
|
+
<App />
|
|
161
|
+
</ParlrProvider>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Prop | Type | Default | Description |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| `workspaceId` | `string` | *required* | Your Parlr workspace ID |
|
|
167
|
+
| `apiBaseUrl` | `string` | `https://api.parlr.chat/api/v1/widget` | REST API base URL |
|
|
168
|
+
| `wsUrl` | `string` | `wss://ws.parlr.chat/ws` | WebSocket endpoint |
|
|
169
|
+
| `locale` | `string` | `"fr"` | BCP-47 locale (`"fr"` or `"en"`) |
|
|
170
|
+
| `debug` | `boolean` | `false` | Enable verbose console logging |
|
|
171
|
+
| `theme` | `Partial<ParlrTheme>` | auto | Theme overrides (see [Theming](#theming)) |
|
|
172
|
+
| `identityToken` | `string` | — | HMAC token for identity verification |
|
|
173
|
+
| `onError` | `(error: ParlrError) => void` | — | Global error callback |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### `<ParlrChat>`
|
|
178
|
+
|
|
179
|
+
Full-featured chat screen. Handles everything: session, WebSocket, messages, input, typing indicators, attachments, CSAT.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<ParlrChat
|
|
183
|
+
user={{ email: 'alice@acme.com', name: 'Alice' }}
|
|
184
|
+
conversationId="conv_123" // optional: resume existing conversation
|
|
185
|
+
onBack={() => navigation.goBack()}
|
|
186
|
+
headerTitle="Support"
|
|
187
|
+
showPreChatForm={true}
|
|
188
|
+
preChatFields={['name', 'email']}
|
|
189
|
+
showSatisfactionSurvey={true}
|
|
190
|
+
/>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Prop | Type | Default | Description |
|
|
194
|
+
|---|---|---|---|
|
|
195
|
+
| `user` | `ParlrUser` | — | Identify the user on mount |
|
|
196
|
+
| `conversationId` | `string` | — | Resume an existing conversation |
|
|
197
|
+
| `onBack` | `() => void` | — | Back button handler (shows arrow if provided) |
|
|
198
|
+
| `headerTitle` | `string` | `"Support"` | Header bar title |
|
|
199
|
+
| `placeholder` | `string` | `"Write a message..."` | Input placeholder |
|
|
200
|
+
| `accentColor` | `string` | `theme.colors.primary` | Accent color override |
|
|
201
|
+
| `showHeader` | `boolean` | `true` | Show/hide header bar |
|
|
202
|
+
| `showPreChatForm` | `boolean` | `false` | Collect user info before first message |
|
|
203
|
+
| `preChatFields` | `Array<'name' \| 'email' \| 'phone'>` | `['name', 'email']` | Pre-chat form fields |
|
|
204
|
+
| `showSatisfactionSurvey` | `boolean` | `true` | Show CSAT after conversation closes |
|
|
205
|
+
| `onConversationClosed` | `() => void` | — | Callback when conversation is closed |
|
|
206
|
+
|
|
207
|
+
**Built-in features:** online/offline status, three-dot menu (close/reopen), optimistic send, agent typing indicator, attachment picker with preview, pagination (load older messages), auto-scroll, closed conversation banner, CSAT survey.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### `<ParlrConversationList>`
|
|
212
|
+
|
|
213
|
+
Displays a list of conversations with last message preview, unread badges, and status.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<ParlrConversationList
|
|
217
|
+
onSelectConversation={(id) => navigation.navigate('Chat', { id })}
|
|
218
|
+
onNewConversation={() => navigation.navigate('Chat')}
|
|
219
|
+
statusFilter="open"
|
|
220
|
+
/>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Prop | Type | Default | Description |
|
|
224
|
+
|---|---|---|---|
|
|
225
|
+
| `onSelectConversation` | `(id: string) => void` | *required* | Tap on a conversation |
|
|
226
|
+
| `onNewConversation` | `() => void` | — | "New conversation" button handler |
|
|
227
|
+
| `headerTitle` | `string` | `"Conversations"` | Header title |
|
|
228
|
+
| `statusFilter` | `'open' \| 'closed' \| 'pending'` | — | Filter by status |
|
|
229
|
+
| `showHeader` | `boolean` | `true` | Show/hide header |
|
|
230
|
+
| `accentColor` | `string` | `"#6366f1"` | Accent color |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Lower-level components
|
|
235
|
+
|
|
236
|
+
These are used internally by `ParlrChat` but are exported for custom UIs:
|
|
237
|
+
|
|
238
|
+
| Component | Description |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `<ChatBubble message={msg} />` | Single message bubble (agent left, contact right) |
|
|
241
|
+
| `<TypingIndicator />` | Animated three-dot typing indicator |
|
|
242
|
+
| `<EmptyState />` | Welcome screen when no messages exist |
|
|
243
|
+
| `<PreChatForm onSubmit={fn} />` | Pre-chat information form |
|
|
244
|
+
| `<SatisfactionSurvey onSubmit={fn} />` | CSAT 1-5 star survey |
|
|
245
|
+
| `<AttachmentPicker onFilePicked={fn} />` | Photo/camera/file picker |
|
|
246
|
+
| `<AttachmentPreview file={f} onSend={fn} />` | Preview before sending |
|
|
247
|
+
| `<RichMessage content={rich} />` | Cards, carousels, quick replies |
|
|
248
|
+
|
|
249
|
+
See the [API Reference](#api-reference) for full prop tables.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Hooks
|
|
254
|
+
|
|
255
|
+
### `useParlr()`
|
|
256
|
+
|
|
257
|
+
Access SDK state from any component inside `<ParlrProvider>`.
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
const {
|
|
261
|
+
isReady, // SDK initialized
|
|
262
|
+
isConnected, // WebSocket connected
|
|
263
|
+
session, // Current JWT session
|
|
264
|
+
conversations, // All conversations
|
|
265
|
+
unreadCount, // Total unread messages
|
|
266
|
+
identify, // Identify or update user
|
|
267
|
+
refreshConversations, // Force refresh
|
|
268
|
+
theme, // Resolved theme
|
|
269
|
+
} = useParlr();
|
|
270
|
+
|
|
271
|
+
// Identify a user
|
|
272
|
+
await identify({
|
|
273
|
+
email: 'alice@acme.com',
|
|
274
|
+
name: 'Alice Martin',
|
|
275
|
+
company: 'Acme Inc',
|
|
276
|
+
customAttributes: { plan: 'pro', mrr: 299 },
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### `useChat(conversationId?)`
|
|
283
|
+
|
|
284
|
+
Manage a single conversation with real-time updates.
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
const {
|
|
288
|
+
messages, // Messages (oldest first)
|
|
289
|
+
isLoading, // Initial load in progress
|
|
290
|
+
hasError, // Error occurred
|
|
291
|
+
conversation, // Active conversation
|
|
292
|
+
agentTyping, // Agent is typing (auto-clears after 5s)
|
|
293
|
+
sendMessage, // Send a text message (optimistic)
|
|
294
|
+
retryMessage, // Retry a failed message
|
|
295
|
+
notifyTyping, // Send typing indicator (debounced 3s)
|
|
296
|
+
loadMore, // Load older messages
|
|
297
|
+
hasMore, // More pages available
|
|
298
|
+
closeConversation, // Close the conversation
|
|
299
|
+
reopenConversation, // Reopen a closed conversation
|
|
300
|
+
} = useChat(conversationId);
|
|
301
|
+
|
|
302
|
+
// Send a message (appears instantly, syncs in background)
|
|
303
|
+
await sendMessage('Hello, I need help with my order');
|
|
304
|
+
|
|
305
|
+
// Retry a failed message
|
|
306
|
+
const failed = messages.find(m => m.status === 'failed');
|
|
307
|
+
if (failed?.clientId) await retryMessage(failed.clientId);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Automatic behaviors:**
|
|
311
|
+
- Messages appear instantly with `sending` status, then update to `sent` on server confirmation
|
|
312
|
+
- Duplicate messages from WebSocket are merged with optimistic messages via `clientId`
|
|
313
|
+
- Failed messages can be retried up to 3 times with exponential backoff (1s, 2s, 4s)
|
|
314
|
+
- If no `conversationId` is provided, a new conversation is created on first `sendMessage()`
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Theming
|
|
319
|
+
|
|
320
|
+
The SDK automatically detects your system's dark/light mode. You can override any value:
|
|
321
|
+
|
|
322
|
+
### Just your brand color (most common)
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
<ParlrProvider
|
|
326
|
+
workspaceId="your-workspace-id"
|
|
327
|
+
theme={{
|
|
328
|
+
colors: {
|
|
329
|
+
primary: '#E91E63',
|
|
330
|
+
contactBubble: '#E91E63',
|
|
331
|
+
},
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Full custom theme
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
<ParlrProvider
|
|
340
|
+
workspaceId="your-workspace-id"
|
|
341
|
+
theme={{
|
|
342
|
+
colors: {
|
|
343
|
+
primary: '#FF6B00',
|
|
344
|
+
primaryText: '#FFFFFF',
|
|
345
|
+
background: '#1A1A2E',
|
|
346
|
+
surface: '#16213E',
|
|
347
|
+
text: '#EAEAEA',
|
|
348
|
+
textSecondary: '#A0A0B0',
|
|
349
|
+
border: '#2A2A4A',
|
|
350
|
+
agentBubble: '#16213E',
|
|
351
|
+
agentText: '#EAEAEA',
|
|
352
|
+
contactBubble: '#FF6B00',
|
|
353
|
+
contactText: '#FFFFFF',
|
|
354
|
+
},
|
|
355
|
+
borderRadius: { bubble: 20, input: 16, button: 12, avatar: 24 },
|
|
356
|
+
spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
|
|
357
|
+
typography: { headerSize: 20, bodySize: 16, captionSize: 12 },
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Programmatic access
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import { useParlr, defaultLightTheme, defaultDarkTheme, mergeTheme } from '@parlr/react-native';
|
|
366
|
+
|
|
367
|
+
// Access resolved theme in components
|
|
368
|
+
const { theme } = useParlr();
|
|
369
|
+
<View style={{ backgroundColor: theme.colors.background }} />
|
|
370
|
+
|
|
371
|
+
// Build a theme from scratch
|
|
372
|
+
const custom = mergeTheme(defaultDarkTheme, { colors: { primary: '#FF6B00' } });
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
<details>
|
|
376
|
+
<summary><strong>Full ParlrTheme interface</strong></summary>
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
interface ParlrTheme {
|
|
380
|
+
colors: {
|
|
381
|
+
primary: string; // Accent color
|
|
382
|
+
primaryText: string; // Text on primary
|
|
383
|
+
background: string; // Screen background
|
|
384
|
+
surface: string; // Card/input background
|
|
385
|
+
surfaceDark: string; // Darker surface (avatars)
|
|
386
|
+
text: string; // Primary text
|
|
387
|
+
textSecondary: string; // Secondary text, timestamps
|
|
388
|
+
border: string; // Borders, dividers
|
|
389
|
+
success: string; // Online indicator
|
|
390
|
+
error: string; // Error states
|
|
391
|
+
agentBubble: string; // Agent bubble background
|
|
392
|
+
agentBubbleDark: string; // Reserved
|
|
393
|
+
agentText: string; // Agent bubble text
|
|
394
|
+
agentTextDark: string; // Reserved
|
|
395
|
+
contactBubble: string; // Contact bubble background
|
|
396
|
+
contactText: string; // Contact bubble text
|
|
397
|
+
};
|
|
398
|
+
borderRadius: {
|
|
399
|
+
bubble: number; // Message bubbles (default: 16)
|
|
400
|
+
input: number; // Text input (default: 12)
|
|
401
|
+
button: number; // Buttons (default: 8)
|
|
402
|
+
avatar: number; // Avatars (default: 20)
|
|
403
|
+
};
|
|
404
|
+
spacing: {
|
|
405
|
+
xs: number; // 4
|
|
406
|
+
sm: number; // 8
|
|
407
|
+
md: number; // 16
|
|
408
|
+
lg: number; // 24
|
|
409
|
+
xl: number; // 32
|
|
410
|
+
};
|
|
411
|
+
typography: {
|
|
412
|
+
headerSize: number; // 18
|
|
413
|
+
bodySize: number; // 15
|
|
414
|
+
captionSize: number; // 12
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
</details>
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Error Handling
|
|
424
|
+
|
|
425
|
+
The SDK exports a typed error hierarchy for precise error handling:
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import {
|
|
429
|
+
ParlrError, // Base class
|
|
430
|
+
ParlrNetworkError, // HTTP errors (has statusCode)
|
|
431
|
+
ParlrAuthError, // Expired or invalid session
|
|
432
|
+
ParlrValidationError, // Invalid data (has field)
|
|
433
|
+
ParlrConnectionError, // WebSocket errors (has code, reason)
|
|
434
|
+
} from '@parlr/react-native';
|
|
435
|
+
|
|
436
|
+
<ParlrProvider
|
|
437
|
+
workspaceId="your-workspace-id"
|
|
438
|
+
onError={(error) => {
|
|
439
|
+
if (error instanceof ParlrAuthError) {
|
|
440
|
+
// Session expired — SDK auto-refreshes it
|
|
441
|
+
} else if (error instanceof ParlrNetworkError) {
|
|
442
|
+
console.warn(`HTTP ${error.statusCode}`);
|
|
443
|
+
} else if (error instanceof ParlrConnectionError) {
|
|
444
|
+
console.warn(`WebSocket closed: ${error.reason}`);
|
|
445
|
+
}
|
|
446
|
+
Sentry.captureException(error);
|
|
447
|
+
}}
|
|
448
|
+
>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Offline Support
|
|
454
|
+
|
|
455
|
+
Queue messages when the device is offline. They are automatically sent when connectivity returns.
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
import {
|
|
459
|
+
queueMessage,
|
|
460
|
+
getQueuedMessages,
|
|
461
|
+
removeFromQueue,
|
|
462
|
+
flushQueue,
|
|
463
|
+
} from '@parlr/react-native';
|
|
464
|
+
|
|
465
|
+
// Queue a message
|
|
466
|
+
queueMessage({ conversationId: 'conv_123', content: 'Hello', clientId: 'uuid', timestamp: Date.now() });
|
|
467
|
+
|
|
468
|
+
// Retrieve and flush the queue
|
|
469
|
+
const queued = await getQueuedMessages();
|
|
470
|
+
await flushQueue(api);
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Uses AsyncStorage when available, falls back to in-memory storage.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Push Notifications
|
|
478
|
+
|
|
479
|
+
Register device tokens for FCM (Android) or APNs (iOS) push notifications:
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
import { registerPushToken, unregisterPushToken } from '@parlr/react-native';
|
|
483
|
+
|
|
484
|
+
// After obtaining the device token from expo-notifications or react-native-push-notification
|
|
485
|
+
await registerPushToken(api, deviceToken, 'ios');
|
|
486
|
+
|
|
487
|
+
// On logout
|
|
488
|
+
await unregisterPushToken(api, deviceToken);
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Identity Verification (HMAC)
|
|
494
|
+
|
|
495
|
+
For production apps, use HMAC verification to prevent contact impersonation. Generate the token server-side:
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
<ParlrProvider
|
|
499
|
+
workspaceId="your-workspace-id"
|
|
500
|
+
identityToken={hmacTokenFromYourBackend}
|
|
501
|
+
>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
The token is sent as `X-Identity-Token` on every identification request.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## API Reference
|
|
509
|
+
|
|
510
|
+
### Types
|
|
511
|
+
|
|
512
|
+
<details>
|
|
513
|
+
<summary><strong>ParlrUser</strong></summary>
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
interface ParlrUser {
|
|
517
|
+
email?: string;
|
|
518
|
+
name?: string;
|
|
519
|
+
firstName?: string;
|
|
520
|
+
lastName?: string;
|
|
521
|
+
externalId?: string;
|
|
522
|
+
phone?: string;
|
|
523
|
+
company?: string;
|
|
524
|
+
customAttributes?: Record<string, unknown>;
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
</details>
|
|
529
|
+
|
|
530
|
+
<details>
|
|
531
|
+
<summary><strong>Session</strong></summary>
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
interface Session {
|
|
535
|
+
token: string; // JWT token
|
|
536
|
+
contactId: string; // Contact ID
|
|
537
|
+
workspaceId: string; // Workspace ID
|
|
538
|
+
expiresAt: string; // ISO 8601 expiration
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
</details>
|
|
543
|
+
|
|
544
|
+
<details>
|
|
545
|
+
<summary><strong>Conversation</strong></summary>
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
interface Conversation {
|
|
549
|
+
id: string;
|
|
550
|
+
status: 'open' | 'resolved' | 'pending' | 'closed';
|
|
551
|
+
subject: string | null;
|
|
552
|
+
lastMessageAt: string | null;
|
|
553
|
+
unreadCount: number;
|
|
554
|
+
assignee?: { id: string; name: string; avatarUrl?: string } | null;
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
</details>
|
|
559
|
+
|
|
560
|
+
<details>
|
|
561
|
+
<summary><strong>Message</strong></summary>
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
interface Message {
|
|
565
|
+
id: string;
|
|
566
|
+
conversationId: string;
|
|
567
|
+
senderType: 'contact' | 'agent';
|
|
568
|
+
senderName?: string;
|
|
569
|
+
senderAvatarUrl?: string;
|
|
570
|
+
content: string;
|
|
571
|
+
createdAt: string;
|
|
572
|
+
clientId?: string; // For optimistic deduplication
|
|
573
|
+
status?: 'sending' | 'sent' | 'failed' | 'read';
|
|
574
|
+
attachments?: Attachment[];
|
|
575
|
+
contentType?: 'text' | 'image' | 'file' | 'rich';
|
|
576
|
+
metadata?: Record<string, unknown>;
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
</details>
|
|
581
|
+
|
|
582
|
+
<details>
|
|
583
|
+
<summary><strong>Attachment</strong></summary>
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
interface Attachment {
|
|
587
|
+
id: string;
|
|
588
|
+
name: string;
|
|
589
|
+
url: string;
|
|
590
|
+
contentType: string;
|
|
591
|
+
size: number;
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
</details>
|
|
596
|
+
|
|
597
|
+
<details>
|
|
598
|
+
<summary><strong>RichContent</strong></summary>
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
interface RichContent {
|
|
602
|
+
type: 'card' | 'carousel' | 'quick_replies' | 'buttons';
|
|
603
|
+
card?: RichCard;
|
|
604
|
+
carousel?: { cards: RichCard[] };
|
|
605
|
+
quickReplies?: { text?: string; replies: Array<{ label: string; value: string }> };
|
|
606
|
+
buttons?: RichAction[];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
interface RichCard {
|
|
610
|
+
title?: string;
|
|
611
|
+
description?: string;
|
|
612
|
+
imageUrl?: string;
|
|
613
|
+
actions?: RichAction[];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
interface RichAction {
|
|
617
|
+
type: 'link' | 'postback' | 'reply';
|
|
618
|
+
label: string;
|
|
619
|
+
value: string;
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
</details>
|
|
624
|
+
|
|
625
|
+
<details>
|
|
626
|
+
<summary><strong>WebSocket Events</strong></summary>
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
interface WsEventMap {
|
|
630
|
+
auth_ok: { contactId: string };
|
|
631
|
+
auth_error: { code?: number; reason?: string; message?: string };
|
|
632
|
+
new_message: WsNewMessagePayload;
|
|
633
|
+
typing_start: { conversationId: string; senderType?: 'agent' | 'contact'; senderName?: string };
|
|
634
|
+
typing_stop: { conversationId: string; senderType?: 'agent' | 'contact'; senderName?: string };
|
|
635
|
+
message_seen: { conversationId: string; messageId: string; seenBy: string };
|
|
636
|
+
conversation_updated: { conversationId: string; status?: string; assignee?: object | null };
|
|
637
|
+
pong: {};
|
|
638
|
+
disconnected: { code?: number; reason?: string };
|
|
639
|
+
error: { code?: string; message?: string };
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**WebSocket behavior:**
|
|
644
|
+
- Auto-reconnect with exponential backoff (1s → 30s max)
|
|
645
|
+
- Keep-alive ping every 25s, timeout at 50s
|
|
646
|
+
- Clean listener teardown on unmount
|
|
647
|
+
|
|
648
|
+
</details>
|
|
649
|
+
|
|
650
|
+
### All Exports
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
// Components
|
|
654
|
+
import {
|
|
655
|
+
ParlrProvider,
|
|
656
|
+
ParlrChat,
|
|
657
|
+
ParlrConversationList,
|
|
658
|
+
ChatBubble,
|
|
659
|
+
TypingIndicator,
|
|
660
|
+
EmptyState,
|
|
661
|
+
PreChatForm,
|
|
662
|
+
SatisfactionSurvey,
|
|
663
|
+
AttachmentPicker,
|
|
664
|
+
AttachmentPreview,
|
|
665
|
+
RichMessage,
|
|
666
|
+
} from '@parlr/react-native';
|
|
667
|
+
|
|
668
|
+
// Hooks
|
|
669
|
+
import { useParlr, useChat } from '@parlr/react-native';
|
|
670
|
+
|
|
671
|
+
// Errors
|
|
672
|
+
import {
|
|
673
|
+
ParlrError,
|
|
674
|
+
ParlrNetworkError,
|
|
675
|
+
ParlrAuthError,
|
|
676
|
+
ParlrValidationError,
|
|
677
|
+
ParlrConnectionError,
|
|
678
|
+
} from '@parlr/react-native';
|
|
679
|
+
|
|
680
|
+
// Theme utilities
|
|
681
|
+
import { defaultLightTheme, defaultDarkTheme, mergeTheme } from '@parlr/react-native';
|
|
682
|
+
|
|
683
|
+
// Offline queue
|
|
684
|
+
import { queueMessage, getQueuedMessages, removeFromQueue, flushQueue } from '@parlr/react-native';
|
|
685
|
+
|
|
686
|
+
// Push notifications
|
|
687
|
+
import { registerPushToken, unregisterPushToken } from '@parlr/react-native';
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## Examples
|
|
693
|
+
|
|
694
|
+
### Minimal integration
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
import { ParlrProvider, ParlrChat } from '@parlr/react-native';
|
|
698
|
+
|
|
699
|
+
export default function App() {
|
|
700
|
+
return (
|
|
701
|
+
<ParlrProvider workspaceId="your-workspace-id">
|
|
702
|
+
<ParlrChat />
|
|
703
|
+
</ParlrProvider>
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### With React Navigation
|
|
709
|
+
|
|
710
|
+
```tsx
|
|
711
|
+
// App.tsx
|
|
712
|
+
import { ParlrProvider } from '@parlr/react-native';
|
|
713
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
714
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
715
|
+
|
|
716
|
+
const Stack = createNativeStackNavigator();
|
|
717
|
+
|
|
718
|
+
export default function App() {
|
|
719
|
+
return (
|
|
720
|
+
<ParlrProvider workspaceId="your-workspace-id" locale="en">
|
|
721
|
+
<NavigationContainer>
|
|
722
|
+
<Stack.Navigator>
|
|
723
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
724
|
+
<Stack.Screen
|
|
725
|
+
name="Support"
|
|
726
|
+
component={SupportScreen}
|
|
727
|
+
options={{ headerShown: false }}
|
|
728
|
+
/>
|
|
729
|
+
</Stack.Navigator>
|
|
730
|
+
</NavigationContainer>
|
|
731
|
+
</ParlrProvider>
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// HomeScreen.tsx
|
|
736
|
+
import { useParlr } from '@parlr/react-native';
|
|
737
|
+
|
|
738
|
+
function HomeScreen({ navigation }) {
|
|
739
|
+
const { unreadCount } = useParlr();
|
|
740
|
+
|
|
741
|
+
return (
|
|
742
|
+
<View>
|
|
743
|
+
<TouchableOpacity onPress={() => navigation.navigate('Support')}>
|
|
744
|
+
<Text>Contact Support {unreadCount > 0 ? `(${unreadCount})` : ''}</Text>
|
|
745
|
+
</TouchableOpacity>
|
|
746
|
+
</View>
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// SupportScreen.tsx
|
|
751
|
+
import { ParlrChat } from '@parlr/react-native';
|
|
752
|
+
|
|
753
|
+
function SupportScreen({ navigation }) {
|
|
754
|
+
return (
|
|
755
|
+
<ParlrChat
|
|
756
|
+
user={{ email: 'alice@acme.com', name: 'Alice' }}
|
|
757
|
+
onBack={() => navigation.goBack()}
|
|
758
|
+
headerTitle="Help & Support"
|
|
759
|
+
showPreChatForm={true}
|
|
760
|
+
/>
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Conversation list + chat
|
|
766
|
+
|
|
767
|
+
```tsx
|
|
768
|
+
import { ParlrConversationList, ParlrChat } from '@parlr/react-native';
|
|
769
|
+
|
|
770
|
+
// List screen
|
|
771
|
+
function ConversationsScreen({ navigation }) {
|
|
772
|
+
return (
|
|
773
|
+
<ParlrConversationList
|
|
774
|
+
onSelectConversation={(id) => navigation.navigate('Chat', { conversationId: id })}
|
|
775
|
+
onNewConversation={() => navigation.navigate('Chat')}
|
|
776
|
+
/>
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Chat screen
|
|
781
|
+
function ChatScreen({ route, navigation }) {
|
|
782
|
+
return (
|
|
783
|
+
<ParlrChat
|
|
784
|
+
conversationId={route.params?.conversationId}
|
|
785
|
+
onBack={() => navigation.goBack()}
|
|
786
|
+
/>
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Custom chat UI with hooks
|
|
792
|
+
|
|
793
|
+
```tsx
|
|
794
|
+
import { useChat, ChatBubble, TypingIndicator } from '@parlr/react-native';
|
|
795
|
+
import { FlatList, TextInput, TouchableOpacity, View, Text } from 'react-native';
|
|
796
|
+
|
|
797
|
+
function CustomChat({ conversationId }) {
|
|
798
|
+
const [text, setText] = useState('');
|
|
799
|
+
const {
|
|
800
|
+
messages, agentTyping, sendMessage, notifyTyping, loadMore, hasMore,
|
|
801
|
+
} = useChat(conversationId);
|
|
802
|
+
|
|
803
|
+
const handleSend = async () => {
|
|
804
|
+
if (!text.trim()) return;
|
|
805
|
+
await sendMessage(text.trim());
|
|
806
|
+
setText('');
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
return (
|
|
810
|
+
<View style={{ flex: 1 }}>
|
|
811
|
+
<FlatList
|
|
812
|
+
data={messages}
|
|
813
|
+
inverted
|
|
814
|
+
keyExtractor={(m) => m.id}
|
|
815
|
+
renderItem={({ item }) => <ChatBubble message={item} />}
|
|
816
|
+
onEndReached={() => hasMore && loadMore()}
|
|
817
|
+
/>
|
|
818
|
+
{agentTyping && <TypingIndicator />}
|
|
819
|
+
<View style={{ flexDirection: 'row', padding: 8 }}>
|
|
820
|
+
<TextInput
|
|
821
|
+
value={text}
|
|
822
|
+
onChangeText={(t) => { setText(t); notifyTyping(true); }}
|
|
823
|
+
style={{ flex: 1 }}
|
|
824
|
+
placeholder="Type a message..."
|
|
825
|
+
/>
|
|
826
|
+
<TouchableOpacity onPress={handleSend}>
|
|
827
|
+
<Text>Send</Text>
|
|
828
|
+
</TouchableOpacity>
|
|
829
|
+
</View>
|
|
830
|
+
</View>
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## Advanced Configuration
|
|
838
|
+
|
|
839
|
+
### Self-hosted Parlr
|
|
840
|
+
|
|
841
|
+
Point the SDK to your own Parlr instance:
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
<ParlrProvider
|
|
845
|
+
workspaceId="your-workspace-id"
|
|
846
|
+
apiBaseUrl="https://your-api.example.com/api/v1/widget"
|
|
847
|
+
wsUrl="wss://your-ws.example.com/ws"
|
|
848
|
+
>
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Debug mode
|
|
852
|
+
|
|
853
|
+
```tsx
|
|
854
|
+
<ParlrProvider workspaceId="your-workspace-id" debug={true}>
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Logs WebSocket events, session lifecycle, API calls, and errors to the console.
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## FAQ
|
|
862
|
+
|
|
863
|
+
<details>
|
|
864
|
+
<summary><strong>How do I get my workspace ID?</strong></summary>
|
|
865
|
+
|
|
866
|
+
Sign up at [app.parlr.chat](https://app.parlr.chat), create a workspace, and find your workspace ID in **Settings > Channels > Mobile SDK**.
|
|
867
|
+
|
|
868
|
+
</details>
|
|
869
|
+
|
|
870
|
+
<details>
|
|
871
|
+
<summary><strong>Does the SDK work with Expo?</strong></summary>
|
|
872
|
+
|
|
873
|
+
Yes. The SDK works with both Expo managed and bare workflows. All optional dependencies (`expo-secure-store`, `expo-image-picker`, `expo-document-picker`) are Expo packages.
|
|
874
|
+
|
|
875
|
+
</details>
|
|
876
|
+
|
|
877
|
+
<details>
|
|
878
|
+
<summary><strong>What happens when the device goes offline?</strong></summary>
|
|
879
|
+
|
|
880
|
+
Messages are queued locally and automatically retried when connectivity returns. The WebSocket reconnects with exponential backoff.
|
|
881
|
+
|
|
882
|
+
</details>
|
|
883
|
+
|
|
884
|
+
<details>
|
|
885
|
+
<summary><strong>Can I use the SDK without any UI components?</strong></summary>
|
|
886
|
+
|
|
887
|
+
Yes. Use `useParlr()` and `useChat()` hooks to build a completely custom UI. The SDK handles all networking, state management, and real-time updates.
|
|
888
|
+
|
|
889
|
+
</details>
|
|
890
|
+
|
|
891
|
+
<details>
|
|
892
|
+
<summary><strong>How does identity verification work?</strong></summary>
|
|
893
|
+
|
|
894
|
+
Generate an HMAC token server-side using your workspace secret and the user's email. Pass it as `identityToken` to `ParlrProvider`. This prevents contact impersonation.
|
|
895
|
+
|
|
896
|
+
</details>
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
## Contributing
|
|
901
|
+
|
|
902
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/parlr/parlr/blob/main/CONTRIBUTING.md) for details.
|
|
903
|
+
|
|
904
|
+
```bash
|
|
905
|
+
git clone https://github.com/parlr/parlr.git
|
|
906
|
+
cd parlr/parlr-react-native
|
|
907
|
+
npm install
|
|
908
|
+
npm run typecheck # Type checking
|
|
909
|
+
npm run test # Run tests
|
|
910
|
+
npm run build # Build (CJS + ESM + d.ts)
|
|
911
|
+
npm run lint # Lint
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
## License
|
|
917
|
+
|
|
918
|
+
MIT © [Parlr](https://parlr.chat)
|