@sagepilot-ai/react-native-sdk 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.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sagepilot 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 ADDED
@@ -0,0 +1,422 @@
1
+ # `@sagepilot-ai/react-native-sdk`
2
+
3
+ Official React Native SDK for adding Sagepilot chat to iOS and Android apps.
4
+
5
+ The SDK opens the Sagepilot-hosted chat experience inside a React Native WebView and provides APIs for setup, identity, unread count, and app-owned launchers.
6
+
7
+ ## Requirements
8
+
9
+ - React 18+
10
+ - React Native 0.72+
11
+ - `react-native-webview` 13+
12
+ - A Sagepilot workspace id and chat channel id
13
+ - Secure token storage for production apps
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @sagepilot-ai/react-native-sdk react-native-webview
19
+ ```
20
+
21
+ For secure persisted sessions, also install one storage library:
22
+
23
+ ```bash
24
+ npm install react-native-keychain
25
+ ```
26
+
27
+ Expo apps can use `expo-secure-store` instead:
28
+
29
+ ```bash
30
+ npx expo install expo-secure-store
31
+ ```
32
+
33
+ ## Configuration Values
34
+
35
+ You configure the SDK with:
36
+
37
+ - `key`: public routing config in the format `workspace_id:channel_id`
38
+ - `tokenStorage`: secure storage adapter for SDK-created customer session tokens
39
+ - optional request, presentation, and behavior settings
40
+
41
+ For normal Sagepilot cloud usage, omit `host`; API calls default to `https://api.sagepilot.ai` and the WebView uses the Sagepilot-hosted widget origin.
42
+
43
+ ```ts
44
+ await SagepilotChat.configure({
45
+ key: "workspace_id:channel_id"
46
+ });
47
+ ```
48
+
49
+ If your workspace uses a dedicated host:
50
+
51
+ ```ts
52
+ await SagepilotChat.configure({
53
+ key: "workspace_id:channel_id",
54
+ host: "https://your-sagepilot-host.com"
55
+ });
56
+ ```
57
+
58
+ The `key` is not a secret. It is safe to ship in a mobile app. Do not put server API keys, signing secrets, or private workspace secrets in a React Native app.
59
+
60
+ ### Supported Configuration Options
61
+
62
+ These options are part of the supported React Native SDK configuration contract.
63
+
64
+ | Option | Purpose |
65
+ |---|---|
66
+ | `key` | Public routing key in the format `workspace_id:channel_id`. |
67
+ | `host` | Sagepilot customer-manager/API host override. Most apps should omit this. |
68
+ | `widgetHost` | Optional hosted widget origin override for development or dedicated widget routing. Most apps should omit this. |
69
+ | `headers` | Additional headers for SDK service requests. Do not pass server API keys or long-lived secrets from a mobile app. |
70
+ | `fetch` | Custom fetch implementation for runtimes that need one. |
71
+ | `tokenStorage` | Secure storage adapter for SDK-created customer session tokens. |
72
+ | `anonymousId` | Optional stable anonymous identifier sent when the SDK creates a customer session. |
73
+ | `metadata` | Optional app metadata merged into the session creation payload. |
74
+ | `deviceInfo` | Optional adapter that returns device metadata to include during session creation. |
75
+ | `presentation.style` | Modal presentation style: `"sheet"`, `"fullScreen"`, or `"push"`. |
76
+ | `presentation.mobile` | Adds `mobile=1` to the hosted widget URL. Defaults to `true` for React Native. |
77
+ | `presentation.showCloseButton` | Controls whether the provider renders a native close button around the WebView. Defaults to `false` so the hosted Sagepilot widget owns close behavior. |
78
+ | `theme.accentColor` | Primary brand color used as the fallback launcher button color. |
79
+ | `theme.logoUrl` | Optional brand logo URL exposed in SDK theme state. |
80
+ | `theme.preferredColorScheme` | Optional hosted chat color scheme hint: `"light"`, `"dark"`, or `"system"`. |
81
+ | `theme.launcher` | Optional chat launcher icon, label, and badge color configuration. |
82
+ | `behavior.preloadWebView` | Preloads a hidden hosted chat WebView after configuration. |
83
+ | `behavior.enableUnreadPolling` | Starts or disables automatic unread-count polling after configuration. |
84
+ | `behavior.unreadPollIntervalMs` | Sets the unread polling interval in milliseconds. |
85
+
86
+ `SagepilotChatProvider` renders the Sagepilot-hosted chat UI in a React Native WebView. Most Sagepilot cloud apps do not need any host override.
87
+
88
+ ### Chat Launcher Theme
89
+
90
+ Use `theme.launcher` to configure the icon button and unread badge colors for your app-owned launcher. The SDK resolves omitted values to Sagepilot defaults, so customers only need to set the colors they want to control.
91
+
92
+ ```ts
93
+ const CHAT_LAUNCHER_CONFIG = {
94
+ label: "Support",
95
+ buttonColor: "#173c2d",
96
+ pressedButtonColor: "#225340",
97
+ disabledButtonColor: "#d8cdbb",
98
+ borderColor: "#2a5e49",
99
+ disabledBorderColor: "#cbbda9",
100
+ iconColor: "#fff8ef",
101
+ iconInsideColor: "#173c2d",
102
+ labelColor: "#d4e9dc",
103
+ disabledContentColor: "#8d8172",
104
+ unreadBadgeColor: "#e76f51",
105
+ unreadBadgeTextColor: "#ffffff",
106
+ unreadBadgeBorderColor: "#efe4d4"
107
+ };
108
+
109
+ await SagepilotChat.configure({
110
+ key: "workspace_id:channel_id",
111
+ theme: {
112
+ accentColor: CHAT_LAUNCHER_CONFIG.buttonColor,
113
+ preferredColorScheme: "system",
114
+ launcher: CHAT_LAUNCHER_CONFIG
115
+ }
116
+ });
117
+ ```
118
+
119
+ Use the resolved launcher theme from `useSagepilotChat()` when rendering your own button:
120
+
121
+ ```tsx
122
+ import { Pressable, Text, View } from "react-native";
123
+ import { MessageCircle } from "lucide-react-native";
124
+ import { useSagepilotChat } from "@sagepilot-ai/react-native-sdk";
125
+
126
+ export function ChatLauncher() {
127
+ const { configured, unreadCount, presentMessages, theme } = useSagepilotChat();
128
+ const launcher = theme.launcher;
129
+
130
+ return (
131
+ <Pressable
132
+ accessibilityRole="button"
133
+ accessibilityLabel={launcher.label}
134
+ disabled={!configured}
135
+ onPress={presentMessages}
136
+ style={({ pressed }) => ({
137
+ alignItems: "center",
138
+ backgroundColor: !configured
139
+ ? launcher.disabledButtonColor
140
+ : pressed
141
+ ? launcher.pressedButtonColor
142
+ : launcher.buttonColor,
143
+ borderColor: !configured ? launcher.disabledBorderColor : launcher.borderColor,
144
+ borderRadius: 28,
145
+ borderWidth: 1,
146
+ height: 56,
147
+ justifyContent: "center",
148
+ width: 56
149
+ })}
150
+ >
151
+ <MessageCircle
152
+ color={!configured ? launcher.disabledContentColor : launcher.iconColor}
153
+ fill={!configured ? launcher.disabledContentColor : launcher.iconInsideColor}
154
+ size={24}
155
+ />
156
+ {unreadCount > 0 ? (
157
+ <View
158
+ style={{
159
+ alignItems: "center",
160
+ backgroundColor: launcher.unreadBadgeColor,
161
+ borderColor: launcher.unreadBadgeBorderColor,
162
+ borderRadius: 9,
163
+ borderWidth: 1,
164
+ minWidth: 18,
165
+ paddingHorizontal: 4,
166
+ position: "absolute",
167
+ right: -2,
168
+ top: -2
169
+ }}
170
+ >
171
+ <Text style={{ color: launcher.unreadBadgeTextColor, fontSize: 11 }}>
172
+ {unreadCount}
173
+ </Text>
174
+ </View>
175
+ ) : null}
176
+ </Pressable>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ## Secure Session Storage
182
+
183
+ The SDK creates opaque customer session tokens at runtime. These tokens are not exposed through hooks or public session APIs.
184
+
185
+ If `tokenStorage` is not provided, tokens are kept in memory only and the session will not persist after the app process restarts. Production apps should pass secure native storage.
186
+
187
+ ### React Native Keychain
188
+
189
+ `react-native-keychain` stores values in iOS Keychain and Android secure storage backed by Android Keystore.
190
+
191
+ ```ts
192
+ import * as Keychain from "react-native-keychain";
193
+ import {
194
+ SagepilotChat,
195
+ createKeychainTokenStorage
196
+ } from "@sagepilot-ai/react-native-sdk";
197
+
198
+ await SagepilotChat.configure({
199
+ key: "workspace_id:channel_id",
200
+ tokenStorage: createKeychainTokenStorage(Keychain)
201
+ });
202
+ ```
203
+
204
+ ### Expo SecureStore
205
+
206
+ Expo SecureStore uses iOS Keychain and Android encrypted storage backed by Android Keystore.
207
+
208
+ ```ts
209
+ import * as SecureStore from "expo-secure-store";
210
+ import { SagepilotChat } from "@sagepilot-ai/react-native-sdk";
211
+
212
+ await SagepilotChat.configure({
213
+ key: "workspace_id:channel_id",
214
+ tokenStorage: {
215
+ getItem: SecureStore.getItemAsync,
216
+ setItem: SecureStore.setItemAsync,
217
+ removeItem: SecureStore.deleteItemAsync
218
+ }
219
+ });
220
+ ```
221
+
222
+ ## Basic Setup
223
+
224
+ Configure the SDK after your app knows the current workspace/channel and, if available, the current user.
225
+
226
+ ```tsx
227
+ import * as Keychain from "react-native-keychain";
228
+ import { useEffect } from "react";
229
+ import {
230
+ SagepilotChat,
231
+ SagepilotChatProvider,
232
+ createKeychainTokenStorage
233
+ } from "@sagepilot-ai/react-native-sdk";
234
+
235
+ const tokenStorage = createKeychainTokenStorage(Keychain);
236
+
237
+ export function SagepilotProvider({ children }: { children: React.ReactNode }) {
238
+ useEffect(() => {
239
+ let cancelled = false;
240
+
241
+ async function setupSagepilot() {
242
+ await SagepilotChat.configure({
243
+ key: "workspace_id:channel_id",
244
+ tokenStorage,
245
+ presentation: {
246
+ style: "sheet",
247
+ mobile: true
248
+ },
249
+ behavior: {
250
+ preloadWebView: true,
251
+ enableUnreadPolling: true
252
+ }
253
+ });
254
+
255
+ if (!cancelled) {
256
+ await SagepilotChat.identify({
257
+ userId: "user_123",
258
+ email: "customer@example.com",
259
+ name: "Customer Name"
260
+ });
261
+ }
262
+ }
263
+
264
+ void setupSagepilot().catch(console.warn);
265
+
266
+ return () => {
267
+ cancelled = true;
268
+ SagepilotChat.destroy();
269
+ };
270
+ }, []);
271
+
272
+ return <SagepilotChatProvider>{children}</SagepilotChatProvider>;
273
+ }
274
+ ```
275
+
276
+ Mount the provider once near the top of your app:
277
+
278
+ ```tsx
279
+ export function App() {
280
+ return (
281
+ <SagepilotProvider>
282
+ <RootNavigator />
283
+ </SagepilotProvider>
284
+ );
285
+ }
286
+ ```
287
+
288
+ ## Opening Chat
289
+
290
+ The SDK does not force a launcher UI. Use the hook to connect Sagepilot to your own button, tab, badge, or help screen.
291
+
292
+ ```tsx
293
+ import { Pressable, Text } from "react-native";
294
+ import { useSagepilotChat } from "@sagepilot-ai/react-native-sdk";
295
+
296
+ export function SupportButton() {
297
+ const { configured, unreadCount, presentMessages } = useSagepilotChat();
298
+
299
+ return (
300
+ <Pressable disabled={!configured} onPress={presentMessages}>
301
+ <Text>
302
+ Support{unreadCount > 0 ? ` (${unreadCount})` : ""}
303
+ </Text>
304
+ </Pressable>
305
+ );
306
+ }
307
+ ```
308
+
309
+ Other open helpers:
310
+
311
+ ```ts
312
+ SagepilotChat.present();
313
+ SagepilotChat.presentMessages();
314
+ SagepilotChat.presentMessageComposer("I need help with my order");
315
+ ```
316
+
317
+ ## Identity
318
+
319
+ Call `identify()` when you know the signed-in app user.
320
+
321
+ ```ts
322
+ await SagepilotChat.identify({
323
+ userId: user.id,
324
+ email: user.email,
325
+ name: user.name,
326
+ phone: user.phone,
327
+ customProperties: {
328
+ plan: "pro",
329
+ storeId: "store_123"
330
+ }
331
+ });
332
+ ```
333
+
334
+ If identity verification is enabled for your workspace, generate `userHash` on your server and pass only the generated hash to the app:
335
+
336
+ ```ts
337
+ await SagepilotChat.identify({
338
+ userId: user.id,
339
+ email: user.email,
340
+ userHash: serverGeneratedUserHash
341
+ });
342
+ ```
343
+
344
+ Never generate `userHash` in the mobile app with a signing secret.
345
+
346
+ ## Logout
347
+
348
+ When the app user signs out, clear Sagepilot identity/session state:
349
+
350
+ ```ts
351
+ await SagepilotChat.logout();
352
+ await SagepilotChat.destroy();
353
+ ```
354
+
355
+ `logout()` removes the known customer identity from the current SDK session. `destroy()` stops polling, clears in-memory state, and removes SDK listeners.
356
+
357
+ ## Public API
358
+
359
+ ### Setup
360
+
361
+ - `SagepilotChat.configure(config)`: initializes the SDK, loads channel config, creates or resumes a customer session, and starts unread polling unless disabled.
362
+ - `SagepilotChat.destroy()`: stops polling, clears in-memory SDK state, and removes listeners.
363
+ - `SagepilotChat.getChannel()`: returns the loaded channel bootstrap data.
364
+
365
+ ### Identity And Session
366
+
367
+ - `SagepilotChat.identify(identity)`: links the active session to a known customer.
368
+ - `SagepilotChat.logout()`: removes known-customer identity from the active session.
369
+ - `SagepilotChat.getSession()`: fetches the current session and returns safe session metadata. It does not expose the session token.
370
+ - `SagepilotChat.getSessionState()`: returns safe local session metadata. It does not expose the session token.
371
+ - `SagepilotChat.getIdentityState()`: returns local identity state.
372
+ - `SagepilotChat.onIdentify(callback)`: subscribes to successful identify events.
373
+
374
+ ### Hosted Chat UI
375
+
376
+ - `SagepilotChat.present()`: opens the hosted chat home screen.
377
+ - `SagepilotChat.presentMessages()`: opens the hosted conversations/messages screen.
378
+ - `SagepilotChat.presentMessageComposer(message?)`: opens a fresh message composer and optionally pre-fills the composer text. It does not auto-send.
379
+ - `SagepilotChat.dismiss()`: closes the hosted chat modal.
380
+ - `SagepilotChat.hide()`: alias for `dismiss()`.
381
+ - `SagepilotChat.toggle()`: opens the chat when closed and closes it when open.
382
+ - `SagepilotChat.isPresented()`: returns whether the hosted chat modal is open.
383
+ - `SagepilotChatProvider`: renders the hosted Sagepilot chat inside a React Native modal WebView.
384
+
385
+ When `SagepilotChatProvider` is mounted, the SDK preloads a hidden hosted WebView after `configure()` so the first visible open can reuse warmed network/cache state. Disable this with `behavior.preloadWebView: false`.
386
+
387
+ ### Unread State
388
+
389
+ - `SagepilotChat.getUnreadCount()`: fetches and returns the current unread count.
390
+ - `SagepilotChat.onUnreadChange(callback)`: subscribes to unread count changes.
391
+ - `SagepilotChat.startUnreadPolling(intervalMs?)`: starts unread polling.
392
+ - `SagepilotChat.stopUnreadPolling()`: stops unread polling.
393
+
394
+ ### Lifecycle Events
395
+
396
+ - `SagepilotChat.onReady(callback)`: fires when SDK configuration completes.
397
+ - `SagepilotChat.onPresent(callback)`: fires when chat opens.
398
+ - `SagepilotChat.onDismiss(callback)`: fires when chat closes.
399
+ - `SagepilotChat.onError(callback)`: subscribes to SDK errors.
400
+ - `SagepilotChat.onStateChange(callback)`: subscribes to local SDK state changes.
401
+
402
+ ### React Hook
403
+
404
+ - `useSagepilotChat()`: returns presentation helpers, unread count, identity state, and common actions for app-owned launchers and badges.
405
+
406
+ ### Storage Helpers
407
+
408
+ - `createKeychainTokenStorage(keychain, options?)`: adapts `react-native-keychain` for secure session token storage.
409
+
410
+ Do not use AsyncStorage for session tokens.
411
+
412
+ ## Runtime Notes
413
+
414
+ The React Native UI uses `react-native-webview` to show the Sagepilot-owned hosted conversation. The SDK injects mobile WebView polish for viewport scaling, text selection, tap highlighting, native bridge message forwarding, and secure hosted-auth handoff.
415
+
416
+ Customers should depend on the public SDK methods and components, not WebView internals or hosted route implementation details.
417
+
418
+ ## License
419
+
420
+ MIT. See [LICENSE.md](./LICENSE.md).
421
+
422
+ The MIT License applies only to this SDK code. It does not grant access to Sagepilot AI services, workspaces, API credentials, hosted infrastructure, models, data, or paid features. Use of Sagepilot AI hosted services, APIs, Workspace IDs, and runtime-generated license keys is governed separately by Sagepilot AI's Terms of Service or the applicable customer agreement.
@@ -0,0 +1 @@
1
+