@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.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +918 -0
  3. package/lib/commonjs/components/AttachmentPicker.js +292 -0
  4. package/lib/commonjs/components/AttachmentPicker.js.map +1 -0
  5. package/lib/commonjs/components/AttachmentPreview.js +200 -0
  6. package/lib/commonjs/components/AttachmentPreview.js.map +1 -0
  7. package/lib/commonjs/components/ChatBubble.js +391 -0
  8. package/lib/commonjs/components/ChatBubble.js.map +1 -0
  9. package/lib/commonjs/components/EmptyState.js +115 -0
  10. package/lib/commonjs/components/EmptyState.js.map +1 -0
  11. package/lib/commonjs/components/ParlrChat.js +745 -0
  12. package/lib/commonjs/components/ParlrChat.js.map +1 -0
  13. package/lib/commonjs/components/ParlrConversationList.js +509 -0
  14. package/lib/commonjs/components/ParlrConversationList.js.map +1 -0
  15. package/lib/commonjs/components/PreChatForm.js +263 -0
  16. package/lib/commonjs/components/PreChatForm.js.map +1 -0
  17. package/lib/commonjs/components/RichMessage.js +284 -0
  18. package/lib/commonjs/components/RichMessage.js.map +1 -0
  19. package/lib/commonjs/components/SatisfactionSurvey.js +292 -0
  20. package/lib/commonjs/components/SatisfactionSurvey.js.map +1 -0
  21. package/lib/commonjs/components/TypingIndicator.js +86 -0
  22. package/lib/commonjs/components/TypingIndicator.js.map +1 -0
  23. package/lib/commonjs/core/api.js +310 -0
  24. package/lib/commonjs/core/api.js.map +1 -0
  25. package/lib/commonjs/core/config.js +40 -0
  26. package/lib/commonjs/core/config.js.map +1 -0
  27. package/lib/commonjs/core/errors.js +73 -0
  28. package/lib/commonjs/core/errors.js.map +1 -0
  29. package/lib/commonjs/core/offlineQueue.js +89 -0
  30. package/lib/commonjs/core/offlineQueue.js.map +1 -0
  31. package/lib/commonjs/core/pushNotifications.js +21 -0
  32. package/lib/commonjs/core/pushNotifications.js.map +1 -0
  33. package/lib/commonjs/core/session.js +130 -0
  34. package/lib/commonjs/core/session.js.map +1 -0
  35. package/lib/commonjs/core/theme.js +110 -0
  36. package/lib/commonjs/core/theme.js.map +1 -0
  37. package/lib/commonjs/core/types.js +6 -0
  38. package/lib/commonjs/core/types.js.map +1 -0
  39. package/lib/commonjs/core/websocket.js +245 -0
  40. package/lib/commonjs/core/websocket.js.map +1 -0
  41. package/lib/commonjs/hooks/useChat.js +462 -0
  42. package/lib/commonjs/hooks/useChat.js.map +1 -0
  43. package/lib/commonjs/hooks/useParlr.js +44 -0
  44. package/lib/commonjs/hooks/useParlr.js.map +1 -0
  45. package/lib/commonjs/index.js +185 -0
  46. package/lib/commonjs/index.js.map +1 -0
  47. package/lib/commonjs/package.json +1 -0
  48. package/lib/commonjs/provider/ParlrContext.js +38 -0
  49. package/lib/commonjs/provider/ParlrContext.js.map +1 -0
  50. package/lib/commonjs/provider/ParlrProvider.js +256 -0
  51. package/lib/commonjs/provider/ParlrProvider.js.map +1 -0
  52. package/lib/module/components/AttachmentPicker.js +287 -0
  53. package/lib/module/components/AttachmentPicker.js.map +1 -0
  54. package/lib/module/components/AttachmentPreview.js +195 -0
  55. package/lib/module/components/AttachmentPreview.js.map +1 -0
  56. package/lib/module/components/ChatBubble.js +386 -0
  57. package/lib/module/components/ChatBubble.js.map +1 -0
  58. package/lib/module/components/EmptyState.js +110 -0
  59. package/lib/module/components/EmptyState.js.map +1 -0
  60. package/lib/module/components/ParlrChat.js +740 -0
  61. package/lib/module/components/ParlrChat.js.map +1 -0
  62. package/lib/module/components/ParlrConversationList.js +504 -0
  63. package/lib/module/components/ParlrConversationList.js.map +1 -0
  64. package/lib/module/components/PreChatForm.js +258 -0
  65. package/lib/module/components/PreChatForm.js.map +1 -0
  66. package/lib/module/components/RichMessage.js +280 -0
  67. package/lib/module/components/RichMessage.js.map +1 -0
  68. package/lib/module/components/SatisfactionSurvey.js +287 -0
  69. package/lib/module/components/SatisfactionSurvey.js.map +1 -0
  70. package/lib/module/components/TypingIndicator.js +81 -0
  71. package/lib/module/components/TypingIndicator.js.map +1 -0
  72. package/lib/module/core/api.js +305 -0
  73. package/lib/module/core/api.js.map +1 -0
  74. package/lib/module/core/config.js +36 -0
  75. package/lib/module/core/config.js.map +1 -0
  76. package/lib/module/core/errors.js +64 -0
  77. package/lib/module/core/errors.js.map +1 -0
  78. package/lib/module/core/offlineQueue.js +82 -0
  79. package/lib/module/core/offlineQueue.js.map +1 -0
  80. package/lib/module/core/pushNotifications.js +16 -0
  81. package/lib/module/core/pushNotifications.js.map +1 -0
  82. package/lib/module/core/session.js +122 -0
  83. package/lib/module/core/session.js.map +1 -0
  84. package/lib/module/core/theme.js +105 -0
  85. package/lib/module/core/theme.js.map +1 -0
  86. package/lib/module/core/types.js +4 -0
  87. package/lib/module/core/types.js.map +1 -0
  88. package/lib/module/core/websocket.js +241 -0
  89. package/lib/module/core/websocket.js.map +1 -0
  90. package/lib/module/hooks/useChat.js +458 -0
  91. package/lib/module/hooks/useChat.js.map +1 -0
  92. package/lib/module/hooks/useParlr.js +40 -0
  93. package/lib/module/hooks/useParlr.js.map +1 -0
  94. package/lib/module/index.js +58 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/module/package.json +1 -0
  97. package/lib/module/provider/ParlrContext.js +35 -0
  98. package/lib/module/provider/ParlrContext.js.map +1 -0
  99. package/lib/module/provider/ParlrProvider.js +251 -0
  100. package/lib/module/provider/ParlrProvider.js.map +1 -0
  101. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts +23 -0
  102. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts +16 -0
  104. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/components/ChatBubble.d.ts +14 -0
  106. package/lib/typescript/commonjs/components/ChatBubble.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/components/EmptyState.d.ts +10 -0
  108. package/lib/typescript/commonjs/components/EmptyState.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/components/ParlrChat.d.ts +34 -0
  110. package/lib/typescript/commonjs/components/ParlrChat.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts +17 -0
  112. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/components/PreChatForm.d.ts +20 -0
  114. package/lib/typescript/commonjs/components/PreChatForm.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/components/RichMessage.d.ts +41 -0
  116. package/lib/typescript/commonjs/components/RichMessage.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts +17 -0
  118. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/components/TypingIndicator.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/TypingIndicator.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/core/api.d.ts +37 -0
  122. package/lib/typescript/commonjs/core/api.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/core/config.d.ts +9 -0
  124. package/lib/typescript/commonjs/core/config.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/core/errors.d.ts +35 -0
  126. package/lib/typescript/commonjs/core/errors.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/core/offlineQueue.d.ts +16 -0
  128. package/lib/typescript/commonjs/core/offlineQueue.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/core/pushNotifications.d.ts +6 -0
  130. package/lib/typescript/commonjs/core/pushNotifications.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/core/session.d.ts +15 -0
  132. package/lib/typescript/commonjs/core/session.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/core/theme.d.ts +43 -0
  134. package/lib/typescript/commonjs/core/theme.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/core/types.d.ts +185 -0
  136. package/lib/typescript/commonjs/core/types.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/core/websocket.d.ts +17 -0
  138. package/lib/typescript/commonjs/core/websocket.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/hooks/useChat.d.ts +35 -0
  140. package/lib/typescript/commonjs/hooks/useChat.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/hooks/useParlr.d.ts +11 -0
  142. package/lib/typescript/commonjs/hooks/useParlr.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/index.d.ts +30 -0
  144. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/package.json +1 -0
  146. package/lib/typescript/commonjs/provider/ParlrContext.d.ts +13 -0
  147. package/lib/typescript/commonjs/provider/ParlrContext.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts +5 -0
  149. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts.map +1 -0
  150. package/lib/typescript/module/components/AttachmentPicker.d.ts +23 -0
  151. package/lib/typescript/module/components/AttachmentPicker.d.ts.map +1 -0
  152. package/lib/typescript/module/components/AttachmentPreview.d.ts +16 -0
  153. package/lib/typescript/module/components/AttachmentPreview.d.ts.map +1 -0
  154. package/lib/typescript/module/components/ChatBubble.d.ts +14 -0
  155. package/lib/typescript/module/components/ChatBubble.d.ts.map +1 -0
  156. package/lib/typescript/module/components/EmptyState.d.ts +10 -0
  157. package/lib/typescript/module/components/EmptyState.d.ts.map +1 -0
  158. package/lib/typescript/module/components/ParlrChat.d.ts +34 -0
  159. package/lib/typescript/module/components/ParlrChat.d.ts.map +1 -0
  160. package/lib/typescript/module/components/ParlrConversationList.d.ts +17 -0
  161. package/lib/typescript/module/components/ParlrConversationList.d.ts.map +1 -0
  162. package/lib/typescript/module/components/PreChatForm.d.ts +20 -0
  163. package/lib/typescript/module/components/PreChatForm.d.ts.map +1 -0
  164. package/lib/typescript/module/components/RichMessage.d.ts +41 -0
  165. package/lib/typescript/module/components/RichMessage.d.ts.map +1 -0
  166. package/lib/typescript/module/components/SatisfactionSurvey.d.ts +17 -0
  167. package/lib/typescript/module/components/SatisfactionSurvey.d.ts.map +1 -0
  168. package/lib/typescript/module/components/TypingIndicator.d.ts +7 -0
  169. package/lib/typescript/module/components/TypingIndicator.d.ts.map +1 -0
  170. package/lib/typescript/module/core/api.d.ts +37 -0
  171. package/lib/typescript/module/core/api.d.ts.map +1 -0
  172. package/lib/typescript/module/core/config.d.ts +9 -0
  173. package/lib/typescript/module/core/config.d.ts.map +1 -0
  174. package/lib/typescript/module/core/errors.d.ts +35 -0
  175. package/lib/typescript/module/core/errors.d.ts.map +1 -0
  176. package/lib/typescript/module/core/offlineQueue.d.ts +16 -0
  177. package/lib/typescript/module/core/offlineQueue.d.ts.map +1 -0
  178. package/lib/typescript/module/core/pushNotifications.d.ts +6 -0
  179. package/lib/typescript/module/core/pushNotifications.d.ts.map +1 -0
  180. package/lib/typescript/module/core/session.d.ts +15 -0
  181. package/lib/typescript/module/core/session.d.ts.map +1 -0
  182. package/lib/typescript/module/core/theme.d.ts +43 -0
  183. package/lib/typescript/module/core/theme.d.ts.map +1 -0
  184. package/lib/typescript/module/core/types.d.ts +185 -0
  185. package/lib/typescript/module/core/types.d.ts.map +1 -0
  186. package/lib/typescript/module/core/websocket.d.ts +17 -0
  187. package/lib/typescript/module/core/websocket.d.ts.map +1 -0
  188. package/lib/typescript/module/hooks/useChat.d.ts +35 -0
  189. package/lib/typescript/module/hooks/useChat.d.ts.map +1 -0
  190. package/lib/typescript/module/hooks/useParlr.d.ts +11 -0
  191. package/lib/typescript/module/hooks/useParlr.d.ts.map +1 -0
  192. package/lib/typescript/module/index.d.ts +30 -0
  193. package/lib/typescript/module/index.d.ts.map +1 -0
  194. package/lib/typescript/module/package.json +1 -0
  195. package/lib/typescript/module/provider/ParlrContext.d.ts +13 -0
  196. package/lib/typescript/module/provider/ParlrContext.d.ts.map +1 -0
  197. package/lib/typescript/module/provider/ParlrProvider.d.ts +5 -0
  198. package/lib/typescript/module/provider/ParlrProvider.d.ts.map +1 -0
  199. package/package.json +120 -0
  200. package/src/components/AttachmentPicker.tsx +310 -0
  201. package/src/components/AttachmentPreview.tsx +209 -0
  202. package/src/components/ChatBubble.tsx +424 -0
  203. package/src/components/EmptyState.tsx +118 -0
  204. package/src/components/ParlrChat.tsx +863 -0
  205. package/src/components/ParlrConversationList.tsx +559 -0
  206. package/src/components/PreChatForm.tsx +313 -0
  207. package/src/components/RichMessage.tsx +353 -0
  208. package/src/components/SatisfactionSurvey.tsx +333 -0
  209. package/src/components/TypingIndicator.tsx +89 -0
  210. package/src/core/api.ts +406 -0
  211. package/src/core/config.ts +39 -0
  212. package/src/core/errors.ts +68 -0
  213. package/src/core/offlineQueue.ts +94 -0
  214. package/src/core/pushNotifications.ts +22 -0
  215. package/src/core/session.ts +156 -0
  216. package/src/core/theme.ts +133 -0
  217. package/src/core/types.ts +237 -0
  218. package/src/core/websocket.ts +270 -0
  219. package/src/hooks/useChat.ts +534 -0
  220. package/src/hooks/useParlr.ts +43 -0
  221. package/src/index.ts +98 -0
  222. package/src/provider/ParlrContext.ts +40 -0
  223. 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> &bull;
21
+ <a href="#components">Components</a> &bull;
22
+ <a href="#hooks">Hooks</a> &bull;
23
+ <a href="#theming">Theming</a> &bull;
24
+ <a href="#api-reference">API Reference</a> &bull;
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** &mdash; 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** &mdash; ~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` | &mdash; | HMAC token for identity verification |
173
+ | `onError` | `(error: ParlrError) => void` | &mdash; | 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` | &mdash; | Identify the user on mount |
196
+ | `conversationId` | `string` | &mdash; | Resume an existing conversation |
197
+ | `onBack` | `() => void` | &mdash; | 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` | &mdash; | 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` | &mdash; | "New conversation" button handler |
227
+ | `headerTitle` | `string` | `"Conversations"` | Header title |
228
+ | `statusFilter` | `'open' \| 'closed' \| 'pending'` | &mdash; | 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 &rarr; 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 &copy; [Parlr](https://parlr.chat)