@lorikeetai/react-native-sdk 1.2.0 → 2.0.0-alpha.2
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 +238 -4
- package/dist/index.cjs +872 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -41
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +159 -41
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +844 -213
- package/dist/index.js.map +1 -1
- package/package.json +35 -27
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LorikeetCX.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,12 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
SDK containing embeddable React Native components that renders the LorikeetCX web widget inside a react-native-webview.
|
|
4
4
|
|
|
5
|
+
> **Alpha.** The surface can still change between releases. Resume, storage and
|
|
6
|
+
> token refresh are host responsibilities today; see [Resuming a
|
|
7
|
+
> conversation](#resuming-a-conversation) and [Identity](#identity).
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
11
|
+
The three native modules are peer dependencies, so install them alongside the
|
|
12
|
+
SDK. Name them explicitly rather than relying on your package manager to add
|
|
13
|
+
them for you: npm and pnpm auto-install peers, but **neither Yarn Classic nor
|
|
14
|
+
Yarn Berry does, and neither fails the install.** Classic prints `has unmet peer
|
|
15
|
+
dependency`, Berry prints `YN0002 ... doesn't provide`, and both exit
|
|
16
|
+
successfully, and the failure surfaces later, when Metro cannot resolve the
|
|
17
|
+
module.
|
|
18
|
+
Metro reports one missing module at a time, so install all three at once.
|
|
19
|
+
|
|
20
|
+
Name the SDK's version too. Alphas go to the `alpha` dist tag, not `latest`, so a
|
|
21
|
+
bare `pnpm add @lorikeetai/react-native-sdk` installs the 1.x line, and no
|
|
22
|
+
semver range reaches a prerelease either.
|
|
23
|
+
|
|
7
24
|
```bash
|
|
8
|
-
pnpm add @lorikeetai/react-native-sdk
|
|
25
|
+
pnpm add @lorikeetai/react-native-sdk@2.0.0-alpha.2 \
|
|
26
|
+
react-native-webview@^13.15.0 \
|
|
27
|
+
react-native-safe-area-context@^5.6.0 \
|
|
28
|
+
react-native-svg@^15.13.0
|
|
9
29
|
```
|
|
10
30
|
|
|
31
|
+
These are native modules, so rebuild rather than just restarting the bundler:
|
|
32
|
+
`pod install` for iOS, a Gradle sync for Android.
|
|
33
|
+
|
|
34
|
+
The peer ranges are bounded to the major versions this release is built and
|
|
35
|
+
tested against, so an unpinned install cannot silently resolve to an untested
|
|
36
|
+
major. Widening them is a change that needs testing, not a version bump.
|
|
37
|
+
|
|
38
|
+
### Upgrading from 1.x
|
|
39
|
+
|
|
40
|
+
- **Peer dependencies.** The three modules above used to arrive transitively.
|
|
41
|
+
Declare them yourself, and rebuild native.
|
|
42
|
+
- **`webviewProps` now reach the WebView.** The drop-in previously discarded
|
|
43
|
+
them. If you were passing props that silently did nothing, they take effect
|
|
44
|
+
now.
|
|
45
|
+
- **Lazy ticket creation.** A ticket is no longer created when the widget opens,
|
|
46
|
+
which changes when conversations appear in your Lorikeet queue and in anything
|
|
47
|
+
counting them.
|
|
48
|
+
|
|
11
49
|
## Basic Usage
|
|
12
50
|
|
|
13
51
|
```tsx
|
|
@@ -29,9 +67,204 @@ export default function Screen() {
|
|
|
29
67
|
- `publicKey` (required): The public key for your Lorikeet account
|
|
30
68
|
- `authToken` (required): Authentication token for the user
|
|
31
69
|
- `baseUrl` (optional): Base URL for the Lorikeet API (defaults to `https://chat.lorikeetcx.ai`)
|
|
32
|
-
- `customisations` (optional): Widget
|
|
70
|
+
- `customisations` (optional): Widget customisation options. Exported as
|
|
71
|
+
`Customisations` from this package, so there is nothing else to install
|
|
33
72
|
- `styles` (optional): Style overrides for the header text
|
|
34
|
-
- `
|
|
73
|
+
- `onEvent` (optional): What the page reported, as typed events. See [Events](#events)
|
|
74
|
+
- `onWebViewMessage` (optional): Handler for raw messages from the webview.
|
|
75
|
+
Deprecated in favour of `onEvent`, and still called
|
|
76
|
+
- `ticketId` (optional): Resume this conversation instead of starting a new one
|
|
77
|
+
- `session` (optional): The session token that authorises `ticketId`
|
|
78
|
+
- `metadata` (optional): Arbitrary context attached to the conversation, for
|
|
79
|
+
routing or for the agent to read. Survives a resume
|
|
80
|
+
- `webviewProps` (optional): Forwarded to the underlying WebView. The SDK's
|
|
81
|
+
defaults for media playback, scaling and the loading state can all be
|
|
82
|
+
overridden; `onMessage` is additive, so the SDK reads the bridge first and
|
|
83
|
+
then calls yours
|
|
84
|
+
|
|
85
|
+
## Resuming a conversation
|
|
86
|
+
|
|
87
|
+
Without `ticketId`, every mount loads `/widget`, and `/widget` mints a new
|
|
88
|
+
ticket. A customer who closes the app and comes back gets an empty chat while
|
|
89
|
+
their previous ticket stays open.
|
|
90
|
+
|
|
91
|
+
The widget reports what you need to resume over the WebView bridge. Store both
|
|
92
|
+
values, scoped to the customer, somewhere that survives an app restart, and pass
|
|
93
|
+
them back on the next mount:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
const [resume, setResume] = useState<{ ticketId?: string; session?: string }>(
|
|
97
|
+
{},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
void loadResumeForCustomer(customerId).then(setResume)
|
|
102
|
+
}, [customerId])
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<LorikeetWidgetView
|
|
106
|
+
publicKey="wk_xxx"
|
|
107
|
+
authToken={token}
|
|
108
|
+
ticketId={resume.ticketId}
|
|
109
|
+
session={resume.session}
|
|
110
|
+
onWebViewMessage={(event) => {
|
|
111
|
+
const message = JSON.parse(event.nativeEvent.data)
|
|
112
|
+
if (message.type === 'persistLorikeetTicketId') {
|
|
113
|
+
void saveResumeForCustomer(customerId, { ticketId: message.ticketId })
|
|
114
|
+
}
|
|
115
|
+
if (message.type === 'persistLorikeetSession') {
|
|
116
|
+
void saveResumeForCustomer(customerId, { session: message.session })
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Pass both or neither. `session` authorises exactly the ticket it was issued
|
|
124
|
+
for, and a resume without it fails with `CUSTOMER_ID_MISSING`.
|
|
125
|
+
|
|
126
|
+
The SDK does not store these for you, and it does not clear them. On logout,
|
|
127
|
+
clear your own copy and call the widget's `logout()`; see
|
|
128
|
+
[Logging out](#logging-out).
|
|
129
|
+
|
|
130
|
+
While `ticketId` is set, the header's new-chat icon reloads that conversation
|
|
131
|
+
rather than starting a new one. To start a new one, clear your stored
|
|
132
|
+
`ticketId` and `session` and let the next mount fall back to `/widget`.
|
|
133
|
+
|
|
134
|
+
## Events
|
|
135
|
+
|
|
136
|
+
`onEvent` reports what the page said, as a `LorikeetWidgetEvent`:
|
|
137
|
+
|
|
138
|
+
| Event | Means |
|
|
139
|
+
| --------------------------------- | --------------------------------------------------------------- |
|
|
140
|
+
| `ready` | The conversation is usable |
|
|
141
|
+
| `ticket_created` | A ticket this page load created. Once per ticket, never a resume |
|
|
142
|
+
| `customer_message` | The customer sent something |
|
|
143
|
+
| `agent_message` | The agent replied. `markUnread` is the page asking for an unread indicator |
|
|
144
|
+
| `close` | The page hid itself, with a `reason` of `ended_by_customer` or `minimised` |
|
|
145
|
+
| `conversation_ended` | It ended without the customer ending it |
|
|
146
|
+
| `new_chat` | The page is asking for a replacement conversation |
|
|
147
|
+
| `persist_ticket_id` | Store this, with the session, to resume later |
|
|
148
|
+
| `persist_session` | The other half of that pair |
|
|
149
|
+
| `logged_out` | `/logout` cleared this frame. See [Logging out](#logging-out) |
|
|
150
|
+
| `logout_failed` | `/logout` did not confirm. The widget stays unavailable |
|
|
151
|
+
| `error` | A page-level failure. `LORIKEET_ERROR_CODES` lists the codes |
|
|
152
|
+
|
|
153
|
+
Names match the web SDK's wherever it has one, so a host integrating both does
|
|
154
|
+
not learn two vocabularies.
|
|
155
|
+
|
|
156
|
+
`preload_complete` is in the union but cannot reach React Native: the preload
|
|
157
|
+
page posts it to a web parent only. Do not wait on it.
|
|
158
|
+
|
|
159
|
+
## Logging out
|
|
160
|
+
|
|
161
|
+
Clearing your own stored `ticketId` and `session` is not enough. The chat keeps
|
|
162
|
+
cookies and its own storage inside the WebView, which native code cannot reach,
|
|
163
|
+
so the next person on the device can reopen the previous customer's
|
|
164
|
+
conversation. Only the chat's `/logout` page can clear those, and only if the
|
|
165
|
+
WebView is navigated to it.
|
|
166
|
+
|
|
167
|
+
With the drop-in, call `logout()` on its ref:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
const widget = useRef<LorikeetWidgetHandle>(null)
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
<LorikeetWidgetView {...props} ref={widget} />
|
|
175
|
+
<Button
|
|
176
|
+
onPress={() => {
|
|
177
|
+
widget.current?.logout()
|
|
178
|
+
void clearResumeForCustomer(customerId)
|
|
179
|
+
}}
|
|
180
|
+
title="Sign out"
|
|
181
|
+
/>
|
|
182
|
+
</>
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The widget stays on that page until the chat confirms, and nothing you pass it
|
|
187
|
+
in the meantime changes that: handing it the next customer's credentials while
|
|
188
|
+
the clear is still running would unmount the page mid-clear and leave the last
|
|
189
|
+
customer's cookies on the device. Pass them once `logged_out` has arrived and
|
|
190
|
+
the next chat starts normally.
|
|
191
|
+
|
|
192
|
+
A logout that does not confirm within ten seconds reports `logout_failed` and
|
|
193
|
+
leaves the widget unavailable rather than opening a chat on a device the
|
|
194
|
+
previous customer's credentials still unlock. Call `logout()` again to retry.
|
|
195
|
+
|
|
196
|
+
That obligation outlives the component. The SDK stores nothing, so a widget
|
|
197
|
+
unmounted before the clear confirmed, by a screen change or an app restart,
|
|
198
|
+
comes back with no memory of it and opens a chat normally, on a device whose
|
|
199
|
+
cookies were never cleared. Record the failure alongside the conversation you
|
|
200
|
+
already store, and call `logout()` again before mounting a chat for anyone
|
|
201
|
+
else. We would rather own this than ask you to, and
|
|
202
|
+
[ADP-3160](https://linear.app/lorikeet/issue/ADP-3160) is where that storage
|
|
203
|
+
layer belongs.
|
|
204
|
+
|
|
205
|
+
If you compose the parts yourself, build the URL and navigate to it:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
const client = new LorikeetClient({ public_key: publicKey, api_base: baseUrl })
|
|
209
|
+
|
|
210
|
+
setUri(client.getLogoutUrl())
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Either way, wait for `logged_out` before treating the device as clean. The
|
|
214
|
+
WebView's load event does not prove anything: an error page fires it too.
|
|
215
|
+
|
|
216
|
+
The anonymous customer's long-lived token is deliberately not among these
|
|
217
|
+
events. It identifies the same anonymous customer for a year, this SDK offers
|
|
218
|
+
no way to hand one back, and the web SDK keeps the same message inside the
|
|
219
|
+
frame. A host that genuinely needs it can read the raw bridge, but storing it
|
|
220
|
+
is storing a credential, and [ADP-3160](https://linear.app/lorikeet/issue/ADP-3160)
|
|
221
|
+
is the work that should remove the need.
|
|
222
|
+
|
|
223
|
+
## Identity
|
|
224
|
+
|
|
225
|
+
The public key, base URL and auth token are one unit, and a conversation belongs
|
|
226
|
+
to the credentials that created it. Never take some of the three from one source
|
|
227
|
+
and the rest from another, even briefly during a render.
|
|
228
|
+
|
|
229
|
+
When any of them change, which includes logout and switching environment, clear
|
|
230
|
+
your stored `ticketId` and `session` at the same time. Keeping them across a
|
|
231
|
+
credential change either resumes the previous customer's conversation on a
|
|
232
|
+
shared device or sends a ticket id to a backend that has never heard of it.
|
|
233
|
+
|
|
234
|
+
Anything you load or persist asynchronously needs to be discarded if the
|
|
235
|
+
credentials moved on while it was in flight; a write that lands late will
|
|
236
|
+
otherwise put the old conversation back.
|
|
237
|
+
|
|
238
|
+
## App lifecycle
|
|
239
|
+
|
|
240
|
+
The drop-in handles two failures that a WebView does not recover from on its own:
|
|
241
|
+
|
|
242
|
+
- **Renderer death.** When the OS kills the WebView's content process, the view
|
|
243
|
+
goes permanently blank with no error. The SDK detects this on both platforms
|
|
244
|
+
and remounts.
|
|
245
|
+
- **A stale stream after a long background.** The chat page gives its message
|
|
246
|
+
stream ten attempts with backoff capped at 30s, then stops trying. If the app
|
|
247
|
+
was backgrounded past that budget, the SDK remounts on return rather than
|
|
248
|
+
showing a conversation whose stream is dead. Shorter absences are left alone,
|
|
249
|
+
so an app switch does not cost the customer a draft message.
|
|
250
|
+
|
|
251
|
+
In both cases the remount resumes the conversation that was on screen. The SDK
|
|
252
|
+
watches the bridge for the live ticket and its session, so a conversation created
|
|
253
|
+
by a mount that had no `ticketId` is not lost, which is what a naive reload would
|
|
254
|
+
do: it would load `/widget` again and mint a second ticket. That memory
|
|
255
|
+
lasts for the life of the component and is scoped to the credentials that
|
|
256
|
+
produced it; surviving a process death is still your storage's job.
|
|
257
|
+
|
|
258
|
+
If you compose the parts yourself rather than using `LorikeetWidgetView`, both
|
|
259
|
+
are yours to handle.
|
|
260
|
+
|
|
261
|
+
## Customisation encoding
|
|
262
|
+
|
|
263
|
+
`customisations` is serialised as base64 of its UTF-8 bytes, which is what the
|
|
264
|
+
chat page decodes. Any Unicode is safe, including emoji and typographic
|
|
265
|
+
punctuation. Earlier versions used the global `btoa`, which accepts Latin-1
|
|
266
|
+
only: a title such as `Self's AI Agent` with a typographic apostrophe threw
|
|
267
|
+
during render.
|
|
35
268
|
|
|
36
269
|
## Headless components
|
|
37
270
|
|
|
@@ -71,7 +304,8 @@ WebView component that handles the widget content.
|
|
|
71
304
|
|
|
72
305
|
- `uri` (required): The URI that the web view will load
|
|
73
306
|
- `baseUrl` (optional): Base URL for the Lorikeet API (defaults to `https://chat.lorikeetcx.ai`)
|
|
74
|
-
- `webviewProps` (optional): Passed through to the webview component
|
|
307
|
+
- `webviewProps` (optional): Passed through to the webview component. The SDK
|
|
308
|
+
adds external-link handling on `onNavigationStateChange` and then calls yours
|
|
75
309
|
|
|
76
310
|
## Development
|
|
77
311
|
|