Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,43 @@ Just add the ChatKit component, give it a client token, and customize the chat e
},
});

return <ChatKit control={control} className="h-[600px] w-[320px]" />;
}
```
return <ChatKit control={control} className="h-[600px] w-[320px]" />;
}
```

### React Native (Expo) preview

Experimental support for Expo and bare React Native apps lives in the `@openai/chatkit-react-native` package. The library ships
streaming HTTP helpers, WebRTC wrappers, voice primitives, and opinionated UI building blocks (`ChatList`, `ChatComposer`).

- Install the package alongside the recommended polyfills:

```bash
pnpm add @openai/chatkit-react-native react-native-polyfill-globals react-native-url-polyfill react-native-get-random-values base-64
```

- Import the polyfills at the top of your app entry point before rendering any ChatKit component:

```ts
import 'react-native-polyfill-globals/auto';
import 'react-native-url-polyfill/auto';
import 'react-native-get-random-values';
import 'base-64';
```

Read the [React Native getting started guide](docs/react-native/getting-started.md) for details on Expo networking, WebRTC
signalling, voice support, and testing recommendations.

### Testing the React Native package

Run the package build to ensure the TypeScript sources compile before publishing:

```bash
pnpm --filter @openai/chatkit-react-native build
```

The command bundles the entry points with `tsup` and validates the generated type declarations. If you are iterating on the
package locally, you can append `--watch` to rebuild on file changes.

## License

Expand Down
122 changes: 122 additions & 0 deletions docs/react-native/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# React Native (Expo) Support

