@clariodesk/react-native
v0.1.1
Published
ClarioDesk React Native SDK — in-app customer support chat, bug reporting, and feedback. Headless client plus a prebuilt UI (./ui). Works in bare RN and Expo (dev build + Expo Go).
Downloads
266
Maintainers
Readme
@clariodesk/react-native
In-app support for React Native — embed support conversations in your app with a few method calls, or drop in the prebuilt chat UI. Mirrors the Flutter SDK 1:1: device-is-identity auth (no JWTs/refresh tokens), live updates over Centrifugo/SSE, presence, read receipts, typing, push, attachments, and CSAT.
Two integration modes, one install:
- Headless (primary) — promise-based methods + live subscriptions/hooks; you render the UI.
- Prebuilt UI (
@clariodesk/react-native/ui) — a themeable drop-in chat you present with one call.
Integrating with an AI agent? (Claude Code, Cursor, Codex) Paste the install prompt and it does this whole README for you — detect, ask, install, verify. Machine-readable docs: llms.txt · rules for AI agents.
Compatibility
Works across the widest surface — no fork crashes; every fork can chat.
| Capability | Bare RN (old + New Arch) | Expo dev build / EAS | Expo Go |
|---|---|---|---|
| Device key | ✅ Secure Enclave / Keystore | ✅ (via config plugin) | ⚠️ software P-256 fallback |
| Secure storage | ✅ react-native-keychain | ✅ expo-secure-store | ✅ expo-secure-store |
| Realtime (Centrifugo + SSE) | ✅ | ✅ | ✅ |
| Push | ✅ (host Firebase) | ✅ | ⛔ remote push N/A in Go |
| Attachments | ✅ | ✅ | ✅ (Expo pickers) |
| Prebuilt UI | ✅ | ✅ | ✅ |
The hardware key needs a dev build (Expo Go can't load native modules); the SDK
transparently falls back to a software ECDSA P-256 key so you can evaluate the
whole flow in Expo Go and ship hardware-backed with no code change. The backend
records the attestation (secure_enclave/tee/strongbox/software) either way.
Install
npm install @clariodesk/react-native
# secure RNG — required; import once at app entry (see below)
npm install react-native-get-random-valuesPick the peers for your stack:
# secure storage
npx expo install expo-secure-store # Expo
npm install react-native-keychain # bare RN
# attachments (optional — only if you attach files)
npx expo install expo-image-picker expo-document-picker expo-image-manipulator # Expo
npm install react-native-image-picker @react-native-documents/picker # bare RN
# connectivity-reactive reconnect (optional)
npm install @react-native-community/netinfoExpo — add the config plugin, then make a dev build (or run in Expo Go for the software tier):
{ "expo": { "plugins": ["@clariodesk/react-native"] } }
// parameterize: ["@clariodesk/react-native", { "enableCamera": false, "enablePush": true }]npx expo prebuild && npx expo run:iosBare RN — autolinking handles the native module. Run
npx install-expo-modules@latest once, then cd ios && pod install.
Headless quick start
// At the very top of index.js — before anything else:
import 'react-native-get-random-values';
import { ClarioDesk } from '@clariodesk/react-native';
// 1. Once at app start. First launch generates the device key + registers; later
// launches reuse it. Realtime defaults to Centrifugo, falling back to SSE.
await ClarioDesk.init({ apiKey: 'pk_live_…' });
// 2. After your own auth knows the user (metadata only — the device key is the
// real identity, so this grants no access).
await ClarioDesk.identify({ externalId: user.id, email: user.email });
// 3. Writes are imperative + optimistic.
const { ticket } = await ClarioDesk.createTicket({ subject: '', body: 'Upload is broken' });
await ClarioDesk.sendMessage(ticket.id, 'Still happening on 1.4.2');
// 4. Reads are live. subscribe* returns an unsubscribe function.
const off = ClarioDesk.subscribeMessages(ticket.id, (messages) => render(messages));
// 5. On sign-out / user switch.
await ClarioDesk.reset();React hooks
Idiomatic state binding (not UI) in a separate entry so the core has no React dep:
import {
useTickets, useMessages, useTyping, useAgentPresence,
useConnection, useSync, usePushOpened,
} from '@clariodesk/react-native/hooks';
const { tickets, loading } = useTickets();
const { messages } = useMessages(ticketId);
const supportTyping = useTyping(ticketId);
const agentOnline = useAgentPresence();
const conn = useConnection(); // 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'fatal'A message with pending: true is optimistic ("sending…"); failed: true means
the send was rejected — render tap-to-retry: ClarioDesk.retryMessage(m). An
offline send stays pending and auto-flushes on reconnect (durable outbox).
Prebuilt UI
import { ClarioDeskProvider, openInbox } from '@clariodesk/react-native/ui';
// Wrap your app once:
<ClarioDeskProvider theme={{ primary: brand.accent, cornerRadius: 18, mode: 'system' }}>
<App />
</ClarioDeskProvider>;
// Open from anywhere (e.g. a support button):
openInbox(); // also: openTicket(id), openNewConversation(), openBugReport()It presents its own screens in a self-owned modal (no react-navigation), derives
light/dark from your theme, and exposes ~50 overridable strings. To enable the
attach button, pass a picker to the provider. Zero native deps beyond the core.
Push
Push is a separate package so Firebase's native code never autolinks into a
headless host. The host owns the messaging plugin; the SDK gets tokens through a
PushTokenProvider seam.
npm install @clariodesk/react-native-push @react-native-firebase/app @react-native-firebase/messagingimport { FirebaseTokenProvider } from '@clariodesk/react-native-push';
await ClarioDesk.init({ apiKey: 'pk_live_…', pushTokenProvider: new FirebaseTokenProvider() });In your shared background/foreground handler, route ClarioDesk messages so they don't collide with your app's own:
messaging().setBackgroundMessageHandler(async (msg) => {
if (ClarioDesk.isClarioMessage(msg.data ?? {})) return; // ours
/* your app's handling */
});On a notification tap call ClarioDesk.openTicketFromPush(data) (or the
usePushOpened hook) to navigate. Registering the background handler in
index.js at startup is the #1 push setup-failure cause — make sure it runs.
Attachments
await ClarioDesk.sendMessage(ticketId, 'see screenshot', {
attachments: [{ uri, mime: 'image/jpeg', kind: 'image' }],
});Pass a MediaPicker / ImageNormalizer via init({ attachments: { picker, normalizer } })
(or upload OutgoingAttachments you built yourself). The SDK signs → normalizes
images to JPEG → uploads (R2 / Cloudflare Stream) → binds the ids at send, with
per-tile progress (attachmentUploadProgress(stubId)). ≤4/msg, 10 MB image /
50 MB video·file.
API surface
init · identify · createTicket · sendMessage · retryMessage · markRead ·
setTyping · submitCsat · fetchTickets · fetchTicket · reset · isInitialized ·
isIdentified, the reactive subscribeTickets/Messages/Typing/AgentPresence/
Connection/Sync/State/PushOpened, push (registerPushToken · isClarioMessage ·
parsePushPayload · openTicketFromPush · get/setNotificationPreferences), and
attachments (attachmentUploadProgress · refreshAttachmentUrl). Hooks mirror the
subscriptions. All types (Ticket, Message, Attachment, readStateOf,
ClarioDeskError, …) are exported.
How auth works
Device-is-identity. On first launch the SDK creates a non-extractable ECDSA P-256
keypair and registers it via challenge-response; every request is signed with that
key (X-ClarioDesk-* headers) — no JWTs/refresh tokens. The publishable pk_* key
only authorizes device registration; it can't read tickets or impersonate users.
License
MIT
