Skip to content
Merged
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
6 changes: 4 additions & 2 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ jobs:
cache-dependency-path: 'yarn.lock'
- name: Install && Build - SDK and Sample App
uses: ./.github/actions/install-and-build-sdk
- name: Build SDK
run: yarn build
- name: Lint
run: yarn lint
- name: Typecheck tests
run: yarn workspace stream-chat-react-native-core test:typecheck
- name: Typecheck packages and example apps
run: yarn typecheck
- name: Test
run: yarn test:coverage
3 changes: 1 addition & 2 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
enableGlobalCache: true

enableHardenedMode: true

enableScripts: false

enableTelemetry: false
Expand All @@ -17,6 +15,7 @@ npmMinimalAgeGate: 3d
npmPreapprovedPackages:
- stream-chat
- react-native-teleport
- react-native-nitro-sound

npmPublishProvenance: true

Expand Down
492 changes: 351 additions & 141 deletions AGENTS.md

Large diffs are not rendered by default.

163 changes: 2 additions & 161 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,165 +2,6 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository Overview
All guidance lives in `AGENTS.md` — the single source shared with every other agent (Copilot, Cursor, Codex, …). The import below pulls it in; do not duplicate content here.

Stream Chat React Native SDK monorepo. The main SDK code lives in `package/` (published as `stream-chat-react-native-core`). Built on top of the `stream-chat` JS client library.

This is a **Yarn 4 (Berry)** workspace monorepo. The Yarn binary lives in `.yarn/releases/yarn-4.15.0.cjs` and is invoked via `yarnPath` in `.yarnrc.yml`; any globally-installed Yarn launcher (e.g. the Homebrew Yarn 1.x) auto-delegates to it. No Corepack required.

Workspaces: `package`, `package/native-package`, `package/expo-package`, `examples/SampleApp`, `examples/ExpoMessaging`, `examples/TypeScriptMessaging`. There is a single root `yarn.lock`.

## Common Commands

All commands below run from the repo root.

### Install

```bash
yarn install # Set up every workspace (single root lockfile)
yarn install --immutable # CI-style; fail if yarn.lock would change
```

The root `package/`'s `postinstall` runs `husky install` and `yarn shared-native:sync` automatically.

### Build

```bash
yarn build # SDK build (commonjs + esm + types)
yarn workspace stream-chat-react-native-core build # Same, explicit form
```

### Lint & Format

```bash
yarn lint # prettier + eslint + translation validation (max-warnings 0)
yarn lint-fix # Auto-fix lint and formatting issues
```

### Test

```bash
yarn test:unit # All unit tests (sets TZ=UTC)
yarn test:coverage # With coverage report
yarn test:typecheck # Type-check tests against tsconfig.test.json (run after any code change)
yarn workspace stream-chat-react-native-core test:unit # Same as `yarn test:unit`
cd package && TZ=UTC npx jest path/to/test.test.tsx # Single test file
```

Always run `yarn test:typecheck` after making code changes — `yarn lint` and `yarn test:unit` do not catch all type errors.

Tests use Jest with `react-native` preset and `@testing-library/react-native`. Test files live alongside source at `src/**/__tests__/*.test.ts(x)`. Mock builders are in `src/mock-builders/`.

To run a single test, you can also temporarily add the file path to the `testRegex` array in `package/jest.config.js`.

### Sample App

```bash
yarn workspace sampleapp start # Metro bundler (alias: cd examples/SampleApp && yarn start)
yarn workspace sampleapp ios # Run iOS
yarn workspace sampleapp android # Run Android
```

## Architecture

### Package Structure

- `package/` — Main SDK (`stream-chat-react-native-core`)
- `package/native-package/` — React Native native module wrappers
- `package/expo-package/` — Expo-compatible wrapper
- `examples/SampleApp/` — Full sample app with navigation
- `release/` — Semantic release scripts

### SDK Source (`package/src/`)

**Component hierarchy**: `<Chat>` → `<Channel>` → `<MessageList>` / `<MessageInput>` / `<Thread>`

- `components/` — UI components (~28 major ones: ChannelList, MessageList, MessageInput, Thread, Poll, ImageGallery, etc.)
- `contexts/` — React Context providers (~33 contexts). The primary way components receive state and callbacks. Key contexts: `ChatContext`, `ChannelContext`, `MessagesContext`, `ThemeContext`, `TranslationContext`
- `hooks/` — Custom hooks (~27+). Access contexts via `useChannelContext()`, `useMessageContext()`, etc.
- `state-store/` — Client-side state stores using `useSyncExternalStore` with selector pattern (audio player, image gallery, message overlay, etc.)
- `store/` — Offline SQLite persistence layer. `OfflineDB` class with mappers for channels, messages, reactions, members, drafts, reminders. Schema in `store/schema.ts`
- `theme/` — Deep theming system (colors, typography, spacing, per-component overrides) via `ThemeContext`
- `i18n/` — Internationalization with i18next (14 languages). `Streami18n` wrapper class
- `middlewares/` — Command UI middlewares (attachments, emoji)
- `icons/` — SVG icon components