This guide captures the minimum setup for adopting `@openai/chatkit-react-native` inside a new or existing React Native project. The instructions assume you are using [Expo](https://docs.expo.dev) with the development build workflow, but also call out fallbacks for bare React Native.

## 1. Install dependencies

```bash
pnpm add @openai/chatkit-react-native
pnpm add react-native-polyfill-globals react-native-url-polyfill react-native-get-random-values base-64
pnpm add expo expo-dev-client expo-av expo-speech expo-file-system expo-document-picker
pnpm add react-native-webrtc
# For bare React Native (non-Expo) projects
pnpm add react-native-fetch-api
```

## 2. Configure polyfills

Add the following imports at the top of your app entry (for example `app/index.tsx` when using Expo Router or `index.js` for bare projects):

```ts
import 'react-native-polyfill-globals/auto';
import 'react-native-url-polyfill/auto';
import 'react-native-get-random-values';
import 'base-64';
```

Polyfills must run before any ChatKit code executes to ensure `TextEncoder`, `TextDecoder`, `ReadableStream`, `URL`, `crypto.getRandomValues`, `atob` and `btoa` are defined.

## 3. Streaming HTTP helpers

`createStreamingFetcher` wraps Expo's `fetch` implementation and falls back to `react-native-fetch-api` for apps not running inside the Expo runtime. It exposes lifecycle hooks that map cleanly onto ChatKit's expectations.

```ts
import { createStreamingFetcher } from '@openai/chatkit-react-native';

const streamingFetch = createStreamingFetcher();

const { response } = await streamingFetch(
'https://api.openai.com/v1/responses',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EXPO_PUBLIC_OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4.1-mini',
input: [{ role: 'user', content: 'Hello!' }],
}),
},
{
onResponseStart: () => console.log('stream started'),
onChunk: (chunk) => console.log('chunk', chunk),
onResponseEnd: () => console.log('stream ended'),
},
);
```

## 4. Realtime via WebRTC

`createWebRTCSession` bridges [`react-native-webrtc`](https://github.com/react-native-webrtc/react-native-webrtc) with ChatKit's realtime API. Provide a signalling adapter that exchanges offers/answers with your backend. The backend should authenticate with OpenAI and create a WebRTC session on behalf of the device.

```ts
const session = await createWebRTCSession({
signalling: {
negotiate: async (offer) => {
const response = await fetch('https://your-server.example.com/negotiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ offer }),
});
if (!response.ok) throw new Error('Failed to negotiate');
return response.json();
},
},
onResponseChunk: (chunk) => console.log('Realtime chunk', chunk),
});

await session.start();
```

For server-to-server integrations or for environments where WebRTC is not available, `createWebSocketSession` provides a lightweight wrapper over the standard `WebSocket` implementation.

## 5. Voice mode

The `useVoiceSession` hook layers [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for microphone capture with [`expo-speech`](https://docs.expo.dev/versions/latest/sdk/speech/) for text-to-speech output.

```tsx
const voice = useVoiceSession();

return (
<View>
<Button title={voice.isRecording ? 'Stop' : 'Record'} onPress={voice.isRecording ? voice.stopRecording : voice.startRecording} />
<Button title="Play response" onPress={() => voice.speak('Processing request…')} />
</View>
);
```

## 6. UI primitives

`ChatList` and `ChatComposer` are unstyled building blocks that work with [`FlatList`](https://reactnative.dev/docs/flatlist) and React Native primitives. They intentionally avoid taking a dependency on any specific design system so you can wrap them with [Gluestack UI](https://gluestack.io/), [React Native Paper](https://callstack.github.io/react-native-paper/) or your own component library.

## 7. Testing & CI

- Run `pnpm --filter @openai/chatkit-react-native build` locally (or with `--watch` while iterating) to confirm the TypeScript
sources compile and the emitted declarations stay up to date.
- Use [Expo Application Services (EAS) Build](https://docs.expo.dev/build/introduction/) to produce reproducible binaries with the required native modules.
- Add simulator coverage for both iOS and Android in CI (e.g. GitHub Actions with `react-native-testing-library`).
- Include integration smoke tests that call the streaming HTTP helper and WebRTC wrapper to catch regressions in networking polyfills.

## 8. Versioning strategy

Mirror the version numbers from the web ChatKit packages. Introduce RN-specific fixes as patch releases and plan for a weekly merge from upstream to stay in sync with API additions.

## 9. Example apps

Two example apps complement this package:

- `examples/expo-chat`: Expo Router, streaming completions, voice capture.
- `examples/react-native-chat`: bare React Native CLI project focusing on WebRTC.

The examples intentionally live outside of the npm package to keep installs lean while still providing real-world references.
130 changes: 130 additions & 0 deletions packages/chatkit-react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# @openai/chatkit-react-native

React Native bindings and opinionated UI utilities for [OpenAI ChatKit](https://github.com/openai/chatkit).

> **Status:** experimental. The API surface will track the web ChatKit package but may contain additional React Native specific helpers. Expect breaking changes while the package matures.

## Installation

```bash
pnpm add @openai/chatkit-react-native
# required peer dependencies
pnpm add react react-native
# recommended polyfills
pnpm add react-native-polyfill-globals react-native-url-polyfill react-native-get-random-values base-64
# optional Expo integrations
pnpm add expo expo-av expo-speech expo-file-system expo-document-picker
# optional non-Expo fallback
pnpm add react-native-fetch-api
# realtime
pnpm add react-native-webrtc
```

Enable the auto-polyfill entry point as early as possible in your app (e.g. `index.js`). This ensures `TextEncoder`, `TextDecoder`, `ReadableStream`, `URL`, `crypto.getRandomValues`, and `atob`/`btoa` are available before ChatKit is used.

```ts
import 'react-native-polyfill-globals/auto';
import 'react-native-url-polyfill/auto';
import 'react-native-get-random-values';
import 'base-64';
```

Expo users should also install [`expo-dev-client`](https://docs.expo.dev/develop/development-builds/introduction/) to exercise native modules locally.

## Quick start

```tsx
import { ChatComposer, ChatList, createStreamingFetcher } from '@openai/chatkit-react-native';

const fetcher = createStreamingFetcher();

function ConversationScreen() {
const [messages, setMessages] = React.useState([]);

const handleSend = async (content: string) => {
const controller = new AbortController();
const { stream } = await fetcher(
'https://api.openai.com/v1/responses',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EXPO_PUBLIC_OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4.1-mini',
input: [{ role: 'user', content }],
}),
signal: controller.signal,
},
{
onResponseStart: () => console.log('response started'),
onChunk: (chunk) => console.log('chunk', chunk),
onResponseEnd: () => console.log('response ended'),
},
);

return () => controller.abort();
};

return (
<>
<ChatList messages={messages} />
<ChatComposer onSend={handleSend} />
</>
);
}
```

## Features

- **Streaming HTTP helpers** with fallbacks for Expo (`expo/fetch`) and bare React Native (`react-native-fetch-api`).
- **Realtime helpers** built on top of `react-native-webrtc` with WebSocket fallback for server-side usage.
- **UI primitives** (`ChatList`, `ChatComposer`) that work with [`FlatList`](https://reactnative.dev/docs/flatlist) and provide opinionated defaults for avatars, timestamps and assistant badges.
- **Voice helpers** to capture microphone input via `expo-av` and synthesize responses using `expo-speech`.

## Realtime signalling

`createWebRTCSession` expects a signalling adapter that exchanges SDP offers/answers with your server. The server can in turn call OpenAI's Realtime API. See `src/realtime` for a reference implementation you can adapt for your backend stack.

```ts
const signalling = {
negotiate: async (offer: RTCSessionDescriptionInit) => {
const response = await fetch('https://example.com/realtime/negotiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ offer }),
});
if (!response.ok) throw new Error('Failed to negotiate');
return response.json();
},
};

const session = await createWebRTCSession({
signalling,
onResponseChunk: (chunk) => console.log(chunk),
});
```

## Example apps

- `examples/expo-chat`: Expo Router sample using `expo-dev-client` and `expo-av` for voice capture.
- `examples/react-native-chat`: bare React Native CLI sample featuring WebRTC and WebSocket fallbacks.

> Example apps are tracked separately to keep this package lightweight.

## Development

The package uses [tsup](https://tsup.egoist.dev/) to emit both ESM and CJS bundles that Metro can consume. Run `pnpm --filter @openai/chatkit-react-native build` to produce distributable artifacts.

To continuously recompile during local development, pass the `--watch` flag:

```bash
pnpm --filter @openai/chatkit-react-native build --watch
```

This keeps the generated output and type declarations in sync with your source edits.

## License

MIT
73 changes: 73 additions & 0 deletions packages/chatkit-react-native/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "@openai/chatkit-react-native",
"version": "0.0.0",
"description": "React Native bindings and utilities for OpenAI ChatKit.",
"type": "module",
"sideEffects": false,
"files": [
"dist",
"README.md"
],
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./polyfills": {
"types": "./dist/polyfills/index.d.ts",
"import": "./dist/polyfills/index.js",
"require": "./dist/polyfills/index.cjs",
"default": "./dist/polyfills/index.js"
}
},
"peerDependencies": {
"react": ">=18",
"react-native": ">=0.74",
"expo": ">=50",
"expo-av": "*",
"expo-speech": "*",
"react-native-fetch-api": "^3.0.0",
"react-native-webrtc": ">=1.108.0"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
},
"expo-av": {
"optional": true
},
"expo-speech": {
"optional": true
},
"react-native-fetch-api": {
"optional": true
},
"react-native-webrtc": {
"optional": true
}
},
"dependencies": {
"@openai/chatkit": "workspace:*"
},
"devDependencies": {
"@types/react": "^18.3.0",
"@types/react-native": "^0.73.0",
"rimraf": "^5.0.0",
"tsup": "^8.0.0",
"typescript": "^5.4.0"
},
"scripts": {
"build": "tsup src/index.ts src/polyfills/index.ts --format esm,cjs --dts --clean --external expo-av --external expo-speech --external expo/fetch --external react-native-fetch-api",
"clean": "rimraf dist"
},
"publishConfig": {
"access": "public",
"provenance": false
},
"license": "MIT"
}
Loading