Skip to main content

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 or the REST API. See Client / mobile SDKs (different scope, by design) on the SDK index.
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.

Scope matrix

The division is deliberate: no surface in this package accepts a secret key, so nothing in your shipped binary can be turned into a full-account credential by extraction. Server-side resources stay server-side.

Installation

The package is TypeScript-first with published type declarations. react and react-native are optional peer dependencies (>=17.0.0 / >=0.70.0) — needed only if you use the hook, and marked optional so headless-only consumers never resolve the types.

Integration order

  1. Construct — with the publishable key, an optional storage adapter, and an appInfo tag. Misuse fails at the constructor, not the first request.
  2. Subscribe — chat.on("message", …), "messages", "typing", "connection", "error" — before connect() so you never miss the initial history reload. Each returns an unsubscribe function.
  3. await chat.connect() — restores or creates the conversation, loads history, and opens the real-time stream.
  4. Send — optimistic local echo first; the Idempotency-Key header pins retries, and generateIdempotencyKey is exported for your own retry loops.
  5. Tear down — chat.dispose() stops the stream and releases the http client; the hook does this for you on unmount.

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.

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.
chat.on(...) returns an unsubscribe function; call chat.disconnect() to stop the real-time stream and retain the conversation id, or chat.dispose() to release everything.

In-app messages and content cards (OrbitInAppClient)

Owned in-app surfaces — assembled server-side into a per-visitor feed, ordered client-side by the exported assembleInAppFeed (same rules the server applies, capped at MAX_CONTENT_CARDS), rendered locally, with impression / click / dismiss reported back. Dismissals persist across launches through the storage adapter.
reportEvent takes the InAppEventType union ("impression" | "click" | "dismiss") — the compiler rejects anything else, and the server 422s what slips through. The closed set and the per-language fix are in InAppEventType: the in-app event enum.

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, so you never vendor a second WebRTC build beside the one you already use. Inbound WebRTC video only.
Pure helpers ship alongside the client — computeGridColumns picks tile-grid columns like the web room does, resolveTileLabel guarantees a non-empty participant label, and parseCaptionPayload / mergeCaption collapse interim→final caption segments the way Zoom/Meet do. Your tile grid matches the web room exactly.

Push token onboarding (backend)

Registering the device’s APNs (iOS) or FCM (Android) token is a backend call against POST /api/v1/push/device-tokens, run with a secret key via the Node SDK or plain HTTP:
No SDK in the backend? The same call over HTTP:
Re-registering an existing token is an upsert — call it on every platform token rollover and last-seen is bumped, never duplicated. Delivery-side mechanics (categories, rich media, scheduling) live in the push channel reference.

Runnable examples

The package’s examples/ directory holds self-contained end-to-end TypeScript files — each takes the publishable key (dv_test_pk_… / dv_live_pk_…) as the first argument so swap sandbox vs live by changing the key: chat-connect.ts (OrbitChatClient connect + send with OrbitApiError.status handling, and the @react-native-async-storage/async-storage adapter injected) and in-app-feed.ts (OrbitInAppClient.fetchFeed() + reportEvent impression + dismiss(surfaceId)). See packages/sdk-react-native/examples/README.md; the process.env fallback only helps when you run the sample under Node — in a real app pass the key inline.

Error taxonomy

Version notes

@devotel-orbit/react-native 0.1.1 pairs @devotel-orbit/web’s request envelope whole-file; the source pins in src/__tests__/ keep the wire contract identical to the web widget across versions. The 0.x line is pre-publish, so minor releases may adjust surface details — pin package-lock.json accordingly. The runtime targets every RN engine >=0.70 (Hermes/JSC), with an AbortSignal.timeout fallback to a manual controller. 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.