@lorikeetai/react-native-sdk 1.2.0 → 2.0.0-alpha.1

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 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.1 \
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
@@ -32,6 +70,109 @@ export default function Screen() {
32
70
  - `customisations` (optional): Widget customization options from `@lorikeetai/web-sdk`
33
71
  - `styles` (optional): Style overrides for the header text
34
72
  - `onWebViewMessage` (optional): Handler for messages from the webview
73
+ - `ticketId` (optional): Resume this conversation instead of starting a new one
74
+ - `session` (optional): The session token that authorises `ticketId`
75
+ - `metadata` (optional): Arbitrary context attached to the conversation, for
76
+ routing or for the agent to read. Survives a resume
77
+ - `webviewProps` (optional): Forwarded to the underlying WebView. The SDK's
78
+ defaults for media playback, scaling and the loading state can all be
79
+ overridden; `onMessage` is additive, so the SDK reads the bridge first and
80
+ then calls yours
81
+
82
+ ## Resuming a conversation
83
+
84
+ Without `ticketId`, every mount loads `/widget`, and `/widget` mints a new
85
+ ticket. A customer who closes the app and comes back gets an empty chat while
86
+ their previous ticket stays open.
87
+
88
+ The widget reports what you need to resume over the WebView bridge. Store both
89
+ values, scoped to the customer, somewhere that survives an app restart, and pass
90
+ them back on the next mount:
91
+
92
+ ```tsx
93
+ const [resume, setResume] = useState<{ ticketId?: string; session?: string }>(
94
+ {},
95
+ )
96
+
97
+ useEffect(() => {
98
+ void loadResumeForCustomer(customerId).then(setResume)
99
+ }, [customerId])
100
+
101
+ return (
102
+ <LorikeetWidgetView
103
+ publicKey="wk_xxx"
104
+ authToken={token}
105
+ ticketId={resume.ticketId}
106
+ session={resume.session}
107
+ onWebViewMessage={(event) => {
108
+ const message = JSON.parse(event.nativeEvent.data)
109
+ if (message.type === 'persistLorikeetTicketId') {
110
+ void saveResumeForCustomer(customerId, { ticketId: message.ticketId })
111
+ }
112
+ if (message.type === 'persistLorikeetSession') {
113
+ void saveResumeForCustomer(customerId, { session: message.session })
114
+ }
115
+ }}
116
+ />
117
+ )
118
+ ```
119
+
120
+ Pass both or neither. `session` authorises exactly the ticket it was issued
121
+ for, and a resume without it fails with `CUSTOMER_ID_MISSING`.
122
+
123
+ The SDK does not store these for you, and it does not clear them. On logout,
124
+ clear your own copy, or the next customer on that device resumes the previous
125
+ one's conversation.
126
+
127
+ While `ticketId` is set, the header's new-chat icon reloads that conversation
128
+ rather than starting a new one. To start a new one, clear your stored
129
+ `ticketId` and `session` and let the next mount fall back to `/widget`.
130
+
131
+ ## Identity
132
+
133
+ The public key, base URL and auth token are one unit, and a conversation belongs
134
+ to the credentials that created it. Never take some of the three from one source
135
+ and the rest from another, even briefly during a render.
136
+
137
+ When any of them change, which includes logout and switching environment, clear
138
+ your stored `ticketId` and `session` at the same time. Keeping them across a
139
+ credential change either resumes the previous customer's conversation on a
140
+ shared device or sends a ticket id to a backend that has never heard of it.
141
+
142
+ Anything you load or persist asynchronously needs to be discarded if the
143
+ credentials moved on while it was in flight; a write that lands late will
144
+ otherwise put the old conversation back.
145
+
146
+ ## App lifecycle
147
+
148
+ The drop-in handles two failures that a WebView does not recover from on its own:
149
+
150
+ - **Renderer death.** When the OS kills the WebView's content process, the view
151
+ goes permanently blank with no error. The SDK detects this on both platforms
152
+ and remounts.
153
+ - **A stale stream after a long background.** The chat page gives its message
154
+ stream ten attempts with backoff capped at 30s, then stops trying. If the app
155
+ was backgrounded past that budget, the SDK remounts on return rather than
156
+ showing a conversation whose stream is dead. Shorter absences are left alone,
157
+ so an app switch does not cost the customer a draft message.
158
+
159
+ In both cases the remount resumes the conversation that was on screen. The SDK
160
+ watches the bridge for the live ticket and its session, so a conversation created
161
+ by a mount that had no `ticketId` is not lost, which is what a naive reload would
162
+ do: it would load `/widget` again and mint a second ticket. That memory
163
+ lasts for the life of the component and is scoped to the credentials that
164
+ produced it; surviving a process death is still your storage's job.
165
+
166
+ If you compose the parts yourself rather than using `LorikeetWidgetView`, both
167
+ are yours to handle.
168
+
169
+ ## Customisation encoding
170
+
171
+ `customisations` is serialised as base64 of its UTF-8 bytes, which is what the
172
+ chat page decodes. Any Unicode is safe, including emoji and typographic
173
+ punctuation. Earlier versions used the global `btoa`, which accepts Latin-1
174
+ only: a title such as `Self's AI Agent` with a typographic apostrophe threw
175
+ during render.
35
176
 
36
177
  ## Headless components
37
178
 
@@ -71,7 +212,8 @@ WebView component that handles the widget content.
71
212
 
72
213
  - `uri` (required): The URI that the web view will load
73
214
  - `baseUrl` (optional): Base URL for the Lorikeet API (defaults to `https://chat.lorikeetcx.ai`)
74
- - `webviewProps` (optional): Passed through to the webview component
215
+ - `webviewProps` (optional): Passed through to the webview component. The SDK
216
+ adds external-link handling on `onNavigationStateChange` and then calls yours
75
217
 
76
218
  ## Development
77
219