> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native SDK: Orbit quickstart

> Official Orbit React Native SDK quickstart — the useOrbitChat hook, headless chat client, in-app messages and content cards, and video rooms for iOS and Android.

# React Native SDK

The Orbit React Native SDK (`@devotel-orbit/react-native`) embeds Orbit's end-user-facing surfaces into iOS and Android apps: in-app chat (the same omnichannel conversation/inbox the web widget uses), owned in-app messages and content cards, and video rooms. It ships a headless `OrbitChatClient` plus a `useOrbitChat` hook, an `OrbitInAppClient` for the in-app feed, and an engine-injected `OrbitVideoRoomClient` for video rooms.

This is a **client SDK**: it authenticates only with a publishable key (`dv_live_pk_…`), because it ships inside a distributed app binary. It intentionally does not wrap the server-side management resources (messaging, voice, contacts, campaigns, verify) — those require a secret key and belong in your backend, via the [Node SDK](/sdks/node) or the REST API. See [Client / mobile SDKs (different scope, by design)](/sdks) on the SDK index.

<Note>
  **Pre-publish — source-only.** The React Native SDK is not yet on npm —
  the install line below fails today. Until first publish, vendor the
  source from the monorepo (`packages/sdk-react-native/`) or build against
  the same conversations API the web widget uses.
</Note>

## Installation

```bash theme={null}
npm install @devotel-orbit/react-native
```

The package is TypeScript-first with published type declarations. `react` and `react-native` are optional peer dependencies — needed only if you use the hook.

## The `useOrbitChat` hook

The hook subscribes a screen to an Orbit conversation: it connects on mount, tears down on unmount, and returns the live message list, connection status, typing indicator, and send methods.

```tsx theme={null}
import { useOrbitChat } from "@devotel-orbit/react-native/react";

function SupportScreen() {
  const { messages, status, typing, send } = useOrbitChat({
    publicKey: "dv_live_pk_…",   // publishable key ONLY — never a secret key
    appInfo: "acme-rn/2.3.1",
  });

  // …render `messages` with your own RN components…
}
```

## Headless chat client (OrbitChatClient)

Prefer the hook for screens; use the headless client directly for non-React code paths. Pass a storage adapter (for example `@react-native-async-storage/async-storage` satisfies the shape unmodified) to keep the conversation across app launches.

```ts theme={null}
import { OrbitChatClient } from "@devotel-orbit/react-native";

const chat = new OrbitChatClient({
  publicKey: "dv_live_pk_…",
  storage: AsyncStorage,   // keeps the conversation across launches
  appInfo: "acme-rn/2.3.1",
});

chat.on("message", (m) => console.log("agent:", m.body));
chat.on("messages", (all) => render(all));
chat.on("typing", (t) => setTyping(t));
chat.on("connection", (s) => setStatus(s));

await chat.connect();
await chat.sendMessage("Hi, I need help with my order");

// Upload a file attachment (10 MB client-side guard):
// await chat.sendAttachment({ uri: fileUri, name: "photo.jpg", type: "image/jpeg", size: 482_133 });
```

`chat.on(...)` returns an unsubscribe function; call `chat.disconnect()` to stop the real-time stream and retain the conversation id.

## In-app messages and content cards (OrbitInAppClient)

Owned in-app surfaces — assembled server-side into a per-visitor feed with `assembleInAppFeed` ordering rules kept in lockstep with the server, rendered locally, with impression / click / dismiss reported back. Dismissals persist across launches through the storage adapter.

```ts theme={null}
import { OrbitInAppClient } from "@devotel-orbit/react-native";

const inApp = new OrbitInAppClient({
  publicKey: "dv_live_pk_…",
  apiUrl: "https://api.orbit.devotel.io",
  storage: AsyncStorage,
});

const feed = await inApp.fetchFeed();
await inApp.reportEvent("impression", feed.cards[0].id);
await inApp.dismiss(feed.cards[0].id);
```

## Video rooms (OrbitVideoRoomClient)

Headless and engine-injected: your backend mints the room token server-side (`POST /api/v1/video/rooms/:id/join`) and hands the token plus server URL to the app. Wire a `VideoRoomEngine` over your native WebRTC stack (for example `@livekit/react-native`) — the package carries no hard WebRTC dependency. Inbound WebRTC video only.

```ts theme={null}
import { OrbitVideoRoomClient } from "@devotel-orbit/react-native";

const room = new OrbitVideoRoomClient({
  token: roomToken,          // minted by your backend — never an API key
  serverUrl: roomServerUrl,
  engine: myVideoEngine,
});

room.on("trackSubscribed", ({ track, participant }) => render(track, participant));
room.on("caption", (caption) => showCaption(caption));
await room.join();

await room.joinBreakout("support-tier-2", { token: breakoutToken });
await room.leave();
```

## Error handling

API failures surface as a typed `OrbitApiError` carrying the HTTP `status` and `responseExcerpt`; when the server sends a `Retry-After` hint it is parsed onto `retryAfterSeconds` (check `"retryAfterSeconds" in err`), so a 429 can be throttled instead of retried in a hot loop. Construction misuse — an empty key or a server-side secret key (`dv_live_sk_…` / `dv_test_sk_…`) — throws `Error` at the constructor.

```ts theme={null}
import { OrbitApiError } from "@devotel-orbit/react-native";

try {
  await chat.sendMessage("Where is my order?");
} catch (err) {
  if (err instanceof OrbitApiError && err.status === 429) {
    // respect err.retryAfterSeconds before retrying
  } else {
    throw err;
  }
}
```

Only the **publishable key** (`dv_live_pk_…` / `dv_test_pk_…`) may be embedded in the app — the client refuses a secret key at construction, because anything bundled in a distributed binary is extractable by any user. Real-time updates use header-authenticated polling, so the key never lands in a URL log.