### Key Patterns

**Component override pattern**: Nearly every UI element is replaceable via props. Parent components (e.g., `Channel`) accept 50+ `React.ComponentType` props for sub-components (`Message`, `MessageContent`, `DateHeader`, `TypingIndicator`, etc.). These props are forwarded into Context providers so deeply nested children can access them without prop drilling.

**Context three-layer pattern**: Each context follows the same structure:

1. `createContext()` with a sentinel default value (`DEFAULT_BASE_CONTEXT_VALUE`)
2. A `<XProvider>` wrapper component
3. A `useXContext()` hook that throws if used outside the provider (suppressed in test env via `isTestEnvironment()`)

Context values are assembled in dedicated `useCreateXContext()` hooks (e.g., `useCreateChannelContext`) that carefully memoize with selective dependencies to avoid unnecessary re-renders.

**Native module abstraction**: `native.ts` defines TypeScript interfaces for all platform-specific capabilities (image picking, compression, haptics, audio/video, clipboard). Implementations are injected at runtime via `registerNativeHandlers()` — `stream-chat-expo` provides Expo implementations, `stream-chat-react-native` provides bare RN ones. Calling an unregistered handler throws with a message to import the right package.

**State stores**: `useSyncExternalStore`-based stores in `state-store/` with `useStateStore(store, selector)` for fine-grained subscriptions outside the context system.

**Memoization**: Components use `React.memo()` with custom `areEqual` comparators (not HOCs) to prevent re-renders.

**Offline-first**: SQLite-backed persistence with sync status tracking and pending task management.

**Builder-bob builds**: Outputs CommonJS (`lib/commonjs`), ESM (`lib/module`), and TypeScript declarations (`lib/typescript`).

### Testing Patterns

Tests use `renderHook()` and `render()` from `@testing-library/react-native`. Components/hooks must be wrapped in the required provider stack (e.g., `Chat` → `Channel` → feature provider).

**Mock builders** (`src/mock-builders/`):

- `api/initiateClientWithChannels.js` — creates a test client + channels in one call
- `generator/` — factories: `generateMessage()`, `generateChannel()`, `generateUser()`, `generateMember()`, `generateStaticMessage(seed)` (deterministic via UUID v5)
- `attachments.js` — `generateImageAttachment()`, `generateFileAttachment()`, `generateAudioAttachment()`

Reanimated and native modules are mocked via Proxy patterns in test setup files.

### Theme System

Themes follow a three-tier token architecture: **Primitives** (raw colors) → **Semantics** (e.g., `colors.error.primary`) → **Components** (per-component overrides). Token references use `$key` string syntax (e.g., `"$blue500"`) and are resolved via topological sort in `theme/topologicalResolution.ts`, so declaration order doesn't matter.

Platform-specific tokens are **generated** files in `src/theme/generated/{light,dark}/StreamTokens.{ios,android,web}.ts` — regenerate via `sync-theme.sh` if design tokens change; don't hand-edit.

Custom themes are passed as `style` prop to `<Chat>`. `mergeThemes()` deep-merges custom style over base theme (deep-cloned via `JSON.parse(JSON.stringify())`). Light/dark mode is auto-detected via `useColorScheme()`.

### Native / Expo Package Relationship

`native-package/` and `expo-package/` are thin wrappers around `stream-chat-react-native-core`. They:

1. Call `registerNativeHandlers()` with platform-specific implementations (native modules vs Expo APIs)
2. Export optional dependency wrappers (`Audio`, `Video`, `FlatList`) from `src/optionalDependencies/`
3. Re-export everything from core: `export * from 'stream-chat-react-native-core'`

Platform branching uses runtime `Platform.select()` / `Platform.OS` checks — there are no `.ios.ts` / `.android.ts` source file splits.

### Chat Component (Root Provider)

`<Chat client={client}>` is the entry point. It:

- Sets SDK metadata on the `stream-chat` client (identifier, device info)
- Disables the JS client's `recoverStateOnReconnect` (the SDK handles recovery itself)
- Registers subscriptions for threads, polls, and reminders (cleaned up on unmount)
- Initializes `OfflineDB` if `enableOfflineSupport` is true
- Wraps children in: `ChatProvider` → `TranslationProvider` → `ThemeProvider` → `ChannelsStateProvider`

### Offline DB

SQLite schema is in `store/schema.ts`. DB versioning uses `PRAGMA user_version` — a version mismatch triggers full DB reinit (no incremental migrations). Current version is tracked in `SqliteClient.dbVersion`.

### Translations

Translation JSON files live in `src/i18n/`. `validate-translations` (run as part of `yarn lint`) checks that no translation key has an empty string value. When adding/updating translations, run `yarn build-translations` (i18next-cli sync) to keep files in sync.

## Conventions

- **Conventional commits** enforced by commitlint: `feat:`, `fix:`, `docs:`, `refactor:`, etc.
- **ESLint 9 flat config** at `package/eslint.config.mjs`, strict (max-warnings 0)
- **Prettier**: single quotes, trailing commas, 100 char width (see `.prettierrc`)
- **TypeScript strict mode** with platform-specific module suffixes (`.ios`, `.android`, `.web`)
- Git branches: PRs target `develop`, `main` is production releases only
- **Shared native sync**: Root `yarn install`'s postinstall runs `yarn shared-native:sync` automatically. Re-run manually with `yarn workspace stream-chat-react-native-core shared-native:sync` after modifying `package/shared-native/`.
- **No Lerna**: the release pipeline uses `yarn workspaces foreach` directly. Release-participating workspaces (core SDK + SampleApp) are hardcoded in `release/release.config.js`.
@AGENTS.md
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [Stream Chat API](https://getstream.io/chat/) product overview
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [React Native Chat Tutorial](https://getstream.io/chat/react-native-chat/tutorial/)
- [AI Agent Skills](#-build-with-ai-agents) for Claude Code, Cursor, and Codex
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
- [Documentation](https://getstream.io/chat/docs/sdk/reactnative)
- [Release Notes](https://github.com/GetStream/stream-chat-react-native/releases)
Expand All @@ -28,6 +29,7 @@
- [Official React Native SDK for Stream Chat](#official-react-native-sdk-for-stream-chat)
- [Contents](#contents)
- [📖 React Native Chat Tutorial](#-react-native-chat-tutorial)
- [🤖 Build with AI Agents](#-build-with-ai-agents)
- [Free for Makers](#free-for-makers)
- [🔮 Example Apps](#-example-apps)
- [💬 Keep in mind](#-keep-in-mind)
Expand All @@ -39,17 +41,36 @@

The best place to start is the [React Native Chat Tutorial](https://getstream.io/chat/react-native-chat/tutorial/). It teaches you how to use this SDK and also shows how to make frequently required changes.

## 🤖 Build with AI Agents

If you build with an AI coding agent, our [agent skills](https://getstream.io/agent-skills/docs/installation/) teach it how to use this SDK correctly. Install them once:

```bash
curl -fsSL https://getstream.io/cli.sh | bash
getstream init
```

Then reach for the [`/stream-react-native`](https://getstream.io/agent-skills/docs/skills/stream-react-native/) skill:

```
/stream-react-native create a new Expo chat app
/stream-react-native upgrade stream-chat-react-native to v9
```

It can scaffold a new Expo or React Native CLI app with the SDK wired up, add Stream to an app you already have, audit an existing integration, or migrate between SDK major versions (including from Sendbird). Works with Claude Code, Cursor, Codex, and any other agent that reads the universal `.agents` location.

Contributing to this repository with an agent instead? See [AGENTS.md](./AGENTS.md) for repository structure, commands, and conventions.

## Free for Makers

Stream is free for most side and hobby projects. To qualify your project/company needs to have < 5 team members and < $10k in monthly revenue.
For complete pricing details visit our [Chat Pricing Page](https://getstream.io/chat/pricing/)

## 🔮 Example Apps

This repo includes 3 example apps. One made with Expo, two in TypeScript. One TypeScript app is a simple implementation for reference, the other is a more full featured app example.
This repo includes 2 example apps. One made with Expo, and a more full featured app example built with the React Native CLI.

- [Expo example](https://github.com/GetStream/stream-chat-react-native/tree/develop/examples/ExpoMessaging)
- [Typescript example](https://github.com/GetStream/stream-chat-react-native/tree/develop/examples/TypeScriptMessaging)
- [Fully featured messaging application](https://github.com/GetStream/stream-chat-react-native/tree/develop/examples/SampleApp)

Besides, our team maintains a dedicated repository for fully-fledged sample applications and demos at [GetStream/react-native-samples](https://github.com/GetStream/react-native-samples). Please consider checking following sample applications:
Expand Down
13 changes: 13 additions & 0 deletions configs/typescript-config/base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Stream Base",
"compilerOptions": {
"target": "ES2022",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true
}
}
24 changes: 24 additions & 0 deletions configs/typescript-config/library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Stream Library (React Native)",
"extends": "./base.json",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-native",
"allowJs": true,
"checkJs": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitUseStrict": false,
"noStrictGenericChecks": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noEmitOnError": false
}
}
16 changes: 16 additions & 0 deletions configs/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@stream-io/typescript-config",
"version": "0.0.0",
"description": "Shared TypeScript config presets for the Stream Chat React Native SDK monorepo.",
"license": "SEE LICENSE IN LICENSE",
"private": true,
"files": [
"base.json",
"library.json"
],
"exports": {
"./base.json": "./base.json",
"./library.json": "./library.json",
"./package.json": "./package.json"
}
}
4 changes: 2 additions & 2 deletions examples/ExpoMessaging/app/channel/[cid]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export default function ChannelScreen() {
payload,
) => {
const { message, defaultHandler, emitter } = payload;
const { shared_location } = message ?? {};
if (emitter === 'messageContent' && shared_location) {
const shared_location = message?.shared_location;
if (emitter === 'messageContent' && message && shared_location) {
// Create url params from shared_location
const params = Object.entries(shared_location)
.map(([key, value]) => `${key}=${value}`)
Expand Down
Loading
Loading